From cf8f6eccf38700944e9f95a5d9d5e532b6baaa1d Mon Sep 17 00:00:00 2001 From: daniel cebrian Date: Fri, 7 Feb 2014 16:16:09 +0100 Subject: [PATCH 01/18] first commit to the branch Added fix to horizontal extension in the TinyMCE RichText-Annotator. Also includes trying to add annotator to Studio. Only thing failing currently is getting TinyMCE to work at all. Adding fixed to merging conflicts Added folksonomic tags for annotations and also added the ability for video annotation to work on studio minus tinymce Adding License to Tags plugin Changed way that token is retrieved so it does not have to create a new view just to retrieve that item. Other fixes: 1. Removed calls in base to tinymce found in the annotation tool so as not to confuse it with the item already existing. 2. Removed references to token and in the url schema and moved the firebase_token_generator out of the student django apps for the time being (eventually it should be retrieved from the original project if applicable) Added ways for instructors to allow students to use diacritic tags in their text annotations Added quick fix to diacritic annotator to work properly based on whether instructor added items or not Removed reference to tinymce in OVA removed instances of courseid as token handling has changed. fixed duplicated code issue for token retriever. It's now its own file Added token_secret to the variables for the annotation module Fixed some quality errors and one retrive_token I missed. --- cms/envs/common.py | 2 +- cms/templates/base.html | 50 ++++- common/djangoapps/student/tests/tests.py | 25 +-- common/djangoapps/student/views.py | 24 --- common/lib/xmodule/xmodule/annotator_token.py | 27 +++ .../xmodule}/firebase_token_generator.py | 31 +-- .../xmodule/tests/test_annotator_token.py | 20 ++ .../xmodule/tests/test_textannotation.py | 15 +- .../xmodule}/tests/test_token_generator.py | 7 +- .../xmodule/tests/test_videoannotation.py | 100 +-------- .../xmodule/xmodule/textannotation_module.py | 34 +-- .../xmodule/xmodule/videoannotation_module.py | 81 +------ .../css/vendor/ova/diacritic-annotator.css | 6 + .../css/vendor/ova/richText-annotator.css | 12 ++ .../static/css/vendor/ova/tags-annotator.css | 2 +- common/static/js/vendor/ova/catch/js/catch.js | 4 +- .../js/vendor/ova/diacritic-annotator.js | 203 ++++++++++++++++++ .../js/vendor/ova/flagging-annotator.js | 3 +- common/static/js/vendor/ova/ova.js | 4 +- common/static/js/vendor/ova/rangeslider.js | 2 +- .../static/js/vendor/ova/reply-annotator.js | 3 +- .../js/vendor/ova/richText-annotator.js | 6 +- .../static/js/vendor/ova/share-annotator.js | 3 +- common/static/js/vendor/ova/tags-annotator.js | 32 ++- common/test/data/tilde/about/index.html~ | 1 - common/test/data/tilde/static/example.txt~ | 1 - lms/djangoapps/notes/views.py | 6 +- lms/envs/common.py | 2 + lms/templates/notes.html | 6 +- lms/templates/textannotation.html | 67 +++--- lms/templates/videoannotation.html | 70 +++--- lms/urls.py | 1 - 32 files changed, 488 insertions(+), 362 deletions(-) create mode 100644 common/lib/xmodule/xmodule/annotator_token.py rename common/{djangoapps/student => lib/xmodule/xmodule}/firebase_token_generator.py (78%) create mode 100644 common/lib/xmodule/xmodule/tests/test_annotator_token.py rename common/{djangoapps/student => lib/xmodule/xmodule}/tests/test_token_generator.py (92%) create mode 100644 common/static/css/vendor/ova/diacritic-annotator.css create mode 100644 common/static/js/vendor/ova/diacritic-annotator.js delete mode 100644 common/test/data/tilde/about/index.html~ delete mode 100644 common/test/data/tilde/static/example.txt~ diff --git a/cms/envs/common.py b/cms/envs/common.py index 9e199e33723c..00e606e42f47 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -319,7 +319,7 @@ 'css/vendor/ui-lightness/jquery-ui-1.8.22.custom.css', 'css/vendor/jquery.qtip.min.css', 'js/vendor/markitup/skins/simple/style.css', - 'js/vendor/markitup/sets/wiki/style.css' + 'js/vendor/markitup/sets/wiki/style.css', ], 'output_filename': 'css/cms-style-vendor.css', }, diff --git a/cms/templates/base.html b/cms/templates/base.html index 93a3ec4ff7f6..658c7f41de9d 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -80,6 +80,20 @@ "accessibility": "js/src/accessibility_tools", "draggabilly": "js/vendor/draggabilly.pkgd", "URI": "js/vendor/URI.min", + "annotator": "js/vendor/ova/annotator-full", + "video.dev": "js/vendor/ova/video.dev", + "vjs.youtube": 'js/vendor/ova/vjs.youtube', + "rangeslider": 'js/vendor/ova/rangeslider', + "share-annotator": 'js/vendor/ova/share-annotator', + "richText-annotator": 'js/vendor/ova/richText-annotator', + "reply-annotator": 'js/vendor/ova/reply-annotator', + "tags-annotator": 'js/vendor/ova/tags-annotator', + "diacritic-annotator": 'js/vendor/ova/diacritic-annotator', + "flagging-annotator": 'js/vendor/ova/flagging-annotator', + "jquery-Watch": 'js/vendor/ova/jquery-Watch', + "ova": 'js/vendor/ova/ova', + "catch": 'js/vendor/ova/catch/js/catch', + "handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2', // externally hosted files "tender": [ @@ -95,7 +109,7 @@ // and leaves the path component intact. "//www.youtube.com/player_api?noext", // if youtube fails to load, fallback on a local file - // so that require doesn't fall over + // so that require doesnt fall over "js/src/youtube_fallback" ] }, @@ -227,6 +241,40 @@ "coffee/src/logger": { exports: "Logger", deps: ["coffee/src/ajax_prefix"] + }, + "video.dev": { + exports:"videojs" + }, + "vjs.youtube": { + deps: ["video.dev"] + }, + "rangeslider": { + deps: ["video.dev"] + }, + "annotator": { + exports: "Annotator" + }, + "share-annotator": { + deps: ["annotator"] + }, + "richText-annotator": { + deps: ["annotator", "tinymce"] + }, + "reply-annotator": { + deps: ["annotator"] + }, + "tags-annotator": { + deps: ["annotator"] + }, + "diacritic-annotator": { + deps: ["annotator"] + }, + "flagging-annotator": { + deps: ["annotator"] + }, + "ova":{ + exports: "ova", + deps: ["annotator", "video.dev", "vjs.youtube", "rangeslider", "share-annotator", "tinymce", "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator", "diacritic-annotator", "jquery-Watch", "catch", "handlebars", "URI"] } }, // load jquery and gettext automatically diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 48d8bb642eb2..93f4fbd7f4a3 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -25,7 +25,7 @@ from student.models import anonymous_id_for_user, user_by_anonymous_id, CourseEnrollment, unique_id_for_user from student.views import (process_survey_link, _cert_info, - change_enrollment, complete_course_mode_info, token) + change_enrollment, complete_course_mode_info) from student.tests.factories import UserFactory, CourseModeFactory import shoppingcart @@ -458,26 +458,3 @@ def test_roundtrip_for_logged_user(self): anonymous_id = anonymous_id_for_user(self.user, self.course.id) real_user = user_by_anonymous_id(anonymous_id) self.assertEqual(self.user, real_user) - - -@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) -class Token(ModuleStoreTestCase): - """ - Test for the token generator. This creates a random course and passes it through the token file which generates the - token that will be passed in to the annotation_storage_url. - """ - request_factory = RequestFactory() - COURSE_SLUG = "100" - COURSE_NAME = "test_course" - COURSE_ORG = "edx" - - def setUp(self): - self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG) - self.user = User.objects.create(username="username", email="username") - self.req = self.request_factory.post('/token?course_id=edx/100/test_course', {'user': self.user}) - self.req.user = self.user - - def test_token(self): - expected = HttpResponse("eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3N1ZWRBdCI6ICIyMDE0LTAxLTIzVDE5OjM1OjE3LjUyMjEwNC01OjAwIiwgImNvbnN1bWVyS2V5IjogInh4eHh4eHh4LXh4eHgteHh4eC14eHh4LXh4eHh4eHh4eHh4eCIsICJ1c2VySWQiOiAidXNlcm5hbWUiLCAidHRsIjogODY0MDB9.OjWz9mzqJnYuzX-f3uCBllqJUa8PVWJjcDy_McfxLvc", mimetype="text/plain") - response = token(self.req) - self.assertEqual(expected.content.split('.')[0], response.content.split('.')[0]) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 4e9226b32556..42c1694dc39e 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -44,7 +44,6 @@ create_comments_service_user, PasswordHistory ) from student.forms import PasswordResetFormNoActive -from student.firebase_token_generator import create_token from verify_student.models import SoftwareSecurePhotoVerification, MidcourseReverificationWindow from certificates.models import CertificateStatuses, certificate_status_for_student @@ -1780,26 +1779,3 @@ def change_email_settings(request): track.views.server_track(request, "change-email-settings", {"receive_emails": "no", "course": course_id}, page='dashboard') return JsonResponse({"success": True}) - - -@login_required -def token(request): - ''' - Return a token for the backend of annotations. - It uses the course id to retrieve a variable that contains the secret - token found in inheritance.py. It also contains information of when - the token was issued. This will be stored with the user along with - the id for identification purposes in the backend. - ''' - course_id = request.GET.get("course_id") - course = course_from_id(course_id) - dtnow = datetime.datetime.now() - dtutcnow = datetime.datetime.utcnow() - delta = dtnow - dtutcnow - newhour, newmin = divmod((delta.days * 24 * 60 * 60 + delta.seconds + 30) // 60, 60) - newtime = "%s%+02d:%02d" % (dtnow.isoformat(), newhour, newmin) - secret = course.annotation_token_secret - custom_data = {"issuedAt": newtime, "consumerKey": secret, "userId": request.user.email, "ttl": 86400} - newtoken = create_token(secret, custom_data) - response = HttpResponse(newtoken, mimetype="text/plain") - return response diff --git a/common/lib/xmodule/xmodule/annotator_token.py b/common/lib/xmodule/xmodule/annotator_token.py new file mode 100644 index 000000000000..55feb589c080 --- /dev/null +++ b/common/lib/xmodule/xmodule/annotator_token.py @@ -0,0 +1,27 @@ +''' +This file contains a function used to retrieve the token for the annotation backend +without having to create a view, but just returning a string instead. + +It can be called from other files by using the following: +from xmodule.annotator_token import retrieve_token +''' +import datetime +from xmodule.firebase_token_generator import create_token + + +def retrieve_token(userid, secret): + ''' + Return a token for the backend of annotations. + It uses the course id to retrieve a variable that contains the secret + token found in inheritance.py. It also contains information of when + the token was issued. This will be stored with the user along with + the id for identification purposes in the backend. + ''' + dtnow = datetime.datetime.now() + dtutcnow = datetime.datetime.utcnow() + delta = dtnow - dtutcnow + newhour, newmin = divmod((delta.days * 24 * 60 * 60 + delta.seconds + 30) // 60, 60) + newtime = "%s%+02d:%02d" % (dtnow.isoformat(), newhour, newmin) + custom_data = {"issuedAt": newtime, "consumerKey": secret, "userId": userid, "ttl": 86400} + newtoken = create_token(secret, custom_data) + return newtoken diff --git a/common/djangoapps/student/firebase_token_generator.py b/common/lib/xmodule/xmodule/firebase_token_generator.py similarity index 78% rename from common/djangoapps/student/firebase_token_generator.py rename to common/lib/xmodule/xmodule/firebase_token_generator.py index f84a85277e47..3d34483f6880 100644 --- a/common/djangoapps/student/firebase_token_generator.py +++ b/common/lib/xmodule/xmodule/firebase_token_generator.py @@ -11,7 +11,6 @@ from base64 import urlsafe_b64encode import hashlib import hmac -import sys try: import json except ImportError: @@ -30,25 +29,17 @@ def create_token(secret, data): return _encode_token(secret, data) -if sys.version_info < (2, 7): - def _encode(bytes_data): - ''' - Takes a json object, string, or binary and - uses python's urlsafe_b64encode to encode data - and make it safe pass along in a url. - To make sure it does not conflict with variables - we make sure equal signs are removed. - More info: docs.python.org/2/library/base64.html - ''' - encoded = urlsafe_b64encode(bytes(bytes_data)) - return encoded.decode('utf-8').replace('=', '') -else: - def _encode(bytes_info): - ''' - Same as above function but for Python 2.7 or later - ''' - encoded = urlsafe_b64encode(bytes_info) - return encoded.decode('utf-8').replace('=', '') +def _encode(bytes_info): + ''' + Takes a json object, string, or binary and + uses python's urlsafe_b64encode to encode data + and make it safe pass along in a url. + To make sure it does not conflict with variables + we make sure equal signs are removed. + More info: docs.python.org/2/library/base64.html + ''' + encoded = urlsafe_b64encode(bytes_info) + return encoded.decode('utf-8').replace('=', '') def _encode_json(obj): diff --git a/common/lib/xmodule/xmodule/tests/test_annotator_token.py b/common/lib/xmodule/xmodule/tests/test_annotator_token.py new file mode 100644 index 000000000000..e190d7518ee8 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_annotator_token.py @@ -0,0 +1,20 @@ +""" +This test will run for annotator_token.py +""" +import unittest + +from xmodule.annotator_token import retrieve_token + + +class TokenRetriever(unittest.TestCase): + """ + Tests to make sure that when passed in a username and secret token, that it will be encoded correctly + """ + def test_token(self): + """ + Test for the token generator. Give an a random username and secret token, it should create the properly encoded string of text. + """ + expected = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3N1ZWRBdCI6ICIyMDE0LTAyLTI3VDE3OjAwOjQyLjQwNjQ0MSswOjAwIiwgImNvbnN1bWVyS2V5IjogImZha2Vfc2VjcmV0IiwgInVzZXJJZCI6ICJ1c2VybmFtZSIsICJ0dGwiOiA4NjQwMH0.Dx1PoF-7mqBOOSGDMZ9R_s3oaaLRPnn6CJgGGF2A5CQ" + response = retrieve_token("username", "fake_secret") + self.assertEqual(expected.split('.')[0], response.split('.')[0]) + self.assertNotEqual(expected.split('.')[2], response.split('.')[2]) diff --git a/common/lib/xmodule/xmodule/tests/test_textannotation.py b/common/lib/xmodule/xmodule/tests/test_textannotation.py index 397e3990ef60..25417411ac0c 100644 --- a/common/lib/xmodule/xmodule/tests/test_textannotation.py +++ b/common/lib/xmodule/xmodule/tests/test_textannotation.py @@ -12,7 +12,6 @@ from . import get_test_system - class TextAnnotationModuleTestCase(unittest.TestCase): ''' text Annotation Module Test Case ''' sample_xml = ''' @@ -26,7 +25,6 @@ class TextAnnotationModuleTestCase(unittest.TestCase):

''' - def setUp(self): """ Makes sure that the Module is declared and mocked with the sample xml above. @@ -38,17 +36,6 @@ def setUp(self): ScopeIds(None, None, None, None) ) - def test_render_content(self): - """ - Tests to make sure the sample xml is rendered and that it forms a valid xmltree - that does not contain a display_name. - """ - content = self.mod._render_content() # pylint: disable=W0212 - self.assertIsNotNone(content) - element = etree.fromstring(content) - self.assertIsNotNone(element) - self.assertFalse('display_name' in element.attrib, "Display Name should have been deleted from Content") - def test_extract_instructions(self): """ Tests to make sure that the instructions are correctly pulled from the sample xml above. @@ -70,5 +57,5 @@ def test_get_html(self): Tests the function that passes in all the information in the context that will be used in templates/textannotation.html """ context = self.mod.get_html() - for key in ['display_name', 'tag', 'source', 'instructions_html', 'content_html', 'annotation_storage']: + for key in ['display_name', 'tag', 'source', 'instructions_html', 'content_html', 'annotation_storage', 'token']: self.assertIn(key, context) diff --git a/common/djangoapps/student/tests/test_token_generator.py b/common/lib/xmodule/xmodule/tests/test_token_generator.py similarity index 92% rename from common/djangoapps/student/tests/test_token_generator.py rename to common/lib/xmodule/xmodule/tests/test_token_generator.py index 1eb09c9173c0..ab2762f0a461 100644 --- a/common/djangoapps/student/tests/test_token_generator.py +++ b/common/lib/xmodule/xmodule/tests/test_token_generator.py @@ -1,13 +1,12 @@ """ This test will run for firebase_token_generator.py. """ +import unittest -from django.test import TestCase +from xmodule.firebase_token_generator import _encode, _encode_json, _encode_token, create_token -from student.firebase_token_generator import _encode, _encode_json, _encode_token, create_token - -class TokenGenerator(TestCase): +class TokenGenerator(unittest.TestCase): """ Tests for the file firebase_token_generator.py """ diff --git a/common/lib/xmodule/xmodule/tests/test_videoannotation.py b/common/lib/xmodule/xmodule/tests/test_videoannotation.py index cb63d055038d..522cea23eda6 100644 --- a/common/lib/xmodule/xmodule/tests/test_videoannotation.py +++ b/common/lib/xmodule/xmodule/tests/test_videoannotation.py @@ -12,7 +12,6 @@ from . import get_test_system - class VideoAnnotationModuleTestCase(unittest.TestCase): ''' Video Annotation Module Test Case ''' sample_xml = ''' @@ -34,100 +33,6 @@ def setUp(self): ScopeIds(None, None, None, None) ) - def test_annotation_class_attr_default(self): - """ - Makes sure that it can detect annotation values in text-form if user - decides to add text to the area below video, video functionality is completely - found in javascript. - """ - xml = 'test' - element = etree.fromstring(xml) - - expected_attr = {'class': {'value': 'annotatable-span highlight'}} - actual_attr = self.mod._get_annotation_class_attr(element) # pylint: disable=W0212 - - self.assertIsInstance(actual_attr, dict) - self.assertDictEqual(expected_attr, actual_attr) - - def test_annotation_class_attr_with_valid_highlight(self): - """ - Same as above but more specific to an area that is highlightable in the appropriate - color designated. - """ - xml = 'test' - - for color in self.mod.highlight_colors: - element = etree.fromstring(xml.format(highlight=color)) - value = 'annotatable-span highlight highlight-{highlight}'.format(highlight=color) - - expected_attr = {'class': { - 'value': value, - '_delete': 'highlight'} - } - actual_attr = self.mod._get_annotation_class_attr(element) # pylint: disable=W0212 - - self.assertIsInstance(actual_attr, dict) - self.assertDictEqual(expected_attr, actual_attr) - - def test_annotation_class_attr_with_invalid_highlight(self): - """ - Same as above, but checked with invalid colors. - """ - xml = 'test' - - for invalid_color in ['rainbow', 'blink', 'invisible', '', None]: - element = etree.fromstring(xml.format(highlight=invalid_color)) - expected_attr = {'class': { - 'value': 'annotatable-span highlight', - '_delete': 'highlight'} - } - actual_attr = self.mod._get_annotation_class_attr(element) # pylint: disable=W0212 - - self.assertIsInstance(actual_attr, dict) - self.assertDictEqual(expected_attr, actual_attr) - - def test_annotation_data_attr(self): - """ - Test that each highlight contains the data information from the annotation itself. - """ - element = etree.fromstring('test') - - expected_attr = { - 'data-comment-body': {'value': 'foo', '_delete': 'body'}, - 'data-comment-title': {'value': 'bar', '_delete': 'title'}, - 'data-problem-id': {'value': '0', '_delete': 'problem'} - } - - actual_attr = self.mod._get_annotation_data_attr(element) # pylint: disable=W0212 - - self.assertIsInstance(actual_attr, dict) - self.assertDictEqual(expected_attr, actual_attr) - - def test_render_annotation(self): - """ - Tests to make sure that the spans designating annotations acutally visually render as annotations. - """ - expected_html = 'z' - expected_el = etree.fromstring(expected_html) - - actual_el = etree.fromstring('z') - self.mod._render_annotation(actual_el) # pylint: disable=W0212 - - self.assertEqual(expected_el.tag, actual_el.tag) - self.assertEqual(expected_el.text, actual_el.text) - self.assertDictEqual(dict(expected_el.attrib), dict(actual_el.attrib)) - - def test_render_content(self): - """ - Like above, but using the entire text, it makes sure that display_name is removed and that there is only one - div encompassing the annotatable area. - """ - content = self.mod._render_content() # pylint: disable=W0212 - element = etree.fromstring(content) - self.assertIsNotNone(element) - self.assertEqual('div', element.tag, 'root tag is a div') - self.assertFalse('display_name' in element.attrib, "Display Name should have been deleted from Content") - def test_extract_instructions(self): """ This test ensures that if an instruction exists it is pulled and @@ -137,6 +42,7 @@ def test_extract_instructions(self): expected_xml = u"

Video Test Instructions.

" actual_xml = self.mod._extract_instructions(xmltree) # pylint: disable=W0212 + self.assertIsNotNone(actual_xml) self.assertEqual(expected_xml.strip(), actual_xml.strip()) @@ -160,6 +66,6 @@ def test_get_html(self): """ Tests to make sure variables passed in truly exist within the html once it is all rendered. """ - context = self.mod.get_html() - for key in ['display_name', 'content_html', 'instructions_html', 'sourceUrl', 'typeSource', 'poster', 'alert', 'annotation_storage']: + context = self.mod.get_html() # pylint: disable=W0212 + for key in ['display_name', 'instructions_html', 'sourceUrl', 'typeSource', 'poster', 'annotation_storage']: self.assertIn(key, context) diff --git a/common/lib/xmodule/xmodule/textannotation_module.py b/common/lib/xmodule/xmodule/textannotation_module.py index 1d732d870925..c6ff6b9b1a0c 100644 --- a/common/lib/xmodule/xmodule/textannotation_module.py +++ b/common/lib/xmodule/xmodule/textannotation_module.py @@ -6,6 +6,7 @@ from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String +from xmodule.annotator_token import retrieve_token import textwrap @@ -30,7 +31,7 @@ class AnnotatableFields(object): scope=Scope.settings, default='Text Annotation', ) - tags = String( + instructor_tags = String( display_name="Tags for Assignments", help="Add tags that automatically highlight in a certain color using the comma-separated form, i.e. imagery:red,parallelism:blue", scope=Scope.settings, @@ -43,6 +44,13 @@ class AnnotatableFields(object): default='None', ) annotation_storage_url = String(help="Location of Annotation backend", scope=Scope.settings, default="http://your_annotation_storage.com", display_name="Url for Annotation Storage") + annotation_token_secret = String(help="Secret string for annotation storage", scope=Scope.settings, default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", display_name="Secret Token String for Annotation") + diacritics = String( + display_name="Diacritic Marks", + help= "Add diacritic marks to be added to a text using the comma-separated form, i.e. markname;urltomark;baseline,markname2;urltomark2;baseline2", + scope=Scope.settings, + default='', + ) class TextAnnotationModule(AnnotatableFields, XModule): @@ -59,15 +67,9 @@ def __init__(self, *args, **kwargs): self.instructions = self._extract_instructions(xmltree) self.content = etree.tostring(xmltree, encoding='unicode') - self.highlight_colors = ['yellow', 'orange', 'purple', 'blue', 'green'] - - def _render_content(self): - """ Renders annotatable content with annotation spans and returns HTML. """ - xmltree = etree.fromstring(self.content) - if 'display_name' in xmltree.attrib: - del xmltree.attrib['display_name'] - - return etree.tostring(xmltree, encoding='unicode') + self.user = "" + if self.runtime.get_real_user is not None: + self.user = self.runtime.get_real_user(self.runtime.anonymous_student_id).email def _extract_instructions(self, xmltree): """ Removes from the xmltree and returns them as a string, otherwise None. """ @@ -82,13 +84,14 @@ def get_html(self): """ Renders parameters to template. """ context = { 'display_name': self.display_name_with_default, - 'tag': self.tags, + 'tag': self.instructor_tags, 'source': self.source, 'instructions_html': self.instructions, - 'content_html': self._render_content(), - 'annotation_storage': self.annotation_storage_url + 'content_html': self.content, + 'annotation_storage': self.annotation_storage_url, + 'token': retrieve_token(self.user, self.annotation_token_secret), + 'diacritic_marks': self.diacritics, } - return self.system.render_template('textannotation.html', context) @@ -101,6 +104,7 @@ class TextAnnotationDescriptor(AnnotatableFields, RawDescriptor): def non_editable_metadata_fields(self): non_editable_fields = super(TextAnnotationDescriptor, self).non_editable_metadata_fields non_editable_fields.extend([ - TextAnnotationDescriptor.annotation_storage_url + TextAnnotationDescriptor.annotation_storage_url, + TextAnnotationDescriptor.annotation_token_secret ]) return non_editable_fields diff --git a/common/lib/xmodule/xmodule/videoannotation_module.py b/common/lib/xmodule/xmodule/videoannotation_module.py index 5f31509d013e..9644c04fa997 100644 --- a/common/lib/xmodule/xmodule/videoannotation_module.py +++ b/common/lib/xmodule/xmodule/videoannotation_module.py @@ -7,6 +7,7 @@ from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String +from xmodule.annotator_token import retrieve_token import textwrap @@ -31,7 +32,7 @@ class AnnotatableFields(object): sourceurl = String(help="The external source URL for the video.", display_name="Source URL", scope=Scope.settings, default="http://video-js.zencoder.com/oceans-clip.mp4") poster_url = String(help="Poster Image URL", display_name="Poster URL", scope=Scope.settings, default="") annotation_storage_url = String(help="Location of Annotation backend", scope=Scope.settings, default="http://your_annotation_storage.com", display_name="Url for Annotation Storage") - + annotation_token_secret = String(help="Secret string for annotation storage", scope=Scope.settings, default="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", display_name="Secret Token String for Annotation") class VideoAnnotationModule(AnnotatableFields, XModule): '''Video Annotation Module''' @@ -55,73 +56,9 @@ def __init__(self, *args, **kwargs): self.instructions = self._extract_instructions(xmltree) self.content = etree.tostring(xmltree, encoding='unicode') - self.highlight_colors = ['yellow', 'orange', 'purple', 'blue', 'green'] - - def _get_annotation_class_attr(self, element): - """ Returns a dict with the CSS class attribute to set on the annotation - and an XML key to delete from the element. - """ - - attr = {} - cls = ['annotatable-span', 'highlight'] - highlight_key = 'highlight' - color = element.get(highlight_key) - - if color is not None: - if color in self.highlight_colors: - cls.append('highlight-' + color) - attr['_delete'] = highlight_key - attr['value'] = ' '.join(cls) - - return {'class': attr} - - def _get_annotation_data_attr(self, element): - """ Returns a dict in which the keys are the HTML data attributes - to set on the annotation element. Each data attribute has a - corresponding 'value' and (optional) '_delete' key to specify - an XML attribute to delete. - """ - - data_attrs = {} - attrs_map = { - 'body': 'data-comment-body', - 'title': 'data-comment-title', - 'problem': 'data-problem-id' - } - - for xml_key in attrs_map.keys(): - if xml_key in element.attrib: - value = element.get(xml_key, '') - html_key = attrs_map[xml_key] - data_attrs[html_key] = {'value': value, '_delete': xml_key} - - return data_attrs - - def _render_annotation(self, element): - """ Renders an annotation element for HTML output. """ - attr = {} - attr.update(self._get_annotation_class_attr(element)) - attr.update(self._get_annotation_data_attr(element)) - - element.tag = 'span' - - for key in attr.keys(): - element.set(key, attr[key]['value']) - if '_delete' in attr[key] and attr[key]['_delete'] is not None: - delete_key = attr[key]['_delete'] - del element.attrib[delete_key] - - def _render_content(self): - """ Renders annotatable content with annotation spans and returns HTML. """ - xmltree = etree.fromstring(self.content) - xmltree.tag = 'div' - if 'display_name' in xmltree.attrib: - del xmltree.attrib['display_name'] - - for element in xmltree.findall('.//annotation'): - self._render_annotation(element) - - return etree.tostring(xmltree, encoding='unicode') + self.user = "" + if self.runtime.get_real_user is not None: + self.user = self.runtime.get_real_user(self.runtime.anonymous_student_id).email def _extract_instructions(self, xmltree): """ Removes from the xmltree and returns them as a string, otherwise None. """ @@ -154,9 +91,8 @@ def get_html(self): 'sourceUrl': self.sourceurl, 'typeSource': extension, 'poster': self.poster_url, - 'alert': self, - 'content_html': self._render_content(), - 'annotation_storage': self.annotation_storage_url + 'annotation_storage': self.annotation_storage_url, + 'token': retrieve_token(self.user, self.annotation_token_secret), } return self.system.render_template('videoannotation.html', context) @@ -171,6 +107,7 @@ class VideoAnnotationDescriptor(AnnotatableFields, RawDescriptor): def non_editable_metadata_fields(self): non_editable_fields = super(VideoAnnotationDescriptor, self).non_editable_metadata_fields non_editable_fields.extend([ - VideoAnnotationDescriptor.annotation_storage_url + VideoAnnotationDescriptor.annotation_storage_url, + VideoAnnotationDescriptor.annotation_token_secret ]) return non_editable_fields diff --git a/common/static/css/vendor/ova/diacritic-annotator.css b/common/static/css/vendor/ova/diacritic-annotator.css new file mode 100644 index 000000000000..c233ca6cb943 --- /dev/null +++ b/common/static/css/vendor/ova/diacritic-annotator.css @@ -0,0 +1,6 @@ +.mark{ + width:10px; + height:10px; + position:absolute; + background-size:contain; +} \ No newline at end of file diff --git a/common/static/css/vendor/ova/richText-annotator.css b/common/static/css/vendor/ova/richText-annotator.css index 395cfc5f173c..1e66d4ff061f 100644 --- a/common/static/css/vendor/ova/richText-annotator.css +++ b/common/static/css/vendor/ova/richText-annotator.css @@ -20,6 +20,18 @@ z-index:3000000000!important; /*To fix full-screen problems*/ } +.mce-container-body { + min-width:400px; +} + +iframe[id^="annotator-field"] { + width: inherit; + min-width: 400px; +} + +div.mce-tinymce.mce-container.mce-panel { + min-width: 400px; +} /* Some change in the design of Annotator */ .annotator-editor .annotator-widget{ diff --git a/common/static/css/vendor/ova/tags-annotator.css b/common/static/css/vendor/ova/tags-annotator.css index e1e58286286a..054e797e967e 100644 --- a/common/static/css/vendor/ova/tags-annotator.css +++ b/common/static/css/vendor/ova/tags-annotator.css @@ -43,7 +43,7 @@ li.token-input-input-token { div.token-input-dropdown { position: absolute; - width: 120px; + width: 400px; background-color: #fff; overflow: hidden; border-left: 1px solid #ccc; diff --git a/common/static/js/vendor/ova/catch/js/catch.js b/common/static/js/vendor/ova/catch/js/catch.js index 113d7d95c818..83ef8a51ecfc 100644 --- a/common/static/js/vendor/ova/catch/js/catch.js +++ b/common/static/js/vendor/ova/catch/js/catch.js @@ -568,7 +568,6 @@ CatchAnnotation.prototype = { var moreBut = this.element.find('.annotationListButtons .moreButtonCatch'); moreBut.html('More'); - setTimeout(); }, // @@ -1095,11 +1094,10 @@ CatchAnnotation.prototype = { annotation = item.data('annotation'); var authorized = permissions.options.userAuthorize('delete', annotation,permissions.user); if(authorized){ - //annotator.deleteAnnotation(annotation); if(confirm('Would you like to delete this reply?')){ annotator.plugins['Store']._apiRequest('destroy', annotation, function(){}); item.remove(); - } + } } } } diff --git a/common/static/js/vendor/ova/diacritic-annotator.js b/common/static/js/vendor/ova/diacritic-annotator.js new file mode 100644 index 000000000000..5f29c2dbd842 --- /dev/null +++ b/common/static/js/vendor/ova/diacritic-annotator.js @@ -0,0 +1,203 @@ +/* + Diacritic Annotator Plugin v1.0 (https://github.com/lduarte1991/diacritic-annotator) + Copyright (C) 2014 Luis F Duarte + License: https://github.com/lduarte1991/diacritic-annotator/blob/master/LICENSE.rst + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +var _ref, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + +Annotator.Plugin.Diacritics = (function(_super) { + __extends(Diacritics, _super); + + //Options will include diacritic name, picture used, baseline + Diacritics.prototype.options = null; + Diacritics.prototype.diacriticmarks = null; + + //initiate diacritics elements + function Diacritics(element,options) { + this.pluginSubmit = __bind(this.pluginSubmit, this); + this.updateDiacritics = __bind(this.updateDiacritics, this); + this.updateViewer = __bind(this.updateViewer, this); + this.getDiacritics = __bind(this.getDiacritics, this); + this.getPos = __bind(this.getPos, this); + this.putMarkatLocation = __bind(this.putMarkatLocation, this); + this.updateEditorForDiacritics = __bind(this.updateEditorForDiacritics, this); + + this.options = options; + this.diacriticmarks = this.getDiacritics(); + _ref = Diacritics.__super__.constructor.apply(this, arguments); + return _ref; + } + + //example variables to be used to receive input in the annotator view + Diacritics.prototype.field = null; + Diacritics.prototype.input = null; + + //this function will initialize the plug in + Diacritics.prototype.pluginInit = function() { + console.log("Diacritics-pluginInit"); + + //Check that annotator is working + if (!Annotator.supported()) { + return; + } + var di = this.diacriticmarks; + + //-- Editor + var self = this; + if(di != 'undefined'){ + $.each(di,function(item){ + self.field = self.annotator.editor.addField({ + type: 'checkbox', //options (textarea,input,select,checkbox) + label: Annotator._t(item), + submit: self.pluginSubmit, + }); + }); + + //-- Viewer + var newview = this.annotator.viewer.addField({ + load: this.updateViewer, + }); + + this.annotator.subscribe('annotationsLoaded', this.updateDiacritics); + this.annotator.subscribe('annotationUploaded', this.updateDiacritics); + this.annotator.subscribe('annotationDeleted', this.updateDiacritics); + this.annotator.subscribe('annotationEditorShown', this.updateEditorForDiacritics, this.field); + + var self = this; + $(window).resize(function() { + self.updateDiacritics(); + }); + } + + return this.input = $(this.field).find(':input'); + }; + + //The following function is run when a person hits submit. + Diacritics.prototype.pluginSubmit = function(field, annotation) { + var checkedItems = $(this.field).find(':input'); + var self = this; + $.each(checkedItems, function(item){ + if(typeof annotation.tags != 'undefined'){ + var index = $.inArray(checkedItems[item].placeholder, annotation.tags); + if(index != -1){ + annotation.tags.splice(index, 1); + if (typeof $($('.annotator-wrapper')[0]).find('div.'+annotation.id)[0] != 'undefined'){ + $($('.annotator-wrapper')[0]).find('div.'+annotation.id)[0].remove(); + } else { + $($('.annotator-wrapper')[0]).find('div.undefined')[0].remove(); + } + } + + if(checkedItems[item].checked == true){ + annotation.tags.unshift(checkedItems[item].placeholder); + self.putMarkatLocation(annotation, checkedItems[item].placeholder); + } + } else { + if(checkedItems[item].checked == true){ + annotation['tags'] = [checkedItems[item].placeholder]; + self.putMarkatLocation(annotation, checkedItems[item].placeholder); + } + } + }); + + } + + Diacritics.prototype.putMarkatLocation = function (annotation, mark){ + var loc = this.getPos(annotation.highlights[0]); + var alignment = this.diacriticmarks[mark][1]; + var imgurl = this.diacriticmarks[mark][0]; + + var newdiv = document.createElement('div'); + var className = 'mark ' + annotation.id; + newdiv.setAttribute('class',className); + if(alignment == 'top'){ + $(newdiv).css('top',""+(loc.y-5)+"px"); + } else if(alignment == 'bottom'){ + $(newdiv).css('top',""+(loc.y+loc.height-5)+"px"); + } else{ + $(newdiv).css('top',""+loc.y+"px"); + } + $(newdiv).css('left',""+(loc.x+(loc.width/2.0)-10)+"px"); + $(newdiv).css('background-image', 'url('+imgurl+')'); + $('.annotator-wrapper')[0].appendChild(newdiv); + } + + Diacritics.prototype.getDiacritics = function(){ + if(typeof this.options.diacritics != 'undefined'){ + var self = this; + var final = new Object(), prelim = this.options.diacritics.split(","); + prelim.forEach(function(item){ + var temp = item.split(";"); + if (temp.length <3) {return undefined;} + final[temp[0]] = [temp[1],temp[2]]; + }); + return final; + } + console.log("Was undefined"); + return undefined; + } + + Diacritics.prototype.getPos = function(el) { + var off = $(el).offset(); + return {x: off.left-$($('.annotator-wrapper')[0]).offset().left, y: off.top-$($('.annotator-wrapper')[0]).offset().top, width:$(el).width(), height:$(el).height()}; + } + + Diacritics.prototype.updateDiacritics = function(){ + $('.mark').remove(); + var annotations = this.annotator.plugins['Store'].annotations; + var self = this; + annotations.forEach(function(ann){ + $.each(self.diacriticmarks, function(item){ + if($.inArray(item, ann.tags) != -1){ + self.putMarkatLocation(ann, item); + } + }); + }); + } + + Diacritics.prototype.updateViewer = function(field,annotation){ + $(field).remove(); + } + + Diacritics.prototype.updateEditorForDiacritics = function(field, annotation){ + if (typeof annotation.tags == 'undefined'){ + return; + } + var self = this; + + var inputItems = $(this.field).find(':input'); + var dictOfItems = {} + $.each(inputItems, function(item){ + inputItems[item].checked = false; + dictOfItems[inputItems[item].placeholder] = inputItems[item]; + }); + annotation.tags.forEach(function(tag){ + if(typeof self.diacriticmarks[tag] != 'undefined'){ + dictOfItems[tag].checked = true; + } + }); + } + + + + return Diacritics; + +})(Annotator.Plugin); \ No newline at end of file diff --git a/common/static/js/vendor/ova/flagging-annotator.js b/common/static/js/vendor/ova/flagging-annotator.js index ac24ffff240d..8aa792334ffe 100644 --- a/common/static/js/vendor/ova/flagging-annotator.js +++ b/common/static/js/vendor/ova/flagging-annotator.js @@ -1,4 +1,3 @@ -// Generated by CoffeeScript 1.6.3 var _ref, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = {}.hasOwnProperty, @@ -101,4 +100,4 @@ Annotator.Plugin.Flagging = (function(_super) { return Flagging; -})(Annotator.Plugin); \ No newline at end of file +})(Annotator.Plugin); diff --git a/common/static/js/vendor/ova/ova.js b/common/static/js/vendor/ova/ova.js index 059af1322f6e..a8bc9c50441a 100644 --- a/common/static/js/vendor/ova/ova.js +++ b/common/static/js/vendor/ova/ova.js @@ -1,6 +1,6 @@ /* Open Video Annotation v1.0 (http://openvideoannotation.org/) -Copyright (C) 2014 CHS (Harvard University), Daniel Cebri�n Robles and Phil Desenne +Copyright (C) 2014 CHS (Harvard University), Daniel Cebrian Robles and Phil Desenne License: https://github.com/CtrHellenicStudies/OpenVideoAnnotation/blob/master/License.rst This program is free software; you can redistribute it and/or @@ -2389,6 +2389,8 @@ OpenVideoAnnotation.Annotator = function (element, options) { if (typeof options.optionsAnnotator.highlightTags!='undefined') this.annotator.addPlugin("HighlightTags", options.optionsAnnotator.highlightTags); + if (typeof options.optionsAnnotator.diacriticMarks != 'undefined') + this.annotator.addPlugin("Diacritics", options.optionsAnnotator.diacriticMarks); /* this.annotator.addPlugin("Filter", { diff --git a/common/static/js/vendor/ova/rangeslider.js b/common/static/js/vendor/ova/rangeslider.js index 51e054fcbad7..e0250b936a80 100644 --- a/common/static/js/vendor/ova/rangeslider.js +++ b/common/static/js/vendor/ova/rangeslider.js @@ -1,6 +1,6 @@ /* RangeSlider v1.0 (https://github.com/danielcebrian/rangeslider-videojs) -Copyright (C) 2014 Daniel Cebri�n Robles +Copyright (C) 2014 Daniel Cebrian Robles License: https://github.com/danielcebrian/rangeslider-videojs/blob/master/License.rst This program is free software; you can redistribute it and/or diff --git a/common/static/js/vendor/ova/reply-annotator.js b/common/static/js/vendor/ova/reply-annotator.js index f88efae2daf0..5fe8a37e30bd 100644 --- a/common/static/js/vendor/ova/reply-annotator.js +++ b/common/static/js/vendor/ova/reply-annotator.js @@ -1,6 +1,6 @@ /* Reply Annotator Plugin v1.0 (https://github.com/danielcebrian/reply-annotator) - Copyright (C) 2014 Daniel Cebri�n Robles + Copyright (C) 2014 Daniel Cebrian Robles License: https://github.com/danielcebrian/reply-annotator/blob/master/License.rst This program is free software; you can redistribute it and/or @@ -17,7 +17,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// Generated by CoffeeScript 1.6.3 var _ref, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = {}.hasOwnProperty, diff --git a/common/static/js/vendor/ova/richText-annotator.js b/common/static/js/vendor/ova/richText-annotator.js index 828d7ef8ef89..426ece43fc7f 100644 --- a/common/static/js/vendor/ova/richText-annotator.js +++ b/common/static/js/vendor/ova/richText-annotator.js @@ -1,6 +1,6 @@ /* Rich Text Annotator Plugin v1.0 (https://github.com/danielcebrian/richText-annotator) -Copyright (C) 2014 Daniel Cebri�n Robles +Copyright (C) 2014 Daniel Cebrian Robles License: https://github.com/danielcebrian/richText-annotator/blob/master/License.rst This program is free software; you can redistribute it and/or @@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// Generated by CoffeeScript 1.6.3 var _ref, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = {}.hasOwnProperty, @@ -36,6 +35,7 @@ Annotator.Plugin.RichText = (function(_super) { toolbar_items_size: 'small', extended_valid_elements : "iframe[src|frameborder|style|scrolling|class|width|height|name|align|id]", toolbar: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media rubric | code ", + resize: "both", } }; @@ -125,7 +125,7 @@ Annotator.Plugin.RichText = (function(_super) { RichText.prototype.updateEditor = function(field, annotation) { var text = typeof annotation.text!='undefined'?annotation.text:''; - tinymce.activeEditor.setContent(text); + tinymce.activeEditor.setContent(text); $(field).remove(); //this is the auto create field by annotator and it is not necessary } diff --git a/common/static/js/vendor/ova/share-annotator.js b/common/static/js/vendor/ova/share-annotator.js index 013933703f36..b6648e32456d 100644 --- a/common/static/js/vendor/ova/share-annotator.js +++ b/common/static/js/vendor/ova/share-annotator.js @@ -1,6 +1,6 @@ /* Share Annotation Plugin v1.0 (https://github.com/danielcebrian/share-annotator) -Copyright (C) 2014 Daniel Cebri�n Robles +Copyright (C) 2014 Daniel Cebrian Robles License: https://github.com/danielcebrian/share-annotator/blob/master/License.rst This program is free software; you can redistribute it and/or @@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// Generated by CoffeeScript 1.6.3 var _ref, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = {}.hasOwnProperty, diff --git a/common/static/js/vendor/ova/tags-annotator.js b/common/static/js/vendor/ova/tags-annotator.js index 36a28fa9de82..69cf5e3df7e2 100644 --- a/common/static/js/vendor/ova/tags-annotator.js +++ b/common/static/js/vendor/ova/tags-annotator.js @@ -1,3 +1,27 @@ +/* +HighlightTags Annotator Plugin v1.0 (https://github.com/lduarte1991/tags-annotator) + Copyright (C) 2014 Luis F Duarte + License: https://github.com/lduarte1991/tags-annotator/blob/master/LICENSE.rst + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/*=============================================================================== + =============================================================================== + =============================================================================== + =============================================================================== + ==============================================================================*/ /* * jQuery Plugin: Tokenizing Autocomplete Text Entry * Version 1.6.0 @@ -22,7 +46,7 @@ var DEFAULT_SETTINGS = { // Display settings hintText: "Type in a search term", - noResultsText: "No results", + noResultsText: "Not Found. Hit ENTER to add a personal tag.", searchingText: "Searching...", deleteText: "×", animateDropdown: true, @@ -271,6 +295,9 @@ $.TokenList = function (input, url_or_data, settings) { add_token($(selected_dropdown_item).data("tokeninput")); hidden_input.change(); return false; + } else { + add_token({id:$(this).val(), name:$(this).val()}); + hidden_input.change(); } break; @@ -859,7 +886,6 @@ $.TokenList.Cache = function (options) { }; }(jQuery)); -// Generated by CoffeeScript 1.6.3 var _ref, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = {}.hasOwnProperty, @@ -886,7 +912,7 @@ Annotator.Plugin.HighlightTags = (function(_super) { HighlightTags.prototype.field = null; HighlightTags.prototype.input = null; HighlightTags.prototype.colors = null; - HighlightTags.prototype.isFirstTime = true; + HighlightTags.prototype.isFirstTime = true; //this function will initialize the plug in. Create your fields here in the editor and viewer. HighlightTags.prototype.pluginInit = function() { diff --git a/common/test/data/tilde/about/index.html~ b/common/test/data/tilde/about/index.html~ deleted file mode 100644 index f24fc29aa68b..000000000000 --- a/common/test/data/tilde/about/index.html~ +++ /dev/null @@ -1 +0,0 @@ -RED diff --git a/common/test/data/tilde/static/example.txt~ b/common/test/data/tilde/static/example.txt~ deleted file mode 100644 index f24fc29aa68b..000000000000 --- a/common/test/data/tilde/static/example.txt~ +++ /dev/null @@ -1 +0,0 @@ -RED diff --git a/lms/djangoapps/notes/views.py b/lms/djangoapps/notes/views.py index b6670a7e0923..de459cd3512a 100644 --- a/lms/djangoapps/notes/views.py +++ b/lms/djangoapps/notes/views.py @@ -4,7 +4,7 @@ from courseware.courses import get_course_with_access from notes.models import Note from notes.utils import notes_enabled_for_course - +from xmodule.annotator_token import retrieve_token @login_required def notes(request, course_id): @@ -22,7 +22,7 @@ def notes(request, course_id): 'course': course, 'notes': notes, 'student': student, - 'storage': storage + 'storage': storage, + 'token': retrieve_token(student.email, course.annotation_token_secret), } - return render_to_response('notes.html', context) diff --git a/lms/envs/common.py b/lms/envs/common.py index ebc339db144c..3c3ec5d0b074 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -807,6 +807,7 @@ 'js/vendor/ova/tags-annotator.js', 'js/vendor/ova/flagging-annotator.js', 'js/vendor/ova/jquery-Watch.js', + 'js/vendor/ova/diacritic-annotator.js', 'js/vendor/ova/ova.js', 'js/vendor/ova/catch/js/catch.js', 'js/vendor/ova/catch/js/handlebars-1.1.2.js', @@ -833,6 +834,7 @@ 'css/vendor/ova/share-annotator.css', 'css/vendor/ova/richText-annotator.css', 'css/vendor/ova/tags-annotator.css', + 'css/vendor/ova/diacritic-annotator.css', 'css/vendor/ova/flagging-annotator.css', 'css/vendor/ova/ova.css', 'js/vendor/ova/catch/css/main.css' diff --git a/lms/templates/notes.html b/lms/templates/notes.html index d8967255814f..e44a78b08ec0 100644 --- a/lms/templates/notes.html +++ b/lms/templates/notes.html @@ -68,10 +68,8 @@

${_('My Notes')}

//Grab uri of the course var parts = window.location.href.split("/"), - uri = '', - courseid; + uri = ''; for (var index = 0; index <= 6; index += 1) uri += parts[index]+"/"; //Get the unit url - courseid = parts[4] + "/" + parts[5] + "/" + parts[6]; var pagination = 100, is_staff = false, options = { @@ -130,7 +128,7 @@

${_('My Notes')}

}, }, auth: { - tokenUrl: location.protocol+'//'+location.host+"/token?course_id="+courseid + token: "${token}" }, store: { // The endpoint of the store on your server. diff --git a/lms/templates/textannotation.html b/lms/templates/textannotation.html index 35326810511c..48f76ce35205 100644 --- a/lms/templates/textannotation.html +++ b/lms/templates/textannotation.html @@ -43,17 +43,13 @@ $(this).parents('.annotatable-section:first').find('.annotatable-instructions')[slideMethod](); } $('.annotatable-toggle-instructions').on('click', onClickHideInstructions); - //Grab uri of the course - var parts = window.location.href.split("/"), - uri = '', - courseid; + var parts = window.location.href.split("/"), + uri = ''; for (var index = 0; index <= 9; index += 1) uri += parts[index]+"/"; //Get the unit url - courseid = parts[4] + "/" + parts[5] + "/" + parts[6]; //Change uri in cms var lms_location = $('.sidebar .preview-button').attr('href'); if (typeof lms_location!='undefined'){ - courseid = parts[4].split(".").join("/"); uri = window.location.protocol; for (var index = 0; index <= 9; index += 1) uri += lms_location.split("/")[index]+"/"; //Get the unit url } @@ -115,7 +111,7 @@ }, }, auth: { - tokenUrl: location.protocol+'//'+location.host+"/token?course_id="+courseid + token: "${token}" }, store: { // The endpoint of the store on your server. @@ -145,6 +141,9 @@ }, highlightTags:{ tag: "${tag}", + }, + diacriticMarks:{ + diacritics: "${diacritic_marks}" } }, optionsVideoJS: {techOrder: ["html5","flash","youtube"]}, @@ -161,31 +160,39 @@ } }, }; - - var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/"; - tinyMCE.baseURL = "${settings.STATIC_URL}" + "js/vendor/ova"; - - //remove old instances - if (Annotator._instances.length !== 0) { - $('#textHolder').annotator("destroy"); - } - delete ova; - //Load the plugin Video/Text Annotation - var ova = new OpenVideoAnnotation.Annotator($('#textHolder'),options); - - - //Catch - var annotator = ova.annotator, + tinyMCE.baseURL = "${settings.STATIC_URL}" + "js/vendor/ova"; + var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/"; + var startova = function(){ + //remove old instances + if (Annotator._instances.length !== 0) { + $('#textHolder').annotator("destroy"); + } + + delete ova; + //Load the plugin Video/Text Annotation + var ova = new OpenVideoAnnotation.Annotator($('#textHolder'),options); + + //Catch + var annotator = ova.annotator, catchOptions = { - media:'text', - externalLink:false, - imageUrlRoot:imgURLRoot, - showMediaSelector: false, - showPublicPrivate: true, - userId:'${user.email}', - pagination:pagination,//Number of Annotations per load in the pagination, - flags:is_staff + media:'text', + externalLink:false, + imageUrlRoot:imgURLRoot, + showMediaSelector: false, + showPublicPrivate: true, + userId:'${user.email}', + pagination:pagination,//Number of Annotations per load in the pagination, + flags:is_staff }, Catch = new CatchAnnotation($('#catchDIV'),catchOptions); + } + + if (typeof Annotator != 'undefined'){ + startova(); + } else{ + require(["ova"], function(ova){ + startova(); + }); + } diff --git a/lms/templates/videoannotation.html b/lms/templates/videoannotation.html index 407ec43193a0..d5f959b0cbba 100644 --- a/lms/templates/videoannotation.html +++ b/lms/templates/videoannotation.html @@ -48,15 +48,13 @@ $('.annotatable-toggle-instructions').on('click', onClickHideInstructions); //Grab uri of the course - var parts = window.location.href.split("/"), - uri = '', - courseid; + var parts = window.location.href.split("/"), + uri = ''; + for (var index = 0; index <= 9; index += 1) uri += parts[index]+"/"; //Get the unit url - courseid = parts[4] + "/" + parts[5] + "/" + parts[6]; //Change uri in cms var lms_location = $('.sidebar .preview-button').attr('href'); if (typeof lms_location!='undefined'){ - courseid = parts[4].split(".").join("/"); uri = window.location.protocol; for (var index = 0; index <= 9; index += 1) uri += lms_location.split("/")[index]+"/"; //Get the unit url } @@ -119,7 +117,7 @@ }, }, auth: { - tokenUrl: location.protocol+'//'+location.host+"/token?course_id="+courseid + token: "${token}" }, store: { // The endpoint of the store on your server. @@ -127,7 +125,6 @@ annotationData: { uri: uri, - }, urls: { @@ -162,33 +159,42 @@ } }, }; - - var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/"; - tinyMCE.baseURL = "${settings.STATIC_URL}" + "js/vendor/ova"; - - //remove old instances - if (Annotator._instances.length !== 0) { - $('#videoHolder').annotator("destroy"); - } - delete ova; - //Load the plugin Video/Text Annotation - var ova = new OpenVideoAnnotation.Annotator($('#videoHolder'),options); - - ova.annotator.addPlugin('Tags'); - + tinyMCE.baseURL = "${settings.STATIC_URL}" + "js/vendor/ova"; + var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/"; + var startova = function(){ - //Catch - var annotator = ova.annotator, + //remove old instances + if (Annotator._instances.length !== 0) { + $('#videoHolder').annotator("destroy"); + } + delete ova; + + //Load the plugin Video/Text Annotation + var ova = new OpenVideoAnnotation.Annotator($('#videoHolder'),options); + ova.annotator.addPlugin('Tags'); + + //Catch + var annotator = ova.annotator, catchOptions = { - media:'video', - externalLink:false, - imageUrlRoot:imgURLRoot, - showMediaSelector: false, - showPublicPrivate: true, - userId:'${user.email}', - pagination:pagination,//Number of Annotations per load in the pagination, - flags:is_staff - }, + media:'video', + externalLink:false, + imageUrlRoot:imgURLRoot, + showMediaSelector: false, + showPublicPrivate: true, + userId:'${user.email}', + pagination:pagination,//Number of Annotations per load in the pagination, + flags:is_staff + }, + Catch = new CatchAnnotation($('#catchDIV'),catchOptions); + } + + if (typeof Annotator != 'undefined'){ + startova(); + } else{ + require(["ova"], function(ova){ + startova(); + }); + } diff --git a/lms/urls.py b/lms/urls.py index bf3cc089cda0..34a8f7ff8286 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -14,7 +14,6 @@ url(r'^update_certificate$', 'certificates.views.update_certificate'), url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware url(r'^dashboard$', 'student.views.dashboard', name="dashboard"), - url(r'^token$', 'student.views.token', name="token"), url(r'^login$', 'student.views.signin_user', name="signin_user"), url(r'^register$', 'student.views.register_user', name="register_user"), From 38c5ee8f20e844902a4045c547ec23b4d4c219d9 Mon Sep 17 00:00:00 2001 From: lduarte1991 Date: Fri, 28 Feb 2014 14:53:54 -0500 Subject: [PATCH 02/18] Added padding to the top of the title (only when it's in the annotatable-wrapper) --- common/static/css/vendor/ova/edx-annotator.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/static/css/vendor/ova/edx-annotator.css b/common/static/css/vendor/ova/edx-annotator.css index 9b0c0de0b8fa..a4bc6f91ca2f 100644 --- a/common/static/css/vendor/ova/edx-annotator.css +++ b/common/static/css/vendor/ova/edx-annotator.css @@ -1,4 +1,8 @@ /*This is written to fix some design problems with edX*/ +.annotatable-wrapper .annotatable-header .annotatable-title{ + padding-top: 20px !important; +} + .annotator-wrapper .annotator-adder button { opacity:0; } From 9b0917a1d28f732d2c645deefd2aa609820779da Mon Sep 17 00:00:00 2001 From: lduarte1991 Date: Fri, 28 Feb 2014 16:22:24 -0500 Subject: [PATCH 03/18] Added logic to make sure that when adding a new component, only 1 annotation component is allowed per unit. --- cms/static/coffee/src/views/unit.coffee | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cms/static/coffee/src/views/unit.coffee b/cms/static/coffee/src/views/unit.coffee index 8b2bc885d08b..bd3fca3fa9a0 100644 --- a/cms/static/coffee/src/views/unit.coffee +++ b/cms/static/coffee/src/views/unit.coffee @@ -117,6 +117,24 @@ define ["jquery", "jquery.ui", "gettext", "backbone", return editor saveNewComponent: (event) => + cat = $(event.currentTarget).data().category + if(cat == "textannotation" || cat == "videoannotation") + found = false + msg = new PromptView.Warning( + title: gettext('Reached Annotation Component Limit'), + message: gettext('Only one (1) annotation item can exist per unit.'), + actions: + primary: + text: gettext('OK'), + click: (view) => + @closeNewComponent(event) + view.hide() + ) + @components().forEach (element)=> + if(element.indexOf("textannotation") != -1 || element.indexOf("videoannotation")!= -1) + found = true; + if(found) + return msg.show() success_callback = => @$newComponentItem.before(editor.$el) editor = @createComponent( From 8ead1339dcadd377d98070a68489ff3d95935bb3 Mon Sep 17 00:00:00 2001 From: lduarte1991 Date: Fri, 28 Feb 2014 17:17:46 -0500 Subject: [PATCH 04/18] Revert "Added logic to make sure that when adding a new component, only 1 annotation component is allowed per unit." This reverts commit 25797a7b08a63d4c4efad9b0bc1885628f5caad3. --- cms/static/coffee/src/views/unit.coffee | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/cms/static/coffee/src/views/unit.coffee b/cms/static/coffee/src/views/unit.coffee index bd3fca3fa9a0..8b2bc885d08b 100644 --- a/cms/static/coffee/src/views/unit.coffee +++ b/cms/static/coffee/src/views/unit.coffee @@ -117,24 +117,6 @@ define ["jquery", "jquery.ui", "gettext", "backbone", return editor saveNewComponent: (event) => - cat = $(event.currentTarget).data().category - if(cat == "textannotation" || cat == "videoannotation") - found = false - msg = new PromptView.Warning( - title: gettext('Reached Annotation Component Limit'), - message: gettext('Only one (1) annotation item can exist per unit.'), - actions: - primary: - text: gettext('OK'), - click: (view) => - @closeNewComponent(event) - view.hide() - ) - @components().forEach (element)=> - if(element.indexOf("textannotation") != -1 || element.indexOf("videoannotation")!= -1) - found = true; - if(found) - return msg.show() success_callback = => @$newComponentItem.before(editor.$el) editor = @createComponent( From 1cc32ee1f53038daa0a2794fbcad6bf7c03cd71b Mon Sep 17 00:00:00 2001 From: lduarte1991 Date: Wed, 19 Mar 2014 12:16:56 -0400 Subject: [PATCH 05/18] Changed the location of the javascript files so they were attached to the module and not loaded everywhere else MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: For some reason it’s not reading the image from the static url even though the links seem to be valid. --- cms/templates/base.html | 4 +- common/lib/xmodule/xmodule/annotator_mixin.py | 59 + .../xmodule/xmodule/css}/ova/annotator.css | 0 .../xmodule/css}/ova/diacritic-annotator.css | 0 .../xmodule/css}/ova/edx-annotator.css | 0 .../xmodule/css}/ova/flagging-annotator.css | 0 .../xmodule/xmodule/css}/ova/ova.css | 0 .../xmodule/xmodule/css}/ova/rangeslider.css | 0 .../xmodule/css}/ova/richText-annotator.css | 0 .../xmodule/css}/ova/share-annotator.css | 0 .../xmodule/css}/ova/tags-annotator.css | 0 .../xmodule/css}/ova/video-js.font.dev.svg | 0 .../xmodule/xmodule/css}/ova/video-js.fw.png | Bin .../xmodule/xmodule/css}/ova/video-js.less | 0 .../xmodule/xmodule/css}/ova/video-js.min.css | 0 .../xmodule/xmodule/css}/ova/video-js.png | Bin .../xmodule/js/src}/ova/catch/css/main.css | 0 .../js/src/ova/catch/img/closeIcon.png | Bin 0 -> 1114 bytes .../js/src/ova/catch/img/expandableIcon.png | Bin 0 -> 1279 bytes .../ova/catch/img/expandableIcon_Arrow.png | Bin 0 -> 3002 bytes .../xmodule/js/src/ova/catch/img/fish.jpg | Bin 0 -> 11572 bytes .../js/src/ova/catch/img/geolocation_icon.png | Bin 0 -> 740 bytes .../js/src/ova/catch/img/groups_icon.png | Bin 0 -> 747 bytes .../js/src/ova/catch/img/privacy_icon.png | Bin 0 -> 635 bytes .../js/src/ova/catch/img/shareControls.png | Bin 0 -> 9691 bytes .../js/src/ova/catch/img/share_icon.png | Bin 0 -> 655 bytes .../xmodule/xmodule/textannotation_module.py | 13 +- .../xmodule/xmodule/videoannotation_module.py | 36 +- common/static/js/vendor/ova/annotator-full.js | 3375 --------- common/static/js/vendor/ova/catch/js/catch.js | 1105 --- .../vendor/ova/catch/js/handlebars-1.1.2.js | 2595 ------- .../js/vendor/ova/diacritic-annotator.js | 203 - .../js/vendor/ova/flagging-annotator.js | 103 - common/static/js/vendor/ova/jquery-Watch.js | 116 - common/static/js/vendor/ova/ova.js | 2555 ------- common/static/js/vendor/ova/rangeslider.js | 1078 --- .../static/js/vendor/ova/reply-annotator.js | 98 - .../js/vendor/ova/richText-annotator.js | 142 - .../static/js/vendor/ova/share-annotator.js | 498 -- common/static/js/vendor/ova/tags-annotator.js | 1126 --- common/static/js/vendor/ova/tinymce.min.js | 9 - common/static/js/vendor/ova/video.dev.js | 6461 ----------------- common/static/js/vendor/ova/vjs.youtube.js | 605 -- lms/envs/common.py | 26 - 44 files changed, 75 insertions(+), 20132 deletions(-) create mode 100644 common/lib/xmodule/xmodule/annotator_mixin.py rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/annotator.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/diacritic-annotator.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/edx-annotator.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/flagging-annotator.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/ova.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/rangeslider.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/richText-annotator.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/share-annotator.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/tags-annotator.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/video-js.font.dev.svg (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/video-js.fw.png (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/video-js.less (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/video-js.min.css (100%) rename common/{static/css/vendor => lib/xmodule/xmodule/css}/ova/video-js.png (100%) rename common/{static/js/vendor => lib/xmodule/xmodule/js/src}/ova/catch/css/main.css (100%) create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/closeIcon.png create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/expandableIcon.png create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/expandableIcon_Arrow.png create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/fish.jpg create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/geolocation_icon.png create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/groups_icon.png create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/privacy_icon.png create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/shareControls.png create mode 100644 common/lib/xmodule/xmodule/js/src/ova/catch/img/share_icon.png delete mode 100644 common/static/js/vendor/ova/annotator-full.js delete mode 100644 common/static/js/vendor/ova/catch/js/catch.js delete mode 100644 common/static/js/vendor/ova/catch/js/handlebars-1.1.2.js delete mode 100644 common/static/js/vendor/ova/diacritic-annotator.js delete mode 100644 common/static/js/vendor/ova/flagging-annotator.js delete mode 100644 common/static/js/vendor/ova/jquery-Watch.js delete mode 100644 common/static/js/vendor/ova/ova.js delete mode 100644 common/static/js/vendor/ova/rangeslider.js delete mode 100644 common/static/js/vendor/ova/reply-annotator.js delete mode 100644 common/static/js/vendor/ova/richText-annotator.js delete mode 100644 common/static/js/vendor/ova/share-annotator.js delete mode 100644 common/static/js/vendor/ova/tags-annotator.js delete mode 100644 common/static/js/vendor/ova/tinymce.min.js delete mode 100644 common/static/js/vendor/ova/video.dev.js delete mode 100644 common/static/js/vendor/ova/vjs.youtube.js diff --git a/cms/templates/base.html b/cms/templates/base.html index 658c7f41de9d..d7b4c4eb0bac 100644 --- a/cms/templates/base.html +++ b/cms/templates/base.html @@ -109,7 +109,7 @@ // and leaves the path component intact. "//www.youtube.com/player_api?noext", // if youtube fails to load, fallback on a local file - // so that require doesnt fall over + // so that require doesn't fall over "js/src/youtube_fallback" ] }, @@ -275,7 +275,7 @@ "ova":{ exports: "ova", deps: ["annotator", "video.dev", "vjs.youtube", "rangeslider", "share-annotator", "tinymce", "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator", "diacritic-annotator", "jquery-Watch", "catch", "handlebars", "URI"] - } + }, }, // load jquery and gettext automatically deps: ["jquery", "gettext"], diff --git a/common/lib/xmodule/xmodule/annotator_mixin.py b/common/lib/xmodule/xmodule/annotator_mixin.py new file mode 100644 index 000000000000..708d0212ad70 --- /dev/null +++ b/common/lib/xmodule/xmodule/annotator_mixin.py @@ -0,0 +1,59 @@ +''' +Annotations Tool Mixin +This file contains global variables and functions used in the various Annotation Tools. +''' +from pkg_resources import resource_string +from lxml import etree + +def getInstructions(xmltree): + """ Removes from the xmltree and returns them as a string, otherwise None. """ + instructions = xmltree.find('instructions') + if instructions is not None: + instructions.tag = 'div' + xmltree.remove(instructions) + return etree.tostring(instructions, encoding='unicode') + return None + +def getExtension(srcurl): + ''' get the extension of a given url ''' + if 'youtu' in srcurl: + return 'video/youtube' + else: + spliturl = srcurl.split(".") + extensionplus1 = spliturl[len(spliturl) - 1] + spliturl = extensionplus1.split("?") + extensionplus2 = spliturl[0] + spliturl = extensionplus2.split("#") + return 'video/' + spliturl[0] + +ANNOTATOR_COMMON_JS = [ + resource_string(__name__, 'js/src/ova/annotator-full.js'), + resource_string(__name__, 'js/src/ova/video.dev.js'), + resource_string(__name__, 'js/src/ova/vjs.youtube.js'), + resource_string(__name__, 'js/src/ova/rangeslider.js'), + resource_string(__name__, 'js/src/ova/share-annotator.js'), + resource_string(__name__, 'js/src/ova/tinymce.min.js'), + resource_string(__name__, 'js/src/ova/richText-annotator.js'), + resource_string(__name__, 'js/src/ova/reply-Annotator.js'), + resource_string(__name__, 'js/src/ova/tags-annotator.js'), + resource_string(__name__, 'js/src/ova/flagging-annotator.js'), + resource_string(__name__, 'js/src/ova/jquery-Watch.js'), + resource_string(__name__, 'js/src/ova/diacritic-annotator.js'), + resource_string(__name__, 'js/src/ova/ova.js'), + resource_string(__name__, 'js/src/ova/catch/js/catch.js'), + resource_string(__name__, 'js/src/ova/catch/js/handlebars-1.1.2.js'), +] + +ANNOTATOR_COMMON_CSS = [ + resource_string(__name__, 'css/ova/edx-annotator.css'), + resource_string(__name__, 'css/ova/annotator.css'), + resource_string(__name__, 'css/ova/video-js.min.css'), + resource_string(__name__, 'css/ova/rangeslider.css'), + resource_string(__name__, 'css/ova/share-annotator.css'), + resource_string(__name__, 'css/ova/richText-annotator.css'), + resource_string(__name__, 'css/ova/tags-annotator.css'), + resource_string(__name__, 'css/ova/diacritic-annotator.css'), + resource_string(__name__, 'css/ova/flagging-annotator.css'), + resource_string(__name__, 'css/ova/ova.css'), + resource_string(__name__, 'js/src/ova/catch/css/main.css'), +] \ No newline at end of file diff --git a/common/static/css/vendor/ova/annotator.css b/common/lib/xmodule/xmodule/css/ova/annotator.css similarity index 100% rename from common/static/css/vendor/ova/annotator.css rename to common/lib/xmodule/xmodule/css/ova/annotator.css diff --git a/common/static/css/vendor/ova/diacritic-annotator.css b/common/lib/xmodule/xmodule/css/ova/diacritic-annotator.css similarity index 100% rename from common/static/css/vendor/ova/diacritic-annotator.css rename to common/lib/xmodule/xmodule/css/ova/diacritic-annotator.css diff --git a/common/static/css/vendor/ova/edx-annotator.css b/common/lib/xmodule/xmodule/css/ova/edx-annotator.css similarity index 100% rename from common/static/css/vendor/ova/edx-annotator.css rename to common/lib/xmodule/xmodule/css/ova/edx-annotator.css diff --git a/common/static/css/vendor/ova/flagging-annotator.css b/common/lib/xmodule/xmodule/css/ova/flagging-annotator.css similarity index 100% rename from common/static/css/vendor/ova/flagging-annotator.css rename to common/lib/xmodule/xmodule/css/ova/flagging-annotator.css diff --git a/common/static/css/vendor/ova/ova.css b/common/lib/xmodule/xmodule/css/ova/ova.css similarity index 100% rename from common/static/css/vendor/ova/ova.css rename to common/lib/xmodule/xmodule/css/ova/ova.css diff --git a/common/static/css/vendor/ova/rangeslider.css b/common/lib/xmodule/xmodule/css/ova/rangeslider.css similarity index 100% rename from common/static/css/vendor/ova/rangeslider.css rename to common/lib/xmodule/xmodule/css/ova/rangeslider.css diff --git a/common/static/css/vendor/ova/richText-annotator.css b/common/lib/xmodule/xmodule/css/ova/richText-annotator.css similarity index 100% rename from common/static/css/vendor/ova/richText-annotator.css rename to common/lib/xmodule/xmodule/css/ova/richText-annotator.css diff --git a/common/static/css/vendor/ova/share-annotator.css b/common/lib/xmodule/xmodule/css/ova/share-annotator.css similarity index 100% rename from common/static/css/vendor/ova/share-annotator.css rename to common/lib/xmodule/xmodule/css/ova/share-annotator.css diff --git a/common/static/css/vendor/ova/tags-annotator.css b/common/lib/xmodule/xmodule/css/ova/tags-annotator.css similarity index 100% rename from common/static/css/vendor/ova/tags-annotator.css rename to common/lib/xmodule/xmodule/css/ova/tags-annotator.css diff --git a/common/static/css/vendor/ova/video-js.font.dev.svg b/common/lib/xmodule/xmodule/css/ova/video-js.font.dev.svg similarity index 100% rename from common/static/css/vendor/ova/video-js.font.dev.svg rename to common/lib/xmodule/xmodule/css/ova/video-js.font.dev.svg diff --git a/common/static/css/vendor/ova/video-js.fw.png b/common/lib/xmodule/xmodule/css/ova/video-js.fw.png similarity index 100% rename from common/static/css/vendor/ova/video-js.fw.png rename to common/lib/xmodule/xmodule/css/ova/video-js.fw.png diff --git a/common/static/css/vendor/ova/video-js.less b/common/lib/xmodule/xmodule/css/ova/video-js.less similarity index 100% rename from common/static/css/vendor/ova/video-js.less rename to common/lib/xmodule/xmodule/css/ova/video-js.less diff --git a/common/static/css/vendor/ova/video-js.min.css b/common/lib/xmodule/xmodule/css/ova/video-js.min.css similarity index 100% rename from common/static/css/vendor/ova/video-js.min.css rename to common/lib/xmodule/xmodule/css/ova/video-js.min.css diff --git a/common/static/css/vendor/ova/video-js.png b/common/lib/xmodule/xmodule/css/ova/video-js.png similarity index 100% rename from common/static/css/vendor/ova/video-js.png rename to common/lib/xmodule/xmodule/css/ova/video-js.png diff --git a/common/static/js/vendor/ova/catch/css/main.css b/common/lib/xmodule/xmodule/js/src/ova/catch/css/main.css similarity index 100% rename from common/static/js/vendor/ova/catch/css/main.css rename to common/lib/xmodule/xmodule/js/src/ova/catch/css/main.css diff --git a/common/lib/xmodule/xmodule/js/src/ova/catch/img/closeIcon.png b/common/lib/xmodule/xmodule/js/src/ova/catch/img/closeIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..cebf5d53be484da05392de61cd7bacd568cdc952 GIT binary patch literal 1114 zcmeAS@N?(olHy`uVBq!ia0vp^xj-Dj!3HG%`&W7}Ft8|kx;TbZ%y~QaVE5Hfk>mF6 z=>->No^nvknkd+&@bHjp_m$o*?$xf{oBlP&a0Z<&+_v?cYjv}bqoeDEr7!d(1X?-@ z5PN+RF zC}5buJWr&|ko^zSoY1S*M;UnpC+YPx%nb#nonJx z{=0s7>8!ckCsr={W@)_ko3`%86|1VXuD@vQn6m%l^vSi$PR`AozgF2tPv3umo1s!r zky#Y~?jq@DY`^xTPf(bzypvb`tJbN9%Ssy(mz@4&ea&*$7L~VKN*e^{Z05VLR5VjO z^y{wDj;#S#Cts7?72zGt#JBPl`!UOT-oJE8?^v;4nYxYt#-?K*g$x$2iVpo6b(AOB zHPm0d(wOI%K~9eGfupl5muY>g4G;al>c)P_Rl9g})_*M*v}BiA8+!lOmmiBZ?M!>F zcvg`@Wct2ZMp{YfKpva6_dP%U>Fdhl&dL<@AM9Of|26J^j>G3mE>E~0-0o6+7Ce_V z!SVMS8H2=GcD7w7oL_OhHsZJO-MlMv*3@a&6!#|u{OWN$t2ArMv}tF~Nl&$(R#ucT zO>HUTddche^J3~%)_QGz#`~IKF-PX%%Ij7KG@_=O<;MKqRQPw|7Ol7Q3jfykyzXD( znVGrt>7>NJ_t#`Szp!M>k!ZKla*3;Fmqli-`Xx}2ekk>O<&FAHk(msueih&HeIT$Q zV&xg74~iQWKHclH%jAEnp~J0hN&G#v&4yn;uzt*n@p%2{WxU4Lj9*_Qc+{6zs!)cO zEZedyd|t@1SN;>O?ujg&-eDsbl&osI?akNkS6x#24ob}HU3%K+L*dz9rb}aM)Dlk3+L|%I=-YJ@K#7r+uXzf;Gvq z3%s>2G3?vKXVA3jec;k9D!JQ1$!lq7e$?|9leFLNUZu&@puLl~c%}L9!z#|*~a)? zyLA7ORngw_Ce$iU`u6R{i67}lgg#`gKJ;L}v2X9%>HBm<*;tL=*82<1WV*-9$F%u? zd~56(ru|IfQTH=aO_eqY|KPZB>G8|W2j27OS>=6=UGiUFO7@B7$1B$#0m}>qPgg&e IbxsLQ08APYy8r+H literal 0 HcmV?d00001 diff --git a/common/lib/xmodule/xmodule/js/src/ova/catch/img/expandableIcon.png b/common/lib/xmodule/xmodule/js/src/ova/catch/img/expandableIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..f9709f57233c4646af4d36bd89ce49c60b1f5f74 GIT binary patch literal 1279 zcmV zO=w+37>1wkHnFiyv=u6dpmrk!N>Rw7f=xFnF8o=z@NZK$1#8nHr(Un0< z!D_{VZmgxQ1BD`p2B`~m(b)PI4Sii?60ad?awd1a$(%b6oJDfy%=>Yk`*ZF+GjmK5 z-I&>S;1S>uFalho86&{Az)4B74fkygW;O;qL;Nwk27ClukaYcjk4hql8vx!1)Z=Z> zQbs>)z*vG~IMtnDk}$I!!0!#84NVx7w2-j>ErFTc30zLqza#_bgugGO3JScOxc{xf zrztx(vnc=rl0G{EGm`F0oRJ+f)B3G8&0q(3&LQYlhm5S4S!exLJz=y1Jns}%=?#y1 zJv+X3%E?AJEuZzs@{)GKDJM6lJVHFAoso3jAtyVMCOvXeN}B^7mh^bSj4em_E;W%d zBUai$VCNd!Ex?eZ-IBiONtJ~m=|qq>#NzQ-?KlkqXJYyvh^*_fq(36}?HeS`1M_P= zZf2)|$C~!*bs|z(ogFbdRcQs%Drsi+l9_#PW>>=YrJ3EEyRKdn`M9S7 z1K*h0v?Pz8>s%=Qm1btf>cYBt*391asK&F0C)A(h8(XE08L!K&rF? zsnQChN-K~mtw5@@0;$ppq)IE0(xv^~u>G$|s&FRT7!flY07ij(fIlOD~iBX+6_icmB`0tc23eE z?_lrGv>%-FQZsI5hrL7Hq=iAFyS(xoc{gIL%BJvqrNy2Alt62xwVAs-!>Y92;QCqZ zDebglmNy>jhb-eJEqo!J@yc^#LMvB=ON{s1k(Rv=YcfmCS) zQl%A0>Cy&s7m$O}rTv_{fE<)At?3+qi%F_*rf6vq7a~7PQiwA}OA8!r&~`dm6^^t) z+FKiJH!bN((n_2tURoHoaQiyV&eb)Hw-}Os0k#3hRy;Hd9LzBtDJ89t28*}Ck~AgB zy0*P-yg5;8r1cY3T7gt)1yZFINUXHJTst@cQN!V5hlP)t+0|9{UkLx3ulHA5Ud>^E z|Bg#H^T1Ov{l-dLcP)-Ecl>EdA4X;rNtiT03A~;vBlQG~56o<$ZC`HJA_%*lI>+Ng zWZcYJal4QW@PPa~O_&3oYJ}LFq}>os zM8ciVx6%7o#2j#2KL~^6i?RC?bbWM57=W!D&=giaJ;36sal^oG4FiUO?|{>}myOLy pSSs;JU}wbc!@y6#Syoy8;$QRYDyGx`{s;g7002ovPDHLkV1nuAVeJ3_ literal 0 HcmV?d00001 diff --git a/common/lib/xmodule/xmodule/js/src/ova/catch/img/expandableIcon_Arrow.png b/common/lib/xmodule/xmodule/js/src/ova/catch/img/expandableIcon_Arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..52e633bdd7f780b8c327c8cfa1706dc695d8dc13 GIT binary patch literal 3002 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 literal 0 HcmV?d00001 diff --git a/common/lib/xmodule/xmodule/js/src/ova/catch/img/fish.jpg b/common/lib/xmodule/xmodule/js/src/ova/catch/img/fish.jpg new file mode 100644 index 0000000000000000000000000000000000000000..519f6fa0a75d9a628ba9f0d9fabcf56374a83d18 GIT binary patch literal 11572 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 literal 0 HcmV?d00001 diff --git a/common/lib/xmodule/xmodule/js/src/ova/catch/img/geolocation_icon.png b/common/lib/xmodule/xmodule/js/src/ova/catch/img/geolocation_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9112bf1b11e65aca3a8acbe5cba56ed4f172c391 GIT binary patch literal 740 zcmeAS@N?(olHy`uVBq!ia0vp^`XJ1~3?yBCCeH&>%*9TgAsieWw;%dH0CGJ7d_r8$ zoH_IU{rmIh&;S4b|MuC+EU%veP`}g(h*PlLpdiU<#&!0cPe*OAh(Ir-(nGz*Ie!)O33_y$lwpFbJxrwvD zBeIx*fm;ZK886+f`vVjdEpd$~aV|a0pR+&8J3s$MrDk5G;>7Z< zyps8xVv8kpV#C&bP!Y>gJb3w;uZYvLPSd#$&#$!I=`3fn@71Q~-a(Uoy9&qIz1wqq z0pq*2pC7KPi7U6=Qs1}ahIJZqQO%^vbu+Fo%70#8sTCRcDASRVw?jI9kL&mV-Q!*qQMB^@k2m%yCnhORw3Bd+p3)k&LoU5g%==uR^!)3wa}TE^ zpS@phw;^12T2IMUmM4|Ik5Aed|2uDfbMoWQSMPV|ukrY!IPVn`zskJEJ@4c1tycfZ z{8K>J%BwFvaQYMVC$lBB>r#G-W&T^`p_LbV zV~673%{%SQzyE*LCR{&Dzostq^NXyyjXzI3zx%c3%G%H3OLF%$Z~0rlW2(GdqR4g6 TtqgTe~DWM4ffh2Ya literal 0 HcmV?d00001 diff --git a/common/lib/xmodule/xmodule/js/src/ova/catch/img/groups_icon.png b/common/lib/xmodule/xmodule/js/src/ova/catch/img/groups_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..add33986331ba0c09ab45947ea11136d4f9a5179 GIT binary patch literal 747 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 literal 0 HcmV?d00001 diff --git a/common/lib/xmodule/xmodule/js/src/ova/catch/img/share_icon.png b/common/lib/xmodule/xmodule/js/src/ova/catch/img/share_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9635527c9494843cd6313299535c64c5c9ae9038 GIT binary patch literal 655 zcmeAS@N?(olHy`uVBq!ia0vp^`XJ1~3?yBCCeH&>%*9TgAsieWw;%dH0CN2Td_r8$ zoH=vz=FR{A|G$3y`uzFx&!0d4`Sa(~r%x|mzWnv;*Uz6nKX~xq`t|F-fB(LC@#4#u zFWae*F0A)vG{_w{PEm`}Xa_hYvq~{CIM*DnHOBsf2PwX=c3falFa-(g^ji|B6W8mRWdv#)`NLn?psGzFBks zL*C+`*}EU4JI%8Gvr6Gy<=T$p{Og@L_QyYPsXnW(Yg}0PrQ}b9mz>SrBlSL#ja57S zHJe2e?S6`;=*+#8nJOwLYNY*8acV`x$!YwOf%^*P`d6^k%N*MO?rUA7-iNOpm4Cg= z)S~a&tdFkx`Pd}q;`%8njd$Pi)Gc1ICwbS+s)^Q1tuLp{oc`$e5C6Pw#>*m0X87Ol z&AIrMk10H)V&?SUtG%DwygDz+|LcEXor(O-^^J3_eumhrOurtU^mpn0jq&?-`<|Yx zGh_bze- from the xmltree and returns them as a string, otherwise None. """ - instructions = xmltree.find('instructions') - if instructions is not None: - instructions.tag = 'div' - xmltree.remove(instructions) - return etree.tostring(instructions, encoding='unicode') - return None + return getInstructions(xmltree) def get_html(self): """ Renders parameters to template. """ diff --git a/common/lib/xmodule/xmodule/videoannotation_module.py b/common/lib/xmodule/xmodule/videoannotation_module.py index 9644c04fa997..27609fa6249c 100644 --- a/common/lib/xmodule/xmodule/videoannotation_module.py +++ b/common/lib/xmodule/xmodule/videoannotation_module.py @@ -8,6 +8,7 @@ from xmodule.raw_module import RawDescriptor from xblock.core import Scope, String from xmodule.annotator_token import retrieve_token +from xmodule.annotator_mixin import getInstructions, getExtension, ANNOTATOR_COMMON_JS, ANNOTATOR_COMMON_CSS import textwrap @@ -36,17 +37,13 @@ class AnnotatableFields(object): class VideoAnnotationModule(AnnotatableFields, XModule): '''Video Annotation Module''' - js = { - 'coffee': [ - resource_string(__name__, 'js/src/javascript_loader.coffee'), - resource_string(__name__, 'js/src/html/display.coffee'), - resource_string(__name__, 'js/src/annotatable/display.coffee'), - ], - 'js': [ - resource_string(__name__, 'js/src/collapsible.js'), - ] - } - css = {'scss': [resource_string(__name__, 'css/annotatable/display.scss')]} + js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'), + resource_string(__name__, 'js/src/collapsible.coffee'), + resource_string(__name__, 'js/src/html/display.coffee'), + resource_string(__name__, 'js/src/annotatable/display.coffee') + ], + 'js': ANNOTATOR_COMMON_JS} + css = {'scss': ANNOTATOR_COMMON_CSS + [resource_string(__name__, 'css/annotatable/display.scss')]} icon_class = 'videoannotation' def __init__(self, *args, **kwargs): @@ -62,24 +59,11 @@ def __init__(self, *args, **kwargs): def _extract_instructions(self, xmltree): """ Removes from the xmltree and returns them as a string, otherwise None. """ - instructions = xmltree.find('instructions') - if instructions is not None: - instructions.tag = 'div' - xmltree.remove(instructions) - return etree.tostring(instructions, encoding='unicode') - return None + return getInstructions(xmltree) def _get_extension(self, srcurl): ''' get the extension of a given url ''' - if 'youtu' in srcurl: - return 'video/youtube' - else: - spliturl = srcurl.split(".") - extensionplus1 = spliturl[len(spliturl) - 1] - spliturl = extensionplus1.split("?") - extensionplus2 = spliturl[0] - spliturl = extensionplus2.split("#") - return 'video/' + spliturl[0] + return getExtension def get_html(self): """ Renders parameters to template. """ diff --git a/common/static/js/vendor/ova/annotator-full.js b/common/static/js/vendor/ova/annotator-full.js deleted file mode 100644 index 3a9f7131d12d..000000000000 --- a/common/static/js/vendor/ova/annotator-full.js +++ /dev/null @@ -1,3375 +0,0 @@ -// Generated by CoffeeScript 1.6.3 -/* -** Annotator v1.2.8-dev-0acc077 -** https://github.com/okfn/annotator/ -** -** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning. -** Dual licensed under the MIT and GPLv3 licenses. -** https://github.com/okfn/annotator/blob/master/LICENSE -** -** Built at: 2013-11-25 17:25:07Z -*/ - - - -/* -// -*/ - -// Generated by CoffeeScript 1.6.3 -var findChild, getNodeName, getNodePosition, simpleXPathJQuery, simpleXPathPure; - -simpleXPathJQuery = function(relativeRoot) { - var jq; - jq = this.map(function() { - var elem, idx, path, tagName; - path = ''; - elem = this; - while ((elem != null ? elem.nodeType : void 0) === Node.ELEMENT_NODE && elem !== relativeRoot) { - tagName = elem.tagName.replace(":", "\\:"); - idx = $(elem.parentNode).children(tagName).index(elem) + 1; - idx = "[" + idx + "]"; - path = "/" + elem.tagName.toLowerCase() + idx + path; - elem = elem.parentNode; - } - return path; - }); - return jq.get(); -}; - -simpleXPathPure = function(relativeRoot) { - var getPathSegment, getPathTo, jq, rootNode; - getPathSegment = function(node) { - var name, pos; - name = getNodeName(node); - pos = getNodePosition(node); - return "" + name + "[" + pos + "]"; - }; - rootNode = relativeRoot; - getPathTo = function(node) { - var xpath; - xpath = ''; - while (node !== rootNode) { - if (node == null) { - throw new Error("Called getPathTo on a node which was not a descendant of @rootNode. " + rootNode); - } - xpath = (getPathSegment(node)) + '/' + xpath; - node = node.parentNode; - } - xpath = '/' + xpath; - xpath = xpath.replace(/\/$/, ''); - return xpath; - }; - jq = this.map(function() { - var path; - path = getPathTo(this); - return path; - }); - return jq.get(); -}; - -findChild = function(node, type, index) { - var child, children, found, name, _i, _len; - if (!node.hasChildNodes()) { - throw new Error("XPath error: node has no children!"); - } - children = node.childNodes; - found = 0; - for (_i = 0, _len = children.length; _i < _len; _i++) { - child = children[_i]; - name = getNodeName(child); - if (name === type) { - found += 1; - if (found === index) { - return child; - } - } - } - throw new Error("XPath error: wanted child not found."); -}; - -getNodeName = function(node) { - var nodeName; - nodeName = node.nodeName.toLowerCase(); - switch (nodeName) { - case "#text": - return "text()"; - case "#comment": - return "comment()"; - case "#cdata-section": - return "cdata-section()"; - default: - return nodeName; - } -}; - -getNodePosition = function(node) { - var pos, tmp; - pos = 0; - tmp = node; - while (tmp) { - if (tmp.nodeName === node.nodeName) { - pos++; - } - tmp = tmp.previousSibling; - } - return pos; -}; - -/* -// -*/ - -// Generated by CoffeeScript 1.6.3 -var $, Util, gettext_annotator, _ref, _t; - -gettext_annotator = function(msgid) { - return msgid; -}; - -_t = function(msgid) { - return gettext_annotator(msgid); -}; - -if (!(typeof jQuery !== "undefined" && jQuery !== null ? (_ref = jQuery.fn) != null ? _ref.jquery : void 0 : void 0)) { - console.error(_t("Annotator requires jQuery: have you included lib/vendor/jquery.js?")); -} - -if (!(JSON && JSON.parse && JSON.stringify)) { - console.error(_t("Annotator requires a JSON implementation: have you included lib/vendor/json2.js?")); -} - -$ = jQuery; - -Util = {}; - -Util.flatten = function(array) { - var flatten; - flatten = function(ary) { - var el, flat, _i, _len; - flat = []; - for (_i = 0, _len = ary.length; _i < _len; _i++) { - el = ary[_i]; - flat = flat.concat(el && $.isArray(el) ? flatten(el) : el); - } - return flat; - }; - return flatten(array); -}; - -Util.contains = function(parent, child) { - var node; - node = child; - while (node != null) { - if (node === parent) { - return true; - } - node = node.parentNode; - } - return false; -}; - -Util.getTextNodes = function(jq) { - var getTextNodes; - getTextNodes = function(node) { - var nodes; - if (node && node.nodeType !== Node.TEXT_NODE) { - nodes = []; - if (node.nodeType !== Node.COMMENT_NODE) { - node = node.lastChild; - while (node) { - nodes.push(getTextNodes(node)); - node = node.previousSibling; - } - } - return nodes.reverse(); - } else { - return node; - } - }; - return jq.map(function() { - return Util.flatten(getTextNodes(this)); - }); -}; - -Util.getLastTextNodeUpTo = function(n) { - var result; - switch (n.nodeType) { - case Node.TEXT_NODE: - return n; - case Node.ELEMENT_NODE: - if (n.lastChild != null) { - result = Util.getLastTextNodeUpTo(n.lastChild); - if (result != null) { - return result; - } - } - break; - } - n = n.previousSibling; - if (n != null) { - return Util.getLastTextNodeUpTo(n); - } else { - return null; - } -}; - -Util.getFirstTextNodeNotBefore = function(n) { - var result; - switch (n.nodeType) { - case Node.TEXT_NODE: - return n; - case Node.ELEMENT_NODE: - if (n.firstChild != null) { - result = Util.getFirstTextNodeNotBefore(n.firstChild); - if (result != null) { - return result; - } - } - break; - } - n = n.nextSibling; - if (n != null) { - return Util.getFirstTextNodeNotBefore(n); - } else { - return null; - } -}; - -Util.readRangeViaSelection = function(range) { - var sel; - sel = Util.getGlobal().getSelection(); - sel.removeAllRanges(); - sel.addRange(range.toRange()); - return sel.toString(); -}; - -Util.xpathFromNode = function(el, relativeRoot) { - var exception, result; - try { - result = simpleXPathJQuery.call(el, relativeRoot); - } catch (_error) { - exception = _error; - console.log("jQuery-based XPath construction failed! Falling back to manual."); - result = simpleXPathPure.call(el, relativeRoot); - } - return result; -}; - -Util.nodeFromXPath = function(xp, root) { - var idx, name, node, step, steps, _i, _len, _ref1; - steps = xp.substring(1).split("/"); - node = root; - for (_i = 0, _len = steps.length; _i < _len; _i++) { - step = steps[_i]; - _ref1 = step.split("["), name = _ref1[0], idx = _ref1[1]; - idx = idx != null ? parseInt((idx != null ? idx.split("]") : void 0)[0]) : 1; - node = findChild(node, name.toLowerCase(), idx); - } - return node; -}; - -Util.escape = function(html) { - return html.replace(/&(?!\w+;)/g, '&').replace(//g, '>').replace(/"/g, '"'); -}; - -Util.uuid = (function() { - var counter; - counter = 0; - return function() { - return counter++; - }; -})(); - -Util.getGlobal = function() { - return (function() { - return this; - })(); -}; - -Util.maxZIndex = function($elements) { - var all, el; - all = (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = $elements.length; _i < _len; _i++) { - el = $elements[_i]; - if ($(el).css('position') === 'static') { - _results.push(-1); - } else { - _results.push(parseInt($(el).css('z-index'), 10) || -1); - } - } - return _results; - })(); - return Math.max.apply(Math, all); -}; - -Util.mousePosition = function(e, offsetEl) { - var offset, _ref1; - if ((_ref1 = $(offsetEl).css('position')) !== 'absolute' && _ref1 !== 'fixed' && _ref1 !== 'relative') { - offsetEl = $(offsetEl).offsetParent()[0]; - } - offset = $(offsetEl).offset(); - return { - top: e.pageY - offset.top, - left: e.pageX - offset.left - }; -}; - -Util.preventEventDefault = function(event) { - return event != null ? typeof event.preventDefault === "function" ? event.preventDefault() : void 0 : void 0; -}; - -/* -// -*/ - -// Generated by CoffeeScript 1.6.3 -var fn, functions, _i, _j, _len, _len1, - __slice = [].slice; - -functions = ["log", "debug", "info", "warn", "exception", "assert", "dir", "dirxml", "trace", "group", "groupEnd", "groupCollapsed", "time", "timeEnd", "profile", "profileEnd", "count", "clear", "table", "error", "notifyFirebug", "firebug", "userObjects"]; - -if (typeof console !== "undefined" && console !== null) { - if (console.group == null) { - console.group = function(name) { - return console.log("GROUP: ", name); - }; - } - if (console.groupCollapsed == null) { - console.groupCollapsed = console.group; - } - for (_i = 0, _len = functions.length; _i < _len; _i++) { - fn = functions[_i]; - if (console[fn] == null) { - console[fn] = function() { - return console.log(_t("Not implemented:") + (" console." + name)); - }; - } - } -} else { - this.console = {}; - for (_j = 0, _len1 = functions.length; _j < _len1; _j++) { - fn = functions[_j]; - this.console[fn] = function() {}; - } - this.console['error'] = function() { - var args; - args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - return alert("ERROR: " + (args.join(', '))); - }; - this.console['warn'] = function() { - var args; - args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; - return alert("WARNING: " + (args.join(', '))); - }; -} - -/* -// -*/ - -// Generated by CoffeeScript 1.6.3 -var Delegator, - __slice = [].slice, - __hasProp = {}.hasOwnProperty; - -Delegator = (function() { - Delegator.prototype.events = {}; - - Delegator.prototype.options = {}; - - Delegator.prototype.element = null; - - function Delegator(element, options) { - this.options = $.extend(true, {}, this.options, options); - this.element = $(element); - this._closures = {}; - this.on = this.subscribe; - this.addEvents(); - } - - Delegator.prototype.addEvents = function() { - var event, _i, _len, _ref, _results; - _ref = Delegator._parseEvents(this.events); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - event = _ref[_i]; - _results.push(this._addEvent(event.selector, event.event, event.functionName)); - } - return _results; - }; - - Delegator.prototype.removeEvents = function() { - var event, _i, _len, _ref, _results; - _ref = Delegator._parseEvents(this.events); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - event = _ref[_i]; - _results.push(this._removeEvent(event.selector, event.event, event.functionName)); - } - return _results; - }; - - Delegator.prototype._addEvent = function(selector, event, functionName) { - var closure, - _this = this; - closure = function() { - return _this[functionName].apply(_this, arguments); - }; - if (selector === '' && Delegator._isCustomEvent(event)) { - this.subscribe(event, closure); - } else { - this.element.delegate(selector, event, closure); - } - this._closures["" + selector + "/" + event + "/" + functionName] = closure; - return this; - }; - - Delegator.prototype._removeEvent = function(selector, event, functionName) { - var closure; - closure = this._closures["" + selector + "/" + event + "/" + functionName]; - if (selector === '' && Delegator._isCustomEvent(event)) { - this.unsubscribe(event, closure); - } else { - this.element.undelegate(selector, event, closure); - } - delete this._closures["" + selector + "/" + event + "/" + functionName]; - return this; - }; - - Delegator.prototype.publish = function() { - this.element.triggerHandler.apply(this.element, arguments); - return this; - }; - - Delegator.prototype.subscribe = function(event, callback) { - var closure; - closure = function() { - return callback.apply(this, [].slice.call(arguments, 1)); - }; - closure.guid = callback.guid = ($.guid += 1); - this.element.bind(event, closure); - return this; - }; - - Delegator.prototype.unsubscribe = function() { - this.element.unbind.apply(this.element, arguments); - return this; - }; - - return Delegator; - -})(); - -Delegator._parseEvents = function(eventsObj) { - var event, events, functionName, sel, selector, _i, _ref; - events = []; - for (sel in eventsObj) { - functionName = eventsObj[sel]; - _ref = sel.split(' '), selector = 2 <= _ref.length ? __slice.call(_ref, 0, _i = _ref.length - 1) : (_i = 0, []), event = _ref[_i++]; - events.push({ - selector: selector.join(' '), - event: event, - functionName: functionName - }); - } - return events; -}; - -Delegator.natives = (function() { - var key, specials, val; - specials = (function() { - var _ref, _results; - _ref = jQuery.event.special; - _results = []; - for (key in _ref) { - if (!__hasProp.call(_ref, key)) continue; - val = _ref[key]; - _results.push(key); - } - return _results; - })(); - return "blur focus focusin focusout load resize scroll unload click dblclick\nmousedown mouseup mousemove mouseover mouseout mouseenter mouseleave\nchange select submit keydown keypress keyup error".split(/[^a-z]+/).concat(specials); -})(); - -Delegator._isCustomEvent = function(event) { - event = event.split('.')[0]; - return $.inArray(event, Delegator.natives) === -1; -}; - -/* -// -*/ - -// Generated by CoffeeScript 1.6.3 -var Range, - __hasProp = {}.hasOwnProperty, - __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - -Range = {}; - -Range.sniff = function(r) { - if (r.commonAncestorContainer != null) { - return new Range.BrowserRange(r); - } else if (typeof r.start === "string") { - return new Range.SerializedRange(r); - } else if (r.start && typeof r.start === "object") { - return new Range.NormalizedRange(r); - } else { - console.error(_t("Could not sniff range type")); - return false; - } -}; - -Range.nodeFromXPath = function(xpath, root) { - var customResolver, evaluateXPath, namespace, node, segment; - if (root == null) { - root = document; - } - evaluateXPath = function(xp, nsResolver) { - var exception; - if (nsResolver == null) { - nsResolver = null; - } - try { - return document.evaluate('.' + xp, root, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; - } catch (_error) { - exception = _error; - console.log("XPath evaluation failed."); - console.log("Trying fallback..."); - return Util.nodeFromXPath(xp, root); - } - }; - if (!$.isXMLDoc(document.documentElement)) { - return evaluateXPath(xpath); - } else { - customResolver = document.createNSResolver(document.ownerDocument === null ? document.documentElement : document.ownerDocument.documentElement); - node = evaluateXPath(xpath, customResolver); - if (!node) { - xpath = ((function() { - var _i, _len, _ref, _results; - _ref = xpath.split('/'); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - segment = _ref[_i]; - if (segment && segment.indexOf(':') === -1) { - _results.push(segment.replace(/^([a-z]+)/, 'xhtml:$1')); - } else { - _results.push(segment); - } - } - return _results; - })()).join('/'); - namespace = document.lookupNamespaceURI(null); - customResolver = function(ns) { - if (ns === 'xhtml') { - return namespace; - } else { - return document.documentElement.getAttribute('xmlns:' + ns); - } - }; - node = evaluateXPath(xpath, customResolver); - } - return node; - } -}; - -Range.RangeError = (function(_super) { - __extends(RangeError, _super); - - function RangeError(type, message, parent) { - this.type = type; - this.message = message; - this.parent = parent != null ? parent : null; - RangeError.__super__.constructor.call(this, this.message); - } - - return RangeError; - -})(Error); - -Range.BrowserRange = (function() { - function BrowserRange(obj) { - this.commonAncestorContainer = obj.commonAncestorContainer; - this.startContainer = obj.startContainer; - this.startOffset = obj.startOffset; - this.endContainer = obj.endContainer; - this.endOffset = obj.endOffset; - } - - BrowserRange.prototype.normalize = function(root) { - var n, node, nr, r; - if (this.tainted) { - console.error(_t("You may only call normalize() once on a BrowserRange!")); - return false; - } else { - this.tainted = true; - } - r = {}; - if (this.startContainer.nodeType === Node.ELEMENT_NODE) { - r.start = Util.getFirstTextNodeNotBefore(this.startContainer.childNodes[this.startOffset]); - r.startOffset = 0; - } else { - r.start = this.startContainer; - r.startOffset = this.startOffset; - } - if (this.endContainer.nodeType === Node.ELEMENT_NODE) { - node = this.endContainer.childNodes[this.endOffset]; - if (node != null) { - n = node; - while ((n != null) && (n.nodeType !== Node.TEXT_NODE)) { - n = n.firstChild; - } - if (n != null) { - r.end = n; - r.endOffset = 0; - } - } - if (r.end == null) { - node = this.endContainer.childNodes[this.endOffset - 1]; - r.end = Util.getLastTextNodeUpTo(node); - r.endOffset = r.end.nodeValue.length; - } - } else { - r.end = this.endContainer; - r.endOffset = this.endOffset; - } - nr = {}; - if (r.startOffset > 0) { - if (r.start.nodeValue.length > r.startOffset) { - nr.start = r.start.splitText(r.startOffset); - } else { - nr.start = r.start.nextSibling; - } - } else { - nr.start = r.start; - } - if (r.start === r.end) { - if (nr.start.nodeValue.length > (r.endOffset - r.startOffset)) { - nr.start.splitText(r.endOffset - r.startOffset); - } - nr.end = nr.start; - } else { - if (r.end.nodeValue.length > r.endOffset) { - r.end.splitText(r.endOffset); - } - nr.end = r.end; - } - nr.commonAncestor = this.commonAncestorContainer; - while (nr.commonAncestor.nodeType !== Node.ELEMENT_NODE) { - nr.commonAncestor = nr.commonAncestor.parentNode; - } - return new Range.NormalizedRange(nr); - }; - - BrowserRange.prototype.serialize = function(root, ignoreSelector) { - return this.normalize(root).serialize(root, ignoreSelector); - }; - - return BrowserRange; - -})(); - -Range.NormalizedRange = (function() { - function NormalizedRange(obj) { - this.commonAncestor = obj.commonAncestor; - this.start = obj.start; - this.end = obj.end; - } - - NormalizedRange.prototype.normalize = function(root) { - return this; - }; - - NormalizedRange.prototype.limit = function(bounds) { - var nodes, parent, startParents, _i, _len, _ref; - nodes = $.grep(this.textNodes(), function(node) { - return node.parentNode === bounds || $.contains(bounds, node.parentNode); - }); - if (!nodes.length) { - return null; - } - this.start = nodes[0]; - this.end = nodes[nodes.length - 1]; - startParents = $(this.start).parents(); - _ref = $(this.end).parents(); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - parent = _ref[_i]; - if (startParents.index(parent) !== -1) { - this.commonAncestor = parent; - break; - } - } - return this; - }; - - NormalizedRange.prototype.serialize = function(root, ignoreSelector) { - var end, serialization, start; - serialization = function(node, isEnd) { - var n, nodes, offset, origParent, textNodes, xpath, _i, _len; - if (ignoreSelector) { - origParent = $(node).parents(":not(" + ignoreSelector + ")").eq(0); - } else { - origParent = $(node).parent(); - } - xpath = Util.xpathFromNode(origParent, root)[0]; - textNodes = Util.getTextNodes(origParent); - nodes = textNodes.slice(0, textNodes.index(node)); - offset = 0; - for (_i = 0, _len = nodes.length; _i < _len; _i++) { - n = nodes[_i]; - offset += n.nodeValue.length; - } - if (isEnd) { - return [xpath, offset + node.nodeValue.length]; - } else { - return [xpath, offset]; - } - }; - start = serialization(this.start); - end = serialization(this.end, true); - return new Range.SerializedRange({ - start: start[0], - end: end[0], - startOffset: start[1], - endOffset: end[1] - }); - }; - - NormalizedRange.prototype.text = function() { - var node; - return ((function() { - var _i, _len, _ref, _results; - _ref = this.textNodes(); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - node = _ref[_i]; - _results.push(node.nodeValue); - } - return _results; - }).call(this)).join(''); - }; - - NormalizedRange.prototype.textNodes = function() { - var end, start, textNodes, _ref; - textNodes = Util.getTextNodes($(this.commonAncestor)); - _ref = [textNodes.index(this.start), textNodes.index(this.end)], start = _ref[0], end = _ref[1]; - return $.makeArray(textNodes.slice(start, +end + 1 || 9e9)); - }; - - NormalizedRange.prototype.toRange = function() { - var range; - range = document.createRange(); - range.setStartBefore(this.start); - range.setEndAfter(this.end); - return range; - }; - - return NormalizedRange; - -})(); - -Range.SerializedRange = (function() { - function SerializedRange(obj) { - this.start = obj.start; - this.startOffset = obj.startOffset; - this.end = obj.end; - this.endOffset = obj.endOffset; - } - - SerializedRange.prototype.normalize = function(root) { - var contains, e, length, node, p, range, targetOffset, tn, _i, _j, _len, _len1, _ref, _ref1; - range = {}; - _ref = ['start', 'end']; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - p = _ref[_i]; - try { - node = Range.nodeFromXPath(this[p], root); - } catch (_error) { - e = _error; - throw new Range.RangeError(p, ("Error while finding " + p + " node: " + this[p] + ": ") + e, e); - } - if (!node) { - throw new Range.RangeError(p, "Couldn't find " + p + " node: " + this[p]); - } - length = 0; - targetOffset = this[p + 'Offset']; - if (p === 'end') { - targetOffset--; - } - _ref1 = Util.getTextNodes($(node)); - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - tn = _ref1[_j]; - if (length + tn.nodeValue.length > targetOffset) { - range[p + 'Container'] = tn; - range[p + 'Offset'] = this[p + 'Offset'] - length; - break; - } else { - length += tn.nodeValue.length; - } - } - if (range[p + 'Offset'] == null) { - throw new Range.RangeError("" + p + "offset", "Couldn't find offset " + this[p + 'Offset'] + " in element " + this[p]); - } - } - contains = document.compareDocumentPosition == null ? function(a, b) { - return a.contains(b); - } : function(a, b) { - return a.compareDocumentPosition(b) & 16; - }; - $(range.startContainer).parents().each(function() { - if (contains(this, range.endContainer)) { - range.commonAncestorContainer = this; - return false; - } - }); - return new Range.BrowserRange(range).normalize(root); - }; - - SerializedRange.prototype.serialize = function(root, ignoreSelector) { - return this.normalize(root).serialize(root, ignoreSelector); - }; - - SerializedRange.prototype.toObject = function() { - return { - start: this.start, - startOffset: this.startOffset, - end: this.end, - endOffset: this.endOffset - }; - }; - - return SerializedRange; - -})(); - -/* -// -*/ - -// Generated by CoffeeScript 1.6.3 -var Annotator, g, _Annotator, _ref, - __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - __hasProp = {}.hasOwnProperty, - __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - -_Annotator = this.Annotator; - -Annotator = (function(_super) { - __extends(Annotator, _super); - - Annotator.prototype.events = { - ".annotator-adder button click": "onAdderClick", - ".annotator-adder button mousedown": "onAdderMousedown", - ".annotator-hl mouseover": "onHighlightMouseover", - ".annotator-hl mouseout": "startViewerHideTimer" - }; - - Annotator.prototype.html = { - adder: '
', - wrapper: '
' - }; - - Annotator.prototype.options = { - readOnly: false - }; - - Annotator.prototype.plugins = {}; - - Annotator.prototype.editor = null; - - Annotator.prototype.viewer = null; - - Annotator.prototype.selectedRanges = null; - - Annotator.prototype.mouseIsDown = false; - - Annotator.prototype.ignoreMouseup = false; - - Annotator.prototype.viewerHideTimer = null; - - function Annotator(element, options) { - this.onDeleteAnnotation = __bind(this.onDeleteAnnotation, this); - this.onEditAnnotation = __bind(this.onEditAnnotation, this); - this.onAdderClick = __bind(this.onAdderClick, this); - this.onAdderMousedown = __bind(this.onAdderMousedown, this); - this.onHighlightMouseover = __bind(this.onHighlightMouseover, this); - this.checkForEndSelection = __bind(this.checkForEndSelection, this); - this.checkForStartSelection = __bind(this.checkForStartSelection, this); - this.clearViewerHideTimer = __bind(this.clearViewerHideTimer, this); - this.startViewerHideTimer = __bind(this.startViewerHideTimer, this); - this.showViewer = __bind(this.showViewer, this); - this.onEditorSubmit = __bind(this.onEditorSubmit, this); - this.onEditorHide = __bind(this.onEditorHide, this); - this.showEditor = __bind(this.showEditor, this); - Annotator.__super__.constructor.apply(this, arguments); - this.plugins = {}; - if (!Annotator.supported()) { - return this; - } - if (!this.options.readOnly) { - this._setupDocumentEvents(); - } - this._setupWrapper()._setupViewer()._setupEditor(); - this._setupDynamicStyle(); - this.adder = $(this.html.adder).appendTo(this.wrapper).hide(); - Annotator._instances.push(this); - } - - Annotator.prototype._setupWrapper = function() { - this.wrapper = $(this.html.wrapper); - this.element.find('script').remove(); - this.element.wrapInner(this.wrapper); - this.wrapper = this.element.find('.annotator-wrapper'); - return this; - }; - - Annotator.prototype._setupViewer = function() { - var _this = this; - this.viewer = new Annotator.Viewer({ - readOnly: this.options.readOnly - }); - this.viewer.hide().on("edit", this.onEditAnnotation).on("delete", this.onDeleteAnnotation).addField({ - load: function(field, annotation) { - if (annotation.text) { - $(field).html(Util.escape(annotation.text)); - } else { - $(field).html("" + (_t('No Comment')) + ""); - } - return _this.publish('annotationViewerTextField', [field, annotation]); - } - }).element.appendTo(this.wrapper).bind({ - "mouseover": this.clearViewerHideTimer, - "mouseout": this.startViewerHideTimer - }); - return this; - }; - - Annotator.prototype._setupEditor = function() { - this.editor = new Annotator.Editor(); - this.editor.hide().on('hide', this.onEditorHide).on('save', this.onEditorSubmit).addField({ - type: 'textarea', - label: _t('Comments') + '\u2026', - load: function(field, annotation) { - return $(field).find('textarea').val(annotation.text || ''); - }, - submit: function(field, annotation) { - return annotation.text = $(field).find('textarea').val(); - } - }); - this.editor.element.appendTo(this.wrapper); - return this; - }; - - Annotator.prototype._setupDocumentEvents = function() { - $(document).bind({ - "mouseup": this.checkForEndSelection, - "mousedown": this.checkForStartSelection - }); - return this; - }; - - Annotator.prototype._setupDynamicStyle = function() { - var max, sel, style, x; - style = $('#annotator-dynamic-style'); - if (!style.length) { - style = $('').appendTo(document.head); - } - sel = '*' + ((function() { - var _i, _len, _ref, _results; - _ref = ['adder', 'outer', 'notice', 'filter']; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - x = _ref[_i]; - _results.push(":not(.annotator-" + x + ")"); - } - return _results; - })()).join(''); - max = Util.maxZIndex($(document.body).find(sel)); - max = Math.max(max, 1000); - style.text([".annotator-adder, .annotator-outer, .annotator-notice {", " z-index: " + (max + 20) + ";", "}", ".annotator-filter {", " z-index: " + (max + 10) + ";", "}"].join("\n")); - return this; - }; - - Annotator.prototype.destroy = function() { - var idx, name, plugin, _ref; - $(document).unbind({ - "mouseup": this.checkForEndSelection, - "mousedown": this.checkForStartSelection - }); - $('#annotator-dynamic-style').remove(); - this.adder.remove(); - this.viewer.destroy(); - this.editor.destroy(); - this.wrapper.find('.annotator-hl').each(function() { - $(this).contents().insertBefore(this); - return $(this).remove(); - }); - this.wrapper.contents().insertBefore(this.wrapper); - this.wrapper.remove(); - this.element.data('annotator', null); - _ref = this.plugins; - for (name in _ref) { - plugin = _ref[name]; - this.plugins[name].destroy(); - } - this.removeEvents(); - idx = Annotator._instances.indexOf(this); - if (idx !== -1) { - return Annotator._instances.splice(idx, 1); - } - }; - - Annotator.prototype.getSelectedRanges = function() { - var browserRange, i, normedRange, r, ranges, rangesToIgnore, selection, _i, _len; - selection = Util.getGlobal().getSelection(); - ranges = []; - rangesToIgnore = []; - if (!selection.isCollapsed) { - ranges = (function() { - var _i, _ref, _results; - _results = []; - for (i = _i = 0, _ref = selection.rangeCount; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { - r = selection.getRangeAt(i); - browserRange = new Range.BrowserRange(r); - normedRange = browserRange.normalize().limit(this.wrapper[0]); - if (normedRange === null) { - rangesToIgnore.push(r); - } - _results.push(normedRange); - } - return _results; - }).call(this); - selection.removeAllRanges(); - } - for (_i = 0, _len = rangesToIgnore.length; _i < _len; _i++) { - r = rangesToIgnore[_i]; - selection.addRange(r); - } - return $.grep(ranges, function(range) { - if (range) { - selection.addRange(range.toRange()); - } - return range; - }); - }; - - Annotator.prototype.createAnnotation = function() { - var annotation; - annotation = {}; - this.publish('beforeAnnotationCreated', [annotation]); - return annotation; - }; - - Annotator.prototype.setupAnnotation = function(annotation) { - var e, normed, normedRanges, r, root, _i, _j, _len, _len1, _ref; - root = this.wrapper[0]; - annotation.ranges || (annotation.ranges = this.selectedRanges); - normedRanges = []; - _ref = annotation.ranges; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - r = _ref[_i]; - try { - normedRanges.push(Range.sniff(r).normalize(root)); - } catch (_error) { - e = _error; - if (e instanceof Range.RangeError) { - this.publish('rangeNormalizeFail', [annotation, r, e]); - } else { - throw e; - } - } - } - if (normedRanges.length!=0){ - annotation.quote = []; - annotation.ranges = []; - annotation.highlights = []; - for (_j = 0, _len1 = normedRanges.length; _j < _len1; _j++) { - normed = normedRanges[_j]; - annotation.quote.push($.trim(normed.text())); - annotation.ranges.push(normed.serialize(this.wrapper[0], '.annotator-hl')); - $.merge(annotation.highlights, this.highlightRange(normed)); - } - annotation.quote = annotation.quote.join(' / '); - $(annotation.highlights).data('annotation', annotation); - } - return annotation; - }; - - Annotator.prototype.updateAnnotation = function(annotation) { - this.publish('beforeAnnotationUpdated', [annotation]); - this.publish('annotationUpdated', [annotation]); - return annotation; - }; - - Annotator.prototype.deleteAnnotation = function(annotation) { - var child, h, _i, _len, _ref; - if (annotation.highlights != null) { - _ref = annotation.highlights; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - h = _ref[_i]; - if (!(h.parentNode != null)) { - continue; - } - child = h.childNodes[0]; - $(h).replaceWith(h.childNodes); - } - } - this.publish('annotationDeleted', [annotation]); - return annotation; - }; - - Annotator.prototype.loadAnnotations = function(annotations) { - var clone, loader, - _this = this; - if (annotations == null) { - annotations = []; - } - loader = function(annList) { - var n, now, _i, _len; - if (annList == null) { - annList = []; - } - now = annList.splice(0, 10); - for (_i = 0, _len = now.length; _i < _len; _i++) { - n = now[_i]; - _this.setupAnnotation(n); - } - if (annList.length > 0) { - return setTimeout((function() { - return loader(annList); - }), 10); - } else { - return _this.publish('annotationsLoaded', [clone]); - } - }; - clone = annotations.slice(); - loader(annotations); - return this; - }; - - Annotator.prototype.dumpAnnotations = function() { - if (this.plugins['Store']) { - return this.plugins['Store'].dumpAnnotations(); - } else { - console.warn(_t("Can't dump annotations without Store plugin.")); - return false; - } - }; - - Annotator.prototype.highlightRange = function(normedRange, cssClass) { - var hl, node, white, _i, _len, _ref, _results; - if (cssClass == null) { - cssClass = 'annotator-hl'; - } - white = /^\s*$/; - hl = $(""); - _ref = normedRange.textNodes(); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - node = _ref[_i]; - if (!white.test(node.nodeValue)) { - _results.push($(node).wrapAll(hl).parent().show()[0]); - } - } - return _results; - }; - - Annotator.prototype.highlightRanges = function(normedRanges, cssClass) { - var highlights, r, _i, _len; - if (cssClass == null) { - cssClass = 'annotator-hl'; - } - highlights = []; - for (_i = 0, _len = normedRanges.length; _i < _len; _i++) { - r = normedRanges[_i]; - $.merge(highlights, this.highlightRange(r, cssClass)); - } - return highlights; - }; - - Annotator.prototype.addPlugin = function(name, options) { - var klass, _base; - if (this.plugins[name]) { - console.error(_t("You cannot have more than one instance of any plugin.")); - } else { - klass = Annotator.Plugin[name]; - if (typeof klass === 'function') { - this.plugins[name] = new klass(this.element[0], options); - this.plugins[name].annotator = this; - if (typeof (_base = this.plugins[name]).pluginInit === "function") { - _base.pluginInit(); - } - } else { - console.error(_t("Could not load ") + name + _t(" plugin. Have you included the appropriate + @@ -55,7 +56,7 @@ <%static:css group='style-app'/> <%static:css group='style-app-extend1'/> <%static:css group='style-app-extend2'/> - + <%static:js group='main_vendor'/> <%block name="headextra"/> diff --git a/lms/templates/textannotation.html b/lms/templates/textannotation.html index 48f76ce35205..17c1a1c13d47 100644 --- a/lms/templates/textannotation.html +++ b/lms/templates/textannotation.html @@ -1,5 +1,9 @@ <%! from django.utils.translation import ugettext as _ %> - +<%namespace name='static' file='/static_content.html'/> + <%static:css group='style-vendor-tinymce-content'/> + <%static:css group='style-vendor-tinymce-skin'/> +