Merge pull request #2299 from lduarte1991/master

Text and Video Annotation Modules - First Iteration
This commit is contained in:
Christina Roberts
2014-01-27 11:18:32 -08:00
79 changed files with 25267 additions and 40 deletions

View File

@@ -0,0 +1,99 @@
'''
Firebase - library to generate a token
License: https://github.com/firebase/firebase-token-generator-python/blob/master/LICENSE
Tweaked and Edited by @danielcebrianr and @lduarte1991
This library will take either objects or strings and use python's built-in encoding
system as specified by RFC 3548. Thanks to the firebase team for their open-source
library. This was made specifically for speaking with the annotation_storage_url and
can be used and expanded, but not modified by anyone else needing such a process.
'''
from base64 import urlsafe_b64encode
import hashlib
import hmac
import sys
try:
import json
except ImportError:
import simplejson as json
__all__ = ['create_token']
TOKEN_SEP = '.'
def create_token(secret, data):
'''
Simply takes in the secret key and the data and
passes it to the local function _encode_token
'''
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_json(obj):
'''
Before a python dict object can be properly encoded,
it must be transformed into a jason object and then
transformed into bytes to be encoded using the function
defined above.
'''
return _encode(bytearray(json.dumps(obj), 'utf-8'))
def _sign(secret, to_sign):
'''
This function creates a sign that goes at the end of the
message that is specific to the secret and not the actual
content of the encoded body.
More info on hashing: http://docs.python.org/2/library/hmac.html
The function creates a hashed values of the secret and to_sign
and returns the digested values based the secure hash
algorithm, 256
'''
def portable_bytes(string):
'''
Simply transforms a string into a bytes object,
which is a series of immutable integers 0<=x<=256.
Always try to encode as utf-8, unless it is not
compliant.
'''
try:
return bytes(string, 'utf-8')
except TypeError:
return bytes(string)
return _encode(hmac.new(portable_bytes(secret), portable_bytes(to_sign), hashlib.sha256).digest()) # pylint: disable=E1101
def _encode_token(secret, claims):
'''
This is the main function that takes the secret token and
the data to be transmitted. There is a header created for decoding
purposes. Token_SEP means that a period/full stop separates the
header, data object/message, and signatures.
'''
encoded_header = _encode_json({'typ': 'JWT', 'alg': 'HS256'})
encoded_claims = _encode_json(claims)
secure_bits = '%s%s%s' % (encoded_header, TOKEN_SEP, encoded_claims)
sig = _sign(secret, secure_bits)
return '%s%s%s' % (secure_bits, TOKEN_SEP, sig)

View File

@@ -0,0 +1,43 @@
"""
This test will run for firebase_token_generator.py.
"""
from django.test import TestCase
from student.firebase_token_generator import _encode, _encode_json, _encode_token, create_token
class TokenGenerator(TestCase):
"""
Tests for the file firebase_token_generator.py
"""
def test_encode(self):
"""
This tests makes sure that no matter what version of python
you have, the _encode function still returns the appropriate result
for a string.
"""
expected = "dGVzdDE"
result = _encode("test1")
self.assertEqual(expected, result)
def test_encode_json(self):
"""
Same as above, but this one focuses on a python dict type
transformed into a json object and then encoded.
"""
expected = "eyJ0d28iOiAidGVzdDIiLCAib25lIjogInRlc3QxIn0"
result = _encode_json({'one': 'test1', 'two': 'test2'})
self.assertEqual(expected, result)
def test_create_token(self):
"""
Unlike its counterpart in student/views.py, this function
just checks for the encoding of a token. The other function
will test depending on time and user.
"""
expected = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJ1c2VySWQiOiAidXNlcm5hbWUiLCAidHRsIjogODY0MDB9.-p1sr7uwCapidTQ0qB7DdU2dbF-hViKpPNN_5vD10t8"
result1 = _encode_token('4c7f4d1c-8ac4-4e9f-84c8-b271c57fcac4', {"userId": "username", "ttl": 86400})
result2 = create_token('4c7f4d1c-8ac4-4e9f-84c8-b271c57fcac4', {"userId": "username", "ttl": 86400})
self.assertEqual(expected, result1)
self.assertEqual(expected, result2)

View File

@@ -20,6 +20,7 @@ from django.contrib.auth.hashers import UNUSABLE_PASSWORD
from django.contrib.auth.tokens import default_token_generator
from django.utils.http import int_to_base36
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@@ -30,7 +31,7 @@ from textwrap import dedent
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, password_reset, password_reset_confirm_wrapper,
change_enrollment, complete_course_mode_info)
change_enrollment, complete_course_mode_info, token, course_from_id)
from student.tests.factories import UserFactory, CourseModeFactory
from student.tests.test_email import mock_render_to_string
@@ -556,3 +557,26 @@ class AnonymousLookupTable(TestCase):
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])

View File

@@ -1443,3 +1443,28 @@ def change_email_settings(request):
track.views.server_track(request, "change-email-settings", {"receive_emails": "no", "course": course_id}, page='dashboard')
return HttpResponse(json.dumps({'success': True}))
from student.firebase_token_generator import create_token
@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

View File

@@ -33,6 +33,8 @@ XMODULES = [
"wrapper = xmodule.wrapper_module:WrapperDescriptor",
"graphical_slider_tool = xmodule.gst_module:GraphicalSliderToolDescriptor",
"annotatable = xmodule.annotatable_module:AnnotatableDescriptor",
"textannotation = xmodule.textannotation_module:TextAnnotationDescriptor",
"videoannotation = xmodule.videoannotation_module:VideoAnnotationDescriptor",
"foldit = xmodule.foldit_module:FolditDescriptor",
"word_cloud = xmodule.word_cloud_module:WordCloudDescriptor",
"hidden = xmodule.hidden_module:HiddenDescriptor",

View File

@@ -41,6 +41,8 @@ class InheritanceMixin(XBlockMixin):
scope=Scope.settings,
)
xqa_key = String(help="DO NOT USE", scope=Scope.settings)
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")
graceperiod = Timedelta(
help="Amount of time after the due date that submissions will be accepted",
scope=Scope.settings,

View File

@@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
"Test for Annotation Xmodule functional logic."
import unittest
from mock import Mock
from lxml import etree
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xmodule.textannotation_module import TextAnnotationModule
from . import get_test_system
class TextAnnotationModuleTestCase(unittest.TestCase):
''' text Annotation Module Test Case '''
sample_xml = '''
<annotatable>
<instructions><p>Test Instructions.</p></instructions>
<p>
One Fish. Two Fish.
Red Fish. Blue Fish.
Oh the places you'll go!
</p>
</annotatable>
'''
def setUp(self):
"""
Makes sure that the Module is declared and mocked with the sample xml above.
"""
self.mod = TextAnnotationModule(
Mock(),
get_test_system(),
DictFieldData({'data': self.sample_xml}),
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.
It also makes sure that if no instructions exist, that it does in fact return nothing.
"""
xmltree = etree.fromstring(self.sample_xml)
expected_xml = u"<div><p>Test Instructions.</p></div>"
actual_xml = self.mod._extract_instructions(xmltree) # pylint: disable=W0212
self.assertIsNotNone(actual_xml)
self.assertEqual(expected_xml.strip(), actual_xml.strip())
xmltree = etree.fromstring('<annotatable>foo</annotatable>')
actual = self.mod._extract_instructions(xmltree) # pylint: disable=W0212
self.assertIsNone(actual)
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']:
self.assertIn(key, context)

View File

@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
"Test for Annotation Xmodule functional logic."
import unittest
from mock import Mock
from lxml import etree
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xmodule.videoannotation_module import VideoAnnotationModule
from . import get_test_system
class VideoAnnotationModuleTestCase(unittest.TestCase):
''' Video Annotation Module Test Case '''
sample_xml = '''
<annotatable>
<instructions><p>Video Test Instructions.</p></instructions>
</annotatable>
'''
sample_sourceurl = "http://video-js.zencoder.com/oceans-clip.mp4"
sample_youtubeurl = "http://www.youtube.com/watch?v=yxLIu-scR9Y"
def setUp(self):
"""
Makes sure that the Video Annotation Module is created.
"""
self.mod = VideoAnnotationModule(
Mock(),
get_test_system(),
DictFieldData({'data': self.sample_xml, 'sourceUrl': self.sample_sourceurl}),
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 = '<annotation title="x" body="y" problem="0">test</annotation>'
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 = '<annotation title="x" body="y" problem="0" highlight="{highlight}">test</annotation>'
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 = '<annotation title="x" body="y" problem="0" highlight="{highlight}">test</annotation>'
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('<annotation title="bar" body="foo" problem="0">test</annotation>')
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 = '<span class="annotatable-span highlight highlight-yellow" data-comment-title="x" data-comment-body="y" data-problem-id="0">z</span>'
expected_el = etree.fromstring(expected_html)
actual_el = etree.fromstring('<annotation title="x" body="y" problem="0" highlight="yellow">z</annotation>')
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
formatted from the <instructions> tags. Otherwise, it should return nothing.
"""
xmltree = etree.fromstring(self.sample_xml)
expected_xml = u"<div><p>Video Test Instructions.</p></div>"
actual_xml = self.mod._extract_instructions(xmltree) # pylint: disable=W0212
self.assertIsNotNone(actual_xml)
self.assertEqual(expected_xml.strip(), actual_xml.strip())
xmltree = etree.fromstring('<annotatable>foo</annotatable>')
actual = self.mod._extract_instructions(xmltree) # pylint: disable=W0212
self.assertIsNone(actual)
def test_get_extension(self):
"""
Tests the function that returns the appropriate extension depending on whether it is
a video from youtube, or one uploaded to the EdX server.
"""
expectedyoutube = 'video/youtube'
expectednotyoutube = 'video/mp4'
result1 = self.mod._get_extension(self.sample_sourceurl) # pylint: disable=W0212
result2 = self.mod._get_extension(self.sample_youtubeurl) # pylint: disable=W0212
self.assertEqual(expectedyoutube, result2)
self.assertEqual(expectednotyoutube, result1)
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']:
self.assertIn(key, context)

View File

@@ -0,0 +1,106 @@
''' Text annotation module '''
from lxml import etree
from pkg_resources import resource_string
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xblock.core import Scope, String
import textwrap
class AnnotatableFields(object):
"""Fields for `TextModule` and `TextDescriptor`."""
data = String(help="XML data for the annotation", scope=Scope.content, default=textwrap.dedent("""\
<annotatable>
<instructions>
<p>
Add the instructions to the assignment here.
</p>
</instructions>
<p>
Lorem ipsum dolor sit amet, at amet animal petentium nec. Id augue nemore postulant mea. Ex eam dicant noluisse expetenda, alia admodum abhorreant qui et. An ceteros expetenda mea, tale natum ipsum quo no, ut pro paulo alienum noluisse.
</p>
</annotatable>
"""))
display_name = String(
display_name="Display Name",
help="Display name for this module",
scope=Scope.settings,
default='Text Annotation',
)
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,
default='imagery:red,parallelism:blue',
)
source = String(
display_name="Source/Citation",
help="Optional for citing source of any material used. Automatic citation can be done using <a href=\"http://easybib.com\">EasyBib</a>",
scope=Scope.settings,
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")
class TextAnnotationModule(AnnotatableFields, XModule):
''' Text Annotation Module '''
js = {'coffee': [],
'js': []}
css = {'scss': [resource_string(__name__, 'css/annotatable/display.scss')]}
icon_class = 'textannotation'
def __init__(self, *args, **kwargs):
super(TextAnnotationModule, self).__init__(*args, **kwargs)
xmltree = etree.fromstring(self.data)
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')
def _extract_instructions(self, xmltree):
""" Removes <instructions> 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 get_html(self):
""" Renders parameters to template. """
context = {
'display_name': self.display_name_with_default,
'tag': self.tags,
'source': self.source,
'instructions_html': self.instructions,
'content_html': self._render_content(),
'annotation_storage': self.annotation_storage_url
}
return self.system.render_template('textannotation.html', context)
class TextAnnotationDescriptor(AnnotatableFields, RawDescriptor):
''' Text Annotation Descriptor '''
module_class = TextAnnotationModule
mako_template = "widgets/raw-edit.html"
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(TextAnnotationDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([
TextAnnotationDescriptor.annotation_storage_url
])
return non_editable_fields

View File

@@ -0,0 +1,172 @@
"""
Module for Video annotations using annotator.
"""
from lxml import etree
from pkg_resources import resource_string
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xblock.core import Scope, String
import textwrap
class AnnotatableFields(object):
""" Fields for `VideoModule` and `VideoDescriptor`. """
data = String(help="XML data for the annotation", scope=Scope.content, default=textwrap.dedent("""\
<annotatable>
<instructions>
<p>
Add the instructions to the assignment here.
</p>
</instructions>
</annotatable>
"""))
display_name = String(
display_name="Display Name",
help="Display name for this module",
scope=Scope.settings,
default='Video Annotation',
)
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")
class VideoAnnotationModule(AnnotatableFields, XModule):
'''Video Annotation Module'''
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': []}
css = {'scss': [resource_string(__name__, 'css/annotatable/display.scss')]}
icon_class = 'videoannotation'
def __init__(self, *args, **kwargs):
super(VideoAnnotationModule, self).__init__(*args, **kwargs)
xmltree = etree.fromstring(self.data)
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')
def _extract_instructions(self, xmltree):
""" Removes <instructions> 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 _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]
def get_html(self):
""" Renders parameters to template. """
extension = self._get_extension(self.sourceurl)
context = {
'display_name': self.display_name_with_default,
'instructions_html': self.instructions,
'sourceUrl': self.sourceurl,
'typeSource': extension,
'poster': self.poster_url,
'alert': self,
'content_html': self._render_content(),
'annotation_storage': self.annotation_storage_url
}
return self.system.render_template('videoannotation.html', context)
class VideoAnnotationDescriptor(AnnotatableFields, RawDescriptor):
''' Video annotation descriptor '''
module_class = VideoAnnotationModule
mako_template = "widgets/raw-edit.html"
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(VideoAnnotationDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([
VideoAnnotationDescriptor.annotation_storage_url
])
return non_editable_fields

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,104 @@
/*This is written to fix some design problems with edX*/
.annotator-wrapper .annotator-adder button {
opacity:0;
}
.annotator-editor a, .annotator-filter .annotator-filter-property label{
line-height: 24px !important;
color: #363636 !important;
font-size: 12px!important;
font-weight: bold !important;
text-shadow: none !important;
}
.annotator-outer ul {
list-style: none !important;
padding-left: 0em !important;
}
.annotator-outer li {
margin-bottom: 0em!important;
}
.vjs-rangeslider-holder span.vjs-time-text{
line-height: 1!important;
float: left;
}
span .annotator-hl{
font:inherit;
}
.vjs-has-started .vjs-loading-spinner {
display: none!important;
}
/*Catch*/
#mainCatch *{
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
/*PublicPrivate Notes in My notes */
.notes-wrapper .PublicPrivate.separator,
.notes-wrapper .PublicPrivate.myNotes{
position:relative;
float:left;
}
.notes-wrapper .PublicPrivate.active *{
color:black;
}
/* My notes buttons */
.notes-wrapper .buttonCatch{
-moz-box-shadow:inset 0px 1px 0px 0px #ffffff;
-webkit-box-shadow:inset 0px 1px 0px 0px #ffffff;
box-shadow:inset 0px 1px 0px 0px #ffffff;
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #ffffff), color-stop(1, #a6a3a3));
background:-moz-linear-gradient(top, #ffffff 5%, #a6a3a3 100%);
background:-webkit-linear-gradient(top, #ffffff 5%, #a6a3a3 100%);
background:-o-linear-gradient(top, #ffffff 5%, #a6a3a3 100%);
background:-ms-linear-gradient(top, #ffffff 5%, #a6a3a3 100%);
background:linear-gradient(to bottom, #ffffff 5%, #a6a3a3 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#a6a3a3',GradientType=0);
background-color:#ffffff;
-moz-border-radius:6px;
-webkit-border-radius:6px;
border-radius:6px;
border:1px solid #c2c2c2;
display:inline-block;
color:#302f2f;
font-family:arial;
font-size:15px;
font-weight:bold;
padding:6px 24px;
text-decoration:none;
text-shadow:0px 1px 0px #ffffff;
margin: 0px 5px 10px 5px;
cursor:pointer;
}
.notes-wrapper .buttonCatch.active{
color:red;
}
.notes-wrapper .buttonCatch:hover {
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #a6a3a3), color-stop(1, #ffffff));
background:-moz-linear-gradient(top, #a6a3a3 5%, #ffffff 100%);
background:-webkit-linear-gradient(top, #a6a3a3 5%, #ffffff 100%);
background:-o-linear-gradient(top, #a6a3a3 5%, #ffffff 100%);
background:-ms-linear-gradient(top, #a6a3a3 5%, #ffffff 100%);
background:linear-gradient(to bottom, #a6a3a3 5%, #ffffff 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#a6a3a3', endColorstr='#ffffff',GradientType=0);
background-color:#a6a3a3;
}
.notes-wrapper .buttonCatch:active {
position:relative;
top:1px;
}
.annotatable-content #sourceCitation {
color:#CCC;
font-style:italic;
font-size:12px;
}

View File

@@ -0,0 +1,27 @@
.flag-icon{
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAsCAMAAAAgsQpJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAytpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDQ1NzExMTM2MEUwMTFFM0I2RjhDOENEOEZGRUY1MzQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDQ1NzExMTI2MEUwMTFFM0I2RjhDOENEOEZGRUY1MzQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5kaWQ6YWNmNmE5ZGQtZDlkOC00YTlhLThmMjAtY2EzNGM5ODcxZDJhIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOmFjZjZhOWRkLWQ5ZDgtNGE5YS04ZjIwLWNhMzRjOTg3MWQyYSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pnen528AAAAJUExURc3Nzf///////3aZ7dEAAAADdFJOU///ANfKDUEAAACzSURBVHjarNRLFoJADETRZ+1/0cpAYuevx8w4XOjQdApZ8SpVxQ+QJYRW4hzV+3GO4rk3hFNCWMG765a7NEhbBtlBxtK86peQNbws/y+teoz/ofiWFbSTtHLTHt0TErddqfMHNxxGWzUbj8SlUIdL5trBc9Br6AKh7PHh5pXc1XM9uQiT9lJYOQc//mGXj51LgrTIPpbOBWmTuizaC/moOZr79k44uDtIJ3cmriao2ekpwADT/geMPTQb1AAAAABJRU5ErkJggg==");
background-repeat: no-repeat;
background-size:13px 13px;
top: 2px;
left: 5px;
float: left;
border: none;
width: 13px;
height: 13px;
cursor: pointer;
position: absolute;
}
.flag-icon-used, .flag-icon:hover{
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAsCAMAAAAgsQpJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAytpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDE0IDc5LjE1MTQ4MSwgMjAxMy8wMy8xMy0xMjowOToxNSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDQ1NzExMEY2MEUwMTFFM0I2RjhDOENEOEZGRUY1MzQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDQ1NzExMEU2MEUwMTFFM0I2RjhDOENEOEZGRUY1MzQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5kaWQ6YWNmNmE5ZGQtZDlkOC00YTlhLThmMjAtY2EzNGM5ODcxZDJhIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOmFjZjZhOWRkLWQ5ZDgtNGE5YS04ZjIwLWNhMzRjOTg3MWQyYSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpBey0IAAAAVUExURQAAAAEBAQ4ODrGxscHBwfj4+P///6napTIAAAAHdFJOU////////wAaSwNGAAABW0lEQVR42oyVWQLDIAhEBwZy/yNX3EpSo+WrSV+GVcT1NVf1682Qfquq/QNSAP4BEhC8+56gAUFOSVKVC9BUg4Sm7zQnh5GwVMWhwngQTR4w9KSRUkWIZuWlZZBNr5NKxbTpHcPP/Cscfp8gHKAT2URE8iO6c7jiYK0QUNlj0vuKu6c1auZFEUdOQ/UcY3FZuqm4eOKiO0Xtug4x1qaG4l6ydsZYwS1ZueLde6+TguXs2CafY3p8TI/SyyjN0rL5szy4LEPD1lZNaRjRO7M4HtbaGkCpcp+zFRiSZS4tPpkjvgTjYATHOY2vIOTBLcFyMki32yFcgZQQ8ju3AFnLZxIl3YFtU9nPGsIv56H6s9fwKHVwkbTv9mMPrNC07SJtgcXcHxZp4wDbL1KNBOKo2XaRek/jdd8jcy/h3fcjH0OwBqsSd5dCAy247S3TwYhQd9dWjnHVjWwfAQYAR+4aJLhjvXgAAAAASUVORK5CYII=");
background-repeat: no-repeat;
background-size:13px 13px;
top: 2px;
left: 5px;
float: left;
border: none;
width: 13px;
height: 13px;
cursor: pointer;
position: absolute;
}

605
common/static/css/vendor/ova/ova.css vendored Normal file
View File

@@ -0,0 +1,605 @@
/* --- BigNewAnnotation --- */
.vjs-default-skin .vjs-big-new-annotation{
float: left;
cursor: pointer;
line-height: 1.6em;
margin: 3% 3% 3% 4%;
top: 41%;
width: 6em;
height: 6em;
background-color: rgba(7,40,50,.7);
border-radius: 6px;
border: 0.25em solid #ccc;
}
.vjs-default-skin .vjs-big-new-annotation div{
width: 100%;
height: 100%;
top: 0em;
font-weight: bold;
font-size: 600%;
padding-top: 0.32em;
}
.vjs-default-skin.vjs-fullscreen .vjs-big-new-annotation{
top: 68%;
width: 8em;
height: 8em;
}
.vjs-default-skin.vjs-fullscreen .vjs-big-new-annotation div{
font-size: 750%;
padding-top: 42%;
}
.vjs-default-skin .vjs-big-new-annotation div:hover {
-webkit-box-shadow: 0 0 1em rgba(255, 255, 255, 1);
-moz-box-shadow: 0 0 1em rgba(255, 255, 255, 1);
box-shadow: 0 0 1em rgba(255, 255, 255, 1);
}
div.video-js > div.vjs-big-new-annotation {
visibility:hidden;
opacity:0;
-webkit-transition: visibility 0s linear 1s,opacity 1s linear;
-moz-transition: visibility 0s linear 1s,opacity 1s linear;
-o-transition: visibility 0s linear 1s,opacity 1s linear;
transition:visibility 0s linear 1s,opacity 1s linear;
}
div.video-js:hover > div.vjs-big-new-annotation {
visibility:visible;
opacity:1;
transition-delay:0s;
}
.vjs-default-skin .vjs-big-new-annotation.ul{
top:0%;
}
.vjs-default-skin .vjs-big-new-annotation.ur{
top:0%;
float:right;
}
.vjs-default-skin .vjs-big-new-annotation.br{
float:right;
}
.vjs-default-skin .vjs-big-new-annotation.c{
top:24%;
left:40%;
}
div.video-js:hover > div.vjs-big-new-annotation.none {
visibility:hidden;
}
.vjs-default-skin.vjs-fullscreen .vjs-big-new-annotation.c{
top:35%;
}
/* --- ControlBar --> AnContainerButtons --- */
.vjs-default-skin .vjs-container-button-annotation{
width: 101px;
}
/* --- ControlBar --> AnContainerButtons --> ShowStatistics --- */
/* --- ControlBar --> AnContainerButtons --> NewAnnotation --- */
/* --- ControlBar --> AnContainerButtons --> ShowAnnotations --- */
.vjs-default-skin .vjs-statistics-annotation,
.vjs-default-skin .vjs-showannotations-annotation,
.vjs-default-skin .vjs-new-annotation{
float: right;
cursor: pointer;
line-height: 1.6em;
background-position: left top;
margin: 4px;
width: 23px;
height: 21px;
background-repeat: no-repeat;
}
.vjs-default-skin .vjs-statistics-annotation.active,
.vjs-default-skin .vjs-showannotations-annotation.active,
.vjs-default-skin .vjs-statistics-annotation:hover,
.vjs-default-skin .vjs-showannotations-annotation:hover,
.vjs-default-skin .vjs-new-annotation:hover {
background-position: center top;
}
.vjs-default-skin .vjs-statistics-annotation:active,
.vjs-default-skin .vjs-showannotations-annotation:active,
.vjs-default-skin .vjs-new-annotation:active {
background-position: center right;
}
.vjs-default-skin .vjs-new-annotation {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEUAAAAVCAYAAAAQAyPeAAAABmJLR0QA/wDoAACU1v3rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QgdAg4VAK182QAACfBJREFUWMOtmHtwVNUdxz/n3LubF4tAMAYEIYC8UiGOaAvYlwUqSqO042MQZgRsZ5R2pr5QW6d2FGEc6VhqLdQiDHQKRpniFBUd0JHUimSAmFB5CAQkD8mDJLvZ5733nNM/drMPgmgNZ+Y32fnt/r4553t+zyMAXlyzZuTw4cPXBwKBOVJKvukyxhCJRo81NzUteGnt2rrDhw+r6ZUvjLy8ZPj6/ILAbIEQ3xgbg5OIHmtrbV5Qv2ddXbj7iLo3f+bIsitGrx9cPGS2FPKbYQswRhMKho6dbD25YEe0vk48t2rV5ZMmT66fNXt2qVYKbQz9Wfn5eRw8cNDZu3fvjM3vRM6MLptcf911N5eG435UP6AFECgyNJ6qcQ7V750xdMcrZ26cPLP+/p/fX5rfbRD9ADcSKM5j+/tvOjvf3TnDHjx48NqpFVNL4/E4l2L19LiMnzDBf6qhYduAQNeBsVdPK+3o8YPpP3ZHl6D4iqn+gYNObbuycNiBW+fcUpp31sVg+g/f5DJ76nf9dTW122wp5cySkhLC4QiXatm2jTFmtETmFwauojPoXTJsRxVhtBkthcwvHz6BeHvwkmEHTD7CmNG2EEIkEgk8L3fjUkoGDBhwUZBIJIIQoo+twWBZFsJCxB3wVK6d8jxamo5iLhKqJcPGIBDk5RfmhpEAaUlsv18Qc9AqF9xTHseaG748DRjD2NJRCKAwvzDHx4wr8fn92Fpr4SkP13VzCKmurmbx4iU4jvOl3rBlyxaOHj3CwoUL+yRcrTVGK+EpyObMICkr/oKXn6y4KOGr/naW4cVxPqgz2P6idPgJAUZrAKGURns6s28EZ0cqrv/j0otin/lDNW6pD++jIEW+gjS2khow2EoblKdybjsQCPDggw+xdesWZs2ahTrvNgCUUixcuIj9+/cTDAZZtmxZBkOANgatFZ4CNysJRuOKJ34zgrWvO9SdFFyoZjiJbh5eOJRJY2wKC5t540MfPp8vCS0NymgMYDyV4yk65lH20E2ENxzCHO6CCxTSYCJC6bIb8I8v5kxRLfY7bfjsJLZWEgNIo5OEnC/RaJSSkhIaGxtpaWnJkcOHj/DAA8t45pmnmTJlCrW1nxAOhzP2rodWCqMVriJHHA+ktDh8WtMThWAkI6GoJBZpZ9FcwUuvtnHmrOHb1w7H7/flYBitQILyVFJUSlwPW9okjp9DhRN4PSkJJdA9Dm3RLuw7x9K0fh+6KcyI68dh+ey0vfYUCLCV1ijl5XiK53kIIXAcB611DtNaa1avfp6KigqWL1/O+vXrKSgooKOjI8dTVDJ8UAqyHa03vxidDKvsziURbuaJpYM43RLn/jsK+e1fwrieD4SVsZep8BEmeRCV2Z9JhZLWBjydrOOp1RLrZNivZxJr6mTQ4qmcfbYa2xVYIuNOyfABqbVGa51hOyUgyP5Oa000GuWxxx5n8uRyNmzYSCAwEKUUjz66HL/fn2FcJe2M0cnb9ZJkZOcXT2fplMCNtfL7B4ZyuiXObTcVY/kG0tkVY8ktISIxkWOvtQIh0J5GeyotKsW4UQrteWl9m9PFyMd/QLSxk9K55QywC4h0hnAXjEJHnSwMDyPA1lrjeW5OonVdByHAdTP61tZWqqqqmDFjOps2baaiooJVq1aybds2Dhw4gFIq/VuBSRHqEXMgloEmmsrbcTelN5ohBa0s/NlA6o4GmT9rKEc+hxUbFbNmXMb0CsnKLYZeZ/ObXlIgEewhHsy0EjrsIIBETxRCMYwxdBVrShdcy7n6M1xZOZX4kXa6V3/MoBtHM/D6Mlqfr0UUJHOK7bfBGGxtDK7j4mZVGSfhgBB4blJ/7tw5Nm3azNhx41i37q/MmTOHRx95mGB3N36fLxlqiUQGw+h0+HSGIRjOJNpQ6gzdUUNHyDAo7yxL7ynmdFOERT8p5d+fePzuFY+EK5gcNihjOBeGeMrDfL6kJwgJXZ81EwtlSFGhOKOAnlOtqOYeOodqJi27leDpNsbeeyPBD07S9PhOiCkGjMgjoDRdx5qQRf4kKT4brRVSK52Oa0FOGKY61B6eXbkKn8/H1q1bueuuu/jVL5fR3d2NEBkLIUTaXgiB0bq3dCYrBX2b2kB+mD89MgStHe6+uZi3PnJ57h/JcMpu7znP3uhk9ZEKhEr+lQpkb0rT4AyQXLPublytKLvnO3S+8SmtT72PjBuESv4GQHgZe5FyR1spBUIisgZBISUYg5CSuvpDHD9+nBMnTlBZWcn8+bfjuC7SskAI0nOYENCLISRaK7T2+hBhso47/VuSiWUBJpbBrhqPl3cIOntMshe5oE3yszYeSJBuhojeA0JSN3BmGUMmjYBJI+h++yidL+xDt0WTl6czBEgvyy51q7bWCikEMqsMSCEwxpCfl0fNvn3MmzePefPmMf7qcSQSiXRFEkJgSQutdQ6GFKCURhv1JXOxIBaL8cNpA9lTB+/thz2fSOIOyFShEUbguUGEKOwzGPZ6oKXBUn1JcaIxRs2aRHRXA+G3PiO8qwEr5mL1VjFj8BIOQghsL0OQJZP4tlIaKUUqFDId6ZgxY3i16jXuuPMObMsiFOqhpqamzyiw+73dlJeXY4xJY0gpMUZjLtD0WRLOBQ2LZkfY/m4zsbhGacWYy85rDrXh5huu4lijQApzXlugMBJsV2K7mX1rY+G1Rcj7RQX1VdWoaAKlNEwUQKasG62ZNH8C7n87sI2FSGHYyN7qoxBSkv2OEovF2LhxA3Pm/JiqqqqLvp/MnTuXFc88TSgUSmNIKdHaoE1fUooK4L7nFNtXjmDhvBEXbcdrjhiWr3UpyDv//yZ7EEsJ7Kz8Q0EerZWvMf7jxYy/73sXxY59eIb2xW/i8/voLW22FMnwiUSjEWNM2vVN6lAYw8kTx3NyzYWfCnr4oqUFy7LAJMcrIQWRaNRTTiTik31np+Z2Q/kih696urEt+hBiWx6eE/biyoloabC988pDQw+NBau/+n3GlliFNplZwCB8goSXwG5v73jl4MHaFeXl16CUyqk+JxsavtbILS0rKzwE4XCYUCj0djzSXtPTuH2Fb/BPcT2TM+kOKPj/R3spBXnqc7xE8O0uFa7ZrQ6tmCMnYFydm3WK8r8eYHY+kpKW/AiRiPsvq7a29vDIkSMnWpIJJVeU9vtN4vSpBvbsqT6+Zs2LS8821lUXBQZNLLTCE1RB+QXe0r6+WNKiILKL9pOvHz/ynz8v/TTcVF1cEJjoFDJhXHxov/e9b3ATOxIHjq9t2blUAMVA0W2VlUumTZv2VH/BT5w4sXnbtn+ujETDnYAGikZc/f0lV46/td/YXqLtaO3u1bdrSGP/6PIpS8blD3tSC21JIdDGIIxAyOSkntYhMOR+l9YhTLsb/PsbHftWGpfO/wH+lr/CdYGcQQAAAABJRU5ErkJggg==');
}
.vjs-default-skin .vjs-showannotations-annotation {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEUAAAAVCAYAAAAQAyPeAAAABmJLR0QA/wDoAACU1v3rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QgdAgcPLA0+6gAABatJREFUWMOtmN9vFFUUx7/nzuyyUNdQCqUUa0oEyo9ETOBFfuiDlvAjATXxhfhgIMbgP2BCjL5BSEwMQYMm4AMaXmxABSkGogHUKgk0LeF3AQMtAq2FbXeX3Zl7zvFhtmuXdsrsLjeZzOTePZ8998w533vvEADs3rWrqbGxcW8ymVxtjEGlTVWRyWav9PX2bvpiz56uixcv8ssbPmuaUd+4NzE52UogqpgNhZfPXrl/r29T98kvu9IPL/G7iRVNc2Y2762tm9ZqyFTGJkBVMJQaunL93vVNh7PdXbRzx44ZCxct6n69tbVBmCGqqKYlEpNw7uw5r6OjY/n+Y5lbzXMWdS9duqYhnYuDq0ATgGSN4vbNM9757o7l0w/vu7Vy0Yrure9tbUg8VFAVcDUA6ibh0C9HvPaf25e7tbW1e5a8tKQhl8vhabThYR/zW1riN2/caHsm+eDsC/OWNQwMxwGtnj3wgFA3c0n82ak322ZPmXV2/ep1DZPu+lBo9fheH61LVsW7znS2ucaYFfX19UinMwCAlgULI3OuXL40br/rulDVZgOTmJJ8HoMpCwA4sHNWZPamD/8Zt9/jGqhosyGTWNzYglx/CgAw++uNkdl9m38Ytz+pCZBqs0tElM/nYa0tDv57YeCJ4LrF00tsHq9/x3FADijnAZZRPptDyogA4xi48TjhkQdhLps92qbEb98gFo/DFRGybOH7ftkZF2ajqhARqDBZBkJiN2ELsyECVAQAiFkgVspmh9mwEQAKl0XBlkveet3i6REdt6GqKKoQYVgG/FEiGJXthwgnGQWrQAGo5ZK3HpUdlinCBgrAVQkCMjLBPzv+KONt2gn/WIXhM+AXfFjzwZ3oWRhWPgZQYcAAbBlsGSCg5+0DgZhTsYbHPhfuzFw6XmjGGoAAl0XAbEMnuHLVK5En8tvpU8VM4aB8wAyEvBgc/6oxMrv1/SCg1hTKhxTMDOHxS2H+oXcis6+++e2o8gFcEYGIBNELaVEFbIRhjAk0RSXIFIvitk0VJVu4coWXCBBhgAhiBWKr93uEIYagVAiKtX5FQhsmvIRAaEUsHnnAo+rRyHrBPa4jQQHyqWHkUpmq2blUOsiQuAuowhVV+J4P3/MmjGakoIwwVIrlM5gGUmmtmj1YYMRigDKDDPDgah8eDWWqZj+81hcEJeZChOEKSzGdaawmof2nI+P2j77jMc0iIqjIyNIZutuc+1ZfGWefUc8SrD6GAeLxtfVk/bYwnR3zW/D/Ig4ALjMDZECFg+DatesiO3qs/SgoZIkQYYjYksn0HJwdmR0WMAUgagEDGB8whfVh1eD2yOzT07aN67ehIFKuCMMQwYxSv6gCZUIOvYYAZoEojzkXR2VPdDAcyUBHAKeC3bITos1OsCLDZRYYQ6AKTvVhNsYYqAp0ghWtmibCUAO4voHrl+93mI0LM7L6MMgYjP6OElWgwr69BEuyQpQrFr+Jv9sIQIDDBJepbPZom5J+Q0H5ZLLZjKoWS+HXE8crd7YoKYRMNmvZy2RixqtIWEMn5FhYL21z7GXEKFwbCEG38zEib2ntGKkFoKAYIW/zMP39A/vOneuEG4+DHKeqyzgO3JiLdDqNoaGho7lM/77h24cQc+mplI0xhEl8CzafOvqA0/tO8HnEjAu3kDEumyc8h40TYurgfiKDDPwfnc7OzotNTU0LHIOW+pkNVTv+980bOHny1LVdu3ZvuXu761RNcuqCKU66hScvHkcyo1+OcTA5cxz917+7dun3z7dcSPeeqpucXOBNQcvcXPUl+VdtLw7nz17bc6d9CwGoA1CzccOGzcuWLfukWnhPT8/+traD2zPZ9CAAAVDz3LxXN8+ev75qts3fv9x54tM3BCiyX5vx4ua5iVkfCYljiCCqICWQCU7qxT4QFKVjxT6Q9vupb74f+Gu7+hj8D6dPKX8nggZiAAAAAElFTkSuQmCC');
}
.vjs-default-skin .vjs-statistics-annotation {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEUAAAAVCAYAAAAQAyPeAAAABmJLR0QA/wDoAACU1v3rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QgdAhEHPk4DDwAABcNJREFUWMOtV21oHOcRfmb2vfPZyoWoimVFjhKFfMh1oC7EUGo3hDRRCAkopT/6w5DEtWkhkNISSH8VTP849IMW4QSHYkMikz+pyIcd7IQEBxtS44AtpBAnjr9aWwq2ZWSfdHe+292Z6Y+TTnvWrk+n9QvLDs++M8zOO/PMvAQAOwYHe7q7u3fl8/mnmBlLXWaGUrl8cmJ8fNMbO3eOnjhxQn468M+elZ3du3LL8/0EoiXbhsGvlk9evjSxaezQm6PFa9/I5tzGnvtW9e5q7/hBPxMvzTYBZorpwvTJM5fObNpXHhulv7z22sofrl079mR/f5eKQM2QZuVyy3D82HH/yJEjG4Y+Lp3vvW/t2COPPN1VrGQhKUwTgHyb4cK5L/2vxo5suHPf7vM/W7tx7KXfvNSVu2agFMaNAXQsw/sHP/IPfHJgg2tvb9+57sfruiqVCm7FmpkJ8FBfX/bc2bPDt+WvHrv/wfVdV2aygKW3feUqoWPVuuztd5wbXr3irmPPPvVM17KLAQyW3vx4gP51j2ZHvxwZdsy8sbOzE8ViqWU7jz/+87r8+ecH67JzDmbWy+Dcivw9mCqELdve8aqry7/727y+L20wtV4mzj3c3YfKZKH1bP7rhrpc+eN/6nLeciCzXkdEVK1WEYZhqkBH9Q0Gz/NAHqjiA6GkO8SoPhHAHsNls4TrPlTSGY/qW8DIZLNwqkqhhAiCIJXxqL6ZQVVhKhQKkDLeDfpEgKkCAIkoNNR0QYnoCysAgxM1SCgNJz0w8Fxd3rv3w5YzBQSoGVQFoQBBhATf2papy5v/vLiDiOoTG8QUBsBCaTjp21+fL+fplw+2nCkqDAPgTGsBSSqfxZbVjftUBKaCQIBAkn52kVkYLR8GTAVgQEKBhFJrTTesBtxm29fcO7ovEhQOGSDAiSpEbnFQCJBa+UAESCr7xdJBdF/Is+VDBhGBiiZkgC6xfACnqlDVhoglRfLmjkcizlzjFNNapoSIHduiuFmjfDOiVRWACBoqNIHFdZHsHt2nTDCaDUoYBolEO4e/8MKLdWxo6O2bEi2hRrSqIa77wPUE6pjD9/9jnmeeeWXh5rI/L2dtLihAtTCDSiF+lKgUigCAnvd+Vccu/PLdxH0A4LIOMINTMwR+gMD344MSgzfFTOvlM1UECsX40WoqBm+GZTKAiYAYuPrdBK5PzwelJ6Jz7dRESxgAuIyDqsCpaD1tKWG8jsPsJvuICKY61zoTp01bImZa6z4sAEmCjzF4HMY3kDgAOBEBiEFJF8E4nHmhI9F9xFAVqIa3YrpfECC1EGCAA4AT+kAcHodRBGOqna5TFTAROOECG4c3w5gAEYWapLgXJ18M5zLQU8BL4NM4vBnm1ToynIiCmUAJ3sfhzTBmhpnCUo7giR1DBcaACxguiPc7Dm+GOfBc9xEQM5gZz0c6zNx6cfOvW8L2DL0925INavNBOfqv7AKdVrGf/NafbdkKEOAJwQnh3onfL9C5//wfWsL+t3oQjqlWPqVyuWRmYCK8s2eoYehLktGEOIkJpXI5FL9UyrDf8ENpl/NChH4xrIhfUja4kDCxakcTj28caReOuA4EyhCqYRU8OXll9/HjI3DZLMjzwJ4Hmn2S5KSHPQ8u41AsFjE9Pb2/UprcPXPhfWTcrSEWZsIyOY+wWth/VYq7P5OvkGEHN5sxTriJnPQmZMzD5VwJJQR7vZGRkRM9PT1rPEZf56qu1I7/99xZHDp0+NTg4I6tFy+MHm7L37FmhVfsk+UPx1Dm4h+PPSwvfYrJM/8+9c0Xr2/9ujh+uGN5fo2/An0PVO5M7ffR9nHsqx47tfP7A1sJQAeAtucGBrasX79+W1rjp0+fHhoefm97qVycAqAA2u5+8LEtqx96NrXtsHr525HP/v4LBeq2n1j5oy0P5O76k5J6TAQ1AxmBuHZTr2MgGBq/1TGQTQaFPR9cObrdAkz9Hw14UPPRoMvjAAAAAElFTkSuQmCC');
}
/* --- ControlBar --> BackAnDisplay --- */
.vjs-default-skin .vjs-back-anpanel-annotation{
float: left;
left: 0em;
right: 0em;
position:absolute;
background-color: rgba(0,0,0,0.3);
width: auto;
/*font-size: .3em;*/
font-size: .9em;
-webkit-transition: top .4s,height .4s,font-size .4s,-webkit-transform .4s;
-moz-transition: top .4s,height .4s,font-size .4s,-moz-transform .4s;
-o-transition: top .4s,height .4s,font-size .4s,-o-transform .4s;
transition: top .4s,height .4s,font-size .4s,transform .4s;
opacity:1;
visibility:visible;
transition-delay:0s;
}
/* --- ControlBar --> BackAnDisplay --> RangeSelectorDisplay --- */
.vjs-default-skin .vjs-rangeselector-anpanel-annotation{
height: 100%;
width: 100%;
position: absolute;
top: -1em;
}
.vjs-default-skin .vjs-back-anpanel-annotation.statistics .vjs-rangeselector-anpanel-annotation{
z-index:2;
}
/* RangeSelectorLeft */
.vjs-default-skin .vjs-leftselector-anpanel-annotation{
height: 100%;
margin-top: 1em;
width:0%;
float:left;
position:absolute;
left: 0%;
}
.vjs-default-skin .vjs-leftselector-anpanel-annotation .vjs-selector-arrow{
z-index:10;
border-left: 1em solid #FFE800;
cursor: e-resize;
}
.vjs-default-skin .vjs-leftselector-anpanel-annotation .vjs-leftselector-back{
right:100%;
border-right: 1px dashed #FFE800;
}
.vjs-default-skin .vjs-leftselector-anpanel-annotation.include .vjs-leftselector-back{
border-right: 1px dashed rgb(255, 163, 0);
}
.vjs-default-skin .vjs-leftselector-anpanel-annotation.include .vjs-selector-arrow{
border-left: 1em solid rgb(255, 163, 0);
}
/* RangeSelectorRight */
.vjs-default-skin .vjs-rightselector-anpanel-annotation{
height: 100%;
margin-top: 1em;
width:100%;
float:right;
position:absolute;
}
.vjs-default-skin .vjs-rightselector-anpanel-annotation .vjs-selector-arrow{
z-index:20;
border-right: 1em solid #FFE800;
margin-left: -1em;
cursor: w-resize;
}
.vjs-default-skin .vjs-rightselector-anpanel-annotation .vjs-rightselector-back{
border-left: 1px dashed #FFE800;
}
.vjs-default-skin .vjs-rightselector-anpanel-annotation.include .vjs-rightselector-back{
border-left: 1px dashed rgb(255, 163, 0);
}
.vjs-default-skin .vjs-rightselector-anpanel-annotation.include .vjs-selector-arrow{
border-right: 1em solid rgb(255, 163, 0);
}
.vjs-default-skin .vjs-leftselector-back,
.vjs-default-skin .vjs-rightselector-back{
height: 100%;
width: 100%;
position:absolute;
background-color: rgba(0,0,0,0.6);
}
.vjs-default-skin .vjs-selector-arrow{
width: 0;
height: 0;
border-top: 1em solid transparent;
border-bottom: 1em solid transparent;
opacity: 0.8;
position: absolute;
top: -2em;
}
/* RangeSelectorBar */
.vjs-default-skin .vjs-barselector-anpanel-annotation{
height: 2em;
margin-top: -1em;
background-color: rgba(0,0,0,0.6);
border: 1px solid rgba(255,255,255,0.6);
position: absolute;
float: left;
visibility:visible;
transition-delay:0s;
}
.vjs-default-skin .vjs-barselector-anpanel-annotation.disable{
visibility:hidden;
opacity:0;
-webkit-transition: visibility 0.5s linear 0.5s,opacity 0.5s linear;
-moz-transition: visibility 0.5s linear 0.5s,opacity 0.5s linear;
-o-transition: visibility 0.5s linear 0.5s,opacity 0.5s linear;
transition:visibility 0.5s linear 0.5s,opacity 0.5s linear;
}
.vjs-default-skin .vjs-barselector-anpanel-annotation .vjs-barselector-left{
float: left;
left: 1em;
}
.vjs-default-skin .vjs-barselector-anpanel-annotation .vjs-barselector-right{
right: 1em;
float: right;
}
.vjs-default-skin .vjs-barselector-anpanel-annotation .vjs-barselector-right,
.vjs-default-skin .vjs-barselector-anpanel-annotation .vjs-barselector-left{
height: 2em;
font-size: 1.3em;
position: absolute;
}
.vjs-default-skin .vjs-rangeselector-anpanel-annotation.active .vjs-barselector-anpanel-annotation{
z-index: 1;
}
/* --- ControlBar --> BackAnDisplay --> AnDisplay --- */
.vjs-default-skin .vjs-anpanel-annotation{
float: left;
width: 100%;
height: 100%;
left: 0em;
right: 0em;
position:absolute;
/*background-color: #FFE800;
background: #FFE800;
background: -moz-linear-gradient(top, #FFE800, #A69700);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FFE800), to(#A69700));
background: -webkit-linear-gradient(top, #FFE800, #A69700);
background: -o-linear-gradient(top, #FFE800, #A69700);
background: -ms-linear-gradient(top, #FFE800, #A69700);
background: linear-gradient(top, #FFE800, #A69700);*/
opacity: 0.8;
overflow: hidden;
}
.vjs-default-skin .vjs-anpanel-annotation .annotation{
height: 1em;
float: left;
width: 100%;
left: 0em;
right: 0em;
cursor:pointer;
position:absolute;
background-color: #FFE800;
background: #FFE800;
background: -moz-linear-gradient(top, #FFE800, #A69700);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FFE800), to(#A69700));
background: -webkit-linear-gradient(top, #FFE800, #A69700);
background: -o-linear-gradient(top, #FFE800, #A69700);
background: -ms-linear-gradient(top, #FFE800, #A69700);
background: linear-gradient(top, #FFE800, #A69700);
opacity: 0.8;
}
.vjs-default-skin .vjs-anpanel-annotation .annotation.active {
background-color: #2DCF02;
background: #2DCF02;
background: -moz-linear-gradient(top, #2DCF02, #114F01);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#2DCF02), to(#114F01));
background: -webkit-linear-gradient(top, #2DCF02, #114F01);
background: -o-linear-gradient(top, #2DCF02, #114F01);
background: -ms-linear-gradient(top, #2DCF02, #114F01);
background: linear-gradient(top, #2DCF02, #114F01);
z-index: 1;
}
.vjs-default-skin .vjs-anpanel-annotation .annotation.point {
border-radius: 50%;
width: 1em;
height: 1em;
margin-left: -0.4em;
}
.vjs-default-skin .vjs-back-anpanel-annotation.statistics .vjs-anpanel-annotation{
visibility:hidden;
}
/* --- ControlBar --> BackAnDisplay --> AnStat --- */
.vjs-default-skin .vjs-anstat-annotation{
float: left;
width: 100%;
height: 100%;
left: 0em;
right: 0em;
position: absolute;
opacity: 0.8;
visibility:visible;
transition-delay:0s;
font-size: 1.2em;
}
.vjs-default-skin .vjs-anstat-annotation.disable{
visibility:hidden;
opacity:0;
}
.vjs-default-skin .vjs-totan-anstat-annotation,
.vjs-default-skin .vjs-maxcon-anstat-annotation{
position: absolute;
top: 0.3em;
background-color: rgba(0,0,0,0.5);
border-radius: 0.5em;
padding: 0.1em;
color: rgb(255, 163, 0);
}
.vjs-default-skin .vjs-totan-anstat-annotation{
float: right;
right:1em;
}
.vjs-default-skin .vjs-maxcon-anstat-annotation{
float: left;
left:1em;
}
/* --- ControlBar --> BackAnDisplay --> --- */
.vjs-default-skin .dashed-line{
float: left;
right: 0em;
position:absolute;
color: #2DCF02;
border-left:0.23em dashed #2DCF02;
border-right:0.23em dashed #2DCF02;
opacity: 0.8;
}
.vjs-default-skin .dashed-line.point{
border-right:0;
}
.vjs-default-skin .box-dashed-line{
height: 1em;
float: left;
right: 0em;
color: #2DCF02;
position:absolute;
background-color:#2DCF02;
opacity: 0.8;
}
.vjs-default-skin .boxup-dashed-line{
height: 1em;
float: left;
right: 0em;
color: #2DCF02;
position:absolute;
opacity: 0.8;
background-color: #2DCF02;
background: #2DCF02;
background: -moz-linear-gradient(top, #2DCF02, #114F01);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#2DCF02), to(#114F01));
background: -webkit-linear-gradient(top, #2DCF02, #114F01);
background: -o-linear-gradient(top, #2DCF02, #114F01);
background: -ms-linear-gradient(top, #2DCF02, #114F01);
background: linear-gradient(top, #2DCF02, #114F01);
z-index: 4;
-webkit-transition: top 1s,-webkit-transform .4s;
-moz-transition: top 1s,-moz-transform .4s;
-o-transition: top 1s,-o-transform .4s;
transition: top 1s,transform .4s;
}
.vjs-default-skin .boxup-dashed-line.point{
border-radius: 50%;
width: 1em;
height: 1em;
margin-left: -0.4em;
}
/* --- ControlBar --> BackAnDisplay --> BackAnDisplayScroll --- */
.vjs-default-skin .vjs-down-scroll-annotation,
.vjs-default-skin .vjs-up-scroll-annotation{
width:2em;
height:2em;
float: left;
right: 0em;
position:absolute;
cursor: pointer;
border-left: 1em solid transparent;
border-right: 1em solid transparent;
/*font-size: .3em;*/
font-size: .9em;
-webkit-transition: top .4s,height .4s,font-size .4s,-webkit-transform .4s;
-moz-transition: top .4s,height .4s,font-size .4s,-moz-transform .4s;
-o-transition: top .4s,height .4s,font-size .4s,-o-transform .4s;
transition: top .4s,height .4s,font-size .4s,transform .4s;
opacity: 0.8;
transition-delay:0s;
}
.vjs-default-skin .vjs-up-scroll-annotation{
border-bottom: 1em solid black;
}
.vjs-default-skin .vjs-down-scroll-annotation{
top: -3em;
border-top: 1em solid black;
}
.vjs-default-skin .vjs-scroll-anpanel-annotation.disable,
.vjs-default-skin .vjs-back-anpanel-annotation.disable{
visibility:hidden;
opacity:0;
/*-webkit-transition: visibility 1s linear 1s,opacity 1s linear;
-moz-transition: visibility 1s linear 1s,opacity 1s linear;
-o-transition: visibility 1s linear 1s,opacity 1s linear;
transition:visibility 1s linear 1s,opacity 1s linear;*/
}
.vjs-default-skin:hover .vjs-back-anpanel-annotation,
.vjs-default-skin:hover .vjs-down-scroll-annotation,
.vjs-default-skin:hover .vjs-up-scroll-annotation {
font-size: .9em;
-webkit-transition: top .2s,height .2s,font-size .2s,-webkit-transform .2s;
-moz-transition: top .2s,height .2s,font-size .2s,-moz-transform .2s;
-o-transition: top .2s,height .2s,font-size .2s,-o-transform .2s;
transition: top .2s,height .2s,font-size .2s,transform .2s;
}
/* --- ControlBar --> BackAnDisplay --> BackAnDisplayScroll--> BackAnDisplayScrollBar --- */
.vjs-default-skin .vjs-scrollbar-anpanel-annotation{
float: left;
position: absolute;
cursor: pointer;
right: 0.5em;
width: 1em;
/*font-size: .3em;*/
font-size: .9em;
background-color: black;
opacity: 0.8;
visibility:visible;
transition-delay:0s;
-webkit-transition: top .4s,height .4s,font-size .4s,-webkit-transform .4s;
-moz-transition: top .4s,height .4s,font-size .4s,-moz-transform .4s;
-o-transition: top .4s,height .4s,font-size .4s,-o-transform .4s;
transition: top .4s,height .4s,font-size .4s,transform .4s;
}
.vjs-default-skin .vjs-scrollbar-anpanel-annotation.disable{
visibility:hidden;
opacity:0;
-webkit-transition: visibility 1s linear 1s,opacity 1s linear;
-moz-transition: visibility 1s linear 1s,opacity 1s linear;
-o-transition: visibility 1s linear 1s,opacity 1s linear;
transition:visibility 1s linear 1s,opacity 1s linear;
}
.vjs-default-skin .vjs-scrollbar-anpanel-annotation:hover{
opacity: 0.8;
visibility:visible;
transition-delay:0s;
}
.vjs-default-skin:hover .vjs-scrollbar-anpanel-annotation{
font-size: .9em;
}
.vjs-default-skin .vjs-scrollbar-selector{
width: 1em;
background-color: gray;
border: 1px solid black;
position:absolute;
height: 3em;
border-radius: 0.4em;
}
/* --- ControlBar --> BackAnDisplay --> BackAnDisplayScroll--> BackAnDisplayScrollTime --- */
.vjs-default-skin .vjs-down-scrolltime-annotation,
.vjs-default-skin .vjs-up-scrolltime-annotation{
width: 100%;
height:1em;
float: left;
position:absolute;
font-size: .9em;
opacity: 0.8;
transition-delay:0s;
right: 3em;
text-align: right;
margin-top:0.2em;
}
.vjs-default-skin .vjs-down-scrolltime-annotation{
top: -2.6em;
margin-top: -0.2em;
}
.vjs-default-skin .vjs-scrolltime-anpanel-annotation span{
font-size: 1.3em;
background-color: rgba(0,0,0,1);
border-radius: 0.5em;
padding: 0.1em;
border: 0.1em solid white;
padding-left: 0.4em;
padding-right: 0.4em;
}
/* ---------------- Modify the CSS of Annotator plugin ---------------- */
.annotator-wrapper.vjs-fullscreen .annotator-adder,
.annotator-wrapper.vjs-fullscreen .annotator-outer,
.annotator-wrapper.vjs-fullscreen .annotator-widget,
.annotator-wrapper.vjs-fullscreen .annotator-notice {
z-index:3000000000; /*To fix full-screen*/
}
/* ---------------- Modify the CSS of Video-js plugin ---------------- */
.vjs-default-skin *, .vjs-default-skin *:before, .vjs-default-skin *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
/* ---------------- Modify the CSS of Range Slider plugin ---------------- */
/* Selection bar in green color */
.vjs-default-skin .vjs-rangeslider-holder.locked span.annotator-hl > div.vjs-selectionbar-RS {
background-color: #2DCF02;
background: #2DCF02;
background: -moz-linear-gradient(top, #2DCF02, #114F01);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#2DCF02), to(#114F01));
background: -webkit-linear-gradient(top, #2DCF02, #114F01);
background: -o-linear-gradient(top, #2DCF02, #114F01);
background: -ms-linear-gradient(top, #2DCF02, #114F01);
background: linear-gradient(top, #2DCF02, #114F01);
}
.vjs-default-skin div.vjs-rangeslider-holder.locked .vjs-rangeslider-handle > div.vjs-selectionbar-line-RS {
background-color: #2DCF02;
}
.vjs-default-skin div.vjs-rangeslider-holder.locked .vjs-rangeslider-handle > div.vjs-selectionbar-arrow-RS {
border-top-color: #2DCF02;
}

View File

@@ -0,0 +1,186 @@
/*Range Slider Bar Time*/
.vjs-default-skin .vjs-timebar-RS{
color: red;
top: -1em;
height: 100%;
position: relative;
background: rgba(100,100,100,.5);/*Quitar*/
}
/*Selection Range Slider Bar Selected*/
.vjs-default-skin .vjs-rangeslider-holder{height: 100%;}
.vjs-default-skin .vjs-selectionbar-RS{
height: 100%;
float: left;
width: 100%;
left: 0em;
right: 0em;
position:absolute;
background-color: #FFE800;
background: #FFE800;
background: -moz-linear-gradient(top, #FFE800, #A69700);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FFE800), to(#A69700));
background: -webkit-linear-gradient(top, #FFE800, #A69700);
background: -o-linear-gradient(top, #FFE800, #A69700);
background: -ms-linear-gradient(top, #FFE800, #A69700);
background: linear-gradient(top, #FFE800, #A69700);
opacity: 0.8;
}
.vjs-default-skin div.vjs-rangeslider-holder.locked > div.vjs-selectionbar-RS {
background-color: #FF6565;
background: #FF6565;
background: -moz-linear-gradient(top, #FF6565, #300000);
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FF6565), to(#300000));
background: -webkit-linear-gradient(top, #FF6565, #300000);
background: -o-linear-gradient(top, #FF6565, #300000);
background: -ms-linear-gradient(top, #FF6565, #300000);
background: linear-gradient(top, #FF6565, #300000);
}
/*Arrow and Handle*/
.vjs-default-skin div.vjs-rangeslider-handle {
position: absolute;
margin-top: 0;
cursor: pointer !important;
background-color: transparent;
}
.vjs-default-skin .vjs-selectionbar-left-RS{height: 100%;left: 0;z-index:10}
.vjs-default-skin .vjs-selectionbar-right-RS{height: 100%;left: 100%;z-index:20}
.vjs-default-skin div.vjs-selectionbar-left-RS,
.vjs-default-skin div.vjs-selectionbar-right-RS {
top: 0em;
position: absolute;
width:0em;
}
.vjs-default-skin div.vjs-selectionbar-arrow-RS {
width: 0;
height: 0;
border-left: 1em solid transparent;
border-right: 1em solid transparent;
border-top: 1em solid #FFF273;
margin-left: -1em;
opacity: 0.8;
position: absolute;
top: -1em;
}
.vjs-default-skin div.vjs-rangeslider-handle.active > div.vjs-selectionbar-arrow-RS {
border-top-color: #5F5FB3;
}
.vjs-default-skin div.vjs-rangeslider-holder.locked .vjs-rangeslider-handle > div.vjs-selectionbar-arrow-RS {
border-top-color: #FF6565;
}
.vjs-default-skin div.vjs-selectionbar-line-RS {
width: 1px;
height: 1em;
background-color: #FFF273;
position:absolute;
top: 0em;
}
.vjs-default-skin div.vjs-rangeslider-handle.active > div.vjs-selectionbar-line-RS {
background-color: #5F5FB3;
}
.vjs-default-skin div.vjs-rangeslider-holder.locked .vjs-rangeslider-handle > div.vjs-selectionbar-line-RS {
background-color: #FF6565;
}
/* Time Panel */
.vjs-default-skin .vjs-timepanel-RS{
width: 100%;
height: 1em;
font-weight: bold;
font-size: 15px;
top: -2em;
position: absolute;
visibility:visible;
opacity:1;
transition-delay:0s;
}
.vjs-default-skin .vjs-timepanel-RS.disable{
visibility:hidden;
opacity:0;
-webkit-transition: visibility 1s linear 1s,opacity 1s linear;
-moz-transition: visibility 1s linear 1s,opacity 1s linear;
-o-transition: visibility 1s linear 1s,opacity 1s linear;
transition:visibility 1s linear 1s,opacity 1s linear;
}
.vjs-default-skin .vjs-timepanel-left-RS,
.vjs-default-skin .vjs-timepanel-right-RS{
font-weight: normal;
font-size: 1em;
color: #666666;
border: 1px solid #666666;
background-color: white;
border-radius: 5px;
position: absolute;
height:116%;
padding-right: 0.3em;
padding-left: 0.3em;
}
.vjs-default-skin .vjs-timepanel-left-RS{
left:0.5%
}
.vjs-default-skin .vjs-timepanel-right-RS{
left:92%
}
/* Control Time Panel */
.vjs-default-skin .vjs-controltimepanel-RS{
width: 18em;
font-size: 1em;
line-height: 3em;
}
.vjs-default-skin .vjs-controltimepanel-RS input{
width: 1.5em;
background: rgba(102, 168, 204, 0.16);
border: 1px solid transparent;
color: black;
font-size: 1em;
margin-left: 2px;
text-align: center;
color: white;
}
.vjs-default-skin .vjs-controltimepanel-left-RS{
width: 50%;
float: left;
}
.vjs-default-skin .vjs-controltimepanel-right-RS{
float:right;
width: 48%;
}
.vjs-default-skin .vjs-controltimepanel-RS input{
margin: 0;
padding: 0;
display: table-cell;
}
/* ---------------- Video-js plugin ---------------- */
.vjs-default-skin *, .vjs-default-skin *:before, .vjs-default-skin *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
}

View File

@@ -0,0 +1,33 @@
.annotator-viewer div:first-of-type.richText-annotation *,
.annotator-viewer div:first-of-type.richText-annotation{
font-style: normal;
font-weight: inherit;
text-align: inherit;
}
.annotator-viewer div:first-of-type.richText-annotation{
padding-top: 22px;
}
/* Fix in the tinymce */
.annotator-viewer div:first-of-type.richText-annotation strong{
font-weight: bold;
}
.annotator-viewer div:first-of-type.richText-annotation em{
font-style: italic;
}
.mce-container {
z-index:3000000000!important; /*To fix full-screen problems*/
}
/* Some change in the design of Annotator */
.annotator-editor .annotator-widget{
min-width: 400px;
}
/*Rubric icon*/
.mce-ico.mce-i-rubric{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QkBBB07nraNoQAAAhZJREFUKM+NkstrE1EUxr+5c08ykztpJtVoazHBF8FgQQzonyBKEZS6FrQKLl0EXBRT0ZULJSs3oii4TyHgu90IlTaL6qouWlv7Ck1N0BSnmZk714WbPHz07M4534+Pw3eAHdTY8A9+Nd9/bshU1DpnO4HXjh2ZY2J9/OSTxHTrnP8PvJYf+BDQ6qEDaQBB43jrTusUFy4oPjsYWYzF+VS91nxLYfdhKgONaQT3W/KMxr1XY5e+qj86f8zsKYYsZ6AvjWFzA8ORHkAnwN8So7evzL/8pzMAXL/Hq8mMv1up371T7Z+/c3n9cKeuDS6Xy6dN07zLuZ56Onk2Ed2/ANJsnE/PQMpgyffle+kYzwazB1+3waVS6X48Hr9BRPB9H57nYXplFKeSt8D1Hriug9XKF0x+Lmw+ys8m2m42DOOn4zhQSsGyLOi6jqONm9isbmFVFlDbaGKx8QaB1rvdlbNhGLAsC0IIGIYBIQSy2ROQ0oOp7wOPraHXEugRvDtnzjmi0SiICEIIEBGklAB9B6cmbG0AUnrY5m73h+m6DsYYTNMEYwxEBMY0hGNVhHkcZigBO9qHlDHS7cwYg23bAIBQKAQigud7IH0XwtxDoHwEIQ9SLKx0wa7rPiaivYyxESklXNeFBg0mjyNQTQSuATMSm6ipuYt//eVcLhdeXl5+UKlUlur1upqamVAv3j3/VCyOD3VqfwF6uLp3q+vMcgAAAABJRU5ErkJggg==');
background-repeat: no-repeat;
}

View File

@@ -0,0 +1,203 @@
/* Editor */
.share-container-annotator{
display: block;
min-width: 100%;
border: none;
margin: 0;
color: rgb(60, 60, 60);
background: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
resize: none;
padding-left: 0.6em;
padding-right: 1em;
height: 3.1em;
}
.share-container-annotator .share-text-annotator{
float:left;
padding-top: 1em;
}
.annotator-editor .share-container-annotator .share-text-annotator{
height: 2.1em;
}
.share-container-annotator .share-button{
top: 0.3em;
outline: 0;
float:left;
position: relative;
text-align: center;
margin: 0;
padding: 0;
margin-left:0.3em;
height: 2.5em;
width: 2.5em;
background-repeat: no-repeat;
background-size: 2.5em;
}
.share-container-annotator .share-button:hover{
opacity: .6;
cursor: pointer;
}
/* Popup */
.share-container-annotator .share-popup-overlay-bg{
display: none;
position: fixed;
top: 0;
left: 0;
height:100%;
width: 100%;
cursor: pointer;
background: #000; /* fallback */
background: rgba(0,0,0,0.75);
padding: 0;
}
.share-container-annotator .share-popup-overlay-bg .share-popup{
background: #fff;
padding: 1% !important;
width: 30%;
position: relative;
top: 15%;
left: 50%;
margin: 0 0 0 -17% !important; /* add negative left margin for half the width to center the div */
cursor: default;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0,0,0,0.9);
}
.share-container-annotator .share-popup-overlay-bg .share-popup-copy,
.share-container-annotator .share-popup-overlay-bg .share-popup-title{
font-style: normal;
font-weight: bold;
font-size: 21px;
padding-top: 0px;
padding-bottom: 20px;
color: #222;
}
.share-container-annotator .share-popup-overlay-bg .share-popup-uri{
width:100%;
border: 1px solid rgb(144, 144, 144);
height: 24px;
margin-bottom: 10px;
}
.share-container-annotator .share-popup-overlay-bg .share-popup-copy{
font-size: 15px;
float: left;
width: 100%;
padding:0;
margin: 1.0em 0em 0.5em 0em !important;
}
.share-container-annotator .share-popup-overlay-bg .share-popup .share-button{
width: 50%;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
border: 1px solid;
margin: 0;
text-align: center;
alignment-baseline: central;
background-position: 0.5em;
height: 4em;
padding-top: 1.3em !important;
}
/* Close button */
.share-container-annotator .share-popup-overlay-bg .close-btn {
cursor: pointer;
position: relative;
display: inline-block;
padding: 0 12px 0 12px;
color: rgb(54, 54, 54);
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.75);
text-decoration: none;
line-height: 24px;
font-size: 12px;
font-weight: bold;
border: 1px solid rgb(162, 162, 162);
background-color: rgb(212, 212, 212);
background-image: -webkit-gradient( linear, left top, left bottom, from(rgb(245, 245, 245)), color-stop(0.5, rgb(210, 210, 210)), color-stop(0.5, rgb(190, 190, 190)), to(rgb(210, 210, 210)) );
background-image: -moz-linear-gradient( to bottom, rgb(245, 245, 245), rgb(210, 210, 210) 50%, rgb(190, 190, 190) 50%, rgb(210, 210, 210) );
background-image: -webkit-linear-gradient( to bottom, rgb(245, 245, 245), rgb(210, 210, 210) 50%, rgb(190, 190, 190) 50%, rgb(210, 210, 210) );
background-image: linear-gradient( to bottom, rgb(245, 245, 245), rgb(210, 210, 210) 50%, rgb(190, 190, 190) 50%, rgb(210, 210, 210) );
-webkit-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.2), inset 0 0 1px rgba(255, 255, 255, 0.8);
-moz-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.2), inset 0 0 1px rgba(255, 255, 255, 0.8);
-o-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.2), inset 0 0 1px rgba(255, 255, 255, 0.8);
box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.2), inset 0 0 1px rgba(255, 255, 255, 0.8);
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-o-border-radius: 5px;
border-radius: 5px;
}
.share-container-annotator .share-popup-overlay-bg .close-btn:hover {
outline: none;
border-color: rgb(67, 90, 160);
background-color: rgb(56, 101, 249);
background-image: -webkit-gradient( linear, left top, left bottom, from(rgb(118, 145, 251)), color-stop(0.5, rgb(80, 117, 251)), color-stop(0.5, rgb(56, 101, 249)), to(rgb(54, 101, 250)) );
background-image: -moz-linear-gradient( to bottom, rgb(118, 145, 251), rgb(80, 117, 251) 50%, rgb(56, 101, 249) 50%, rgb(54, 101, 250) );
background-image: -webkit-linear-gradient( to bottom, rgb(118, 145, 251), rgb(80, 117, 251) 50%, rgb(56, 101, 249) 50%, rgb(54, 101, 250) );
background-image: linear-gradient( to bottom, rgb(118, 145, 251), rgb(80, 117, 251) 50%, rgb(56, 101, 249) 50%, rgb(54, 101, 250) );
color: rgb(255, 255, 255);
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.42);
}
.share-container-annotator .share-popup-overlay-bg .close-btn:active {
border-color: rgb(112, 12, 73);
background-color: rgb(209, 46, 142);
background-image: -webkit-gradient( linear, left top, left bottom, from(rgb(252, 124, 202)), color-stop(0.5, rgb(232, 93, 178)), color-stop(0.5, rgb(209, 46, 142)), to(rgb(255, 0, 156)) );
background-image: -moz-linear-gradient( to bottom, rgb(252, 124, 202), rgb(232, 93, 178) 50%, rgb(209, 46, 142) 50%, rgb(255, 0, 156) );
background-image: -webkit-linear-gradient( to bottom, rgb(252, 124, 202), rgb(232, 93, 178) 50%, rgb(209, 46, 142) 50%, rgb(255, 0, 156) );
background-image: linear-gradient( to bottom, rgb(252, 124, 202), rgb(232, 93, 178) 50%, rgb(209, 46, 142) 50%, rgb(255, 0, 156) );
}
/* Viewer */
.annotator-viewer .share-viewer-annotator div.share-text-annotator,
.annotator-viewer .share-viewer-annotator div.share-button{
border-top: 0;
padding: 0;
background-size: 2em;
margin-top: -0.2em;
top: -0.5em;
font-style: normal;
}
/* Buttons */
.share-container-annotator .share-facebook-annotator {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wDDBEyHq7FE8kAAAo8SURBVFjDtZhZjF1XlYa/tfY559atcs0uDxnsJN0iGAGJcIgYgkAgoEULCYH6oZtIIPGAExwgQYQExAsSCgmJEcIMfkCAQOEhQjSIILUYlAi6W93CYkg6jSUnHmJXYlelXNOtutPei4e9z1DBDlO40tW955x99vn3Wv//r7WPkD53f+0o9xzYH/8fedypyKggowgIgvHifgSwOGuwENaD+e49B66zu488xj0ffMWWcdz5pf/mvtteC8BdR373MhX3rpHC3ZQ7udo5yf5eAIMZPthGf+D/rzcMPw8h/ODeA69cuOurv+Vzt1wXx93+ue/yhbv+NYL76u/emzn36dmp4trpbQVFLiBCCAH+DgBFFDNjs+dZWOkPVzv9R4P3H7r31uuPfeLwUe49uB95oGd8rCXc+aWj79A8/8ZVO7ft2D6V2/qm5/yFHqsbA7FgLzpAA/JcmR4vbG6yIHfCyXObsrjcfUwGm2++9yOvWfz4oZ/GFH/0/kdnstb4t6/YOfZPV8y19dxS154+vyFDj6nEMS8C4S768cHYNpLZ1ZeNMVJk/P70il/t9O479OEbPgWgN+YQjFe2W7p/x1Qhz6307NSzHekPvIFJMOOv/foQ6PY9FowiE8bHcmYnc7ZPFWyfLJgez2nlYiudvjw1v44PQ9k103ICb7zt849cDpC96pMPagjh2onRbML7wNnFDbr9IU5VvP/ruRfMGA49r943y7V7J5gebzFSOPJM0FJ0Ag//51k5euyCBTN55rmu7Z4dkTyTKzc7g13A2UxCEMJwKs+02Oh5ubDSwzk1M5O/DVzg5rdfzQ37ZnAqiMQ8i2jKd5y+VSgD7yV3sLzWY26ysCLTzPobbYBsfem0TLa3q1mgNxjS63taRRD7G3ThA7z1hl3c+LJZREjgatWWx2BYMMLQE3Jhc3NAbxAEC+KHPd0GZGYiIQSCD4hA/K8vqL7ns1+im8frBrMTBfv3bUd1axK2gqvv9z5gPuAVsRAihhCvZmCEEEktwQgh8OdRr5RmSpeUcjcunxtl+1TrjxeXAJ4536GzOcQwLqz2EIyhDzgnBAsJT2AkRtASYkPF8MHzgr4sWsUyRqMOnQEqMDfVot1yiEgFysxQVR766Ql+9qtnyDPFMPwwUOSSMAgWDLOAmbEdyLwPeO8JPpqeDY2gniYHKyBCI2qNSEpVG0CN0ZGswbsa5PJ6nx/98gxFIYR+jIKFkKpV5KMFY+g9fui5HMhWlldZC+eZnRpnbNsoG70N1LVRFUQUESqwZkSDkKYQDTOwABDAdMvimrw7dmoFVRgMDQUChlOQZEkbfkCns8H5c4s8++wCP3kdZL1Bn/7yKheWlul0Njh54hmmJsZotVqMtEdotQpcluGcQ1URpxFZQmHA3p1jvHTvBN4MFWHv7rEt4MrfXdMjvO01l4EJpVb+6zdn+P/j5xgOBmQa2DEWWFpaYW2tAx+DTBBEY5rMB3rdPquAsAGiqDryPKNoFWR5Rp7n5LnDuQznFFXlql07+Je3XMXABzBwKpiFip8lyCt2jvGeHVdBzCqZUx574jRnzi4xPprTHskAcBrnZwmymC4BU0ARNJmpYCgGdHtDur1hg3OOzCmSDHj5pVOICEXmqpSXoMpIAjinuIuJTjTRSUCVZJ7QTRGkvFh+y3OJ+CUfq7yIYAgW4nFodDt/ucELgtbAkBQowEFWehgioIJo5FqwCFAaN5XVoFy5SAx+u108T/F11JoGf7Ha2W7lyerrIMRnKHQgqyYVSRFTDEVTlKBxk5EWU6pbUBF+8etnWVrtRy9V4Q37d3PT9bu3CEWAx598ju//7ASSyoQI/ObYAtvGisa8jYBIKRJqcCKS+JNsRiMfRQScqx5mCCqKKZw4u86Tp9dABOeE3XNjFcBmaZs/3+GHj5xExJJDGU6Moijw3leUEo38ZrlMMVobbQlUEzek5oikZcd0xWtqIE5xaakqgtOL13IVpciz1O6Xvhkrh2zhfbVdoh4tpd1ERVm6IVaHqG6TSjaNylFHlMqyLtGpSVNsITlHMn61inv1/JCRUlqSXysepOipVkClSeaqNRC0VJ3QeMAlFCsOJN4Vq3dATOO5FAjKRUgCWK64jE8ZalOtlZV8UbRpNw6HEBLX5E9GkGouEat3xpJAIpRGKQgYZFjTRrRSk9HkYDynjWMrF1G1WUq5yecS+6w6+pF/sclO9hV7oYo2iMACZIglbtQWUltODa4Cj2C4LaKqxxL5eokIxvMuRY8oDgGjTHHTYhQ2GzZTWklZcswaXGyAE3H1SqUGFR8oldIv3o4nCkDiXQ2y9ECpcJA4aLXC6vZOEgapFB1vcvG4nAgXsZc0cUlUppdUMaKIhZgU0/icZDOGmsSqkVyk8sFSkalJENmixgocDXCNWo026SCXDGAdAa04aCmSiCEiYpWrCPSTikOyiyyrH2xNw0wcrG6m4YvS4GhZq19AxXH+VJnNJ6HEBTrVmPVy07wGqhIwbwwGgZFWxthojg+YNNRbprxMawmuqjpVN6Rb9ykX9cG4mOi3rkq9DzA+VtBqZXT7ARWB14POTDqfu+HS8lqv125ldvncNgvBokuJWPlAMWl0Q7GkqUZfFIl1WZ0DyV5AxSW/pXI3UTUzoVU4u2xulOHQ6Gz0enk27MpdoK+ffjiM5SvHl1bWl9Y6A/5xzwRz022zgIGIGVaZeZN3qeuJxy42t0n565vG4nKf+cUe8ws95he6XFgbsLLua6NGMRMLJiIqtmf3NnbNtu3JM6tmvvfUbHZuvipOt95yYNfxjeu+7Mb2vPtV+2aDaibHTq/J4oWu9Qci0afURFRiqWpWmLLjcZRlc/tkwexklhpZwwg4heXVHmfPbxoEEQIigXauduXOEdm7u23zi5s8cXzBT8nxu985df8XP3D/mUEG8Kapb5/ztI6c2Che/j+Pu5fsu2Y8XP+SaetsBlntDGzgY5VGIneaZi44RGt1lwKxaq8cwBwWAjOTBTOTuZgFEwKtXGR6PEPFwvGnO3JqfkVm9MQPrmn970MfuP/MAMB9/Lb3cvsDR3nnayeeHm11n+l0s6tPPZftWFwe6MCbFFlmzjlxTsmcok5QdWSqqNO4cXKKU0WzyEOXuXisghNJv6DOyITEX2zQ98wvdjl2asWWlpY6O90T/76n+NVnHzj84DGAz9xyfUzxHQffz6HD3+TTB2/KO/zDqxcGe/75wmDnDV7a0yIuvv55XvNqUvYwjY5bU7skZRtgkN4UYLH3CxgSQowsZhaGwxYrZ2eypx+dcScfvu/wQ08B3HnwPdx3+Hu1H9x+8Ga+cPg7HLntSk5y42QvtHcGdMIHXKphYFX/0XzZljyNurczq984WP32oX4Zb2CCqpgKvUyHSy1WFj57+MebAHfc+m8c+sqDjbY1fe740Ps49OVvAXDngTfj3ax4b5hPXGpugSykM89/v9sc98clTxtl1amSuYFN9/6DT359AMCtH7yZrxz5TjX+DwV2mkY3fbDAAAAAAElFTkSuQmCC');
}
.share-container-annotator .share-twitter-annotator {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wDDBEyN+x3i6UAAAriSURBVFjDnZhrjF3XVcd/a53HvXfuzNieiY2d2q6dh0NJQkPACTVEhArlQwlIcflQoxZoSEIDLaqqCpCKQAgJhNRviEQkadqUNK4ixVTCFREWqRSJtrHSpqQJips0cZz4lbHH877nnsdefNj7nHvueJJSjrU955577t5r/9f6/9daW2hd9x+f48FbtvLp4/ORs6pvqrG1vpcwxp4oEF4SBRxY/VL7x2bNT+pbE8HK0kWq2QO3XJEB/PG3T/PAgfeNrcmf/OfblNMp/7x/G/cfnztQVfZbvUgOdmLdHYmIKIj5t6W1riAYIONWt63CEMT83bqvqEDKqlocVDxbVO6pK4bzX/+72z9g9z3zGg99+JqRgfc89w6P3LqNTz13/q+qyv50Vz+enUkjJiJFFUrzM8roJyCGrMMTWw/x6IHV9+afKhAJ5AYruePsoGShqI499KHtdwB87PN/zde/+DfIvd8+y8MHdnDfd85/tqjcP9w0k6ab0tgGpZOTqyXLhfMIyWjtMUPrZxugaHY5ovX/Fuzdlkbs7scmInJiKedCVr3w8IHtN4+5+J7/OrOzNI7um0o+uL2fMJeVvLZUoCIeKQEx706D8NwvIoBKCyHv1MsQa/bjRq4HwxmkCj87ndp0qvLduUGeZ8PPP3L7nn8kIE1ZVbf2Yvngtl7M0rDixGIOYRFnYCYMHcQq7JtK2D/b4Veu6PLzW1K2diMGlcOZYOFd1/rrxBrEzMzPGWLXBVMzZ5xYziUrjX1TaZojv/0HPWKA+JMPPaV5nl+7c6pDJNgry7kQEDHzrssqx1WTCXftmuK66YQ00sZp72Qlz74z4Ni5tYZIbfdYiN8WsyAYai2qLecV5waF7ZhIZEJtV/bot67m0K+fUHp9dVU5m6qwUlayWlTBnYYJOHPs6EXcfc0mbtzSIYlqHvvpt3Zifmf3JB+a7SLqN1SPmlpmXnpqjzhcg7ZHFFSF04NSRKCr1ndlPgUQqznMuSgSWCl9UDmxJsojlFtme+ycSABjvjB+nEOOMBM5rutAZMLH907xvfmM3NaTw28U87HpzBDUB5A4zI24vlpYTUaxIo8A1DknzlyIBz+5M4cz7+JI4de29bxxpXGqgL0pXN8xuiK8OPCzx6rcuLmDM2shbEGOWuwNsV2FEKpf98haszkXNCy2oOgEMfVGSpAWowJmujEAZwtjOhYuOT+RCsQCSxVMR/D+fsx/nHPEul5ehEQ8W4XgIQvRJy5IjoQMZH6TwdDYTDAzXCC+w9CW4rmWu0yENWeB4aPYd+H7/TNddkwk6+Wb0hknVwuOnl6hMIgDCK5B1k9kwWvWylZxZY7KjQuoqbUUf7RcYVCZjCUt17qf6cYN2usF+hdmuty1a4rH31jk6bOrJOLJgkmTo7w3vcFV8HV8frDGwuoq7x/m9OOI3DniKEICDU3G00E+0uNGNQbOiFq7Xp/xBJhQQ4GP791EqsK/nV5B1acoNaNwjtWyYi0vOb+WcWEtqxE0Lg1z5gYZy5Hy5tKAXhLTiyL6acxkHI+t5MZykL9OFpBgdAQSpTG2cH5DOdAR+LmOkIpxx/YJvn9pwKvLOVlZMigqhlXFUl5yYXPEwrAgc36lWEyIRYlEMbwol5SsFhWX8pxERxGfVTA0iMxQkVB+CU4gx8hNkGokM67JFrDijOcGjtv6EZs7MQkVp5bXAoNdcCtEIkQiTWjFiCAiQWSVSKVZfExxgcwZq27cx7ZBMbAByAjCRTMqZ0Qq7JxIiVUonQuqGJZDUJVm2VjFy4WG4klEUAl/xyji2ViYIBj/n6tyRiSjzQKoeO1x5lC1xkgRDS4ORnnAfPJV8YapEgJ5xOLc2WUy4jYsrcavzBm/3NcmF7+1WgCKqmHOUFGUkPxb4h4r6tFSQUMpNdqFjBWlpUGxgYGzMXT13Y2MRLi2I2xPfdS+tJjx0tIQVa/BiPeKiPeZiiIBmNjEPILBMK1jMoyoRZLCGaVbvzjcPKFsT/Un+9iM+WHJg6/Ns1A4UhWcRZi5pnJqRk0SqRGzkKWDkRoQbMNVGuTmxlB1VrcE7345M06tFbyxWvDV1xd4eWlIP45wzoViV3BApJ6AtU1jMTiyXAOL/T9tsbh0RuHGHRy/h0DXqJlBLMLz8wNeXMrZnES+plYF57xYB2LW0lVPpBKKH9FgkNbxF9i9HkFnFO1hYx3l5ZdIkJWEz+6b5ckDVwaxD0ZpWLMGRf2oAyb2VYtvDZsXtUZ0PK42ikEnxuncheJ2YxenYszESlfh6n7K396wlb98aY44xJRhKDbWnDUx6Br3hhhsejZ/FwemiQjTEZzJL2fxN+crSqveRR+FyoyZWPjYtoQ9HeEXt3S5aXOXHy4OUfM9ChKkxuqVPTiqoQtrS8uIyRCp8upyDsBNk8piaRQ2PhQjFSMRNhhGV2G5Mr5yNgeBXqzs7SdE7SyGjBBsJTCtMwfrvqyNLBw8e2EAwL6JmNlIGFa+EPhphplwsTDWqlCadWLi9Zpbx6GMzlO0tqgmS20Ywe0OeGFhyGJeYmZ8ZmdC5YyhM/KfYmTON5sTkTdwqXSYemNUW17DELFGPXQUbyNq1/Ii+MrixErBN88NEIHJCD63O+XqrhIjLJewWMBi+R6j8Gc7n/iZFBBKZ5xaK70466iyaAjbljEN/YcDEh0VDNKSgQQ4/PYKvUi4632TXNkx7r0y5VTmmC+Nwvnfv9sZkgA7OsJVvQjM+MHikFeWCyL1LYWI70uTEITGKAhjw5kKljvH7iSlNEhrMMXrUV1MPHZqlf9ZKfnCdZtIFa6p/fV/qm78HO8MK/7+R4tUNTHNUJQKx2wnxuEonBGZFy297QsHq24+eHuh8C3MVZMdnMkIahkxG4XjC0PuOn6Bx95a5fSgZKl0rJT2E8elvOLxt1Y49L0LPq2FJFCztwSunUopHFzK8tVN508uA8R/Nof95mDpRxdWs8HaVKf3gU2pLZWZrDlfhdDKvBLqNxXhqbMZXzudMREJm1INuTtUnWatHtsonXExr0gFtiSRb4iCW0V8Jb23n9hsGslLCxnJYOXkDV/+8zMNSfYc+8pzyWD5+RcWMgRk/0yXK7tx6OJ8b1yFhO7MmnjdnCidSMkcZAaZU4ZOyCyMCjLn68ipWElUqEK8VyEzIcK1/cRumE7lTFbaW4PC7fz+00eGbxRLY+nzU39032+8dvBzR+Jef+rmzR2b7cRSOONi6XNu7WaVdWKudYER+B8krOlxzXzfXZ/JON97Y0Yvgi2hy391eWivD0V2HHvsXyZf/s5f/NOT/3oGIPrdT/weP3zxv5m9fv+bNzz/jVcW9ty4/6RMbJofllIikkZ+54lqa8jYSFVJREgiz8REhUh8oo/D6UOkECNEXjqIEIaVcS4r7eWFzF1cXsl2Pf2lJ3pnX//iA48f/vEYte6+514efeRhbv39+/VXL5z4pYv77/joyp7rby07k7NeEmUcwaZEq8skHetlauRcOHypw8JcOB80C/eg5TDrzp1+Y8sPnjlW5sOnH3ziyTcB7rzzTo4ePTpy8SfvvpsvP/ooAJ/5yO2TMbZVRKYtiuNaRUZnN/Vp1egc0TY4l5bW0bA/h5HWM0Ewc47c0uTSan9m7kuPP5EDHDx4kCNHjlxewh06dIjDhw83n2/7w0+zTaoxpWuIKkJ9jC7vVU237tS1PpvRm5zkmRdf5uy3/r15a3p6mqWlpebz/wJ5Ut7CxANClQAAAABJRU5ErkJggg==');
}
.share-container-annotator .share-google-annotator {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AsJEzYNSj34jgAACUBJREFUWMO9mF1sHNUVx3/3zuxs1t7EEEOcDxJBGycCFBMaRzFSw1v5KN9BSA1QaEQfEBVtpJYWUKMQwUMlmhdo+oZQRVNCQoooaqAEAX0oUqkKIQ2IKNAmNiixieP4c2d3557Th70zmV2veaAVK408M75z7v98/c8519D8M/7S3PPX+RPAzO8smMnpurQCCIAQkJduumXDgsAurjvXtGakcxkHVt5JoElb6UYNrjjG5CX7MRLOiWKJUTZHddIVCtgAjaKCvHLwk0OP7/rbCY+nHubART+8+BvLf7ay9/fRxMR6bYBr+oUsZbq4loLU2u+slvq8zznVM4F1xTkBnh841nTOEM0WwKV3d3Pv9dfvvHrLW786+UU8GXoTFRSiB3p6ngrPjK53om2dW48qTMVKwUl7CwJV6pyeGsXKvDkBjgUJLphG2gaQcmFn+NPf/HzVB5seOvynEDAKEbB0QWXmWjdTAYO2i78kmmE6ViKnc1hQqZJwpnL2SwFOhAlSmkSNtkdoMJcuL94FvB56IPN6S6UlOjmJVmMwpq1uUqpQrSk6F0CUSiBMVCexLpkT4HSUQH1irhQ0GEXrHQuBcgqwEIuUqgbEWjAGVW/IHNbEKKGpEprqHFtbCqZO0RWwUpgTYOgsNSlhTM5RqlmcGAOTcSEASlmSnBQJ7iiXCcOQwFqstdggaNwbg7GWwEzS9f4tGGPntGAHlqv/vmJOcKqKAHdqD6KKikNEERXEOUSVJHFUKtMGCFMLGgO2ZC1REGCDAGstQRBgjWmAtRZjDNYW/2eyU1VC1QZYMagqTgQRiziHc6DOmoZP2nysIuAFZFqLNP6Xe/9VwbXeq2pjv/weWdC0ZKHmyJM2YFQVY8xXApl+mxmijcI6i3ubIigHKtXqS0ACiAgi7XkxCIImQMYYkiRpIolUtuT2ze8XzrJg7hJfYtpZM43RxUuWsLinhyBsFlWr1Tj0/vsZ+BRcX18fw8PDnDp1KnOlthgjM1ATwPyC3EcigjWN3FegVq2yatUqbtm0iVq1SpIkXLF2LatXrwbgnXfe4bPBQV599VVEpMmC09PTPPTww7y4bx8v7t2LtbbtvuotOhtgXhvvPhsEmXtcknDZ5Zfz6507eeaZZ3jl5ZcBcM6x44kn2LhxI6tXr2bH9u2g2gCQizdUqVQq1Gu1rHVpikWRBgafpLOSJM1UybtZBPVxVowifrx1KzYI+Ovbb2OMwRhDFEU8tm0blUqF7u5ufrJ1K7VaDRHBJQmJv+pJ0njnHPV6ncRfGUPkMGirBaXVzSJozgVGlcVLl7J8+XJEhLhSabjQWkSEIAx54+BBvnvDDazp68ticv3AAL29vagq1Timq6uLDQMDdJ13HgYICwX27d3LmdHRhlu9gWbHoKeOFJwYAz7+0lgqFAqUOjqIKxUuXLSIocFB8G4xxnDs2LEmzhQRuru7Wdnbi4iQJAmFQoFFPT04L7NYnEchDHHerXkvNgE0PjBFBIzBeAuKcxAEGBFmpqeZnJigWCyy9sorOX78eKMM+gSqxlXCMOTw4cPElQqlUon9+/bx/O7dAIyPj/PKgQPsef559vkkEecodXRk3Cot1GbzSZJaI13o0njwMTg4NMTRo0dRhRtuvJEgCDKNa7Uaa/rWMDg4xM4nnySKoixuy+UyneUy88tlAIpRxHz/XC6XMcY06nCbamVbB4I0KdKy4zw4EaFeq/HLRx9laGiQZcuW8dTTT3PxJZewsLubLffdx8qVvfzgnu83lUeXynAOp9oYeBScyDnZzmVGSb2orUStuRgUb03rY1OMAU81E+PjbLn3Xr61rp8VK5bznWuu4fY77kCc467Nm6lVqxm9pNUppZhCGPLcc8/xybFj56pQzq0iAh60eoKfTTOpVjkXZ1p6zUSEf/7jXf64fz9HjhxBnBDHVTo6O86FR/qdl+lECAsFDr7+Ov/+9NMsIbKk8PLTPWltFkzOeprrLFL3ZIA9t1WrVapxzOEPPuDs2Big7NjxOH19fcSVCurcOdemijs3S5a0yk9jvjWLUwaXtDQZg/X0ob7UGWOYiWN6enq49rrruGLtWlYsX4ECURRx/sKFbHtsB7979lleeGEPxWKxqa5KS5VKAWquWcgbaZaLNdedaIsLnHOU5s3jF488wt4X93PRRRfx2127uO22W7lqw3o2b97M6dNfEASWu++5h741fTifmc5Xj6b71MJetnoXp9mcKhV4kAustUu6Fiz4HsZks4zJdTIiwpM7d7Lx2xt54P77eWHPHmZmZigVi3R2djI2NsYbBw/S37+eRYsuZGzsLIfee6+5Cc1VilZKaW2KnXOnJiYnX7atRN1KMapKtVrlpptvZt26fl77y2t8+OERSqUShgZdJM5hjGF0dJQdj20nDAt0dXU1U4m/T3LWzHspX0GkXQySxqCnCONcVmdr9Tpr+vqI45gkSXDONapAztqpkh9//DGqyokTx3FJ0tShz9X3Scu7tlmsuRjUFm4yxvCvw4ep1Wps2DDABd0XEMexH3BcZoFKHDMwcBUjIyO89eabmfXSmJPc2jz9tLo39WT7maSl2IsIYRjyh927+eijj6jX6zy1axfr+vspFAqEYYi1lqhQ4NLLLuNHDz7I9m3bOHHiRLMCaZLkQLpcAmaVJ0fSqVdCYFkQBFcu6el5SVUx6YjpKSftijs6Orh10+30r+vnm70rOTs2xsjICFNTU0xNTjE+fpYDB/7M558NEUXFpjmn7RSXb2bzVNNQ4NDJ4eEtswCKL2nGx1d270th2nYZYymV5iG+UQBwviFtd3Ki7UDmO+r8mNEAeejk8PCWsN0BEKqotdkJkslNcelAZIyhVqs2Wbjd7Puls3BuBsmefbvXOtWpc66ayTD+cCaXQKblyNVo7tzGrzW+uWidsZuUz2d0m7+5mTsBNPTrHTAuqiPAItPcQWSdjvElsOnYtY07TZsBPA+u9d7kvJACrdfrhwAN0uM3YL6InC1G0Xpoc/jZEgKznluG/Vnv53D/LOWMwYkcHT1z5llV/Tw9NJ8PLAZ6wyDoK5fLA9baC77mQ3Q1IHG1eqwSx++KyIfAf1IARWCBB7kM6AbKQOFrOu1P60QVGAdGgM+A0yZXUQpApwfW6UEHc4XU//ln/MSRADEwBUwC8X8BTYvqIryh+pIAAAAASUVORK5CYII=');
}
.share-container-annotator .share-email-annotator {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAABIUExURf///7KysrKyss/Pz9DQ0NHR0dLS0u7u7vDw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///37mbPQAAAACdFJOUwCWb//7SQAAAQNJREFUSMft1M2SgyAQRtHkC0RtbP6V93/TWZikFDuQ2c7krrCKU1gKXC7f/lNX9LvuAVB6AUdw78y/V2CEXltpjEcwmabQMNMR0GwbQsPOVAF2AXqR0wiOK2DYh/RGaKTg2VTA+pgXUWgsOXorghUq1ymsDVBOQqE0QYFK+xRKBxyFQumCvVAoH4ACFbe2+f0VnkKhfLICnmsoPJ46X2l7q1u4PUYdgNdxeY2aQDp4b/40+5AyxM2NLGw+YhcisrhbM+J5e9NsPWLKUinCnw7QZBg+hCgVggfXR3QksHVezlkGVZfAADIzs5Ving1hqO6lYZyIjBzRNA7He+nXV+W3v98P7gg+L5LCf08AAAAASUVORK5CYII=');
}
.share-container-annotator .share-button-annotator {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAvCAMAAABE+WOeAAAAA3NCSVQICAjb4U/gAAAATlBMVEXMzMzZ2dn////r6+vPz8/n5+f5+fnl5eXT09P19fXz8/Pg4ODX19f7+/vR0dHp6enu7u7d3d339/fj4+PV1dX////b29vt7e3h4eHx8fHJkyUPAAAAGnRSTlP//wD//////////////////////////////zDtagYAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAedEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzUuMasfSOsAAAAVdEVYdENyZWF0aW9uIFRpbWUAOC8xNi8xM7KZeroAAAFnSURBVEiJrZaNbsMgDIS9jpCQ0dBoVaW9/4vOQHD4OVIh9aRKLf0UG9scoduYqF7Qs1UUpeys3/Buo1Kbu+BnQpo7/KogznmtiMcPr0MIv1/gRHvNLy0zcXH0dPxYSh483cR/TBmBerlPKYMUYT75FWQsndJpZRUeFTIr4SGVeFjJlg8ZUb5wnU94Bn8cwr/u9X5ZLvD1iHn9SjYmW908r1t6+eM0bNGvlCSB3e4MfqOovGO62XqRO6U702SZl+LH+E9uiwHTFKSYT9+NbNH1DgJXVHiZl/urS+c86s8VD/r/UX40H9mvgZ06+VQ8qWfXV2L9pb+xX767TcuTLJifn/44hPlp9mdNMfS5NJz/p/HzCcZ5652vBzwux/kCDVKnYeYRLvxBjm+2veQPyH/AeIj/IH8D/OlvIKN2nDL/BP7c2G3hz8D/Kzuv/B9FyPvV3C/D99fw/dgN0bt/vcbud6+x94e3+gd13Qxqz2VnfwAAAABJRU5ErkJggg==');
}
/* Api */
.annotator-wrapper .annotator-hl.api {
background:rgba(0, 190, 99, 0.3);
}

View File

@@ -0,0 +1,97 @@
/* Editor */
li.token-input-token {
overflow: hidden;
height: auto !important;
margin: 3px;
padding: 1px 3px;
background-color: #eff2f7;
color: #000;
cursor: default;
border: 1px solid #ccd5e4;
font-size: 11px;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
display: inline;
white-space: nowrap;
}
li.token-input-token p {
display: inline;
padding: 0;
margin: 0;
}
li.token-input-token span {
color: #a6b3cf;
margin-left: 5px;
font-weight: bold;
cursor: pointer;
}
li.token-input-selected-token {
background-color: #5670a6;
border: 1px solid #3b5998;
color: #fff;
}
li.token-input-input-token {
margin: 0;
padding: 0;
list-style-type: none;
}
div.token-input-dropdown {
position: absolute;
width: 120px;
background-color: #fff;
overflow: hidden;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
cursor: default;
font-size: 11px;
font-family: Verdana;
z-index: 1000000000;
}
div.token-input-dropdown p {
margin: 0;
padding: 5px;
font-weight: bold;
color: #777;
}
div.token-input-dropdown ul {
margin: 0;
padding: 0;
}
div.token-input-dropdown ul li {
background-color: #fff;
padding: 3px;
margin: 0;
list-style-type: none;
}
div.token-input-dropdown ul li.token-input-dropdown-item {
background-color: #fff;
}
div.token-input-dropdown ul li.token-input-dropdown-item2 {
background-color: #fff;
}
div.token-input-dropdown ul li em {
font-weight: bold;
font-style: normal;
}
div.token-input-dropdown ul li.token-input-selected-dropdown-item {
background-color: #3b5998;
color: #fff;
}
.token-input-list{
padding:0px;
}

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
This is a custom SVG font generated by IcoMoon.
<iconset grid="16"></iconset>
</metadata>
<defs>
<font id="VideoJS" horiz-adv-x="512" >
<font-face units-per-em="512" ascent="480" descent="-32" />
<missing-glyph horiz-adv-x="512" />
<glyph class="hidden" unicode="&#xf000;" d="M0,480L 512 -32L0 -32 z" horiz-adv-x="0" />
<glyph unicode="&#xe002;" d="M 64,416L 224,416L 224,32L 64,32zM 288,416L 448,416L 448,32L 288,32z" data-tags="pause, media control, audio" />
<glyph unicode="&#xe003;" d="M 200.666,440.666 C 213.5,453.5 224,449.15 224,431 L 224,17 C 224-1.15 213.5-5.499 200.666,7.335 L 80,128 L 0,128 L 0,320 L 80,320 L 200.666,440.666 Z" data-tags="volume-mute, speaker, media control, audio, mute" />
<glyph unicode="&#xe004;" d="M 274.51,109.49c-6.143,0-12.284,2.343-16.971,7.029c-9.373,9.373-9.373,24.568,0,33.941
c 40.55,40.55, 40.55,106.529,0,147.078c-9.373,9.373-9.373,24.569,0,33.941c 9.373,9.372, 24.568,9.372, 33.941,0
c 59.265-59.265, 59.265-155.696,0-214.961C 286.794,111.833, 280.652,109.49, 274.51,109.49zM 200.666,440.666 C 213.5,453.5 224,449.15 224,431 L 224,17 C 224-1.15 213.5-5.499 200.666,7.335 L 80,128 L 0,128 L 0,320 L 80,320 L 200.666,440.666 Z" data-tags="volume-low, speaker, media control, audio" />
<glyph unicode="&#xe005;" d="M 359.765,64.235c-6.143,0-12.284,2.343-16.971,7.029c-9.372,9.372-9.372,24.568,0,33.941
c 65.503,65.503, 65.503,172.085,0,237.588c-9.372,9.373-9.372,24.569,0,33.941c 9.372,9.371, 24.569,9.372, 33.941,0
C 417.532,335.938, 440,281.696, 440,224c0-57.695-22.468-111.938-63.265-152.735C 372.049,66.578, 365.907,64.235, 359.765,64.235zM 274.51,109.49c-6.143,0-12.284,2.343-16.971,7.029c-9.373,9.373-9.373,24.568,0,33.941
c 40.55,40.55, 40.55,106.529,0,147.078c-9.373,9.373-9.373,24.569,0,33.941c 9.373,9.372, 24.568,9.372, 33.941,0
c 59.265-59.265, 59.265-155.696,0-214.961C 286.794,111.833, 280.652,109.49, 274.51,109.49zM 200.666,440.666 C 213.5,453.5 224,449.15 224,431 L 224,17 C 224-1.15 213.5-5.499 200.666,7.335 L 80,128 L 0,128 L 0,320 L 80,320 L 200.666,440.666 Z" data-tags="volume-medium, speaker, media control, audio" />
<glyph unicode="&#xe006;" d="M 445.020,18.98c-6.143,0-12.284,2.343-16.971,7.029c-9.372,9.373-9.372,24.568,0,33.941
C 471.868,103.771, 496.001,162.030, 496.001,224c0,61.969-24.133,120.229-67.952,164.049c-9.372,9.373-9.372,24.569,0,33.941
c 9.372,9.372, 24.569,9.372, 33.941,0c 52.885-52.886, 82.011-123.2, 82.011-197.99c0-74.791-29.126-145.104-82.011-197.99
C 457.304,21.323, 451.162,18.98, 445.020,18.98zM 359.765,64.235c-6.143,0-12.284,2.343-16.971,7.029c-9.372,9.372-9.372,24.568,0,33.941
c 65.503,65.503, 65.503,172.085,0,237.588c-9.372,9.373-9.372,24.569,0,33.941c 9.372,9.371, 24.569,9.372, 33.941,0
C 417.532,335.938, 440,281.696, 440,224c0-57.695-22.468-111.938-63.265-152.735C 372.049,66.578, 365.907,64.235, 359.765,64.235zM 274.51,109.49c-6.143,0-12.284,2.343-16.971,7.029c-9.373,9.373-9.373,24.568,0,33.941
c 40.55,40.55, 40.55,106.529,0,147.078c-9.373,9.373-9.373,24.569,0,33.941c 9.373,9.372, 24.568,9.372, 33.941,0
c 59.265-59.265, 59.265-155.696,0-214.961C 286.794,111.833, 280.652,109.49, 274.51,109.49zM 200.666,440.666 C 213.5,453.5 224,449.15 224,431 L 224,17 C 224-1.15 213.5-5.499 200.666,7.335 L 80,128 L 0,128 L 0,320 L 80,320 L 200.666,440.666 Z" horiz-adv-x="544" data-tags="volume-high, speaker, media control, audio" />
<glyph unicode="&#xe007;" d="M 256,480L 96,224L 256-32L 416,224 z" data-tags="diamonds, bet, game, gamble, cards" />
<glyph unicode="&#xe008;" d="M 0,480 L 687.158,480 L 687.158-35.207 L 0-35.207 L 0,480 z M 622.731,224.638 C 621.878,314.664 618.46,353.922 597.131,381.656 C 593.291,387.629 586.038,391.042 580.065,395.304 C 559.158,410.669 460.593,416.211 346.247,416.211 C 231.896,416.211 128.642,410.669 108.162,395.304 C 101.762,391.042 94.504,387.629 90.242,381.656 C 69.331,353.922 66.349,314.664 65.069,224.638 C 66.349,134.607 69.331,95.353 90.242,67.62 C 94.504,61.22 101.762,58.233 108.162,53.967 C 128.642,38.18 231.896,33.060 346.247,32.207 C 460.593,33.060 559.158,38.18 580.065,53.967 C 586.038,58.233 593.291,61.22 597.131,67.62 C 618.46,95.353 621.878,134.607 622.731,224.638 z M 331.179,247.952 C 325.389,318.401 287.924,359.905 220.901,359.905 C 159.672,359.905 111.54,304.689 111.54,215.965 C 111.54,126.859 155.405,71.267 227.907,71.267 C 285.79,71.267 326.306,113.916 332.701,184.742 L 263.55,184.742 C 260.81,158.468 249.843,138.285 226.69,138.285 C 190.136,138.285 183.435,174.462 183.435,212.92 C 183.435,265.854 198.665,292.886 223.951,292.886 C 246.492,292.886 260.81,276.511 262.939,247.952 L 331.179,247.952 z M 570.013,247.952 C 564.228,318.401 526.758,359.905 459.74,359.905 C 398.507,359.905 350.379,304.689 350.379,215.965 C 350.379,126.859 394.244,71.267 466.746,71.267 C 524.625,71.267 565.14,113.916 571.536,184.742 L 502.384,184.742 C 499.649,158.468 488.682,138.285 465.529,138.285 C 428.971,138.285 422.27,174.462 422.27,212.92 C 422.27,265.854 437.504,292.886 462.785,292.886 C 485.327,292.886 499.649,276.511 501.778,247.952 L 570.013,247.952 z " horiz-adv-x="687.158" data-tags="Closed_captioning_symbol" />
<glyph unicode="&#xe009;" d="M 64,416L 448,416L 448,32L 64,32z" data-tags="stop, media control, audio, square" />
<glyph unicode="&#xe00a;" d="M 192,416A64,64 12780 1 1 320,416A64,64 12780 1 1 192,416zM 327.765,359.765A64,64 12780 1 1 455.765,359.765A64,64 12780 1 1 327.765,359.765zM 416,224A32,32 12780 1 1 480,224A32,32 12780 1 1 416,224zM 359.765,88.235A32,32 12780 1 1 423.765,88.23500000000001A32,32 12780 1 1 359.765,88.23500000000001zM 224.001,32A32,32 12780 1 1 288.001,32A32,32 12780 1 1 224.001,32zM 88.236,88.235A32,32 12780 1 1 152.236,88.23500000000001A32,32 12780 1 1 88.236,88.23500000000001zM 72.236,359.765A48,48 12780 1 1 168.236,359.765A48,48 12780 1 1 72.236,359.765zM 28,224A36,36 12780 1 1 100,224A36,36 12780 1 1 28,224z" data-tags="spinner, loading, busy, wait, wheel" />
<glyph unicode="&#xe00b;" d="M 224,192 L 224-16 L 144,64 L 48-32 L 0,16 L 96,112 L 16,192 ZM 512,432 L 416,336 L 496,256 L 288,256 L 288,464 L 368,384 L 464,480 Z" data-tags="contract, minimize, shrink, collapse" />
<glyph unicode="&#xe00c;" d="M 256,448 C 397.385,448 512,354.875 512,240 C 512,125.124 397.385,32 256,32 C 242.422,32 229.095,32.867 216.088,34.522 C 161.099-20.467 95.463-30.328 32-31.776 L 32-18.318 C 66.268-1.529 96,29.052 96,64 C 96,68.877 95.621,73.665 94.918,78.348 C 37.020,116.48 0,174.725 0,240 C 0,354.875 114.615,448 256,448 Z" data-tags="bubble, comment, chat, talk" />
<glyph unicode="&#xe00d;" d="M 256,480C 114.615,480,0,365.385,0,224s 114.615-256, 256-256s 256,114.615, 256,256S 397.385,480, 256,480z M 256,352
c 70.692,0, 128-57.308, 128-128s-57.308-128-128-128s-128,57.308-128,128S 185.308,352, 256,352z M 408.735,71.265
C 367.938,30.468, 313.695,8, 256,8c-57.696,0-111.938,22.468-152.735,63.265C 62.468,112.062, 40,166.304, 40,224
c0,57.695, 22.468,111.938, 63.265,152.735l 33.941-33.941c0,0,0,0,0,0c-65.503-65.503-65.503-172.085,0-237.588
C 168.937,73.475, 211.125,56, 256,56c 44.874,0, 87.062,17.475, 118.794,49.206c 65.503,65.503, 65.503,172.084,0,237.588l 33.941,33.941
C 449.532,335.938, 472,281.695, 472,224C 472,166.304, 449.532,112.062, 408.735,71.265z" data-tags="spinner, loading, busy, wait, wheel" />
<glyph unicode="&#xe01e;" d="M 512,224c-0.639,33.431-7.892,66.758-21.288,97.231c-13.352,30.5-32.731,58.129-56.521,80.96
c-23.776,22.848-51.972,40.91-82.492,52.826C 321.197,466.979, 288.401,472.693, 256,472c-32.405-0.641-64.666-7.687-94.167-20.678
c-29.524-12.948-56.271-31.735-78.367-54.788c-22.112-23.041-39.58-50.354-51.093-79.899C 20.816,287.104, 15.309,255.375, 16,224
c 0.643-31.38, 7.482-62.574, 20.067-91.103c 12.544-28.55, 30.738-54.414, 53.055-75.774c 22.305-21.377, 48.736-38.252, 77.307-49.36
C 194.988-3.389, 225.652-8.688, 256-8c 30.354,0.645, 60.481,7.277, 88.038,19.457c 27.575,12.141, 52.558,29.74, 73.183,51.322
c 20.641,21.57, 36.922,47.118, 47.627,74.715c 6.517,16.729, 10.94,34.2, 13.271,51.899c 0.623-0.036, 1.249-0.060, 1.881-0.060
c 17.673,0, 32,14.326, 32,32c0,0.898-0.047,1.786-0.119,2.666L 512,223.999 z M 461.153,139.026c-11.736-26.601-28.742-50.7-49.589-70.59
c-20.835-19.905-45.5-35.593-72.122-45.895C 312.828,12.202, 284.297,7.315, 256,8c-28.302,0.649-56.298,6.868-81.91,18.237
c-25.625,11.333-48.842,27.745-67.997,47.856c-19.169,20.099-34.264,43.882-44.161,69.529C 51.997,169.264, 47.318,196.729, 48,224
c 0.651,27.276, 6.664,54.206, 17.627,78.845c 10.929,24.65, 26.749,46.985, 46.123,65.405c 19.365,18.434, 42.265,32.935, 66.937,42.428
C 203.356,420.208, 229.755,424.681, 256,424c 26.25-0.653, 52.114-6.459, 75.781-17.017c 23.676-10.525, 45.128-25.751, 62.812-44.391
c 17.698-18.629, 31.605-40.647, 40.695-64.344C 444.412,274.552, 448.679,249.219, 448,224l 0.119,0 c-0.072-0.88-0.119-1.768-0.119-2.666
c0-16.506, 12.496-30.087, 28.543-31.812C 473.431,172.111, 468.278,155.113, 461.153,139.026z" data-tags="spinner, loading, busy, wait, wheel" />
<glyph unicode="&#xe01f;" d="M 256,480 C 116.626,480 3.271,368.619 0.076,230.013 C 3.036,350.945 94.992,448 208,448 C 322.875,448 416,347.712 416,224 C 416,197.49 437.49,176 464,176 C 490.51,176 512,197.49 512,224 C 512,365.385 397.385,480 256,480 ZM 256-32 C 395.374-32 508.729,79.381 511.924,217.987 C 508.964,97.055 417.008,0 304,0 C 189.125,0 96,100.288 96,224 C 96,250.51 74.51,272 48,272 C 21.49,272 0,250.51 0,224 C 0,82.615 114.615-32 256-32 Z" data-tags="spinner, loading, busy, wait, wheel" />
<glyph unicode="&#xe00e;" d="M 432,128c-22.58,0-42.96-9.369-57.506-24.415L 158.992,211.336C 159.649,215.462, 160,219.689, 160,224
s-0.351,8.538-1.008,12.663l 215.502,107.751C 389.040,329.369, 409.42,320, 432,320c 44.183,0, 80,35.817, 80,80S 476.183,480, 432,480
s-80-35.817-80-80c0-4.311, 0.352-8.538, 1.008-12.663L 137.506,279.585C 122.96,294.63, 102.58,304, 80,304c-44.183,0-80-35.818-80-80
c0-44.184, 35.817-80, 80-80c 22.58,0, 42.96,9.369, 57.506,24.414l 215.502-107.751C 352.352,56.538, 352,52.311, 352,48
c0-44.184, 35.817-80, 80-80s 80,35.816, 80,80C 512,92.182, 476.183,128, 432,128z" data-tags="share, social" />
<glyph unicode="&#xe001;" d="M 96,416L 416,224L 96,32 z" data-tags="play, media control, audio" />
<glyph unicode="&#xe000;" d="M 512,480 L 512,272 L 432,352 L 336,256 L 288,304 L 384,400 L 304,480 ZM 224,144 L 128,48 L 208-32 L 0-32 L 0,176 L 80,96 L 176,192 Z" data-tags="expand, enlarge, maximize, fullscreen" />
<glyph unicode="&#x20;" horiz-adv-x="256" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -0,0 +1,949 @@
/*!
Video.js Default Styles (http://videojs.com)
Version GENERATED_AT_BUILD
Create your own skin at http://designer.videojs.com
*/
// To customize the player skin, change the values of the variables or edit the
// CSS below.
// (This file uses LESS. Learn more at http://lesscss.org/)
// The base font size controls the size of everything, not just text. All
// diminensions use em-based sizes so that the scale along with the font size.
// Try increasing it to 20px and see what happens.
@base-font-size: 10px;
@touch-device-font-size: 15px;
// The main font color controls the color of the text and the icons (font icons)
@main-font-color: #CCCCCC; // e.g. rgb(255, 255, 255) or #ffffff
// The default color of control backgrounds is mostly black but with a little
// bit of blue so it can still be seen on all black video frames, which are
// common.
@control-bg-color: #07141E; // e.g. rgb(255, 255, 255) or #ffffff
@control-bg-alpha: 0.7; // 1.0 = 100% opacity, 0.0 = 0% opacity
// The slider bar color is used for the progress bar and the volume bar
@slider-bar-color: #66A8CC; // e.g. rgb(255, 255, 255) or #ffffff
// The background of the progress bar and volume bar have a lined pattern that
// is created from a base64 encoded image. You can generate your own pattern at
// http://www.patternify.com/ then replace the value in the quotes with your own
@slider-bar-pattern: ~'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC';
// The color of the slider background
@slider-background-color: #333333;
@slider-background-alpha: 0.9; // 1.0 = 100% opacity, 0.0 = 0% opacity
// The "Big Play Button" is the play button that shows before the video plays.
// To center it set the align values to center and middle. The typical location
// of the button is the center, but there is trend towards moving it to a corner
// where it gets out of the way of valuable content in the poster image.
@big-play-align: left; // left, center, or right
@big-play-vertical-align: top; // top, middle, or bottom
// The button colors match the control colors by default but you can customize
// them by replace the variables (@control-bg-color) with your own color values.
@big-play-bg-color: @control-bg-color;
@big-play-bg-alpha: @control-bg-alpha;
// The font size is what makes the big play button, big. All width/height values
// use ems, which are a multiple of the font size.
// If the @base-font-size is 10px, then 3em equals 30px.
@big-play-font-size: 3em;
// Now that font size is set, the following em values will be a multiple of the
// new font size. If @big-play-font-size is 3em (30px), then setting the any of
// the following values to 2em would equal 60px. 2 * font-size
@big-play-margin: 0.5em;
@big-play-width: 4em;
@big-play-height: 2.6em;
@big-play-border-radius: 0.8em;
@big-play-border-width: 0.1em;
@big-play-border-color: #3b4249;
/* SKIN
================================================================================
The main class name for all skin-specific styles. To make your own skin,
replace all occurances of 'vjs-default-skin' with a new name. Then add your new
skin name to your video tag instead of the default skin.
e.g. <video class="video-js my-skin-name">
*/
.vjs-default-skin {
color: @main-font-color;
}
/* Custom Icon Font
--------------------------------------------------------------------------------
The control icons are from a custom font. Each icon corresponds to a character
(e.g. "\e001"). Font icons allow for easy scaling and coloring of icons.
*/
@font-face{
font-family: 'VideoJS';
src: url('font/vjs.eot');
src: url('font/vjs.eot?#iefix') format('embedded-opentype'),
url('font/vjs.woff') format('woff'),
url('font/vjs.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
// Icon font character values
@play-icon: "\e001";
@pause-icon: "\e002";
@volume-muted-icon: "\e003";
@volume-low-icon: "\e004";
@volume-mid-icon: "\e005";
@volume-high-icon: "\e006";
@fullscreen-enter-icon: "\e000";
@fullscreen-exit-icon: "\e00b";
@square-icon: "\e009";
@spinner-icon: "\e00a";
@spinner2-icon: "\e00d";
@spinner3-icon: "\e01e";
@spinner4-icon: "\e01f";
@subtitles-icon: "\e00c";
@captions-icon: "\e008";
@share-icon: "\e00e";
/* Base UI Component Classes
--------------------------------------------------------------------------------
*/
/* Slider - used for Volume bar and Seek bar */
.vjs-default-skin .vjs-slider {
/* Replace browser focus hightlight with handle highlight *///
outline: 0;
position: relative;
cursor: pointer;
padding: 0;
.background-color-with-alpha(@slider-background-color, @slider-background-alpha);
}
.vjs-default-skin .vjs-slider:focus {
.box-shadow(0 0 2em #fff);
}
.vjs-default-skin .vjs-slider-handle {
position: absolute;
/* Needed for IE6 *///
left: 0;
top: 0;
}
.vjs-default-skin .vjs-slider-handle:before {
content: @square-icon;
font-family: VideoJS;
font-size: 1em;
line-height: 1;
text-align: center;
text-shadow: 0em 0em 1em #fff;
position: absolute;
top: 0;
left: 0;
/* Rotate the square icon to make a diamond *///
.transform(rotate(-45deg));
}
/* Control Bar
--------------------------------------------------------------------------------
The default control bar that is a container for most of the controls.
*/
.vjs-default-skin .vjs-control-bar {
/* Start hidden *///
display: none;
position: absolute;
/* Place control bar at the bottom of the player box/video.
If you want more margin below the control bar, add more height. *///
bottom: 0;
/* Use left/right to stretch to 100% width of player div *///
left: 0;
right: 0;
/* Height includes any margin you want above or below control items *///
height: 3.0em;
.background-color-with-alpha(@control-bg-color, @control-bg-alpha);
}
/* Show the control bar only once the video has started playing */
.vjs-default-skin.vjs-has-started .vjs-control-bar {
display: block;
/* Visibility needed to make sure things hide in older browsers too. */
visibility: visible;
opacity: 1;
@trans: visibility 0.1s, opacity 0.1s; // Var needed because of comma
.transition(@trans);
}
/* Hide the control bar when the video is playing and the user is inactive */
.vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
display: block;
visibility: hidden;
opacity: 0;
@trans: visibility 1.0s, opacity 1.0s;
.transition(@trans);
}
.vjs-default-skin.vjs-controls-disabled .vjs-control-bar {
display: none;
}
.vjs-default-skin.vjs-using-native-controls .vjs-control-bar {
display: none;
}
/* IE8 is flakey with fonts, and you have to change the actual content to force
fonts to show/hide properly.
- "\9" IE8 hack didn't work for this
- Found in XP IE8 from http://modern.ie. Does not show up in "IE8 mode" in IE9
*/
@ie8screen: ~"\0screen";
.vjs-default-skin.vjs-user-inactive.vjs-playing .vjs-control-bar :before {
@media @ie8screen { content: ""; }
}
/* General styles for individual controls. */
.vjs-default-skin .vjs-control {
outline: none;
position: relative;
float: left;
text-align: center;
margin: 0;
padding: 0;
height: 3.0em;
width: 4em;
}
/* FontAwsome button icons */
.vjs-default-skin .vjs-control:before {
font-family: VideoJS;
font-size: 1.5em;
line-height: 2;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
}
/* Replacement for focus outline */
.vjs-default-skin .vjs-control:focus:before,
.vjs-default-skin .vjs-control:hover:before {
text-shadow: 0em 0em 1em rgba(255, 255, 255, 1);
}
.vjs-default-skin .vjs-control:focus {
/* outline: 0; *///
/* keyboard-only users cannot see the focus on several of the UI elements when
this is set to 0 */
}
/* Hide control text visually, but have it available for screenreaders */
.vjs-default-skin .vjs-control-text {
.hide-visually;
}
/* Play/Pause
--------------------------------------------------------------------------------
*/
.vjs-default-skin .vjs-play-control {
width: 5em;
cursor: pointer;
}
.vjs-default-skin .vjs-play-control:before {
content: @play-icon;
}
.vjs-default-skin.vjs-playing .vjs-play-control:before {
content: @pause-icon;
}
/* Volume/Mute
-------------------------------------------------------------------------------- */
.vjs-default-skin .vjs-mute-control,
.vjs-default-skin .vjs-volume-menu-button {
cursor: pointer;
float: right;
}
.vjs-default-skin .vjs-mute-control:before,
.vjs-default-skin .vjs-volume-menu-button:before {
content: @volume-high-icon;
}
.vjs-default-skin .vjs-mute-control.vjs-vol-0:before,
.vjs-default-skin .vjs-volume-menu-button.vjs-vol-0:before {
content: @volume-muted-icon;
}
.vjs-default-skin .vjs-mute-control.vjs-vol-1:before,
.vjs-default-skin .vjs-volume-menu-button.vjs-vol-1:before {
content: @volume-low-icon;
}
.vjs-default-skin .vjs-mute-control.vjs-vol-2:before,
.vjs-default-skin .vjs-volume-menu-button.vjs-vol-2:before {
content: @volume-mid-icon;
}
.vjs-default-skin .vjs-volume-control {
width: 5em;
float: right;
}
.vjs-default-skin .vjs-volume-bar {
width: 5em;
height: 0.6em;
margin: 1.1em auto 0;
}
.vjs-default-skin .vjs-volume-menu-button .vjs-menu-content {
height: 2.9em;
}
.vjs-default-skin .vjs-volume-level {
position: absolute;
top: 0;
left: 0;
height: 0.5em;
background: @slider-bar-color
url(@slider-bar-pattern)
-50% 0 repeat;
}
.vjs-default-skin .vjs-volume-bar .vjs-volume-handle {
width: 0.5em;
height: 0.5em;
}
.vjs-default-skin .vjs-volume-handle:before {
font-size: 0.9em;
top: -0.2em;
left: -0.2em;
width: 1em;
height: 1em;
}
.vjs-default-skin .vjs-volume-menu-button .vjs-menu .vjs-menu-content {
width: 6em;
left: -4em;
}
/* Progress
--------------------------------------------------------------------------------
*/
.vjs-default-skin .vjs-progress-control {
position: absolute;
left: 0;
right: 0;
width: auto;
font-size: 0.3em;
height: 1em;
/* Set above the rest of the controls. *///
top: -1em;
/* Shrink the bar slower than it grows. *///
.transition(all 0.4s);
}
/* On hover, make the progress bar grow to something that's more clickable.
This simply changes the overall font for the progress bar, and this
updates both the em-based widths and heights, as wells as the icon font */
.vjs-default-skin:hover .vjs-progress-control {
font-size: .9em;
/* Even though we're not changing the top/height, we need to include them in
the transition so they're handled correctly. */
.transition(all 0.2s);
}
/* Box containing play and load progresses. Also acts as seek scrubber. */
.vjs-default-skin .vjs-progress-holder {
height: 100%;
}
/* Progress Bars */
.vjs-default-skin .vjs-progress-holder .vjs-play-progress,
.vjs-default-skin .vjs-progress-holder .vjs-load-progress {
position: absolute;
display: block;
height: 100%;
margin: 0;
padding: 0;
/* Needed for IE6 *///
left: 0;
top: 0;
}
.vjs-default-skin .vjs-play-progress {
/*
Using a data URI to create the white diagonal lines with a transparent
background. Surprisingly works in IE8.
Created using http://www.patternify.com
Changing the first color value will change the bar color.
Also using a paralax effect to make the lines move backwards.
The -50% left position makes that happen.
*/
background: @slider-bar-color
url(@slider-bar-pattern)
-50% 0 repeat;
}
.vjs-default-skin .vjs-load-progress {
background: rgb(100, 100, 100) /* IE8- Fallback */;
background: rgba(255, 255, 255, 0.4);
}
.vjs-default-skin .vjs-seek-handle {
width: 1.5em;
height: 100%;
}
.vjs-default-skin .vjs-seek-handle:before {
padding-top: 0.1em /* Minor adjustment */;
}
/* Time Display
--------------------------------------------------------------------------------
*/
.vjs-default-skin .vjs-time-controls {
font-size: 1em;
/* Align vertically by making the line height the same as the control bar *///
line-height: 3em;
}
.vjs-default-skin .vjs-current-time { float: left; }
.vjs-default-skin .vjs-duration { float: left; }
/* Remaining time is in the HTML, but not included in default design */
.vjs-default-skin .vjs-remaining-time { display: none; float: left; }
.vjs-time-divider { float: left; line-height: 3em; }
/* Fullscreen
--------------------------------------------------------------------------------
*/
.vjs-default-skin .vjs-fullscreen-control {
width: 3.8em;
cursor: pointer;
float: right;
}
.vjs-default-skin .vjs-fullscreen-control:before {
content: @fullscreen-enter-icon;
}
/* Switch to the exit icon when the player is in fullscreen */
.vjs-default-skin.vjs-fullscreen .vjs-fullscreen-control:before {
content: @fullscreen-exit-icon;
}
/* Big Play Button (play button at start)
--------------------------------------------------------------------------------
Positioning of the play button in the center or other corners can be done more
easily in the skin designer. http://designer.videojs.com/
*/
.vjs-default-skin .vjs-big-play-button {
// Calculate total width/height so we're able to center the button
@total-width: @big-play-width + (@big-play-border-width * 2);
@total-height: @big-play-height + (@big-play-border-width * 2);
// Position the button using the absolute-align mixin (bottom of page)
.absolute-align(@big-play-align, @big-play-margin, @total-width);
.absolute-align(@big-play-vertical-align, @big-play-margin, @total-height);
font-size: @big-play-font-size;
display: block;
z-index: 2;
position: absolute;
width: @big-play-width;
height: @big-play-height;
text-align: center;
vertical-align: middle;
cursor: pointer;
opacity: 1;
/* Need a slightly gray bg so it can be seen on black backgrounds *///
.background-color-with-alpha(@big-play-bg-color, @big-play-bg-alpha);
border: @big-play-border-width solid @big-play-border-color;
.border-radius(@big-play-border-radius);
.box-shadow(0px 0px 1em rgba(255, 255, 255, 0.25));
.transition(all 0.4s);
}
/* Optionally center */
.vjs-default-skin.vjs-big-play-centered .vjs-big-play-button {
@total-width: @big-play-width + (@big-play-border-width * 2);
@total-height: @big-play-height + (@big-play-border-width * 2);
.absolute-align(center, @big-play-margin, @total-width);
.absolute-align(middle, @big-play-margin, @total-height);
}
/* Hide if controls are disabled */
.vjs-default-skin.vjs-controls-disabled .vjs-big-play-button {
display: none;
}
/* Hide when video starts playing */
.vjs-default-skin.vjs-has-started .vjs-big-play-button {
display: none;
}
/* Hide on mobile devices. Remove when we stop using native controls
by default on mobile */
.vjs-default-skin.vjs-using-native-controls .vjs-big-play-button {
display: none;
}
.vjs-default-skin:hover .vjs-big-play-button,
.vjs-default-skin .vjs-big-play-button:focus {
outline: 0;
border-color: #fff;
/* IE8 needs a non-glow hover state *///
background-color: rgb(80, 80, 80);
background-color: rgba(50, 50, 50, 0.75);
.box-shadow(0 0 3em #fff);
.transition(all 0s);
}
.vjs-default-skin .vjs-big-play-button:before {
content: @play-icon;
font-family: VideoJS;
/* In order to center the play icon vertically we need to set the line height
to the same as the button height */
line-height: @big-play-height;
text-shadow: 0.05em 0.05em 0.1em #000;
text-align: center /* Needed for IE8 */;
position: absolute;
left: 0;
width: 100%;
height: 100%;
}
/* Loading Spinner
--------------------------------------------------------------------------------
*/
.vjs-loading-spinner {
display: none;
position: absolute;
top: 50%;
left: 50%;
font-size: 4em;
line-height: 1;
width: 1em;
height: 1em;
margin-left: -0.5em;
margin-top: -0.5em;
opacity: 0.75;
.animation(spin 1.5s infinite linear);
}
.vjs-default-skin .vjs-loading-spinner:before {
content: @spinner3-icon;
font-family: VideoJS;
position: absolute;
top: 0;
left: 0;
width: 1em;
height: 1em;
text-align: center;
text-shadow: 0em 0em 0.1em #000;
}
@-moz-keyframes spin {
0% { -moz-transform: rotate(0deg); }
100% { -moz-transform: rotate(359deg); }
}
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(359deg); }
}
@-o-keyframes spin {
0% { -o-transform: rotate(0deg); }
100% { -o-transform: rotate(359deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(359deg); }
}
/* Menu Buttons (Captions/Subtitles/etc.)
--------------------------------------------------------------------------------
*/
.vjs-default-skin .vjs-menu-button {
float: right;
cursor: pointer;
}
.vjs-default-skin .vjs-menu {
display: none;
position: absolute;
bottom: 0;
left: 0em; /* (Width of vjs-menu - width of button) / 2 */
width: 0em;
height: 0em;
margin-bottom: 3em;
border-left: 2em solid transparent;
border-right: 2em solid transparent;
border-top: 1.55em solid rgb(0, 0, 0); /* Same width top as ul bottom */
border-top-color: rgba(7, 40, 50, 0.5); /* Same as ul background */
}
/* Button Pop-up Menu */
.vjs-default-skin .vjs-menu-button .vjs-menu .vjs-menu-content {
display: block;
padding: 0; margin: 0;
position: absolute;
width: 10em;
bottom: 1.5em; /* Same bottom as vjs-menu border-top */
max-height: 15em;
overflow: auto;
left: -5em; /* Width of menu - width of button / 2 */
.background-color-with-alpha(@control-bg-color, @control-bg-alpha);
.box-shadow(-0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2));
}
.vjs-default-skin .vjs-menu-button:hover .vjs-menu {
display: block;
}
.vjs-default-skin .vjs-menu-button ul li {
list-style: none;
margin: 0;
padding: 0.3em 0 0.3em 0;
line-height: 1.4em;
font-size: 1.2em;
text-align: center;
text-transform: lowercase;
}
.vjs-default-skin .vjs-menu-button ul li.vjs-selected {
background-color: #000;
}
.vjs-default-skin .vjs-menu-button ul li:focus,
.vjs-default-skin .vjs-menu-button ul li:hover,
.vjs-default-skin .vjs-menu-button ul li.vjs-selected:focus,
.vjs-default-skin .vjs-menu-button ul li.vjs-selected:hover {
outline: 0;
color: #111;
.background-color-with-alpha(rgb(255, 255, 255), 0.75);
.box-shadow(0 0 1em rgba(255, 255, 255, 1));
}
.vjs-default-skin .vjs-menu-button ul li.vjs-menu-title {
text-align: center;
text-transform: uppercase;
font-size: 1em;
line-height: 2em;
padding: 0;
margin: 0 0 0.3em 0;
font-weight: bold;
cursor: default;
}
/* Subtitles Button */
.vjs-default-skin .vjs-subtitles-button:before { content: @subtitles-icon; }
/* Captions Button */
.vjs-default-skin .vjs-captions-button:before {
content: @captions-icon;
}
/* Replacement for focus outline */
.vjs-default-skin .vjs-captions-button:focus .vjs-control-content:before,
.vjs-default-skin .vjs-captions-button:hover .vjs-control-content:before {
.box-shadow(0 0 1em rgba(255, 255, 255, 1));
}
/*
REQUIRED STYLES (be careful overriding)
================================================================================
When loading the player, the video tag is replaced with a DIV,
that will hold the video tag or object tag for other playback methods.
The div contains the video playback element (Flash or HTML5) and controls,
and sets the width and height of the video.
** If you want to add some kind of border/padding (e.g. a frame), or special
positioning, use another containing element. Otherwise you risk messing up
control positioning and full window mode. **
*/
.video-js {
background-color: #000;
position: relative;
padding: 0;
/* Start with 10px for base font size so other dimensions can be em based and
easily calculable. */
font-size: @base-font-size;
/* Allow poster to be vertially aligned. */
vertical-align: middle;
/* display: table-cell; */ /*This works in Safari but not Firefox.*/
/* Provide some basic defaults for fonts */
font-weight: normal;
font-style: normal;
/* Avoiding helvetica: issue #376 */
font-family: Arial, sans-serif;
/* Turn off user selection (text highlighting) by default.
The majority of player components will not be text blocks.
Text areas will need to turn user selection back on. */
.user-select(none);
}
/* Playback technology elements expand to the width/height of the containing div
<video> or <object> */
.video-js .vjs-tech {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Fix for Firefox 9 fullscreen (only if it is enabled). Not needed when
checking fullScreenEnabled. */
.video-js:-moz-full-screen { position: absolute; }
/* Fullscreen Styles */
body.vjs-full-window {
padding: 0;
margin: 0;
height: 100%;
/* Fix for IE6 full-window. http://www.cssplay.co.uk/layouts/fixed.html *///
overflow-y: auto;
}
.video-js.vjs-fullscreen {
position: fixed;
overflow: hidden;
z-index: 1000;
left: 0;
top: 0;
bottom: 0;
right: 0;
width: 100% !important;
height: 100% !important;
/* IE6 full-window (underscore hack) *///
_position: absolute;
}
.video-js:-webkit-full-screen {
width: 100% !important;
height: 100% !important;
}
.video-js.vjs-fullscreen.vjs-user-inactive {
cursor: none;
}
/* Poster Styles */
.vjs-poster {
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: contain;
cursor: pointer;
height: 100%;
margin: 0;
padding: 0;
position: relative;
width: 100%;
}
.vjs-poster img {
display: block;
margin: 0 auto;
max-height: 100%;
padding: 0;
width: 100%;
}
/* Hide the poster when native controls are used otherwise it covers them */
.video-js.vjs-using-native-controls .vjs-poster {
display: none;
}
/* Text Track Styles */
/* Overall track holder for both captions and subtitles */
.video-js .vjs-text-track-display {
text-align: center;
position: absolute;
bottom: 4em;
/* Leave padding on left and right *///
left: 1em;
right: 1em;
}
/* Individual tracks */
.video-js .vjs-text-track {
display: none;
font-size: 1.4em;
text-align: center;
margin-bottom: 0.1em;
/* Transparent black background, or fallback to all black (oldIE) *///
.background-color-with-alpha(rgb(0, 0, 0), 0.5);
}
.video-js .vjs-subtitles { color: #fff /* Subtitles are white */; }
.video-js .vjs-captions { color: #fc6 /* Captions are yellow */; }
.vjs-tt-cue { display: block; }
/* Hide disabled or unsupported controls */
.vjs-default-skin .vjs-hidden { display: none; }
.vjs-lock-showing {
display: block !important;
opacity: 1;
visibility: visible;
}
// MIXINS
// =============================================================================
// Mixins are a LESS feature and are used to add vendor prefixes to CSS rules
// when needed.
// https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow
.box-shadow (@string: 0 0 1em rgba(0, 0, 0, 0.25)) {
/* box-shadow *///
-webkit-box-shadow: @string;
-moz-box-shadow: @string;
box-shadow: @string;
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius
.border-radius (@string: 5px) {
/* border-radius *///
-webkit-border-radius: @string;
-moz-border-radius: @string;
border-radius: @string;
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/transition
.transition (@string: all 1s linear) {
/* transition *///
-webkit-transition: @string;
-moz-transition: @string;
-o-transition: @string;
transition: @string;
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/transition
.transition-delay (@string: 1s) {
/* transition-delay *///
-webkit-transition-delay: @string;
-moz-transition-delay: @string;
-o-transition-delay: @string;
transition-delay: @string;
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/animation
.animation (@string: spin 1s infinite linear) {
/* animation *///
-webkit-animation: @string;
-moz-animation: @string;
-o-animation: @string;
animation: @string;
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/transform
.transform (@string: rotate(-45deg)) {
/* transform *///
-webkit-transform: @string;
-moz-transform: @string;
-ms-transform: @string;
-o-transform: @string;
transform: @string;
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/user-select
.user-select (@string: none) {
/* user-select *///
-webkit-user-select: @string;
-moz-user-select: @string;
-ms-user-select: @string;
user-select: @string;
}
// Hide something visually but keep available for screen readers.
// http://h5bp.com/v
.hide-visually () {
/* hide-visually *///
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position:
absolute;
width: 1px;
}
// Align an object with absolute positioning
// Used to align the Big Play Button in the corners or center
.absolute-align (@align, @margin, @length) when (@align = top) {
top: @margin;
}
.absolute-align (@align, @margin, @length) when (@align = bottom) {
bottom: @margin;
}
.absolute-align (@align, @margin, @length) when (@align = left) {
left: @margin;
}
.absolute-align (@align, @margin, @length) when (@align = right) {
right: @margin;
}
.absolute-align (@align, @margin, @length) when (@align = center) {
/* Center it horizontally *///
left: 50%;
margin-left: -(@length / 2);
}
.absolute-align (@align, @margin, @length) when (@align = middle) {
/* Center it vertically *///
top: 50%;
margin-top: -(@length / 2);
}
// http://stackoverflow.com/questions/637921/opacity-of-background-but-not-the-text
.background-color-with-alpha (@color, @alpha) {
@rgba: rgba(red(@color), green(@color), blue(@color), @alpha);
/* background-color-with-alpha *///
background-color: @color;
background-color: @rgba;
// No longer using MS filters because they break border radius in IE9
// @argb: argb(@rgba);
// filter: ~"progid:DXImageTransform.Microsoft.gradient(startColorstr=@{argb}, endColorstr=@{argb})";
// -ms-filter: ~"progid:DXImageTransform.Microsoft.gradient(startColorstr=@{argb}, endColorstr=@{argb})";
}
.border-color-with-alpha (@color, @alpha) {
@rgba: rgba(red(@color), green(@color), blue(@color), @alpha);
/* border-color-with-alpha *///
border-color: @color;
border-color: @rgba;
}
// NOTES ON LESS (tracking learnings so we don't forget)
// =============================================================================
// * We want this file to continue to be accessible by people who don't know
// LESS but know CSS. This means finding the balance between using the most
// valuable LESS features (e.g. variables) and keeping it looking like CSS.
// So it's best to avoid advanced LESS features like conditional statements.
// (we're using one for the big play button position because that's a hot
// topic)
//
// * We care about the readability of the CSS output of LESS, which means we
// have to be careful about what features of LESS we use. (if you're building
// your own skin this may not apply)
// 1. Comments inside of rules (strangely) have an extra line added after
// them in the CSS output. To avoid this we can add a LESS comment after
// the CSS comment.
// /* comment *///
//
// 2. In a rule with nested rules, any comments outside of a rule are moved
// to the top of the parent rule. i.e. it might look like:
// /* title of rule 1 */
// /* title of rule 2 */
// .rule1 {}
// .rule2 {}
// This is why we aren't using nested rules inside of the
// vjs-default-skin class.
/* -----------------------------------------------------------------------------
The original source of this file lives at
https://github.com/videojs/video.js/blob/master/src/css/video-js.less */

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
This is a custom SVG font generated by IcoMoon.
<iconset grid="16"></iconset>
</metadata>
<defs>
<font id="VideoJS" horiz-adv-x="512" >
<font-face units-per-em="512" ascent="480" descent="-32" />
<missing-glyph horiz-adv-x="512" />
<glyph class="hidden" unicode="&#xf000;" d="M0,480L 512 -32L0 -32 z" horiz-adv-x="0" />
<glyph unicode="&#xe002;" d="M 64,416L 224,416L 224,32L 64,32zM 288,416L 448,416L 448,32L 288,32z" />
<glyph unicode="&#xe003;" d="M 200.666,440.666 C 213.5,453.5 224,449.15 224,431 L 224,17 C 224-1.15 213.5-5.499 200.666,7.335 L 80,128 L 0,128 L 0,320 L 80,320 L 200.666,440.666 Z" />
<glyph unicode="&#xe004;" d="M 274.51,109.49c-6.143,0-12.284,2.343-16.971,7.029c-9.373,9.373-9.373,24.568,0,33.941
c 40.55,40.55, 40.55,106.529,0,147.078c-9.373,9.373-9.373,24.569,0,33.941c 9.373,9.372, 24.568,9.372, 33.941,0
c 59.265-59.265, 59.265-155.696,0-214.961C 286.794,111.833, 280.652,109.49, 274.51,109.49zM 200.666,440.666 C 213.5,453.5 224,449.15 224,431 L 224,17 C 224-1.15 213.5-5.499 200.666,7.335 L 80,128 L 0,128 L 0,320 L 80,320 L 200.666,440.666 Z" />
<glyph unicode="&#xe005;" d="M 359.765,64.235c-6.143,0-12.284,2.343-16.971,7.029c-9.372,9.372-9.372,24.568,0,33.941
c 65.503,65.503, 65.503,172.085,0,237.588c-9.372,9.373-9.372,24.569,0,33.941c 9.372,9.371, 24.569,9.372, 33.941,0
C 417.532,335.938, 440,281.696, 440,224c0-57.695-22.468-111.938-63.265-152.735C 372.049,66.578, 365.907,64.235, 359.765,64.235zM 274.51,109.49c-6.143,0-12.284,2.343-16.971,7.029c-9.373,9.373-9.373,24.568,0,33.941
c 40.55,40.55, 40.55,106.529,0,147.078c-9.373,9.373-9.373,24.569,0,33.941c 9.373,9.372, 24.568,9.372, 33.941,0
c 59.265-59.265, 59.265-155.696,0-214.961C 286.794,111.833, 280.652,109.49, 274.51,109.49zM 200.666,440.666 C 213.5,453.5 224,449.15 224,431 L 224,17 C 224-1.15 213.5-5.499 200.666,7.335 L 80,128 L 0,128 L 0,320 L 80,320 L 200.666,440.666 Z" />
<glyph unicode="&#xe006;" d="M 445.020,18.98c-6.143,0-12.284,2.343-16.971,7.029c-9.372,9.373-9.372,24.568,0,33.941
C 471.868,103.771, 496.001,162.030, 496.001,224c0,61.969-24.133,120.229-67.952,164.049c-9.372,9.373-9.372,24.569,0,33.941
c 9.372,9.372, 24.569,9.372, 33.941,0c 52.885-52.886, 82.011-123.2, 82.011-197.99c0-74.791-29.126-145.104-82.011-197.99
C 457.304,21.323, 451.162,18.98, 445.020,18.98zM 359.765,64.235c-6.143,0-12.284,2.343-16.971,7.029c-9.372,9.372-9.372,24.568,0,33.941
c 65.503,65.503, 65.503,172.085,0,237.588c-9.372,9.373-9.372,24.569,0,33.941c 9.372,9.371, 24.569,9.372, 33.941,0
C 417.532,335.938, 440,281.696, 440,224c0-57.695-22.468-111.938-63.265-152.735C 372.049,66.578, 365.907,64.235, 359.765,64.235zM 274.51,109.49c-6.143,0-12.284,2.343-16.971,7.029c-9.373,9.373-9.373,24.568,0,33.941
c 40.55,40.55, 40.55,106.529,0,147.078c-9.373,9.373-9.373,24.569,0,33.941c 9.373,9.372, 24.568,9.372, 33.941,0
c 59.265-59.265, 59.265-155.696,0-214.961C 286.794,111.833, 280.652,109.49, 274.51,109.49zM 200.666,440.666 C 213.5,453.5 224,449.15 224,431 L 224,17 C 224-1.15 213.5-5.499 200.666,7.335 L 80,128 L 0,128 L 0,320 L 80,320 L 200.666,440.666 Z" horiz-adv-x="544" />
<glyph unicode="&#xe007;" d="M 256,480L 96,224L 256-32L 416,224 z" />
<glyph unicode="&#xe008;" d="M 0,480 L 687.158,480 L 687.158-35.207 L 0-35.207 L 0,480 z M 622.731,224.638 C 621.878,314.664 618.46,353.922 597.131,381.656 C 593.291,387.629 586.038,391.042 580.065,395.304 C 559.158,410.669 460.593,416.211 346.247,416.211 C 231.896,416.211 128.642,410.669 108.162,395.304 C 101.762,391.042 94.504,387.629 90.242,381.656 C 69.331,353.922 66.349,314.664 65.069,224.638 C 66.349,134.607 69.331,95.353 90.242,67.62 C 94.504,61.22 101.762,58.233 108.162,53.967 C 128.642,38.18 231.896,33.060 346.247,32.207 C 460.593,33.060 559.158,38.18 580.065,53.967 C 586.038,58.233 593.291,61.22 597.131,67.62 C 618.46,95.353 621.878,134.607 622.731,224.638 z M 331.179,247.952 C 325.389,318.401 287.924,359.905 220.901,359.905 C 159.672,359.905 111.54,304.689 111.54,215.965 C 111.54,126.859 155.405,71.267 227.907,71.267 C 285.79,71.267 326.306,113.916 332.701,184.742 L 263.55,184.742 C 260.81,158.468 249.843,138.285 226.69,138.285 C 190.136,138.285 183.435,174.462 183.435,212.92 C 183.435,265.854 198.665,292.886 223.951,292.886 C 246.492,292.886 260.81,276.511 262.939,247.952 L 331.179,247.952 z M 570.013,247.952 C 564.228,318.401 526.758,359.905 459.74,359.905 C 398.507,359.905 350.379,304.689 350.379,215.965 C 350.379,126.859 394.244,71.267 466.746,71.267 C 524.625,71.267 565.14,113.916 571.536,184.742 L 502.384,184.742 C 499.649,158.468 488.682,138.285 465.529,138.285 C 428.971,138.285 422.27,174.462 422.27,212.92 C 422.27,265.854 437.504,292.886 462.785,292.886 C 485.327,292.886 499.649,276.511 501.778,247.952 L 570.013,247.952 z " horiz-adv-x="687.158" />
<glyph unicode="&#xe009;" d="M 64,416L 448,416L 448,32L 64,32z" />
<glyph unicode="&#xe00a;" d="M 192,416A64,64 12780 1 1 320,416A64,64 12780 1 1 192,416zM 327.765,359.765A64,64 12780 1 1 455.765,359.765A64,64 12780 1 1 327.765,359.765zM 416,224A32,32 12780 1 1 480,224A32,32 12780 1 1 416,224zM 359.765,88.235A32,32 12780 1 1 423.765,88.23500000000001A32,32 12780 1 1 359.765,88.23500000000001zM 224.001,32A32,32 12780 1 1 288.001,32A32,32 12780 1 1 224.001,32zM 88.236,88.235A32,32 12780 1 1 152.236,88.23500000000001A32,32 12780 1 1 88.236,88.23500000000001zM 72.236,359.765A48,48 12780 1 1 168.236,359.765A48,48 12780 1 1 72.236,359.765zM 28,224A36,36 12780 1 1 100,224A36,36 12780 1 1 28,224z" />
<glyph unicode="&#xe00b;" d="M 224,192 L 224-16 L 144,64 L 48-32 L 0,16 L 96,112 L 16,192 ZM 512,432 L 416,336 L 496,256 L 288,256 L 288,464 L 368,384 L 464,480 Z" />
<glyph unicode="&#xe00c;" d="M 256,448 C 397.385,448 512,354.875 512,240 C 512,125.124 397.385,32 256,32 C 242.422,32 229.095,32.867 216.088,34.522 C 161.099-20.467 95.463-30.328 32-31.776 L 32-18.318 C 66.268-1.529 96,29.052 96,64 C 96,68.877 95.621,73.665 94.918,78.348 C 37.020,116.48 0,174.725 0,240 C 0,354.875 114.615,448 256,448 Z" />
<glyph unicode="&#xe00d;" d="M 256,480C 114.615,480,0,365.385,0,224s 114.615-256, 256-256s 256,114.615, 256,256S 397.385,480, 256,480z M 256,352
c 70.692,0, 128-57.308, 128-128s-57.308-128-128-128s-128,57.308-128,128S 185.308,352, 256,352z M 408.735,71.265
C 367.938,30.468, 313.695,8, 256,8c-57.696,0-111.938,22.468-152.735,63.265C 62.468,112.062, 40,166.304, 40,224
c0,57.695, 22.468,111.938, 63.265,152.735l 33.941-33.941c0,0,0,0,0,0c-65.503-65.503-65.503-172.085,0-237.588
C 168.937,73.475, 211.125,56, 256,56c 44.874,0, 87.062,17.475, 118.794,49.206c 65.503,65.503, 65.503,172.084,0,237.588l 33.941,33.941
C 449.532,335.938, 472,281.695, 472,224C 472,166.304, 449.532,112.062, 408.735,71.265z" />
<glyph unicode="&#xe01e;" d="M 512,224c-0.639,33.431-7.892,66.758-21.288,97.231c-13.352,30.5-32.731,58.129-56.521,80.96
c-23.776,22.848-51.972,40.91-82.492,52.826C 321.197,466.979, 288.401,472.693, 256,472c-32.405-0.641-64.666-7.687-94.167-20.678
c-29.524-12.948-56.271-31.735-78.367-54.788c-22.112-23.041-39.58-50.354-51.093-79.899C 20.816,287.104, 15.309,255.375, 16,224
c 0.643-31.38, 7.482-62.574, 20.067-91.103c 12.544-28.55, 30.738-54.414, 53.055-75.774c 22.305-21.377, 48.736-38.252, 77.307-49.36
C 194.988-3.389, 225.652-8.688, 256-8c 30.354,0.645, 60.481,7.277, 88.038,19.457c 27.575,12.141, 52.558,29.74, 73.183,51.322
c 20.641,21.57, 36.922,47.118, 47.627,74.715c 6.517,16.729, 10.94,34.2, 13.271,51.899c 0.623-0.036, 1.249-0.060, 1.881-0.060
c 17.673,0, 32,14.326, 32,32c0,0.898-0.047,1.786-0.119,2.666L 512,223.999 z M 461.153,139.026c-11.736-26.601-28.742-50.7-49.589-70.59
c-20.835-19.905-45.5-35.593-72.122-45.895C 312.828,12.202, 284.297,7.315, 256,8c-28.302,0.649-56.298,6.868-81.91,18.237
c-25.625,11.333-48.842,27.745-67.997,47.856c-19.169,20.099-34.264,43.882-44.161,69.529C 51.997,169.264, 47.318,196.729, 48,224
c 0.651,27.276, 6.664,54.206, 17.627,78.845c 10.929,24.65, 26.749,46.985, 46.123,65.405c 19.365,18.434, 42.265,32.935, 66.937,42.428
C 203.356,420.208, 229.755,424.681, 256,424c 26.25-0.653, 52.114-6.459, 75.781-17.017c 23.676-10.525, 45.128-25.751, 62.812-44.391
c 17.698-18.629, 31.605-40.647, 40.695-64.344C 444.412,274.552, 448.679,249.219, 448,224l 0.119,0 c-0.072-0.88-0.119-1.768-0.119-2.666
c0-16.506, 12.496-30.087, 28.543-31.812C 473.431,172.111, 468.278,155.113, 461.153,139.026z" />
<glyph unicode="&#xe01f;" d="M 256,480 C 116.626,480 3.271,368.619 0.076,230.013 C 3.036,350.945 94.992,448 208,448 C 322.875,448 416,347.712 416,224 C 416,197.49 437.49,176 464,176 C 490.51,176 512,197.49 512,224 C 512,365.385 397.385,480 256,480 ZM 256-32 C 395.374-32 508.729,79.381 511.924,217.987 C 508.964,97.055 417.008,0 304,0 C 189.125,0 96,100.288 96,224 C 96,250.51 74.51,272 48,272 C 21.49,272 0,250.51 0,224 C 0,82.615 114.615-32 256-32 Z" />
<glyph unicode="&#xe00e;" d="M 432,128c-22.58,0-42.96-9.369-57.506-24.415L 158.992,211.336C 159.649,215.462, 160,219.689, 160,224
s-0.351,8.538-1.008,12.663l 215.502,107.751C 389.040,329.369, 409.42,320, 432,320c 44.183,0, 80,35.817, 80,80S 476.183,480, 432,480
s-80-35.817-80-80c0-4.311, 0.352-8.538, 1.008-12.663L 137.506,279.585C 122.96,294.63, 102.58,304, 80,304c-44.183,0-80-35.818-80-80
c0-44.184, 35.817-80, 80-80c 22.58,0, 42.96,9.369, 57.506,24.414l 215.502-107.751C 352.352,56.538, 352,52.311, 352,48
c0-44.184, 35.817-80, 80-80s 80,35.816, 80,80C 512,92.182, 476.183,128, 432,128z" />
<glyph unicode="&#xe001;" d="M 96,416L 416,224L 96,32 z" />
<glyph unicode="&#xe000;" d="M 512,480 L 512,272 L 432,352 L 336,256 L 288,304 L 384,400 L 304,480 ZM 224,144 L 128,48 L 208-32 L 0-32 L 0,176 L 80,96 L 176,192 Z" />
<glyph unicode="&#x20;" horiz-adv-x="256" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3386 @@
// 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, _gettext, _ref, _t;
gettext = null;
if (typeof Gettext !== "undefined" && Gettext !== null) {
_gettext = new Gettext({
domain: "annotator"
});
gettext = function(msgid) {
return _gettext.gettext(msgid);
};
} else {
gettext = function(msgid) {
return msgid;
};
}
_t = function(msgid) {
return gettext(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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
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: '<div class="annotator-adder"><button>' + _t('Annotate') + '</button></div>',
wrapper: '<div class="annotator-wrapper"></div>'
};
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("<i>" + (_t('No Comment')) + "</i>");
}
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 = $('<style id="annotator-dynamic-style"></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 = $("<span class='" + cssClass + "'></span>");
_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 <script> tag?"));
}
}
return this;
};
Annotator.prototype.showEditor = function(annotation, location) {
this.editor.element.css(location);
this.editor.load(annotation);
this.publish('annotationEditorShown', [this.editor, annotation]);
return this;
};
Annotator.prototype.onEditorHide = function() {
this.publish('annotationEditorHidden', [this.editor]);
return this.ignoreMouseup = false;
};
Annotator.prototype.onEditorSubmit = function(annotation) {
return this.publish('annotationEditorSubmit', [this.editor, annotation]);
};
Annotator.prototype.showViewer = function(annotations, location) {
this.viewer.element.css(location);
this.viewer.load(annotations);
return this.publish('annotationViewerShown', [this.viewer, annotations]);
};
Annotator.prototype.startViewerHideTimer = function() {
if (!this.viewerHideTimer) {
return this.viewerHideTimer = setTimeout(this.viewer.hide, 250);
}
};
Annotator.prototype.clearViewerHideTimer = function() {
clearTimeout(this.viewerHideTimer);
return this.viewerHideTimer = false;
};
Annotator.prototype.checkForStartSelection = function(event) {
if (!(event && this.isAnnotator(event.target))) {
this.startViewerHideTimer();
}
return this.mouseIsDown = true;
};
Annotator.prototype.checkForEndSelection = function(event) {
var container, range, _i, _len, _ref;
this.mouseIsDown = false;
if (this.ignoreMouseup) {
return;
}
this.selectedRanges = this.getSelectedRanges();
_ref = this.selectedRanges;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
range = _ref[_i];
container = range.commonAncestor;
if ($(container).hasClass('annotator-hl')) {
container = $(container).parents('[class!=annotator-hl]')[0];
}
if (this.isAnnotator(container)) {
return;
}
}
if (event && this.selectedRanges.length) {
return this.adder.css(Util.mousePosition(event, this.wrapper[0])).show();
} else {
return this.adder.hide();
}
};
Annotator.prototype.isAnnotator = function(element) {
return !!$(element).parents().addBack().filter('[class^=annotator-]').not(this.wrapper).length;
};
Annotator.prototype.onHighlightMouseover = function(event) {
var annotations;
this.clearViewerHideTimer();
if (this.mouseIsDown || this.viewer.isShown()) {
return false;
}
annotations = $(event.target).parents('.annotator-hl').addBack().map(function() {
return $(this).data("annotation");
});
return this.showViewer($.makeArray(annotations), Util.mousePosition(event, this.wrapper[0]));
};
Annotator.prototype.onAdderMousedown = function(event) {
if (event != null) {
event.preventDefault();
}
return this.ignoreMouseup = true;
};
Annotator.prototype.onAdderClick = function(event) {
var annotation, cancel, cleanup, position, save,
_this = this;
if (event != null) {
event.preventDefault();
}
position = this.adder.position();
this.adder.hide();
annotation = this.setupAnnotation(this.createAnnotation());
$(annotation.highlights).addClass('annotator-hl-temporary');
save = function() {
cleanup();
$(annotation.highlights).removeClass('annotator-hl-temporary');
return _this.publish('annotationCreated', [annotation]);
};
cancel = function() {
cleanup();
return _this.deleteAnnotation(annotation);
};
cleanup = function() {
_this.unsubscribe('annotationEditorHidden', cancel);
return _this.unsubscribe('annotationEditorSubmit', save);
};
this.subscribe('annotationEditorHidden', cancel);
this.subscribe('annotationEditorSubmit', save);
return this.showEditor(annotation, position);
};
Annotator.prototype.onEditAnnotation = function(annotation) {
var cleanup, offset, update,
_this = this;
offset = this.viewer.element.position();
update = function() {
cleanup();
return _this.updateAnnotation(annotation);
};
cleanup = function() {
_this.unsubscribe('annotationEditorHidden', cleanup);
return _this.unsubscribe('annotationEditorSubmit', update);
};
this.subscribe('annotationEditorHidden', cleanup);
this.subscribe('annotationEditorSubmit', update);
this.viewer.hide();
return this.showEditor(annotation, offset);
};
Annotator.prototype.onDeleteAnnotation = function(annotation) {
this.viewer.hide();
return this.deleteAnnotation(annotation);
};
return Annotator;
})(Delegator);
Annotator.Plugin = (function(_super) {
__extends(Plugin, _super);
function Plugin(element, options) {
Plugin.__super__.constructor.apply(this, arguments);
}
Plugin.prototype.pluginInit = function() {};
Plugin.prototype.destroy = function() {
return this.removeEvents();
};
return Plugin;
})(Delegator);
g = Util.getGlobal();
if (((_ref = g.document) != null ? _ref.evaluate : void 0) == null) {
$.getScript('http://assets.annotateit.org/vendor/xpath.min.js');
}
if (g.getSelection == null) {
$.getScript('http://assets.annotateit.org/vendor/ierange.min.js');
}
if (g.JSON == null) {
$.getScript('http://assets.annotateit.org/vendor/json2.min.js');
}
if (g.Node == null) {
g.Node = {
ELEMENT_NODE: 1,
ATTRIBUTE_NODE: 2,
TEXT_NODE: 3,
CDATA_SECTION_NODE: 4,
ENTITY_REFERENCE_NODE: 5,
ENTITY_NODE: 6,
PROCESSING_INSTRUCTION_NODE: 7,
COMMENT_NODE: 8,
DOCUMENT_NODE: 9,
DOCUMENT_TYPE_NODE: 10,
DOCUMENT_FRAGMENT_NODE: 11,
NOTATION_NODE: 12
};
}
Annotator.$ = $;
Annotator.Delegator = Delegator;
Annotator.Range = Range;
Annotator.Util = Util;
Annotator._instances = [];
Annotator._t = _t;
Annotator.supported = function() {
return (function() {
return !!this.getSelection;
})();
};
Annotator.noConflict = function() {
Util.getGlobal().Annotator = _Annotator;
return this;
};
$.fn.annotator = function(options) {
var args;
args = Array.prototype.slice.call(arguments, 1);
var temp = this.each(function() {
var instance;
instance = $.data(this, 'annotator');
if (instance) {
return options && instance[options].apply(instance, args);
} else {
instance = new Annotator(this, options);
return $.data(this, 'annotator', instance);
}
});
return temp;
};
this.Annotator = Annotator;
/*
//
*/
// Generated by CoffeeScript 1.6.3
var __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.Widget = (function(_super) {
__extends(Widget, _super);
Widget.prototype.classes = {
hide: 'annotator-hide',
invert: {
x: 'annotator-invert-x',
y: 'annotator-invert-y'
}
};
function Widget(element, options) {
Widget.__super__.constructor.apply(this, arguments);
this.classes = $.extend({}, Annotator.Widget.prototype.classes, this.classes);
}
Widget.prototype.destroy = function() {
this.removeEvents();
return this.element.remove();
};
Widget.prototype.checkOrientation = function() {
var current, offset, viewport, widget, window;
this.resetOrientation();
window = $(Annotator.Util.getGlobal());
widget = this.element.children(":first");
offset = widget.offset();
viewport = {
top: window.scrollTop(),
right: window.width() + window.scrollLeft()
};
current = {
top: offset.top,
right: offset.left + widget.width()
};
if ((current.top - viewport.top) < 0) {
this.invertY();
}
if ((current.right - viewport.right) > 0) {
this.invertX();
}
return this;
};
Widget.prototype.resetOrientation = function() {
this.element.removeClass(this.classes.invert.x).removeClass(this.classes.invert.y);
return this;
};
Widget.prototype.invertX = function() {
this.element.addClass(this.classes.invert.x);
return this;
};
Widget.prototype.invertY = function() {
this.element.addClass(this.classes.invert.y);
return this;
};
Widget.prototype.isInvertedY = function() {
return this.element.hasClass(this.classes.invert.y);
};
Widget.prototype.isInvertedX = function() {
return this.element.hasClass(this.classes.invert.x);
};
return Widget;
})(Delegator);
/*
//
*/
// Generated by CoffeeScript 1.6.3
var __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.Editor = (function(_super) {
__extends(Editor, _super);
Editor.prototype.events = {
"form submit": "submit",
".annotator-save click": "submit",
".annotator-cancel click": "hide",
".annotator-cancel mouseover": "onCancelButtonMouseover",
"textarea keydown": "processKeypress"
};
Editor.prototype.classes = {
hide: 'annotator-hide',
focus: 'annotator-focus'
};
Editor.prototype.html = "<div class=\"annotator-outer annotator-editor\">\n <form class=\"annotator-widget\">\n <ul class=\"annotator-listing\"></ul>\n <div class=\"annotator-controls\">\n <a href=\"#cancel\" class=\"annotator-cancel\">" + _t('Cancel') + "</a>\n<a href=\"#save\" class=\"annotator-save annotator-focus\">" + _t('Save') + "</a>\n </div>\n </form>\n</div>";
Editor.prototype.options = {};
function Editor(options) {
this.onCancelButtonMouseover = __bind(this.onCancelButtonMouseover, this);
this.processKeypress = __bind(this.processKeypress, this);
this.submit = __bind(this.submit, this);
this.load = __bind(this.load, this);
this.hide = __bind(this.hide, this);
this.show = __bind(this.show, this);
Editor.__super__.constructor.call(this, $(this.html)[0], options);
this.fields = [];
this.annotation = {};
}
Editor.prototype.show = function(event) {
Annotator.Util.preventEventDefault(event);
this.element.removeClass(this.classes.hide);
this.element.find('.annotator-save').addClass(this.classes.focus);
this.checkOrientation();
this.element.find(":input:first").focus();
this.setupDraggables();
return this.publish('show');
};
Editor.prototype.hide = function(event) {
Annotator.Util.preventEventDefault(event);
this.element.addClass(this.classes.hide);
return this.publish('hide');
};
Editor.prototype.load = function(annotation) {
var field, _i, _len, _ref;
this.annotation = annotation;
this.publish('load', [this.annotation]);
_ref = this.fields;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
field = _ref[_i];
field.load(field.element, this.annotation);
}
return this.show();
};
Editor.prototype.submit = function(event) {
var field, _i, _len, _ref;
Annotator.Util.preventEventDefault(event);
_ref = this.fields;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
field = _ref[_i];
field.submit(field.element, this.annotation);
}
this.publish('save', [this.annotation]);
return this.hide();
};
Editor.prototype.addField = function(options) {
var element, field, input;
field = $.extend({
id: 'annotator-field-' + Annotator.Util.uuid(),
type: 'input',
label: '',
load: function() {},
submit: function() {}
}, options);
input = null;
element = $('<li class="annotator-item" />');
field.element = element[0];
switch (field.type) {
case 'textarea':
input = $('<textarea />');
break;
case 'input':
case 'checkbox':
input = $('<input />');
break;
case 'select':
input = $('<select />');
}
element.append(input);
input.attr({
id: field.id,
placeholder: field.label
});
if (field.type === 'checkbox') {
input[0].type = 'checkbox';
element.addClass('annotator-checkbox');
element.append($('<label />', {
"for": field.id,
html: field.label
}));
}
this.element.find('ul:first').append(element);
this.fields.push(field);
return field.element;
};
Editor.prototype.checkOrientation = function() {
var controls, list;
Editor.__super__.checkOrientation.apply(this, arguments);
list = this.element.find('.annotator-listing');
controls = this.element.find('.annotator-controls');
if (this.element.hasClass(this.classes.invert.y)) {
controls.insertBefore(list);
} else if (controls.is(':first-child')) {
controls.insertAfter(list);
}
return this;
};
Editor.prototype.processKeypress = function(event) {
if (event.keyCode === 27) {
return this.hide();
} else if (event.keyCode === 13 && !event.shiftKey) {
return this.submit();
}
};
Editor.prototype.onCancelButtonMouseover = function() {
return this.element.find('.' + this.classes.focus).removeClass(this.classes.focus);
};
Editor.prototype.setupDraggables = function() {
var classes, controls, cornerItem, editor, mousedown, onMousedown, onMousemove, onMouseup, resize, textarea, throttle,
_this = this;
this.element.find('.annotator-resize').remove();
if (this.element.hasClass(this.classes.invert.y)) {
cornerItem = this.element.find('.annotator-item:last');
} else {
cornerItem = this.element.find('.annotator-item:first');
}
if (cornerItem) {
$('<span class="annotator-resize"></span>').appendTo(cornerItem);
}
mousedown = null;
classes = this.classes;
editor = this.element;
textarea = null;
resize = editor.find('.annotator-resize');
controls = editor.find('.annotator-controls');
throttle = false;
onMousedown = function(event) {
if (event.target === this) {
mousedown = {
element: this,
top: event.pageY,
left: event.pageX
};
textarea = editor.find('textarea:first');
$(window).bind({
'mouseup.annotator-editor-resize': onMouseup,
'mousemove.annotator-editor-resize': onMousemove
});
return event.preventDefault();
}
};
onMouseup = function() {
mousedown = null;
return $(window).unbind('.annotator-editor-resize');
};
onMousemove = function(event) {
var diff, directionX, directionY, height, width;
if (mousedown && throttle === false) {
diff = {
top: event.pageY - mousedown.top,
left: event.pageX - mousedown.left
};
if (mousedown.element === resize[0]) {
height = textarea.outerHeight();
width = textarea.outerWidth();
directionX = editor.hasClass(classes.invert.x) ? -1 : 1;
directionY = editor.hasClass(classes.invert.y) ? 1 : -1;
textarea.height(height + (diff.top * directionY));
textarea.width(width + (diff.left * directionX));
if (textarea.outerHeight() !== height) {
mousedown.top = event.pageY;
}
if (textarea.outerWidth() !== width) {
mousedown.left = event.pageX;
}
} else if (mousedown.element === controls[0]) {
editor.css({
top: parseInt(editor.css('top'), 10) + diff.top,
left: parseInt(editor.css('left'), 10) + diff.left
});
mousedown.top = event.pageY;
mousedown.left = event.pageX;
}
throttle = true;
return setTimeout(function() {
return throttle = false;
}, 1000 / 60);
}
};
resize.bind('mousedown', onMousedown);
return controls.bind('mousedown', onMousedown);
};
return Editor;
})(Annotator.Widget);
/*
//
*/
// Generated by CoffeeScript 1.6.3
var LinkParser,
__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.Viewer = (function(_super) {
__extends(Viewer, _super);
Viewer.prototype.events = {
".annotator-edit click": "onEditClick",
".annotator-delete click": "onDeleteClick"
};
Viewer.prototype.classes = {
hide: 'annotator-hide',
showControls: 'annotator-visible'
};
Viewer.prototype.html = {
element: "<div class=\"annotator-outer annotator-viewer\">\n <ul class=\"annotator-widget annotator-listing\"></ul>\n</div>",
item: "<li class=\"annotator-annotation annotator-item\">\n <span class=\"annotator-controls\">\n <a href=\"#\" title=\"View as webpage\" class=\"annotator-link\">View as webpage</a>\n <button title=\"Edit\" class=\"annotator-edit\">Edit</button>\n <button title=\"Delete\" class=\"annotator-delete\">Delete</button>\n </span>\n</li>"
};
Viewer.prototype.options = {
readOnly: false
};
function Viewer(options) {
this.onDeleteClick = __bind(this.onDeleteClick, this);
this.onEditClick = __bind(this.onEditClick, this);
this.load = __bind(this.load, this);
this.hide = __bind(this.hide, this);
this.show = __bind(this.show, this);
Viewer.__super__.constructor.call(this, $(this.html.element)[0], options);
this.item = $(this.html.item)[0];
this.fields = [];
this.annotations = [];
}
Viewer.prototype.show = function(event) {
var controls,
_this = this;
Annotator.Util.preventEventDefault(event);
controls = this.element.find('.annotator-controls').addClass(this.classes.showControls);
setTimeout((function() {
return controls.removeClass(_this.classes.showControls);
}), 500);
this.element.removeClass(this.classes.hide);
return this.checkOrientation().publish('show');
};
Viewer.prototype.isShown = function() {
return !this.element.hasClass(this.classes.hide);
};
Viewer.prototype.hide = function(event) {
Annotator.Util.preventEventDefault(event);
this.element.addClass(this.classes.hide);
return this.publish('hide');
};
Viewer.prototype.load = function(annotations) {
var annotation, controller, controls, del, edit, element, field, item, link, links, list, _i, _j, _len, _len1, _ref, _ref1;
this.annotations = annotations || [];
list = this.element.find('ul:first').empty();
_ref = this.annotations;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
annotation = _ref[_i];
item = $(this.item).clone().appendTo(list).data('annotation', annotation);
controls = item.find('.annotator-controls');
link = controls.find('.annotator-link');
edit = controls.find('.annotator-edit');
del = controls.find('.annotator-delete');
links = new LinkParser(annotation.links || []).get('alternate', {
'type': 'text/html'
});
if (links.length === 0 || (links[0].href == null)) {
link.remove();
} else {
link.attr('href', links[0].href);
}
if (this.options.readOnly) {
edit.remove();
del.remove();
} else {
controller = {
showEdit: function() {
return edit.removeAttr('disabled');
},
hideEdit: function() {
return edit.attr('disabled', 'disabled');
},
showDelete: function() {
return del.removeAttr('disabled');
},
hideDelete: function() {
return del.attr('disabled', 'disabled');
}
};
}
_ref1 = this.fields;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
field = _ref1[_j];
element = $(field.element).clone().appendTo(item)[0];
field.load(element, annotation, controller);
}
}
this.publish('load', [this.annotations]);
return this.show();
};
Viewer.prototype.addField = function(options) {
var field;
field = $.extend({
load: function() {}
}, options);
field.element = $('<div />')[0];
this.fields.push(field);
field.element;
return this;
};
Viewer.prototype.onEditClick = function(event) {
return this.onButtonClick(event, 'edit');
};
Viewer.prototype.onDeleteClick = function(event) {
return this.onButtonClick(event, 'delete');
};
Viewer.prototype.onButtonClick = function(event, type) {
var item;
item = $(event.target).parents('.annotator-annotation');
return this.publish(type, [item.data('annotation')]);
};
return Viewer;
})(Annotator.Widget);
LinkParser = (function() {
function LinkParser(data) {
this.data = data;
}
LinkParser.prototype.get = function(rel, cond) {
var d, k, keys, match, v, _i, _len, _ref, _results;
if (cond == null) {
cond = {};
}
cond = $.extend({}, cond, {
rel: rel
});
keys = (function() {
var _results;
_results = [];
for (k in cond) {
if (!__hasProp.call(cond, k)) continue;
v = cond[k];
_results.push(k);
}
return _results;
})();
_ref = this.data;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
d = _ref[_i];
match = keys.reduce((function(m, k) {
return m && (d[k] === cond[k]);
}), true);
if (match) {
_results.push(d);
} else {
continue;
}
}
return _results;
};
return LinkParser;
})();
/*
//
*/
// Generated by CoffeeScript 1.6.3
var Annotator,
__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 = Annotator || {};
Annotator.Notification = (function(_super) {
__extends(Notification, _super);
Notification.prototype.events = {
"click": "hide"
};
Notification.prototype.options = {
html: "<div class='annotator-notice'></div>",
classes: {
show: "annotator-notice-show",
info: "annotator-notice-info",
success: "annotator-notice-success",
error: "annotator-notice-error"
}
};
function Notification(options) {
this.hide = __bind(this.hide, this);
this.show = __bind(this.show, this);
Notification.__super__.constructor.call(this, $(this.options.html).appendTo(document.body)[0], options);
}
Notification.prototype.show = function(message, status) {
if (status == null) {
status = Annotator.Notification.INFO;
}
this.currentStatus = status;
$(this.element).addClass(this.options.classes.show).addClass(this.options.classes[this.currentStatus]).html(Util.escape(message || ""));
setTimeout(this.hide, 5000);
return this;
};
Notification.prototype.hide = function() {
if (this.currentStatus == null) {
this.currentStatus = Annotator.Notification.INFO;
}
$(this.element).removeClass(this.options.classes.show).removeClass(this.options.classes[this.currentStatus]);
return this;
};
return Notification;
})(Delegator);
Annotator.Notification.INFO = 'info';
Annotator.Notification.SUCCESS = 'success';
Annotator.Notification.ERROR = 'error';
$(function() {
var notification;
notification = new Annotator.Notification;
Annotator.showNotification = notification.show;
return Annotator.hideNotification = notification.hide;
});
/*
//
*/
// Generated by CoffeeScript 1.6.3
var _ref,
__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.Unsupported = (function(_super) {
__extends(Unsupported, _super);
function Unsupported() {
_ref = Unsupported.__super__.constructor.apply(this, arguments);
return _ref;
}
Unsupported.prototype.options = {
message: Annotator._t("Sorry your current browser does not support the Annotator")
};
Unsupported.prototype.pluginInit = function() {
var _this = this;
if (!Annotator.supported()) {
return $(function() {
Annotator.showNotification(_this.options.message);
if ((window.XMLHttpRequest === void 0) && (ActiveXObject !== void 0)) {
return $('html').addClass('ie6');
}
});
}
};
return Unsupported;
})(Annotator.Plugin);
/*
//
*/
// Generated by CoffeeScript 1.6.3
var base64Decode, base64UrlDecode, createDateFromISO8601, parseToken,
__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; };
createDateFromISO8601 = function(string) {
var d, date, offset, regexp, time, _ref;
regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\\.([0-9]+))?)?" + "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
d = string.match(new RegExp(regexp));
offset = 0;
date = new Date(d[1], 0, 1);
if (d[3]) {
date.setMonth(d[3] - 1);
}
if (d[5]) {
date.setDate(d[5]);
}
if (d[7]) {
date.setHours(d[7]);
}
if (d[8]) {
date.setMinutes(d[8]);
}
if (d[10]) {
date.setSeconds(d[10]);
}
if (d[12]) {
date.setMilliseconds(Number("0." + d[12]) * 1000);
}
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= (_ref = d[15] === '-') != null ? _ref : {
1: -1
};
}
offset -= date.getTimezoneOffset();
time = Number(date) + (offset * 60 * 1000);
date.setTime(Number(time));
return date;
};
base64Decode = function(data) {
var ac, b64, bits, dec, h1, h2, h3, h4, i, o1, o2, o3, tmp_arr;
if (typeof atob !== "undefined" && atob !== null) {
return atob(data);
} else {
b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
i = 0;
ac = 0;
dec = "";
tmp_arr = [];
if (!data) {
return data;
}
data += '';
while (i < data.length) {
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;
if (h3 === 64) {
tmp_arr[ac++] = String.fromCharCode(o1);
} else if (h4 === 64) {
tmp_arr[ac++] = String.fromCharCode(o1, o2);
} else {
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
}
return tmp_arr.join('');
}
};
base64UrlDecode = function(data) {
var i, m, _i, _ref;
m = data.length % 4;
if (m !== 0) {
for (i = _i = 0, _ref = 4 - m; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
data += '=';
}
}
data = data.replace(/-/g, '+');
data = data.replace(/_/g, '/');
return base64Decode(data);
};
parseToken = function(token) {
var head, payload, sig, _ref;
_ref = token.split('.'), head = _ref[0], payload = _ref[1], sig = _ref[2];
return JSON.parse(base64UrlDecode(payload));
};
Annotator.Plugin.Auth = (function(_super) {
__extends(Auth, _super);
Auth.prototype.options = {
token: null,
tokenUrl: '/auth/token',
autoFetch: true
};
function Auth(element, options) {
Auth.__super__.constructor.apply(this, arguments);
this.waitingForToken = [];
if (this.options.token) {
this.setToken(this.options.token);
} else {
this.requestToken();
}
}
Auth.prototype.requestToken = function() {
var _this = this;
this.requestInProgress = true;
return $.ajax({
url: this.options.tokenUrl,
dataType: 'text',
xhrFields: {
withCredentials: true
}
}).done(function(data, status, xhr) {
return _this.setToken(data);
}).fail(function(xhr, status, err) {
var msg;
msg = Annotator._t("Couldn't get auth token:");
console.error("" + msg + " " + err, xhr);
return Annotator.showNotification("" + msg + " " + xhr.responseText, Annotator.Notification.ERROR);
}).always(function() {
return _this.requestInProgress = false;
});
};
Auth.prototype.setToken = function(token) {
var _results,
_this = this;
this.token = token;
this._unsafeToken = parseToken(token);
if (this.haveValidToken()) {
if (this.options.autoFetch) {
this.refreshTimeout = setTimeout((function() {
return _this.requestToken();
}), (this.timeToExpiry() - 2) * 1000);
}
this.updateHeaders();
_results = [];
while (this.waitingForToken.length > 0) {
_results.push(this.waitingForToken.pop()(this._unsafeToken));
}
return _results;
} else {
console.warn(Annotator._t("Didn't get a valid token."));
if (this.options.autoFetch) {
console.warn(Annotator._t("Getting a new token in 10s."));
return setTimeout((function() {
return _this.requestToken();
}), 10 * 1000);
}
}
};
Auth.prototype.haveValidToken = function() {
var allFields;
allFields = this._unsafeToken && this._unsafeToken.issuedAt && this._unsafeToken.ttl && this._unsafeToken.consumerKey;
if (allFields && this.timeToExpiry() > 0) {
return true;
} else {
return false;
}
};
Auth.prototype.timeToExpiry = function() {
var expiry, issue, now, timeToExpiry;
now = new Date().getTime() / 1000;
issue = createDateFromISO8601(this._unsafeToken.issuedAt).getTime() / 1000;
expiry = issue + this._unsafeToken.ttl;
timeToExpiry = expiry - now;
if (timeToExpiry > 0) {
return timeToExpiry;
} else {
return 0;
}
};
Auth.prototype.updateHeaders = function() {
var current;
current = this.element.data('annotator:headers');
return this.element.data('annotator:headers', $.extend(current, {
'x-annotator-auth-token': this.token
}));
};
Auth.prototype.withToken = function(callback) {
if (callback == null) {
return;
}
if (this.haveValidToken()) {
return callback(this._unsafeToken);
} else {
this.waitingForToken.push(callback);
if (!this.requestInProgress) {
return this.requestToken();
}
}
};
return Auth;
})(Annotator.Plugin);
/*
//
*/
// Generated by CoffeeScript 1.6.3
var __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; },
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
Annotator.Plugin.Store = (function(_super) {
__extends(Store, _super);
Store.prototype.events = {
'annotationCreated': 'annotationCreated',
'annotationDeleted': 'annotationDeleted',
'annotationUpdated': 'annotationUpdated'
};
Store.prototype.options = {
annotationData: {},
emulateHTTP: false,
loadFromSearch: false,
prefix: '/store',
urls: {
create: '/annotations',
read: '/annotations/:id',
update: '/annotations/:id',
destroy: '/annotations/:id',
search: '/search'
}
};
function Store(element, options) {
this._onError = __bind(this._onError, this);
this._onLoadAnnotationsFromSearch = __bind(this._onLoadAnnotationsFromSearch, this);
this._onLoadAnnotations = __bind(this._onLoadAnnotations, this);
this._getAnnotations = __bind(this._getAnnotations, this);
Store.__super__.constructor.apply(this, arguments);
this.annotations = [];
}
Store.prototype.pluginInit = function() {
if (!Annotator.supported()) {
return;
}
if (this.annotator.plugins.Auth) {
return this.annotator.plugins.Auth.withToken(this._getAnnotations);
} else {
return this._getAnnotations();
}
};
Store.prototype._getAnnotations = function() {
if (this.options.loadFromSearch) {
return this.loadAnnotationsFromSearch(this.options.loadFromSearch);
} else {
return this.loadAnnotations();
}
};
Store.prototype.annotationCreated = function(annotation) {
var _this = this;
if (__indexOf.call(this.annotations, annotation) < 0) {
this.registerAnnotation(annotation);
return this._apiRequest('create', annotation, function(data) {
if (data.id == null) {
console.warn(Annotator._t("Warning: No ID returned from server for annotation "), annotation);
}
return _this.updateAnnotation(annotation, data);
});
} else {
return this.updateAnnotation(annotation, {});
}
};
Store.prototype.annotationUpdated = function(annotation) {
var _this = this;
if (__indexOf.call(this.annotations, annotation) >= 0) {
return this._apiRequest('update', annotation, (function(data) {
return _this.updateAnnotation(annotation, data);
}));
}
};
Store.prototype.annotationDeleted = function(annotation) {
var _this = this;
if (__indexOf.call(this.annotations, annotation) >= 0) {
return this._apiRequest('destroy', annotation, (function() {
return _this.unregisterAnnotation(annotation);
}));
}
};
Store.prototype.registerAnnotation = function(annotation) {
return this.annotations.push(annotation);
};
Store.prototype.unregisterAnnotation = function(annotation) {
return this.annotations.splice(this.annotations.indexOf(annotation), 1);
};
Store.prototype.updateAnnotation = function(annotation, data) {
if (__indexOf.call(this.annotations, annotation) < 0) {
console.error(Annotator._t("Trying to update unregistered annotation!"));
} else {
$.extend(annotation, data);
}
return $(annotation.highlights).data('annotation', annotation);
};
Store.prototype.loadAnnotations = function() {
return this._apiRequest('read', null, this._onLoadAnnotations);
};
Store.prototype._onLoadAnnotations = function(data) {
var a, annotation, annotationMap, newData, _i, _j, _len, _len1, _ref;
if (data == null) {
data = [];
}
annotationMap = {};
_ref = this.annotations;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
a = _ref[_i];
annotationMap[a.id] = a;
}
newData = [];
for (_j = 0, _len1 = data.length; _j < _len1; _j++) {
a = data[_j];
if (annotationMap[a.id]) {
annotation = annotationMap[a.id];
this.updateAnnotation(annotation, a);
} else {
newData.push(a);
}
}
this.annotations = this.annotations.concat(newData);
return this.annotator.loadAnnotations(newData.slice());
};
Store.prototype.loadAnnotationsFromSearch = function(searchOptions) {
return this._apiRequest('search', searchOptions, this._onLoadAnnotationsFromSearch);
};
Store.prototype._onLoadAnnotationsFromSearch = function(data) {
if (data == null) {
data = {};
}
return this._onLoadAnnotations(data.rows || []);
};
Store.prototype.dumpAnnotations = function() {
var ann, _i, _len, _ref, _results;
_ref = this.annotations;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
ann = _ref[_i];
_results.push(JSON.parse(this._dataFor(ann)));
}
return _results;
};
Store.prototype._apiRequest = function(action, obj, onSuccess) {
var id, options, request, url;
id = obj && obj.id;
url = this._urlFor(action, id);
options = this._apiRequestOptions(action, obj, onSuccess);
request = $.ajax(url, options);
request._id = id;
request._action = action;
return request;
};
Store.prototype._apiRequestOptions = function(action, obj, onSuccess) {
var data, method, opts;
method = this._methodFor(action);
opts = {
type: method,
headers: this.element.data('annotator:headers'),
dataType: "json",
success: onSuccess || function() {},
error: this._onError
};
if (this.options.emulateHTTP && (method === 'PUT' || method === 'DELETE')) {
opts.headers = $.extend(opts.headers, {
'X-HTTP-Method-Override': method
});
opts.type = 'POST';
}
if (action === "search") {
opts = $.extend(opts, {
data: obj
});
return opts;
}
data = obj && this._dataFor(obj);
if (this.options.emulateJSON) {
opts.data = {
json: data
};
if (this.options.emulateHTTP) {
opts.data._method = method;
}
return opts;
}
opts = $.extend(opts, {
data: data,
contentType: "application/json; charset=utf-8"
});
return opts;
};
Store.prototype._urlFor = function(action, id) {
var url;
url = this.options.prefix != null ? this.options.prefix : '';
url += this.options.urls[action];
url = url.replace(/\/:id/, id != null ? '/' + id : '');
url = url.replace(/:id/, id != null ? id : '');
return url;
};
Store.prototype._methodFor = function(action) {
var table;
table = {
'create': 'POST',
'read': 'GET',
'update': 'PUT',
'destroy': 'DELETE',
'search': 'GET'
};
return table[action];
};
Store.prototype._dataFor = function(annotation) {
var data, highlights;
highlights = annotation.highlights;
delete annotation.highlights;
$.extend(annotation, this.options.annotationData);
data = JSON.stringify(annotation);
if (highlights) {
annotation.highlights = highlights;
}
return data;
};
Store.prototype._onError = function(xhr) {
var action, message;
action = xhr._action;
message = Annotator._t("Sorry we could not ") + action + Annotator._t(" this annotation");
if (xhr._action === 'search') {
message = Annotator._t("Sorry we could not search the store for annotations");
} else if (xhr._action === 'read' && !xhr._id) {
message = Annotator._t("Sorry we could not ") + action + Annotator._t(" the annotations from the store");
}
switch (xhr.status) {
case 401:
message = Annotator._t("Sorry you are not allowed to ") + action + Annotator._t(" this annotation");
break;
case 404:
message = Annotator._t("Sorry we could not connect to the annotations store");
break;
case 500:
message = Annotator._t("Sorry something went wrong with the annotation store");
}
Annotator.showNotification(message, Annotator.Notification.ERROR);
return console.error(Annotator._t("API request failed:") + (" '" + xhr.status + "'"));
};
return Store;
})(Annotator.Plugin);
/*
//
*/
// Generated by CoffeeScript 1.6.3
var __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.Permissions = (function(_super) {
__extends(Permissions, _super);
Permissions.prototype.events = {
'beforeAnnotationCreated': 'addFieldsToAnnotation'
};
Permissions.prototype.options = {
showViewPermissionsCheckbox: true,
showEditPermissionsCheckbox: true,
userId: function(user) {
return user;
},
userString: function(user) {
return user;
},
userAuthorize: function(action, annotation, user) {
var token, tokens, _i, _len;
if (annotation.permissions) {
tokens = annotation.permissions[action] || [];
if (tokens.length === 0) {
return true;
}
for (_i = 0, _len = tokens.length; _i < _len; _i++) {
token = tokens[_i];
if (this.userId(user) === token) {
return true;
}
}
return false;
} else if (annotation.user) {
if (user) {
return this.userId(user) === this.userId(annotation.user);
} else {
return false;
}
}
return true;
},
user: '',
permissions: {
'read': [],
'update': [],
'delete': [],
'admin': []
}
};
function Permissions(element, options) {
this._setAuthFromToken = __bind(this._setAuthFromToken, this);
this.updateViewer = __bind(this.updateViewer, this);
this.updateAnnotationPermissions = __bind(this.updateAnnotationPermissions, this);
this.updatePermissionsField = __bind(this.updatePermissionsField, this);
this.addFieldsToAnnotation = __bind(this.addFieldsToAnnotation, this);
Permissions.__super__.constructor.apply(this, arguments);
if (this.options.user) {
this.setUser(this.options.user);
delete this.options.user;
}
}
Permissions.prototype.pluginInit = function() {
var createCallback, self,
_this = this;
if (!Annotator.supported()) {
return;
}
self = this;
createCallback = function(method, type) {
return function(field, annotation) {
return self[method].call(self, type, field, annotation);
};
};
if (!this.user && this.annotator.plugins.Auth) {
this.annotator.plugins.Auth.withToken(this._setAuthFromToken);
}
if (this.options.showViewPermissionsCheckbox === true) {
this.annotator.editor.addField({
type: 'checkbox',
label: Annotator._t('Allow anyone to <strong>view</strong> this annotation'),
load: createCallback('updatePermissionsField', 'read'),
submit: createCallback('updateAnnotationPermissions', 'read')
});
}
if (this.options.showEditPermissionsCheckbox === true) {
this.annotator.editor.addField({
type: 'checkbox',
label: Annotator._t('Allow anyone to <strong>edit</strong> this annotation'),
load: createCallback('updatePermissionsField', 'update'),
submit: createCallback('updateAnnotationPermissions', 'update')
});
}
this.annotator.viewer.addField({
load: this.updateViewer
});
if (this.annotator.plugins.Filter) {
return this.annotator.plugins.Filter.addFilter({
label: Annotator._t('User'),
property: 'user',
isFiltered: function(input, user) {
var keyword, _i, _len, _ref;
user = _this.options.userString(user);
if (!(input && user)) {
return false;
}
_ref = input.split(/\s*/);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
keyword = _ref[_i];
if (user.indexOf(keyword) === -1) {
return false;
}
}
return true;
}
});
}
};
Permissions.prototype.setUser = function(user) {
return this.user = user;
};
Permissions.prototype.addFieldsToAnnotation = function(annotation) {
if (annotation) {
annotation.permissions = this.options.permissions;
if (this.user) {
return annotation.user = this.user;
}
}
};
Permissions.prototype.authorize = function(action, annotation, user) {
if (user === void 0) {
user = this.user;
}
if (this.options.userAuthorize) {
return this.options.userAuthorize.call(this.options, action, annotation, user);
} else {
return true;
}
};
Permissions.prototype.updatePermissionsField = function(action, field, annotation) {
var input;
field = $(field).show();
input = field.find('input').removeAttr('disabled');
if (!this.authorize('admin', annotation)) {
field.hide();
}
if (this.authorize(action, annotation || {}, null)) {
return input.attr('checked', 'checked');
} else {
return input.removeAttr('checked');
}
};
Permissions.prototype.updateAnnotationPermissions = function(type, field, annotation) {
var dataKey;
if (!annotation.permissions) {
annotation.permissions = this.options.permissions;
}
dataKey = type + '-permissions';
if ($(field).find('input').is(':checked')) {
return annotation.permissions[type] = [];
} else {
return annotation.permissions[type] = [this.options.userId(this.user)];
}
};
Permissions.prototype.updateViewer = function(field, annotation, controls) {
var user, username;
field = $(field);
username = this.options.userString(annotation.user);
if (annotation.user && username && typeof username === 'string') {
user = Annotator.Util.escape(this.options.userString(annotation.user));
field.html(user).addClass('annotator-user');
} else {
field.remove();
}
if (controls) {
if (!this.authorize('update', annotation)) {
controls.hideEdit();
}
if (!this.authorize('delete', annotation)) {
return controls.hideDelete();
}
}
};
Permissions.prototype._setAuthFromToken = function(token) {
return this.setUser(token.userId);
};
return Permissions;
})(Annotator.Plugin);
/*
//
*/
// Generated by CoffeeScript 1.6.3
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; },
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
Annotator.Plugin.AnnotateItPermissions = (function(_super) {
__extends(AnnotateItPermissions, _super);
function AnnotateItPermissions() {
this._setAuthFromToken = __bind(this._setAuthFromToken, this);
this.updateAnnotationPermissions = __bind(this.updateAnnotationPermissions, this);
this.updatePermissionsField = __bind(this.updatePermissionsField, this);
this.addFieldsToAnnotation = __bind(this.addFieldsToAnnotation, this);
_ref = AnnotateItPermissions.__super__.constructor.apply(this, arguments);
return _ref;
}
AnnotateItPermissions.prototype.options = {
showViewPermissionsCheckbox: true,
showEditPermissionsCheckbox: true,
groups: {
world: 'group:__world__',
authenticated: 'group:__authenticated__',
consumer: 'group:__consumer__'
},
userId: function(user) {
return user.userId;
},
userString: function(user) {
return user.userId;
},
userAuthorize: function(action, annotation, user) {
var action_field, permissions, _ref1, _ref2, _ref3, _ref4;
permissions = annotation.permissions || {};
action_field = permissions[action] || [];
if (_ref1 = this.groups.world, __indexOf.call(action_field, _ref1) >= 0) {
return true;
} else if ((user != null) && (user.userId != null) && (user.consumerKey != null)) {
if (user.userId === annotation.user && user.consumerKey === annotation.consumer) {
return true;
} else if (_ref2 = this.groups.authenticated, __indexOf.call(action_field, _ref2) >= 0) {
return true;
} else if (user.consumerKey === annotation.consumer && (_ref3 = this.groups.consumer, __indexOf.call(action_field, _ref3) >= 0)) {
return true;
} else if (user.consumerKey === annotation.consumer && (_ref4 = user.userId, __indexOf.call(action_field, _ref4) >= 0)) {
return true;
} else if (user.consumerKey === annotation.consumer && user.admin) {
return true;
} else {
return false;
}
} else {
return false;
}
},
permissions: {
'read': ['group:__world__'],
'update': [],
'delete': [],
'admin': []
}
};
AnnotateItPermissions.prototype.addFieldsToAnnotation = function(annotation) {
if (annotation) {
annotation.permissions = this.options.permissions;
if (this.user) {
annotation.user = this.user.userId;
return annotation.consumer = this.user.consumerKey;
}
}
};
AnnotateItPermissions.prototype.updatePermissionsField = function(action, field, annotation) {
var input;
field = $(field).show();
input = field.find('input').removeAttr('disabled');
if (!this.authorize('admin', annotation)) {
field.hide();
}
if (this.user && this.authorize(action, annotation || {}, {
userId: '__nonexistentuser__',
consumerKey: this.user.consumerKey
})) {
return input.attr('checked', 'checked');
} else {
return input.removeAttr('checked');
}
};
AnnotateItPermissions.prototype.updateAnnotationPermissions = function(type, field, annotation) {
var dataKey;
if (!annotation.permissions) {
annotation.permissions = this.options.permissions;
}
dataKey = type + '-permissions';
if ($(field).find('input').is(':checked')) {
return annotation.permissions[type] = [type === 'read' ? this.options.groups.world : this.options.groups.consumer];
} else {
return annotation.permissions[type] = [];
}
};
AnnotateItPermissions.prototype._setAuthFromToken = function(token) {
return this.setUser(token);
};
return AnnotateItPermissions;
})(Annotator.Plugin.Permissions);
/*
//
*/
// Generated by CoffeeScript 1.6.3
var __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.Filter = (function(_super) {
__extends(Filter, _super);
Filter.prototype.events = {
".annotator-filter-property input focus": "_onFilterFocus",
".annotator-filter-property input blur": "_onFilterBlur",
".annotator-filter-property input keyup": "_onFilterKeyup",
".annotator-filter-previous click": "_onPreviousClick",
".annotator-filter-next click": "_onNextClick",
".annotator-filter-clear click": "_onClearClick"
};
Filter.prototype.classes = {
active: 'annotator-filter-active',
hl: {
hide: 'annotator-hl-filtered',
active: 'annotator-hl-active'
}
};
Filter.prototype.html = {
element: "<div class=\"annotator-filter\">\n <strong>" + Annotator._t('Navigate:') + "</strong>\n<span class=\"annotator-filter-navigation\">\n <button class=\"annotator-filter-previous\">" + Annotator._t('Previous') + "</button>\n<button class=\"annotator-filter-next\">" + Annotator._t('Next') + "</button>\n</span>\n<strong>" + Annotator._t('Filter by:') + "</strong>\n</div>",
filter: "<span class=\"annotator-filter-property\">\n <label></label>\n <input/>\n <button class=\"annotator-filter-clear\">" + Annotator._t('Clear') + "</button>\n</span>"
};
Filter.prototype.options = {
appendTo: 'body',
filters: [],
addAnnotationFilter: true,
isFiltered: function(input, property) {
var keyword, _i, _len, _ref;
if (!(input && property)) {
return false;
}
_ref = input.split(/\s+/);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
keyword = _ref[_i];
if (property.indexOf(keyword) === -1) {
return false;
}
}
return true;
}
};
function Filter(element, options) {
this._onPreviousClick = __bind(this._onPreviousClick, this);
this._onNextClick = __bind(this._onNextClick, this);
this._onFilterKeyup = __bind(this._onFilterKeyup, this);
this._onFilterBlur = __bind(this._onFilterBlur, this);
this._onFilterFocus = __bind(this._onFilterFocus, this);
this.updateHighlights = __bind(this.updateHighlights, this);
var _base;
element = $(this.html.element).appendTo((options != null ? options.appendTo : void 0) || this.options.appendTo);
Filter.__super__.constructor.call(this, element, options);
(_base = this.options).filters || (_base.filters = []);
this.filter = $(this.html.filter);
this.filters = [];
this.current = 0;
}
Filter.prototype.pluginInit = function() {
var filter, _i, _len, _ref;
_ref = this.options.filters;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i];
this.addFilter(filter);
}
this.updateHighlights();
this._setupListeners()._insertSpacer();
if (this.options.addAnnotationFilter === true) {
return this.addFilter({
label: Annotator._t('Annotation'),
property: 'text'
});
}
};
Filter.prototype.destroy = function() {
var currentMargin, html;
Filter.__super__.destroy.apply(this, arguments);
html = $('html');
currentMargin = parseInt(html.css('padding-top'), 10) || 0;
html.css('padding-top', currentMargin - this.element.outerHeight());
return this.element.remove();
};
Filter.prototype._insertSpacer = function() {
var currentMargin, html;
html = $('html');
currentMargin = parseInt(html.css('padding-top'), 10) || 0;
html.css('padding-top', currentMargin + this.element.outerHeight());
return this;
};
Filter.prototype._setupListeners = function() {
var event, events, _i, _len;
events = ['annotationsLoaded', 'annotationCreated', 'annotationUpdated', 'annotationDeleted'];
for (_i = 0, _len = events.length; _i < _len; _i++) {
event = events[_i];
this.annotator.subscribe(event, this.updateHighlights);
}
return this;
};
Filter.prototype.addFilter = function(options) {
var f, filter;
filter = $.extend({
label: '',
property: '',
isFiltered: this.options.isFiltered
}, options);
if (!((function() {
var _i, _len, _ref, _results;
_ref = this.filters;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
f = _ref[_i];
if (f.property === filter.property) {
_results.push(f);
}
}
return _results;
}).call(this)).length) {
filter.id = 'annotator-filter-' + filter.property;
filter.annotations = [];
filter.element = this.filter.clone().appendTo(this.element);
filter.element.find('label').html(filter.label).attr('for', filter.id);
filter.element.find('input').attr({
id: filter.id,
placeholder: Annotator._t('Filter by ') + filter.label + '\u2026'
});
filter.element.find('button').hide();
filter.element.data('filter', filter);
this.filters.push(filter);
}
return this;
};
Filter.prototype.updateFilter = function(filter) {
var annotation, annotations, input, property, _i, _len, _ref;
filter.annotations = [];
this.updateHighlights();
this.resetHighlights();
input = $.trim(filter.element.find('input').val());
if (input) {
annotations = this.highlights.map(function() {
return $(this).data('annotation');
});
_ref = $.makeArray(annotations);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
annotation = _ref[_i];
property = annotation[filter.property];
if (filter.isFiltered(input, property)) {
filter.annotations.push(annotation);
}
}
return this.filterHighlights();
}
};
Filter.prototype.updateHighlights = function() {
this.highlights = this.annotator.element.find('.annotator-hl:visible');
return this.filtered = this.highlights.not(this.classes.hl.hide);
};
Filter.prototype.filterHighlights = function() {
var activeFilters, annotation, annotations, filtered, highlights, index, uniques, _i, _len, _ref;
activeFilters = $.grep(this.filters, function(filter) {
return !!filter.annotations.length;
});
filtered = ((_ref = activeFilters[0]) != null ? _ref.annotations : void 0) || [];
if (activeFilters.length > 1) {
annotations = [];
$.each(activeFilters, function() {
return $.merge(annotations, this.annotations);
});
uniques = [];
filtered = [];
$.each(annotations, function() {
if ($.inArray(this, uniques) === -1) {
return uniques.push(this);
} else {
return filtered.push(this);
}
});
}
highlights = this.highlights;
for (index = _i = 0, _len = filtered.length; _i < _len; index = ++_i) {
annotation = filtered[index];
highlights = highlights.not(annotation.highlights);
}
highlights.addClass(this.classes.hl.hide);
this.filtered = this.highlights.not(this.classes.hl.hide);
return this;
};
Filter.prototype.resetHighlights = function() {
this.highlights.removeClass(this.classes.hl.hide);
this.filtered = this.highlights;
return this;
};
Filter.prototype._onFilterFocus = function(event) {
var input;
input = $(event.target);
input.parent().addClass(this.classes.active);
return input.next('button').show();
};
Filter.prototype._onFilterBlur = function(event) {
var input;
if (!event.target.value) {
input = $(event.target);
input.parent().removeClass(this.classes.active);
return input.next('button').hide();
}
};
Filter.prototype._onFilterKeyup = function(event) {
var filter;
filter = $(event.target).parent().data('filter');
if (filter) {
return this.updateFilter(filter);
}
};
Filter.prototype._findNextHighlight = function(previous) {
var active, annotation, current, index, next, offset, operator, resetOffset;
if (!this.highlights.length) {
return this;
}
offset = previous ? 0 : -1;
resetOffset = previous ? -1 : 0;
operator = previous ? 'lt' : 'gt';
active = this.highlights.not('.' + this.classes.hl.hide);
current = active.filter('.' + this.classes.hl.active);
if (!current.length) {
current = active.eq(offset);
}
annotation = current.data('annotation');
index = active.index(current[0]);
next = active.filter(":" + operator + "(" + index + ")").not(annotation.highlights).eq(resetOffset);
if (!next.length) {
next = active.eq(resetOffset);
}
return this._scrollToHighlight(next.data('annotation').highlights);
};
Filter.prototype._onNextClick = function(event) {
return this._findNextHighlight();
};
Filter.prototype._onPreviousClick = function(event) {
return this._findNextHighlight(true);
};
Filter.prototype._scrollToHighlight = function(highlight) {
highlight = $(highlight);
this.highlights.removeClass(this.classes.hl.active);
highlight.addClass(this.classes.hl.active);
return $('html, body').animate({
scrollTop: highlight.offset().top - (this.element.height() + 20)
}, 150);
};
Filter.prototype._onClearClick = function(event) {
return $(event.target).prev('input').val('').keyup().blur();
};
return Filter;
})(Annotator.Plugin);
/*
//
*/
// Generated by CoffeeScript 1.6.3
var __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.Markdown = (function(_super) {
__extends(Markdown, _super);
Markdown.prototype.events = {
'annotationViewerTextField': 'updateTextField'
};
function Markdown(element, options) {
this.updateTextField = __bind(this.updateTextField, this);
if ((typeof Showdown !== "undefined" && Showdown !== null ? Showdown.converter : void 0) != null) {
Markdown.__super__.constructor.apply(this, arguments);
this.converter = new Showdown.converter();
} else {
console.error(Annotator._t("To use the Markdown plugin, you must include Showdown into the page first."));
}
}
Markdown.prototype.updateTextField = function(field, annotation) {
var text;
text = Annotator.Util.escape(annotation.text || '');
return $(field).html(this.convert(text));
};
Markdown.prototype.convert = function(text) {
return this.converter.makeHtml(text);
};
return Markdown;
})(Annotator.Plugin);
/*
//
*/
// Generated by CoffeeScript 1.6.3
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.Tags = (function(_super) {
__extends(Tags, _super);
function Tags() {
this.setAnnotationTags = __bind(this.setAnnotationTags, this);
this.updateField = __bind(this.updateField, this);
_ref = Tags.__super__.constructor.apply(this, arguments);
return _ref;
}
Tags.prototype.options = {
parseTags: function(string) {
var tags;
string = $.trim(string);
tags = [];
if (string) {
tags = string.split(/\s+/);
}
return tags;
},
stringifyTags: function(array) {
return array.join(" ");
}
};
Tags.prototype.field = null;
Tags.prototype.input = null;
Tags.prototype.pluginInit = function() {
if (!Annotator.supported()) {
return;
}
this.field = this.annotator.editor.addField({
label: Annotator._t('Add some tags here') + '\u2026',
load: this.updateField,
submit: this.setAnnotationTags
});
this.annotator.viewer.addField({
load: this.updateViewer
});
if (this.annotator.plugins.Filter) {
this.annotator.plugins.Filter.addFilter({
label: Annotator._t('Tag'),
property: 'tags',
isFiltered: Annotator.Plugin.Tags.filterCallback
});
}
return this.input = $(this.field).find(':input');
};
Tags.prototype.parseTags = function(string) {
return this.options.parseTags(string);
};
Tags.prototype.stringifyTags = function(array) {
return this.options.stringifyTags(array);
};
Tags.prototype.updateField = function(field, annotation) {
var value;
value = '';
if (annotation.tags) {
value = this.stringifyTags(annotation.tags);
}
return this.input.val(value);
};
Tags.prototype.setAnnotationTags = function(field, annotation) {
return annotation.tags = this.parseTags(this.input.val());
};
Tags.prototype.updateViewer = function(field, annotation) {
field = $(field);
if (annotation.tags && $.isArray(annotation.tags) && annotation.tags.length) {
return field.addClass('annotator-tags').html(function() {
var string;
return string = $.map(annotation.tags, function(tag) {
return '<span class="annotator-tag">' + Annotator.Util.escape(tag) + '</span>';
}).join(' ');
});
} else {
return field.remove();
}
};
return Tags;
})(Annotator.Plugin);
Annotator.Plugin.Tags.filterCallback = function(input, tags) {
var keyword, keywords, matches, tag, _i, _j, _len, _len1;
if (tags == null) {
tags = [];
}
matches = 0;
keywords = [];
if (input) {
keywords = input.split(/\s+/g);
for (_i = 0, _len = keywords.length; _i < _len; _i++) {
keyword = keywords[_i];
if (tags.length) {
for (_j = 0, _len1 = tags.length; _j < _len1; _j++) {
tag = tags[_j];
if (tag.indexOf(keyword) !== -1) {
matches += 1;
}
}
}
}
}
return matches === keywords.length;
};
/*
//
*/
// Generated by CoffeeScript 1.6.3
var __hasProp = {}.hasOwnProperty,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
Annotator.prototype.setupPlugins = function(config, options) {
var name, opts, pluginConfig, plugins, uri, win, _i, _len, _results;
if (config == null) {
config = {};
}
if (options == null) {
options = {};
}
win = Annotator.Util.getGlobal();
plugins = ['Unsupported', 'Auth', 'Tags', 'Filter', 'Store', 'AnnotateItPermissions'];
if (win.Showdown) {
plugins.push('Markdown');
}
uri = win.location.href.split(/#|\?/).shift() || '';
pluginConfig = {
Tags: {},
Filter: {
filters: [
{
label: Annotator._t('User'),
property: 'user'
}, {
label: Annotator._t('Tags'),
property: 'tags'
}
]
},
Auth: {
tokenUrl: config.tokenUrl || 'http://annotateit.org/api/token'
},
Store: {
prefix: config.storeUrl || 'http://annotateit.org/api',
annotationData: {
uri: uri
},
loadFromSearch: {
uri: uri
}
}
};
for (name in options) {
if (!__hasProp.call(options, name)) continue;
opts = options[name];
if (__indexOf.call(plugins, name) < 0) {
plugins.push(name);
}
}
$.extend(true, pluginConfig, options);
_results = [];
for (_i = 0, _len = plugins.length; _i < _len; _i++) {
name = plugins[_i];
if (!(name in pluginConfig) || pluginConfig[name]) {
_results.push(this.addPlugin(name, pluginConfig[name]));
} else {
_results.push(void 0);
}
}
return _results;
};
/*
//
*/
//@ sourceMappingURL=annotator-full.map

View File

@@ -0,0 +1,516 @@
#mainCatch body,
#mainCatch div,
#mainCatch p,
#mainCatch canvas,
#mainCatch h1,
#mainCatch h2 {
margin: 0;
padding: 0;
}
#mainCatch h1{
font-size: 1.6em;
}
#mainCatch body {
font-family: Helvetica, Arial, sans-serif;
}
#mainCatch div {
overflow: hidden;
}
#mainCatch header {
margin: 30px auto;
text-align: center;
width: 100%;
}
#mainCatch #main {
min-width: 640px;
padding-bottom: 100px;
}
/* GRID FORMATTING */
#mainCatch .field {
padding: 0 10px;
float: left;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ANNOTATION LIST */
#mainCatch.annotationListContainer {
width: 100%;
margin: 0 auto;
}
#mainCatch .annotationList {
width: 100%;
font-size:0.8em;
border: 2px solid #dddddd;
padding: 0;
}
#mainCatch .annotationList .ui-tabs-nav{
background: transparent;
border-width: 0px 0px 1px 0px;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
border-radius: 0px;
margin: 0;
}
#mainCatch .annotationList li {
list-style: none;
position: relative;
top: 0;
margin: 1px .2em 0 0;
border-bottom-width: 0;
white-space: nowrap;
float: left;
padding: .5em 1em;
text-decoration: none;
}
#mainCatch .annotationList .ui-state-default{
border: 1px solid #d3d3d3;
background: #fff 50% 50% repeat-x;
font-weight: normal;
color: #555555;
cursor:pointer;
}
#mainCatch .annotationList .ui-state-default:hover{
border: 1px solid #999999;
background: #fff 50% 50% repeat-x;
font-weight: normal;
color: #212121;
}
#mainCatch .annotationList .ui-state-default.active{
border: 1px solid #aaaaaa;
background: #dadada 50% 50% repeat-x;
font-weight: normal;
color: #212121;
cursor:default;
}
#mainCatch .annotationList .header {
background-color:whitesmoke;
font-weight: bold;
border-bottom: 2px solid #dddddd;
clear:left;
border-top:2px solid #dadada;
}
/* ANNOTATION ROW */
#mainCatch .annotationRow {
width: 100%;
padding-top:12px;
height: 30px;
}
#mainCatch .annotationRow.item {
cursor: pointer;
}
#mainCatch .expandableIcon img, .closeDetailIcon img {
max-width: none !important;
height: 16px;
}
#mainCatch .expandableIcon .visible {
display: block;
}
#mainCatch .expandableIcon .hidden {
display: none;
}
#mainCatch .annotationItem.odd .annotationRow {
/* background-color: #eaffea; */
}
#mainCatch .annotationItem.even .annotationRow {
/* background-color: #f4fff4; */
}
#mainCatch .annotationRow .expandableIcon {
width: 4%;
}
#mainCatch .annotationRow .annotatedBy {
width: 12%;
}
#mainCatch .annotationRow .body {
width: 32%;
}
#mainCatch .annotationRow .start {
width: 8%;
}
#mainCatch .annotationRow .end {
width: 8%;
}
#mainCatch .annotationRow .annotatedAt {
width: 25%;
}
#mainCatch .annotationRow .totalreplies {
width: 9%;
}
#mainCatch .annotationItem.open .annotationRow {
display: none;
}
/* ANNOTATION DETAIL */
#mainCatch .annotationItem.closed .annotationDetail {
display: none;
}
#mainCatch .annotationDetail {
border-top:1px solid #cccccc;
border-bottom:1px solid #cccccc;
padding-left:30px;
padding-bottom:15px;
}
#mainCatch .annotationDetail .detailHeader {
font-style: italic;
margin-top:10px;
cursor: pointer;
}
#mainCatch .annotationDetail .quote {
font-style:italic;
color: #666666;
background-color: #f7f579;
padding: 7px 10px 7px 10px;
margin: 10px 20px 5px 0px;
font-size: 16px;
cursor: pointer;
}
#mainCatch .annotationDetail .quote .quoteText{
padding: 7px;
display: inline;
}
#mainCatch .annotationDetail .quote .quoteItem{
font-size: 30px;
display: inline;
}
#mainCatch .annotationDetail .quote p, #mainCatch .annotationDetail .body p {
padding:5px 0;
}
/* PLAY MEDIA BUTTON STYLING EXTERNALLY GENERATED */
#mainCatch .playMediaButton {
width:18em;
font-weight: bold;
text-align: center;
margin-top: 15px;
}
#mainCatch .playMediaButton {
border-top: 2px solid #d3bd89;
border-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69;
border-left: 2px solid #b3ad69;
background: #f7f579;
background: -webkit-gradient(linear, left top, left bottom, from(#f7e734), to(#f7f579));
background: -webkit-linear-gradient(top, #f7e734, #f7f579);
background: -moz-linear-gradient(top, #f7e734, #f7f579);
background: -ms-linear-gradient(top, #f7e734, #f7f579);
background: -o-linear-gradient(top, #f7e734, #f7f579);
padding: 10px 20px;
-webkit-border-radius: 24px;
-moz-border-radius: 24px;
border-radius: 24px;
color: #000000;
}
#mainCatch .playMediaButton:hover {
border-top: 2px solid #d3bd89;
border-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69;
border-left: 2px solid #b3ad69;
background: #f5c105;
color: #080708;
}
#mainCatch .playMediaButton:active {
border-top: 2px solid #d3bd89;
border-bottom: 2px solid #b3ad69;
border-right: 2px solid #b3ad69;
border-left: 2px solid #b3ad69;
background: #eb7f0c;
}
/* TAGS */
#mainCatch .tags {
margin-bottom: 30px;
}
/* TAG BUTTON */
#mainCatch .tag {
max-width:200px;
font-weight: bold;
text-align: center;
float: left;
margin-right: 10px;
}
#mainCatch .tag {
border-top: 2px solid #999999;
border-bottom: 2px solid #999999;
border-right: 2px solid #999999;
border-left: 2px solid #999999;
background: #dddddd;
background: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));
background: -webkit-linear-gradient(top, #eeeeee, #dddddd);
background: -moz-linear-gradient(top, #eeeeee, #dddddd);
background: -ms-linear-gradient(top, #eeeeee, #dddddd);
background: -o-linear-gradient(top, #eeeeee, #dddddd);
padding: 5px 10px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
color: #000000;
}
#mainCatch .tag:hover {
background: #cccccc;
}
#mainCatch .tag:active {
background: #bbbbbb;
}
/* VIDEO ANNOTATION DETAIL */
#mainCatch .geolocationIcon img {
height: 28px;
}
#mainCatch .videoAnnotationDetail .body {
padding: 5px 0;
}
/*PublicPrivate Notes in My notes */
#mainCatch .annotationListButtons span{
display:none;
}
#mainCatch .annotationListButtons .PublicPrivate{
cursor: pointer;
}
#mainCatch .annotationListButtons .PublicPrivate.separator,
#mainCatch .annotationListButtons .PublicPrivate.myNotes{
position:relative;
float:left;
}
#mainCatch .annotationListButtons .PublicPrivate.active *{
color:black;
text-decoration: underline;
}
/* My notes buttons */
#mainCatch .annotationListButtons .moreButtonCatch{
-moz-box-shadow:inset 0px 1px 0px 0px #ffffff;
-webkit-box-shadow:inset 0px 1px 0px 0px #ffffff;
box-shadow:inset 0px 1px 0px 0px #ffffff;
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #ffffff), color-stop(1, #a6a3a3));
background:-moz-linear-gradient(top, #ffffff 5%, #a6a3a3 100%);
background:-webkit-linear-gradient(top, #ffffff 5%, #a6a3a3 100%);
background:-o-linear-gradient(top, #ffffff 5%, #a6a3a3 100%);
background:-ms-linear-gradient(top, #ffffff 5%, #a6a3a3 100%);
background:linear-gradient(to bottom, #ffffff 5%, #a6a3a3 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#a6a3a3',GradientType=0);
background-color:#ffffff;
-moz-border-radius:6px;
-webkit-border-radius:6px;
border-radius:6px;
border:1px solid #c2c2c2;
display:inline-block;
color:#302f2f;
font-family:arial;
font-size:15px;
font-weight:bold;
padding:6px 24px;
text-decoration:none;
text-shadow:0px 1px 0px #ffffff;
margin: 0px 5px 10px 5px;
cursor:pointer;
}
#mainCatch .annotationListButtons .PublicPrivate{
-moz-box-shadow:inset 0px 1px 0px 0px #ffffff;
-webkit-box-shadow:inset 0px 1px 0px 0px #ffffff;
box-shadow:inset 0px 1px 0px 0px #ffffff;
background-color:#fff;
border:1px solid #c2c2c2;
display:inline-block;
color:#302f2f;
font-family:arial;
font-size:15px;
font-weight:bold;
padding:6px 24px;
text-decoration:none;
margin: 0px 0px 10px 0px;
cursor:pointer;
width:140px;
text-align:center;
}
#mainCatch .annotationListButtons .PublicPrivate.myNotes{
-moz-border-top-left-radius:6px;
-webkit-border-top-left-radius:6px;
border-radius-top-left:6px;
-moz-border-bottom-left-radius:6px;
-webkit-border-bottom-left-radius:6px;
border-radius-bottom-left:6px;
}
#mainCatch .annotationListButtons .PublicPrivate.public{
-moz-border-top-right-radius:6px;
-webkit-border-top-right-radius:6px;
border-radius-top-right:6px;
-moz-border-bottom-right-radius:6px;
-webkit-border-bottom-right-radius:6px;
border-radius-bottom-right:6px;
}
#mainCatch .annotationListButtons .PublicPrivate.active:hover,
#mainCatch .annotationListButtons .PublicPrivate.active{
color:#302f2f;
background-color:#dadada;
cursor:default;
}
#mainCatch .annotationListButtons .moreButtonCatch:hover {
background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #a6a3a3), color-stop(1, #ffffff));
background:-moz-linear-gradient(top, #a6a3a3 5%, #ffffff 100%);
background:-webkit-linear-gradient(top, #a6a3a3 5%, #ffffff 100%);
background:-o-linear-gradient(top, #a6a3a3 5%, #ffffff 100%);
background:-ms-linear-gradient(top, #a6a3a3 5%, #ffffff 100%);
background:linear-gradient(to bottom, #a6a3a3 5%, #ffffff 100%);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#a6a3a3', endColorstr='#ffffff',GradientType=0);
background-color:#a6a3a3;
}
#mainCatch .annotationListButtons .PublicPrivate:hover{
background-color:#868383;
}
#mainCatch .annotationListButtons .publicPrivate:active,
#mainCatch .annotationListButtons .moreButtonCatch:active {
position:relative;
top:1px;
}
/* More Button */
#mainCatch .annotationListButtons .moreButtonCatch{
width: 100%;
margin: 0;
text-align: center;
}
/* control Panel */
#mainCatch .controlPanel .groups_button,
#mainCatch .controlPanel .share-container-annotator{
position: relative;
float:left;
cursor:pointer;
}
#mainCatch .controlPanel .share-container-annotator{
min-width: 0;
padding-left: 4px;
top: -3px;
}
#mainCatch .controlReplies div{
position: relative;
float: left;
text-decoration: underline;
margin: 5px;
cursor: pointer;
}
#mainCatch .searchbox input{
margin: 0;
padding: 0;
width: 60%;
margin-left: 10px;
display: inline;
float: left;
}
#mainCatch .searchbox .dropdown-list{
width: 18%;
float: left;
margin-top: 5px;
}
#mainCatch .searchbox .search-icon{
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAA3NCSVQICAjb4U/gAAAApVBMVEVmZmaPj4/f399mZmbBwcF8fHykpKTX19dycnLl5eWDg4O5ubmfn5/MzMyHh4fp6em1tbVsbGx5eXmZmZnu7u7h4eGLi4uZmZl+fn7ExMTd3d1wcHDx8fHR0dHj4+NqamqBgYGFhYV0dHS7u7va2tp6enrn5+eoqKhubm6NjY2hoaGJiYnr6+vv7+/Hx8d2dna3t7fDw8PPz8+9vb2RkZGnp6f39/dv4zATAAAAN3RSTlP///////////////////////////////////////////////////////////////////////8AEFmdiwAAAAlwSFlzAAALEgAACxIB0t1+/AAAAB50RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNS4xqx9I6wAAABZ0RVh0Q3JlYXRpb24gVGltZQAxMi8xNi8xM93x4EAAAAF1SURBVDiNjdThbsIgEADgk8jWYtdZsaZ22pqqmW7GGZPd+z/aDnDWApXyq9AvcAcHgAMbtJ/xRKapnMQBuJ2BabU4P4GlgLbVo6oPvnENmqJoavXVXP2w/KCf2ctKR5pk1JnlPrhK6dei/B/aFdQ9+uCEluPzduxEAddjD9wARJ1N2R0AhAsZTfjZHU1oyncHTikRazSnKMcOlAA/dui0DdKBFHphwz1tgw9yG858cERJ25DSThx4cVNUySwdWNH2/HZHpXd7kCoie30cvGbeDcctLfT9MEFMh52tPVCdIezvRbGOqDuy0zNlpurlkOjzriRlDNy5EKZw140u7YgXkSlyzvwQ40UNnba3b8P9co3FjdbpRcfMeiDi/Px1PCbLExUeHTVs4j740ColozwMkanVBQtDjCMrzj6IuXoSeBmGJqN29X6ITD0fDQtD8yClLAyRNe2cTyEy9dgULAzNnFoGIDJVgWIA1HIQRCaEHARvbTD8A+tpEWRTORVbAAAAAElFTkSuQmCC');
width: 16px;
background-size: cover;
height: 16px;
background-repeat: no-repeat;
position: relative;
float: left;
margin-left: 10px;
margin-top: 10px;
cursor:pointer;
}
#mainCatch .searchbox .search-icon:hover{
opacity:0.5;
box-shadow: 2px 4px 5px #888888;
}
#mainCatch .selectors{
width:40%;
position:relative;
float:left;
}
#mainCatch .searchbox{
width:60%;
position:relative;
float:right;
}
#mainCatch .searchbox .searchinst{
float:left;
position:relative;
padding-right:5px;
margin-top:8px;
}
#mainCatch .replies .replyItem .deleteReply{
text-decoration: underline;
cursor: pointer;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

View File

@@ -0,0 +1,1107 @@
//The name of the plugin that the user will write in the html
window.CatchAnnotation = ("CatchAnnotation" in window) ? CatchAnnotation : {};
window.CatchSources = ("CatchSources" in window) ? CatchSources : {};
//
// HTML TEMPLATES
//
CatchSources.HTMLTEMPLATES = function(root){
var root = root || '';
return {
//Main
annotationList:
'<div class="annotationListButtons">'+
'{{{ PublicPrivate }}}'+
'</div>'+
'<div class="annotationList">'+
'{{{ MediaSelector }}}'+
'<div class="header">'+
'<div class="annotationRow">'+
'<div class="expandableIcon field">'+
'&nbsp <!-- TODO: better way to ensure width upon hide -->'+
'</div>'+
'<div class="annotatedBy field">'+
'User'+
'</div>'+
'<div class="body field">'+
'Annotation'+
'</div>'+
'{{#if videoFormat}}'+
'<div class="start field">'+
'Start'+
'</div>'+
'<div class="end field">'+
'End'+
'</div>'+
'{{/if}}'+
'<div class="totalreplies field">'+
'#Replies'+
'</div>'+
'<div class="annotatedAt field">'+
'Date posted'+
'</div>'+
'</div>'+
'</div>'+
'{{#each annotationItems}}'+
'{{{ this }}}'+
'{{/each}}'+
'</div>'+
'<div class="annotationListButtons">'+
'<div class="moreButtonCatch">More</div>'+
'</div>',
//Main->PublicPrivate
annotationPublicPrivate:
'<div class="selectors"><div class="PublicPrivate myNotes active">My Notes<span class="action">myNotes</span></div>'+
'<div class="PublicPrivate public"> Public<span class="action">public</span></div></div>'+
'<div class="searchbox"><div class="searchinst">Search</div><select class="dropdown-list">'+
'<option>Users</option>'+
'<option>Tags</option>'+
'<option>Annotation Text</option>'+
'</select><input type="text" name="search"/><div class="search-icon" alt="Run search."></div></div>',
//Main->MediaSelector
annotationMediaSelector:
'<ul class="ui-tabs-nav">'+
'<li class="ui-state-default" media="text">'+
'Text'+
'</li>'+
'<li class="ui-state-default" media="video">'+
'Video'+
'</li>'+
'</ul>',
// '<div class="selButtonCatch">Text<span class="action">text</span></div>'+
// '<div class="selButtonCatch">Video<span class="action">video</span></div>',
// '<div class="selButtonCatch">Images<span class="action">image</span></div>'+
// '<div class="selButtonCatch">Audio<span class="action">audio</span></div>'+
// '<div class="selButtonCatch">Maps<span class="action">map</span></div>'+
// '<div class="selButtonCatch">3D studio<span class="action">3d</span></div>',
//Main->ContainerRow
annotationItem:
'<div class="annotationItem {{ evenOrOdd }} {{ openOrClosed }}" annotationId="{{ id }}">'+
'{{{ annotationRow }}}'+
'{{{ annotationDetail }}}'+
'</div>',
//Main->ContainerRow->Reply
annotationReply:
//<blockquote style="font-size:90%"><p><em>On Sep 14, 2013 4:35 PM <a href="#">jharvard</a>, wrote from [<a href="#">show map location</a>]:</em></p><p>{{{ an.text }}}</p></blockquote>
'{{#if annotations}}'+
'{{#each annotations}}'+
'<blockquote class="replyItem" annotationId="{{this.id}}" style="font-size:90%">'+
'<p>'+
'On {{ this.updated }} <!--<a href="index.php?r=user/user/view&id={{{this.user.id}}}">-->{{{ this.user.name }}}<!--</a>-->{{#if this.geolocation}}, wrote from {{/if}}'+
'{{#if geolocation}}'+
'<span class="geolocationIcon">'+
'<img src="'+root+'geolocation_icon.png"width="25" height="25" alt="Location Map" title="Show Location Map" data-dropdown="myLocationMap"/>'+
'<span class="idAnnotation" style="display:none">{{{ this.id }}}</span>'+
'<span class="latitude" style="display:none">{{{ this.geolocation.latitude }}}</span>'+
'<span class="longitude" style="display:none">{{{ this.geolocation.longitude }}}</span>'+
'</span>'+
'<div id="myLocationMap" data-dropdown-content class="f-dropdown content">'+
'<div class="map"></div>'+
'</div>'+
'{{/if}}'+
'<div class="deleteReply">Delete</div>'+
'</p>'+
'<p>'+
'{{#if this.text}}'+
'{{{this.text}}}'+
'{{else}}'+
'-'+
'{{/if}}'+
'</p>'+
'</blockquote>'+
'{{/each}}'+
'{{/if}}',
//Main->ContainerRow->Row
annotationRow:
'<div class="annotationRow item">'+
'<div class="expandableIcon field">'+
'<img src="'+root+'expandableIcon.png" alt="View Details" />'+
'&nbsp'+
'</div>'+
'<div class="annotatedBy field">'+
'{{ user.name }}'+
'</div>'+
'<div class="body field">'+
'{{#if plainText}}'+
'{{deparagraph plainText}}'+
'{{else}}'+
'-'+
'{{/if}}'+
'</div>'+
'<div class="start field">'+
'{{ rangeTime.start }}'+
'</div>'+
'<div class="end field">'+
'{{ rangeTime.end }}'+
'</div>'+
'<div class="totalreplies field">'+
'{{ totalComments }}'+
'</div>'+
'<div class="annotatedAt field">'+
'{{ updated }}'+
'</div>'+
'</div>',
//Main->ContainerRow->DetailRow
annotationDetail:
'<div class="annotationDetail">'+
'<div class="detailHeader">'+
'<span class="closeDetailIcon">'+
'<img src="'+root+'closeIcon.png" alt="Hide Details" />'+
'</span>'+
'On {{ updated }} <!--<a href="index.php?r=user/user/view&id={{{user.id}}}">-->{{{ user.name }}}<!--</a>-->{{#if geolocation}}, wrote from {{/if}}'+
'{{#if geolocation}}'+
'<span class="geolocationIcon">'+
'<img src="'+root+'geolocation_icon.png"width="25" height="25" alt="Location Map" title="Show Location Map" data-dropdown="myLocationMap"/>'+
'<span class="idAnnotation" style="display:none">{{{ id }}}</span>'+
'<span class="latitude" style="display:none">{{{ geolocation.latitude }}}</span>'+
'<span class="longitude" style="display:none">{{{ geolocation.longitude }}}</span>'+
'</span>'+
'<div id="myLocationMap" data-dropdown-content class="f-dropdown content">'+
'<div class="map"></div>'+
'</div>'+
'{{/if}}'+
'</div>'+
'<div class="quote">'+
'<div style="text-align: center">'+
'<div class="quoteItem">“</div><div class="quoteText">{{{ quote }}}</div><div class="quoteItem">”</div></div>'+
'<span class="idAnnotation" style="display:none">{{{ id }}}</span>'+
'<span class="uri" style="display:none">{{{uri}}}</span>'+
'</div>'+
'<div class="body">'+
'{{{ text }}}'+
'</div>'+
'<div class="controlReplies">'+
'<div class="newReply" style="text-decoration:underline">Reply</div>&nbsp;'+
'<div class="hideReplies" style="text-decoration:underline;display:{{#if hasReplies}}block{{else}}none{{/if}}">Show Replies</div>&nbsp;'+
'{{#if authToEditButton}}'+
'<div class="editAnnotation" style="text-decoration:underline">Edit</div>'+
'{{/if}}'+
'{{#if authToDeleteButton}}'+
'<div class="deleteAnnotation" style="text-decoration:underline">Delete</div>'+
'{{/if}}'+
'</div>'+
'<div class="replies"></div>'+
'{{#if tags}}'+
'<div class="tags">'+
'<h3>Tags:</h3>'+
'{{#each tags}}'+
'<div class="tag">'+
'{{this}}'+
'</div>'+
'{{/each}}'+
'</div>'+
'{{/if}}'+
'<div class="controlPanel">'+
//'<img class="privacy_button" src="'+root+'privacy_icon.png" width="36" height="36" alt="Privacy Settings" title="Privacy Settings">'+
// '<img class="groups_button" src="'+root+'groups_icon.png" width="36" height="36" alt="Groups Access" title="Groups Access">'+
//'<img class="share_button" src="'+root+'share_icon.png" width="36" height="36" alt="Share Annotation" title="Share Annotation"/>'+
'</div>'+
'</div>',
//Main->ContainerRow->DetailRow (Video)
videoAnnotationDetail:
'<div class="annotationDetail videoAnnotationDetail">'+
'<div class="detailHeader">'+
'<span class="closeDetailIcon">'+
'<img src="'+root+'closeIcon.png" alt="Hide Details" />'+
'</span>'+
'On {{ updated }} <!--<a href="index.php?r=user/user/view&id={{{user.id}}}">-->{{{ user.name }}}<!--</a>-->{{#if geolocation}}, wrote from {{/if}}'+
'{{#if geolocation}}'+
'<span class="geolocationIcon">'+
'<img src="'+root+'geolocation_icon.png"width="25" height="25" alt="Location Map" title="Show Location Map" data-dropdown="myLocationMap"/>'+
'<span class="idAnnotation" style="display:none">{{{ id }}}</span>'+
'<span class="latitude" style="display:none">{{{ geolocation.latitude }}}</span>'+
'<span class="longitude" style="display:none">{{{ geolocation.longitude }}}</span>'+
'</span>'+
'<div id="myLocationMap" data-dropdown-content class="f-dropdown content">'+
'<div class="map"></div>'+
'</div>'+
'{{/if}}'+
'</div>'+
'<div class="playMediaButton">'+
'Play segment {{{ rangeTime.start }}} - {{{ rangeTime.end }}}'+
'<span class="idAnnotation" style="display:none">{{{ id }}}</span>'+
'<span class="uri" style="display:none">{{{uri}}}</span>'+
'<span class="container" style="display:none">{{{target.container}}}</span>'+
'</div>'+
'<div class="body">'+
'{{{ text }}}'+
'</div>'+
'<div class="controlReplies">'+
'<div class="newReply" style="text-decoration:underline">Reply</div>&nbsp;'+
'<div class="hideReplies" style="text-decoration:underline;display:{{#if hasReplies}}block{{else}}none{{/if}}">Show Replies</div>&nbsp;'+
'{{#if authToEditButton}}'+
'<div class="editAnnotation" style="text-decoration:underline">Edit</div>'+
'{{/if}}'+
'{{#if authToDeleteButton}}'+
'<div class="deleteAnnotation" style="text-decoration:underline">Delete</div>'+
'{{/if}}'+
'</div>'+
'<div class="replies"></div>'+
'{{#if tags}}'+
'<div class="tags">'+
'<h3>Tags:</h3>'+
'{{#each tags}}'+
'<div class="tag">'+
'{{this}}'+
'</div>'+
'{{/each}}'+
'</div>'+
'{{/if}}'+
'<div class="controlPanel">'+
//'<img class="privacy_button" src="'+root+'privacy_icon.png" width="36" height="36" alt="Privacy Settings" title="Privacy Settings">'+
// '<img class="groups_button" src="'+root+'groups_icon.png" width="36" height="36" alt="Groups Access" title="Groups Access">'+
// '<img class="reply_button" src="'+root+'groups_icon.png" width="36" height="36" alt="Reply" title="Reply" idAnnotation="{{{ id }}}">'+
//'<img class="share_button" src="'+root+'share_icon.png" width="36" height="36" alt="Share Annotation" title="Share Annotation"/>'+
'</div>'+
'</div>',
};
};
CatchAnnotation = function (element, options) {
//local variables
var $ = jQuery,
options = options || {};
//Options
var defaultOptions = {
media: 'text',
userId: '', //this is an integer and its value is the userId to see user annotations
externalLink: false,//This is true if you want to open the link in a new URL. However, it is false if you want to open the url in the same page
showMediaSelector: true, //whether show the selector of Media Annotations or not
showPublicPrivate: true, //Whether show Public or Private Annotation Selector
pagination: 50, //Number of Annotations per load in the pagination
flags:false //This checks to see if user is staff and has access to see flags
};
this.options = $.extend( true, defaultOptions, options );
//element
this.element = element;
//clean boolean
this.clean = false;
//Reset element an create a new element div
element.html('<div id="mainCatch" class="annotationListContainer"></div>');
//INIT
var self = this;
$( document ).ready(function() {
self.init();
self.refreshCatch(true);
var moreBut = self.element.find('.annotationListButtons .moreButtonCatch');
moreBut.hide();
});
return this;
}
CatchAnnotation.prototype = {
init: function(){
//Set variables
//Initial Templates
this.TEMPLATENAMES = [
"annotationList", //Main
"annotationPublicPrivate", //Main->PublicPrivate
"annotationMediaSelector", //Main->MediaSelector
"annotationItem", //Main->ContainerRow
"annotationReply",//Main->ContainerRow->Reply
"annotationRow", //Main->ContainerRow->Row
"annotationDetail",//Main->ContainerRow->DetailRow
"videoAnnotationDetail"//Main->ContainerRow->DetailRow (Video)
];
//annotator
var wrapper = $('.annotator-wrapper').parent()[0],
annotator = $.data(wrapper, 'annotator');
this.annotator = annotator;
//Subscribe to annotator
this._subscribeAnnotator();
//
// Handlebars Register Library
//
Handlebars.registerHelper('deparagraph', function(txt) {
var dpg = txt.replace("<p>", "").replace("</p>", "");
return dpg;
});
//Compile templates
this.HTMLTEMPLATES = CatchSources.HTMLTEMPLATES(this.options.imageUrlRoot);
this.TEMPLATES = {};
this._compileTemplates();
},
//
// GLOBAL UTILITIES
//
refreshCatch: function(newInstance) {
var mediaType = this.options.media || 'text',
annotationItems = [],
index = 0,
annotations = this.annotator.plugins['Store'].annotations || [],
el = $("#mainCatch.annotationListContainer"),
self = this,
newInstance = newInstance || false;
annotations.forEach(function(annotation) {
var isMedia = annotation.media==self.options.media,
isUser = (typeof self.options.userId!='undefined' && self.options.userId!='' && self.options.userId!=null)?
self.options.userId == annotation.user.id:true,
isInList = newInstance?false:self._isInList(annotation);
if (isMedia && isUser && !isInList){
var item = jQuery.extend(true, {}, annotation);
self._formatCatch(item);
//Authorized
var permissions = self.annotator.plugins.Permissions,
authorized = permissions.options.userAuthorize('delete', annotation,permissions.user),
updateAuthorized = permissions.options.userAuthorize('update', annotation,permissions.user);
item.authToDeleteButton = authorized;
item.authToEditButton = updateAuthorized;
item.hasReplies = (item.totalComments > 0);
var html = self.TEMPLATES.annotationItem({
item: item,
id: item.id,
evenOrOdd: index % 2 ? "odd" : "even",
openOrClosed: "closed",
annotationRow: self.TEMPLATES.annotationRow(item),
annotationDetail: (mediaType === "video") ? self.TEMPLATES.videoAnnotationDetail(item):self.TEMPLATES.annotationDetail(item),
});
index++;
annotationItems.push(html);
}
});
if (newInstance){
var videoFormat = (mediaType === "video") ? true:false;
el.html(self.TEMPLATES.annotationList({
annotationItems: annotationItems,
videoFormat: videoFormat,
PublicPrivate: self.options.showPublicPrivate?self.TEMPLATES.annotationPublicPrivate():'',
MediaSelector: self.options.showMediaSelector?self.TEMPLATES.annotationMediaSelector():'',
}));
}else{
var list = $("#mainCatch .annotationList");
annotationItems.forEach(function(annotation) {
list.append($(annotation));
});
}
//Set SelButtons to media
var SelButtons = el.find('.annotationList li').removeClass('active'); //reset
for (var index=0;index<SelButtons.length;index++) {
var span = $(SelButtons[index]);
if (span.attr("media")==this.options.media) $(SelButtons[index]).addClass('active');
}
//Set PublicPrivate
var PublicPrivateButtons = el.find('.annotationListButtons .PublicPrivate').removeClass('active'); //reset
for (var index=0;index<PublicPrivateButtons.length;index++) {
var span = $(PublicPrivateButtons[index]).find('span'),
isUser = (typeof self.options.userId!='undefined' && self.options.userId!='' && self.options.userId!=null);
if (isUser && span.html()=="myNotes") $(PublicPrivateButtons[index]).addClass('active');
else if (!isUser && span.html()=="public") $(PublicPrivateButtons[index]).addClass('active');
}
//reset all old events
el.off();
//Bind functions
var openAnnotationItem = this.__bind(this._openAnnotationItem,this),
closeAnnotationItem = this.__bind(this._closeAnnotationItem,this),
onGeolocationClick = this.__bind(this._onGeolocationClick,this),
onPlaySelectionClick = this.__bind(this._onPlaySelectionClick,this),
onShareControlsClick = this.__bind(this._onShareControlsClick,this),
onSelectionButtonClick = this.__bind(this._onSelectionButtonClick,this),
onPublicPrivateButtonClick = this.__bind(this._onPublicPrivateButtonClick,this),
onQuoteMediaButton = this.__bind(this._onQuoteMediaButton,this),
onControlRepliesClick = this.__bind(this._onControlRepliesClick,this),
onMoreButtonClick = this.__bind(this._onMoreButtonClick,this),
onSearchButtonClick = this.__bind(this._onSearchButtonClick, this),
onDeleteReplyButtonClick = this.__bind(this._onDeleteReplyButtonClick,this);
//Open Button
el.on("click", ".annotationItem .annotationRow", openAnnotationItem);
//Close Button
el.on("click", ".annotationItem .detailHeader", closeAnnotationItem);
//Geolocation button
el.on("click",".annotationItem .detailHeader .geolocationIcon img", onGeolocationClick);
//controlPanel buttons
el.on("click",".annotationItem .annotationDetail .controlPanel", onShareControlsClick);
//VIDEO
if (this.options.media=='video') {
//PlaySelection button
el.on("click",".annotationItem .annotationDetail .playMediaButton", onPlaySelectionClick);
}
//TEXT
if (this.options.media=='text') {
//PlaySelection button
el.on("click",".annotationItem .annotationDetail .quote", onQuoteMediaButton);
}
//controlReplies
el.on("click",".annotationItem .controlReplies", onControlRepliesClick);
//Selection Buttons
el.on("click",".annotationList li", onSelectionButtonClick);
//PublicPrivate Buttons
el.on("click",".annotationListButtons .PublicPrivate", onPublicPrivateButtonClick);
//More Button
el.on("click",".annotationListButtons .moreButtonCatch", onMoreButtonClick);
//Search Button
el.on("click",".searchbox .search-icon", onSearchButtonClick);
//Delete Reply Button
el.on("click", ".replies .replyItem .deleteReply", onDeleteReplyButtonClick);
},
changeMedia: function(media) {
var media = media || 'text';
this.options.media = media;
this._refresh();
this.refreshCatch(true);
this.checkTotAnnotations();
},
changeUserId: function(userId) {
var userId = userId || '';
this.options.userId = userId;
this._refresh();
this.refreshCatch(true);
this.checkTotAnnotations();
},
loadAnnotations: function() {
var annotator = this.annotator,
loadFromSearch = annotator.plugins.Store.options.loadFromSearch,
loadedAn = this.element.find('.annotationList .annotationItem').length;
loadedAn = typeof loadedAn!='undefined' ?loadedAn:0;
loadFromSearch.limit = this.options.pagination;
loadFromSearch.offset = loadedAn;
loadFromSearch.media = this.options.media;
loadFromSearch.userid = this.options.userId;
// Dani had this for some reason. we can't remember. but if something
// breaks, uncomment next line.
//annotator.plugins['Store'].loadAnnotationsFromSearch(loadFromSearch);
//Make sure to be openned all annotations for this pagination
loadFromSearch.limit = this.options.pagination+loadedAn;
loadFromSearch.offset = 0;
annotator.plugins['Store'].loadAnnotationsFromSearch(loadFromSearch);
//text loading annotations
var moreBut = this.element.find('.annotationListButtons .moreButtonCatch');
moreBut.html('Please wait, loading...');
},
//check whether is necessary more button or not
checkTotAnnotations: function(){
var annotator = this.annotator,
loadFromSearch = annotator.plugins.Store.options.loadFromSearch,
oldLimit = loadFromSearch.limit,
oldOffset = loadFromSearch.offset,
self = this;
loadFromSearch.limit = 0;
loadFromSearch.offset = 0;
loadFromSearch.media = this.options.media;
loadFromSearch.userid = this.options.userId;
var onSuccess = function(response){
var totAn = self.element.find('.annotationList .annotationItem').length,
maxAn = response.total,
moreBut = self.element.find('.annotationListButtons .moreButtonCatch');
if (totAn<maxAn && totAn > 0)
moreBut.show();
else
moreBut.hide();
}
var obj = loadFromSearch,
action = 'search';
var id, options, url;
id = obj && obj.id;
url = annotator.plugins['Store']._urlFor(action, id);
options = annotator.plugins['Store']._apiRequestOptions(action, obj, onSuccess);
$.ajax(url, options);
//reset values
loadFromSearch.limit = oldLimit;
loadFromSearch.offset = oldOffset;
//set More button text
var moreBut = this.element.find('.annotationListButtons .moreButtonCatch');
moreBut.html('More');
setTimeout();
},
//
// LOCAL UTILITIES
//
_subscribeAnnotator: function(){
var self = this,
annotator = this.annotator;
//Subscribe to Annotator changes
annotator.subscribe("annotationsLoaded", function (annotations){
self.refreshCatch(self.clean);
//hide or show more button
self.checkTotAnnotations();
});
annotator.subscribe("annotationUpdated", function (annotation){
self.refreshCatch(true);
self.checkTotAnnotations();
});
annotator.subscribe("annotationDeleted", function (annotation){
var annotations = annotator.plugins['Store'].annotations,
tot = typeof annotations !='undefined'?annotations.length:0,
attempts = 0; // max 100
//This is to watch the annotations object, to see when is deleted the annotation
var ischanged = function(){
var new_tot = annotator.plugins['Store'].annotations.length;
if (attempts<100)
setTimeout(function(){
if (new_tot != tot){
self.refreshCatch(true);
self.checkTotAnnotations();
}else{
attempts++;
ischanged();
}
},100); //wait for the change in the annotations
};
ischanged();
});
annotator.subscribe("annotationCreated", function (annotation){
var attempts = 0; // max 100
//wait to get an annotation id
var ischanged = function(){
if (attempts<100)
setTimeout(function(){
if (typeof annotation.id!='undefined'){
self.refreshCatch();
if (typeof annotation.parent != 'undefined' && annotation.parent != '0'){
var replies = $("[annotationid="+annotation.parent+"]").find(".controlReplies .hideReplies");
replies.show();
replies.click();
replies.click();
}
}else{
attempts++;
ischanged();
}
},100); //wait for annotation id
};
ischanged();
});
},
__bind: function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
_compileTemplates: function() {
var self = this;
//Change the html tags to functions
this.TEMPLATENAMES.forEach(function(templateName) {
self.TEMPLATES[templateName] = Handlebars.compile(self.HTMLTEMPLATES[templateName]);
});
},
_isVideoJS: function (an){
var annotator = this.annotator,
rt = an.rangeTime,
isOpenVideojs = (typeof annotator.mplayer != 'undefined'),
isVideo = (typeof an.media!='undefined' && an.media=='video'),
isNumber = (typeof rt!='undefined' && !isNaN(parseFloat(rt.start)) && isFinite(rt.start) && !isNaN(parseFloat(rt.end)) && isFinite(rt.end));
return (isOpenVideojs && isVideo && isNumber);
},
_isInList: function (an){
var annotator = this.annotator,
isInList = false,
list = $('#mainCatch .annotationList .annotationRow.item');
for (_i = 0, _len = list.length; _i < _len; _i++) {
if ($(list[_i]).parent().attr('annotationid') == an.id)
isInList = true;
}
return isInList;
},
_formatCatch: function(item) {
var item = item || {};
if(this._isVideoJS(item)){
//format time
item.rangeTime.start= typeof vjs!='undefined'?vjs.formatTime(item.rangeTime.start):item.rangeTime.start;
item.rangeTime.end= typeof vjs!='undefined'?vjs.formatTime(item.rangeTime.end):item.rangeTime.end;
}
//format date
if(typeof item.updated!='undefined' && typeof createDateFromISO8601!='undefined')
item.updated = createDateFromISO8601(item.updated);
//format geolocation
if(typeof item.geolocation!='undefined' && (typeof item.geolocation.latitude=='undefined'||item.geolocation.latitude==''))
delete item.geolocation;
/* NEW VARIABLES */
//set plainText for Catch
item.plainText = item.text.replace(/&(lt|gt);/g, function (strMatch, p1){
return (p1 == "lt")? "<" : ">";
});//Change to < and > tags
item.plainText = item.plainText.replace(/<\/?[^>]+(>|$)/g, "").replace('&nbsp;',''); //remove all the html tags
//Flags
if(!this.options.flags && typeof item.tags != 'undefined' && item.tags.length > 0){
for(var len=item.tags.length, index = len-1; index >= 0; --index){
var currTag = item.tags[index];
if(currTag.indexOf("flagged-") != -1){
item.tags.splice(index);
}
}
}
},
//
// EVENT HANDLER
//
_openAnnotationItem: function(evt) {
var isClosed = $(evt.currentTarget).closest(".annotationItem").hasClass("closed");
if (isClosed) {
$(evt.currentTarget).closest(".annotationItem").removeClass("closed").addClass("open");
//Add Share button
var shareControl = $(evt.currentTarget).closest(".annotationItem").find('.annotationDetail .controlPanel:first'),
annotator = this.annotator,
idAnnotation = shareControl.parent().find('.idAnnotation').html(),
uri = shareControl.parent().find('.uri').html();
//remove the last share container
shareControl.find('.share-container-annotator').remove();
shareControl.append(annotator.plugins.Share.buildHTMLShareButton("",idAnnotation));
//Set actions button
annotator.plugins.Share.buttonsActions(shareControl[0],1,uri);
} else {
$(evt.currentTarget).closest(".annotationItem").removeClass("open").addClass("closed");
}
},
_closeAnnotationItem: function(evt) {
var existEvent = typeof evt.target!='undefined' && typeof evt.target.localName!='undefined';
if(existEvent && evt.target.parentNode.className!='geolocationIcon'){
this._openAnnotationItem(evt);
}
},
_onGeolocationClick: function(evt) {
var latitude = $(evt.target).parent().find('.latitude').html(),
longitude = $(evt.target).parent().find('.longitude').html();
var imgSrc = '<img src="http://maps.googleapis.com/maps/api/staticmap?center='+latitude+','+longitude+'&zoom=14&size=500x500&sensor=false&markers=color:green%7Clabel:G%7C'+latitude+','+longitude+'">';
$(evt.target).parents('.detailHeader:first').find('#myLocationMap .map').html(imgSrc);
},
_onPlaySelectionClick: function(evt) {
var id = $(evt.target).find('.idAnnotation').html(),
uri = $(evt.target).find('.uri').html();
container = $(evt.target).find('.container').html();
if(this.options.externalLink){
uri += (uri.indexOf('?') >= 0)?'&ovaId='+id:'?ovaId='+id;
location.href = uri;
}else{
var isContainer = typeof this.annotator.an!='undefined' && typeof this.annotator.an[container]!='undefined',
ovaInstance = isContainer? this.annotator.an[container]:null;
if(ovaInstance!=null){
var allannotations = this.annotator.plugins['Store'].annotations,
ovaId = id,
player = ovaInstance.player;
for (var item in allannotations) {
var an = allannotations[item];
if (typeof an.id!='undefined' && an.id == ovaId){//this is the annotation
if(this._isVideoJS(an)){//It is a video
if (player.id_ == an.target.container && player.tech.options_.source.src == an.target.src){
var anFound = an;
var playFunction = function(){
//Fix problem with youtube videos in the first play. The plugin don't have this trigger
if (player.techName == 'Youtube'){
var startAPI = function(){
ovaInstance.showAnnotation(anFound);
}
if (ovaInstance.loaded)
startAPI();
else
player.one('loadedRangeSlider', startAPI);//show Annotations once the RangeSlider is loaded
}else{
ovaInstance.showAnnotation(anFound);
}
$('html,body').animate({
scrollTop: $("#"+player.id_).offset().top},
'slow');
};
if (player.paused()) {
player.play();
player.one('playing',playFunction);
}else{
playFunction();
}
return false;//this will stop the code to not set a new player.one.
}
}
}
}
}
}
},
_onQuoteMediaButton: function(evt){
var quote = $(evt.target).hasClass('quote')?$(evt.target):$(evt.target).parents('.quote:first'),
id = quote.find('.idAnnotation').html(),
uri = quote.find('.uri').html();
if (typeof id=='undefined' || id==''){
this.refreshCatch();
this.checkTotAnnotations();
id = quote.find('.idAnnotation').html();
//clickPlaySelection(evt);
}
if(this.options.externalLink){
uri += (uri.indexOf('?') >= 0)?'&ovaId='+id:'?ovaId='+id;
location.href = uri;
}else{
var allannotations = this.annotator.plugins['Store'].annotations,
ovaId = id;
for (var item in allannotations) {
var an = allannotations[item];
if (typeof an.id!='undefined' && an.id == ovaId){//this is the annotation
if(!this._isVideoJS(an)){
var hasRanges = typeof an.ranges!='undefined' && typeof an.ranges[0] !='undefined',
startOffset = hasRanges?an.ranges[0].startOffset:'',
endOffset = hasRanges?an.ranges[0].endOffset:'';
if(typeof startOffset!='undefined' && typeof endOffset!='undefined'){
$(an.highlights).parent().find('.annotator-hl').removeClass('api');
//change the color
$(an.highlights).addClass('api');
//animate to the annotation
$('html,body').animate({
scrollTop: $(an.highlights[0]).offset().top},
'slow');
}
}
}
}
}
},
_refreshReplies: function(evt){
var item = $(evt.target).parents('.annotationItem:first'),
anId = item.attr('annotationId');
var replyElem = $(evt.target).parents('.annotationItem:first').find('.replies');
var annotator = this.annotator,
loadFromSearchURI = annotator.plugins.Store.options.loadFromSearch.uri,
self = this,
action='search',
loadFromSearch={
limit:-1,
parentid:anId,
uri:loadFromSearchURI,
},
onSuccess=function(data){
if (data == null) data = {};
annotations = data.rows || [];
var _i,_len;
for (_i = 0, _len = annotations.length; _i < _len; _i++) {
self._formatCatch(annotations[_i]);
}
replyElem.html(self.TEMPLATES.annotationReply({
annotations: annotations
}));
var replyItems = $('.replies .replyItem');
if(typeof replyItems != 'undefined' && replyItems.length > 0){
annotations.forEach(function(ann){
replyItems.each(function(item){
var id = $(replyItems[item]).attr('annotationid');
if(id == ann.id){
var perm = self.annotator.plugins.Permissions;
if(!perm.options.userAuthorize('delete',ann,perm.user)){
$(replyItems[item]).find('.deleteReply').remove();
}else{
$(replyItems[item]).data('annotation',ann);
}
}
});
});
}
};
var id, options, request, url,
store = this.annotator.plugins.Store;
id = loadFromSearch && loadFromSearch.id;
url = store._urlFor(action, id);
options = store._apiRequestOptions(action, loadFromSearch, onSuccess);
request = $.ajax(url, options);
request._id = id;
request._action = action;
},
_onControlRepliesClick: function(evt){
var action = $(evt.target)[0].className;
if(action=='newReply'){
var item = $(evt.target).parents('.annotationItem:first'),
id = item.attr('annotationId');
//Pre-show Adder
this.annotator.adder.show();
//Get elements
var replyElem = $(evt.target).parents('.annotationItem:first').find('.annotationDetail'),
adder =this.annotator.adder,
wrapper = $('.annotator-wrapper');
//Calculate Editor position
var positionLeft = videojs.findPosition($(evt.target).parent().find('.newReply')[0]),
positionAnnotator = videojs.findPosition(wrapper[0]),
positionAdder = {};
positionAdder.left = positionLeft.left - positionAnnotator.left;
positionAdder.top = positionLeft.top + 20 - positionAnnotator.top;
adder.css(positionAdder);
//Open a new annotator dialog
this.annotator.onAdderClick();
//Set vertical editor
this.annotator.editor.resetOrientation();
this.annotator.editor.invertY();
this.annotator.editor.element.find('.annotator-widget').css('min-width',replyElem.css('width'));
//set parent
var parentValue = $(this.annotator.editor.element).find(".reply-item span.parent-annotation");
parentValue.html(id);
var self = this;
}else if(action=='hideReplies'){
var oldAction = $(evt.target).html();
if (oldAction=='Show Replies'){
$(evt.target).html('Hide Replies');
}else{
$(evt.target).html('Show Replies');
var replyElem = $(evt.target).parents('.annotationItem:first').find('.replies');
replyElem.html('');
return false;
}
//search
this._refreshReplies(evt);
}else if(action=='deleteAnnotation'){
if(confirm("Would you like to delete the annotation?")){
var annotator = this.annotator,
item = $(evt.target).parents('.annotationItem:first'),
id = item.attr('annotationId'),
store = annotator.plugins.Store,
annotations = store.annotations,
permissions = annotator.plugins.Permissions;
var annotation;
annotations.forEach(function(ann){
if(ann.id == id)
annotation = ann;
});
var authorized = permissions.options.userAuthorize('delete', annotation,permissions.user);
if(authorized)
annotator.deleteAnnotation(annotation);
}
}else if(action=='editAnnotation'){
var annotator = this.annotator,
item = $(evt.target).parents('.annotationItem:first'),
id = item.attr('annotationId'),
store = annotator.plugins.Store,
annotations = store.annotations,
permissions = annotator.plugins.Permissions;
var annotation;
annotations.forEach(function(ann){
if(ann.id == id)
annotation = ann;
});
var authorized = permissions.options.userAuthorize('update', annotation,permissions.user);
if(authorized){
//Get elements
var wrapper = $('.annotator-wrapper');
//Calculate Editor position
var positionLeft = videojs.findPosition($(evt.target).parent().find('.editAnnotation')[0]),
positionAnnotator = videojs.findPosition(wrapper[0]),
positionAdder = {};
positionAdder.left = positionLeft.left - positionAnnotator.left;
positionAdder.top = positionLeft.top + 20 - positionAnnotator.top;
var cleanup, offset, update,
_this = this.annotator;
offset = positionAdder;
update = function() {
cleanup();
return _this.updateAnnotation(annotation);
};
cleanup = function() {
_this.unsubscribe('annotationEditorHidden', cleanup);
return _this.unsubscribe('annotationEditorSubmit', update);
};
this.annotator.subscribe('annotationEditorHidden', cleanup);
this.annotator.subscribe('annotationEditorSubmit', update);
this.annotator.viewer.hide();
this.annotator.showEditor(annotation, offset);
}
}
},
_onShareControlsClick: function(evt) {
var action = $(evt.target)[0].className;
//TODO- Decide whether privacy or group button
if(action=='privacy_button'){
//location.href = "index.php?r=video/privacy&id="+videoId;
}else if(action=='groups_button'){
alert("Coming soon...");
}else if(action=='reply_button'){
var item = $(evt.target).parents('.annotationItem:first'),
id = item.attr('annotationId');
//New annotation
var an = this.annotator.setupAnnotation(this.annotator.createAnnotation());
an.text="010";
an.parent = id;
//Store the annotation
//this.annotator.plugins.Store.annotationCreated(an);
}else if(action=='share_button'){
}
},
_onPublicPrivateButtonClick: function(evt) {
var action = $(evt.target).find('span'),
userId = '';
//Get userI
userId = (action.html()=="myNotes")? this.annotator.plugins.Permissions.user.id : '';
//Change userid and refresh
this.changeUserId(userId);
},
_onSelectionButtonClick: function(evt){
var but = $(evt.target),
action = but.attr('media');
//Get action
if (action.length<=0) action="text"; //By default
//Change media and refresh
this.changeMedia(action);
},
_onMoreButtonClick: function(evt){
this.clean = false;
var moreBut = this.element.find('.annotationListButtons .moreButtonCatch'),
isLoading = moreBut.html()=='More'?false:true;
if(!isLoading)
this.loadAnnotations();
},
_refresh:function(searchtype,searchInput){
var searchtype = searchtype || "",
searchInput = searchInput || "";
this.clean = true;
this._clearAnnotator();
var annotator = this.annotator,
loadFromSearch = annotator.plugins.Store.options.loadFromSearch;
loadFromSearch.limit = this.options.pagination;
loadFromSearch.offset = 0;
loadFromSearch.media = this.options.media;
loadFromSearch.userid = this.options.userId;
loadFromSearch.username = "";
loadFromSearch.tag = "";
loadFromSearch.text = "";
if (searchtype == "Users"){
loadFromSearch.username = searchInput;
} else if(searchtype == "Tags"){
loadFromSearch.tag = searchInput;
} else{
loadFromSearch.text = searchInput;
}
annotator.plugins['Store'].loadAnnotationsFromSearch(loadFromSearch);
},
_onSearchButtonClick: function(evt){
var searchtype = this.element.find('.searchbox .dropdown-list').val();
var searchInput = this.element.find('.searchbox input').val();
this._refresh(searchtype,searchInput);
},
_clearAnnotator: function(){
var annotator = this.annotator,
store = annotator.plugins.Store,
annotations = store.annotations.slice();
annotations.forEach(function(ann){
var child, h, _i, _len, _ref;
if (ann.highlights != null) {
_ref = ann.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);
}
}
store.unregisterAnnotation(ann);
});
},
_onDeleteReplyButtonClick : function(evt){
var annotator = this.annotator,
item = $(evt.target).parents('.replyItem:first'),
id = item.attr('annotationid'),
permissions = annotator.plugins.Permissions,
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();
}
}
}
}

View File

@@ -0,0 +1,2595 @@
/*!
handlebars v1.1.2
Copyright (C) 2011 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@license
*/
var Handlebars = (function() {
// handlebars/safe-string.js
var __module4__ = (function() {
"use strict";
var __exports__;
// Build out our basic SafeString type
function SafeString(string) {
this.string = string;
}
SafeString.prototype.toString = function() {
return "" + this.string;
};
__exports__ = SafeString;
return __exports__;
})();
// handlebars/utils.js
var __module3__ = (function(__dependency1__) {
"use strict";
var __exports__ = {};
var SafeString = __dependency1__;
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
function escapeChar(chr) {
return escape[chr] || "&amp;";
}
function extend(obj, value) {
for(var key in value) {
if(value.hasOwnProperty(key)) {
obj[key] = value[key];
}
}
}
__exports__.extend = extend;var toString = Object.prototype.toString;
__exports__.toString = toString;
// Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
var isFunction = function(value) {
return typeof value === 'function';
};
// fallback for older versions of Chrome and Safari
if (isFunction(/x/)) {
isFunction = function(value) {
return typeof value === 'function' && toString.call(value) === '[object Function]';
};
}
var isFunction;
__exports__.isFunction = isFunction;
var isArray = Array.isArray || function(value) {
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
};
__exports__.isArray = isArray;
function escapeExpression(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof SafeString) {
return string.toString();
} else if (!string && string !== 0) {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = "" + string;
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
}
__exports__.escapeExpression = escapeExpression;function isEmpty(value) {
if (!value && value !== 0) {
return true;
} else if (isArray(value) && value.length === 0) {
return true;
} else {
return false;
}
}
__exports__.isEmpty = isEmpty;
return __exports__;
})(__module4__);
// handlebars/exception.js
var __module5__ = (function() {
"use strict";
var __exports__;
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
function Exception(/* message */) {
var tmp = Error.prototype.constructor.apply(this, arguments);
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (var idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
}
Exception.prototype = new Error();
__exports__ = Exception;
return __exports__;
})();
// handlebars/base.js
var __module2__ = (function(__dependency1__, __dependency2__) {
"use strict";
var __exports__ = {};
/*globals Exception, Utils */
var Utils = __dependency1__;
var Exception = __dependency2__;
var VERSION = "1.1.2";
__exports__.VERSION = VERSION;var COMPILER_REVISION = 4;
__exports__.COMPILER_REVISION = COMPILER_REVISION;
var REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '>= 1.0.0'
};
__exports__.REVISION_CHANGES = REVISION_CHANGES;
var isArray = Utils.isArray,
isFunction = Utils.isFunction,
toString = Utils.toString,
objectType = '[object Object]';
function HandlebarsEnvironment(helpers, partials) {
this.helpers = helpers || {};
this.partials = partials || {};
registerDefaultHelpers(this);
}
__exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
constructor: HandlebarsEnvironment,
logger: logger,
log: log,
registerHelper: function(name, fn, inverse) {
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
},
registerPartial: function(name, str) {
if (toString.call(name) === objectType) {
Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
}
};
function registerDefaultHelpers(instance) {
instance.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Missing helper: '" + arg + "'");
}
});
instance.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
if (isFunction(context)) { context = context.call(this); }
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if (isArray(context)) {
if(context.length > 0) {
return instance.helpers.each(context, options);
} else {
return inverse(this);
}
} else {
return fn(context);
}
});
instance.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
if(context && typeof context === 'object') {
if (isArray(context)) {
for(var j = context.length; i<j; i++) {
if (data) {
data.index = i;
data.first = (i === 0)
data.last = (i === (context.length-1));
}
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) { data.key = key; }
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
instance.registerHelper('if', function(conditional, options) {
if (isFunction(conditional)) { conditional = conditional.call(this); }
// Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
instance.registerHelper('unless', function(conditional, options) {
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
});
instance.registerHelper('with', function(context, options) {
if (isFunction(context)) { context = context.call(this); }
if (!Utils.isEmpty(context)) return options.fn(context);
});
instance.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
instance.log(level, context);
});
}
var logger = {
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
// State enum
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
level: 3,
// can be overridden in the host environment
log: function(level, obj) {
if (logger.level <= level) {
var method = logger.methodMap[level];
if (typeof console !== 'undefined' && console[method]) {
console[method].call(console, obj);
}
}
}
};
__exports__.logger = logger;
function log(level, obj) { logger.log(level, obj); }
__exports__.log = log;var createFrame = function(object) {
var obj = {};
Utils.extend(obj, object);
return obj;
};
__exports__.createFrame = createFrame;
return __exports__;
})(__module3__, __module5__);
// handlebars/runtime.js
var __module6__ = (function(__dependency1__, __dependency2__, __dependency3__) {
"use strict";
var __exports__ = {};
/*global Utils */
var Utils = __dependency1__;
var Exception = __dependency2__;
var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;
function checkRevision(compilerInfo) {
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Error("Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Error("Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
}
}
}
// TODO: Remove this line and break up compilePartial
function template(templateSpec, env) {
if (!env) {
throw new Error("No environment passed to template");
}
var invokePartialWrapper;
if (env.compile) {
invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
// TODO : Check this for all inputs and the options handling (partial flag, etc). This feels
// like there should be a common exec path
var result = invokePartial.apply(this, arguments);
if (result) { return result; }
var options = { helpers: helpers, partials: partials, data: data };
partials[name] = env.compile(partial, { data: data !== undefined }, env);
return partials[name](context, options);
};
} else {
invokePartialWrapper = function(partial, name /* , context, helpers, partials, data */) {
var result = invokePartial.apply(this, arguments);
if (result) { return result; }
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
};
}
// Just add water
var container = {
escapeExpression: Utils.escapeExpression,
invokePartial: invokePartialWrapper,
programs: [],
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
programWrapper = program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = program(i, fn);
}
return programWrapper;
},
merge: function(param, common) {
var ret = param || common;
if (param && common && (param !== common)) {
ret = {};
Utils.extend(ret, common);
Utils.extend(ret, param);
}
return ret;
},
programWithDepth: programWithDepth,
noop: noop,
compilerInfo: null
};
return function(context, options) {
options = options || {};
var namespace = options.partial ? options : env,
helpers,
partials;
if (!options.partial) {
helpers = options.helpers;
partials = options.partials;
}
var result = templateSpec.call(
container,
namespace, context,
helpers,
partials,
options.data);
if (!options.partial) {
checkRevision(container.compilerInfo);
}
return result;
};
}
__exports__.template = template;function programWithDepth(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
var prog = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
prog.program = i;
prog.depth = args.length;
return prog;
}
__exports__.programWithDepth = programWithDepth;function program(i, fn, data) {
var prog = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
prog.program = i;
prog.depth = 0;
return prog;
}
__exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data) {
var options = { partial: true, helpers: helpers, partials: partials, data: data };
if(partial === undefined) {
throw new Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, options);
}
}
__exports__.invokePartial = invokePartial;function noop() { return ""; }
__exports__.noop = noop;
return __exports__;
})(__module3__, __module5__, __module2__);
// handlebars.runtime.js
var __module1__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
"use strict";
var __exports__;
var base = __dependency1__;
// Each of these augment the Handlebars object. No need to setup here.
// (This is done to easily share code between commonjs and browse envs)
var SafeString = __dependency2__;
var Exception = __dependency3__;
var Utils = __dependency4__;
var runtime = __dependency5__;
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
var create = function() {
var hb = new base.HandlebarsEnvironment();
Utils.extend(hb, base);
hb.SafeString = SafeString;
hb.Exception = Exception;
hb.Utils = Utils;
hb.VM = runtime;
hb.template = function(spec) {
return runtime.template(spec, hb);
};
return hb;
};
var Handlebars = create();
Handlebars.create = create;
__exports__ = Handlebars;
return __exports__;
})(__module2__, __module4__, __module5__, __module3__, __module6__);
// handlebars/compiler/ast.js
var __module7__ = (function(__dependency1__) {
"use strict";
var __exports__ = {};
var Exception = __dependency1__;
function ProgramNode(statements, inverseStrip, inverse) {
this.type = "program";
this.statements = statements;
this.strip = {};
if(inverse) {
this.inverse = new ProgramNode(inverse, inverseStrip);
this.strip.right = inverseStrip.left;
} else if (inverseStrip) {
this.strip.left = inverseStrip.right;
}
}
__exports__.ProgramNode = ProgramNode;function MustacheNode(rawParams, hash, open, strip) {
this.type = "mustache";
this.hash = hash;
this.strip = strip;
var escapeFlag = open[3] || open[2];
this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
var id = this.id = rawParams[0];
var params = this.params = rawParams.slice(1);
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
var eligibleHelper = this.eligibleHelper = id.isSimple;
// a mustache is definitely a helper if:
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
this.isHelper = eligibleHelper && (params.length || hash);
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
}
__exports__.MustacheNode = MustacheNode;function PartialNode(partialName, context, strip) {
this.type = "partial";
this.partialName = partialName;
this.context = context;
this.strip = strip;
}
__exports__.PartialNode = PartialNode;function BlockNode(mustache, program, inverse, close) {
if(mustache.id.original !== close.path.original) {
throw new Exception(mustache.id.original + " doesn't match " + close.path.original);
}
this.type = "block";
this.mustache = mustache;
this.program = program;
this.inverse = inverse;
this.strip = {
left: mustache.strip.left,
right: close.strip.right
};
(program || inverse).strip.left = mustache.strip.right;
(inverse || program).strip.right = close.strip.left;
if (inverse && !program) {
this.isInverse = true;
}
}
__exports__.BlockNode = BlockNode;function ContentNode(string) {
this.type = "content";
this.string = string;
}
__exports__.ContentNode = ContentNode;function HashNode(pairs) {
this.type = "hash";
this.pairs = pairs;
}
__exports__.HashNode = HashNode;function IdNode(parts) {
this.type = "ID";
var original = "",
dig = [],
depth = 0;
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i].part;
original += (parts[i].separator || '') + part;
if (part === ".." || part === "." || part === "this") {
if (dig.length > 0) { throw new Exception("Invalid path: " + original); }
else if (part === "..") { depth++; }
else { this.isScoped = true; }
}
else { dig.push(part); }
}
this.original = original;
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
this.stringModeValue = this.string;
}
__exports__.IdNode = IdNode;function PartialNameNode(name) {
this.type = "PARTIAL_NAME";
this.name = name.original;
}
__exports__.PartialNameNode = PartialNameNode;function DataNode(id) {
this.type = "DATA";
this.id = id;
}
__exports__.DataNode = DataNode;function StringNode(string) {
this.type = "STRING";
this.original =
this.string =
this.stringModeValue = string;
}
__exports__.StringNode = StringNode;function IntegerNode(integer) {
this.type = "INTEGER";
this.original =
this.integer = integer;
this.stringModeValue = Number(integer);
}
__exports__.IntegerNode = IntegerNode;function BooleanNode(bool) {
this.type = "BOOLEAN";
this.bool = bool;
this.stringModeValue = bool === "true";
}
__exports__.BooleanNode = BooleanNode;function CommentNode(comment) {
this.type = "comment";
this.comment = comment;
}
__exports__.CommentNode = CommentNode;
return __exports__;
})(__module5__);
// handlebars/compiler/parser.js
var __module9__ = (function() {
"use strict";
var __exports__;
/* Jison generated parser */
var handlebars = (function(){
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"root":3,"statements":4,"EOF":5,"program":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"inMustache_repetition0":28,"inMustache_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash":35,"hash_repetition_plus0":36,"hashSegment":37,"ID":38,"EQUALS":39,"DATA":40,"pathSegments":41,"SEP":42,"$accept":0,"$end":1},
terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",38:"ID",39:"EQUALS",40:"DATA",42:"SEP"},
productions_: [0,[3,2],[3,1],[6,2],[6,3],[6,2],[6,1],[6,1],[6,0],[4,1],[4,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[7,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[35,1],[37,3],[26,1],[26,1],[26,1],[30,2],[21,1],[41,3],[41,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[36,1],[36,2]],
performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
var $0 = $$.length - 1;
switch (yystate) {
case 1: return new yy.ProgramNode($$[$0-1]);
break;
case 2: return new yy.ProgramNode([]);
break;
case 3:this.$ = new yy.ProgramNode([], $$[$0-1], $$[$0]);
break;
case 4:this.$ = new yy.ProgramNode($$[$0-2], $$[$0-1], $$[$0]);
break;
case 5:this.$ = new yy.ProgramNode($$[$0-1], $$[$0], []);
break;
case 6:this.$ = new yy.ProgramNode($$[$0]);
break;
case 7:this.$ = new yy.ProgramNode([]);
break;
case 8:this.$ = new yy.ProgramNode([]);
break;
case 9:this.$ = [$$[$0]];
break;
case 10: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
break;
case 11:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]);
break;
case 12:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]);
break;
case 13:this.$ = $$[$0];
break;
case 14:this.$ = $$[$0];
break;
case 15:this.$ = new yy.ContentNode($$[$0]);
break;
case 16:this.$ = new yy.CommentNode($$[$0]);
break;
case 17:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0]));
break;
case 18:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0]));
break;
case 19:this.$ = {path: $$[$0-1], strip: stripFlags($$[$0-2], $$[$0])};
break;
case 20:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0]));
break;
case 21:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0]));
break;
case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1], stripFlags($$[$0-3], $$[$0]));
break;
case 23:this.$ = stripFlags($$[$0-1], $$[$0]);
break;
case 24:this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]];
break;
case 25:this.$ = [[$$[$0]], null];
break;
case 26:this.$ = $$[$0];
break;
case 27:this.$ = new yy.StringNode($$[$0]);
break;
case 28:this.$ = new yy.IntegerNode($$[$0]);
break;
case 29:this.$ = new yy.BooleanNode($$[$0]);
break;
case 30:this.$ = $$[$0];
break;
case 31:this.$ = new yy.HashNode($$[$0]);
break;
case 32:this.$ = [$$[$0-2], $$[$0]];
break;
case 33:this.$ = new yy.PartialNameNode($$[$0]);
break;
case 34:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0]));
break;
case 35:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0]));
break;
case 36:this.$ = new yy.DataNode($$[$0]);
break;
case 37:this.$ = new yy.IdNode($$[$0]);
break;
case 38: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2];
break;
case 39:this.$ = [{part: $$[$0]}];
break;
case 42:this.$ = [];
break;
case 43:$$[$0-1].push($$[$0]);
break;
case 46:this.$ = [$$[$0]];
break;
case 47:$$[$0-1].push($$[$0]);
break;
}
},
table: [{3:1,4:2,5:[1,3],8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[3]},{5:[1,16],8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[2,2]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{4:20,6:18,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{4:20,6:22,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{17:23,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:29,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:30,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:31,21:24,30:25,38:[1,28],40:[1,27],41:26},{21:33,26:32,32:[1,34],33:[1,35],38:[1,28],41:26},{1:[2,1]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{10:36,20:[1,37]},{4:38,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,7],22:[1,13],23:[1,14],25:[1,15]},{7:39,8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,6],22:[1,13],23:[1,14],25:[1,15]},{17:23,18:[1,40],21:24,30:25,38:[1,28],40:[1,27],41:26},{10:41,20:[1,37]},{18:[1,42]},{18:[2,42],24:[2,42],28:43,32:[2,42],33:[2,42],34:[2,42],38:[2,42],40:[2,42]},{18:[2,25],24:[2,25]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],38:[2,37],40:[2,37],42:[1,44]},{21:45,38:[1,28],41:26},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],38:[2,39],40:[2,39],42:[2,39]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,40],21:50,27:49,38:[1,28],41:26},{18:[2,33],38:[2,33]},{18:[2,34],38:[2,34]},{18:[2,35],38:[2,35]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{21:51,38:[1,28],41:26},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,3],22:[1,13],23:[1,14],25:[1,15]},{4:52,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,5],22:[1,13],23:[1,14],25:[1,15]},{14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,44],21:56,24:[2,44],29:53,30:60,31:54,32:[1,57],33:[1,58],34:[1,59],35:55,36:61,37:62,38:[1,63],40:[1,27],41:26},{38:[1,64]},{18:[2,36],24:[2,36],32:[2,36],33:[2,36],34:[2,36],38:[2,36],40:[2,36]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,65]},{18:[2,41]},{18:[1,66]},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],25:[1,15]},{18:[2,24],24:[2,24]},{18:[2,43],24:[2,43],32:[2,43],33:[2,43],34:[2,43],38:[2,43],40:[2,43]},{18:[2,45],24:[2,45]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],38:[2,26],40:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],38:[2,27],40:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],38:[2,28],40:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],38:[2,29],40:[2,29]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],38:[2,30],40:[2,30]},{18:[2,31],24:[2,31],37:67,38:[1,68]},{18:[2,46],24:[2,46],38:[2,46]},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],38:[2,39],39:[1,69],40:[2,39],42:[2,39]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],40:[2,38],42:[2,38]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{18:[2,47],24:[2,47],38:[2,47]},{39:[1,69]},{21:56,30:60,31:70,32:[1,57],33:[1,58],34:[1,59],38:[1,28],40:[1,27],41:26},{18:[2,32],24:[2,32],38:[2,32]}],
defaultActions: {3:[2,2],16:[2,1],50:[2,41]},
parseError: function parseError(str, hash) {
throw new Error(str);
},
parse: function parse(input) {
var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
this.lexer.setInput(input);
this.lexer.yy = this.yy;
this.yy.lexer = this.lexer;
this.yy.parser = this;
if (typeof this.lexer.yylloc == "undefined")
this.lexer.yylloc = {};
var yyloc = this.lexer.yylloc;
lstack.push(yyloc);
var ranges = this.lexer.options && this.lexer.options.ranges;
if (typeof this.yy.parseError === "function")
this.parseError = this.yy.parseError;
function popStack(n) {
stack.length = stack.length - 2 * n;
vstack.length = vstack.length - n;
lstack.length = lstack.length - n;
}
function lex() {
var token;
token = self.lexer.lex() || 1;
if (typeof token !== "number") {
token = self.symbols_[token] || token;
}
return token;
}
var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
while (true) {
state = stack[stack.length - 1];
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol === null || typeof symbol == "undefined") {
symbol = lex();
}
action = table[state] && table[state][symbol];
}
if (typeof action === "undefined" || !action.length || !action[0]) {
var errStr = "";
if (!recovering) {
expected = [];
for (p in table[state])
if (this.terminals_[p] && p > 2) {
expected.push("'" + this.terminals_[p] + "'");
}
if (this.lexer.showPosition) {
errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
} else {
errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
}
this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
}
}
if (action[0] instanceof Array && action.length > 1) {
throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
}
switch (action[0]) {
case 1:
stack.push(symbol);
vstack.push(this.lexer.yytext);
lstack.push(this.lexer.yylloc);
stack.push(action[1]);
symbol = null;
if (!preErrorSymbol) {
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
if (recovering > 0)
recovering--;
} else {
symbol = preErrorSymbol;
preErrorSymbol = null;
}
break;
case 2:
len = this.productions_[action[1]][1];
yyval.$ = vstack[vstack.length - len];
yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
if (ranges) {
yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
}
r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
if (typeof r !== "undefined") {
return r;
}
if (len) {
stack = stack.slice(0, -1 * len * 2);
vstack = vstack.slice(0, -1 * len);
lstack = lstack.slice(0, -1 * len);
}
stack.push(this.productions_[action[1]][0]);
vstack.push(yyval.$);
lstack.push(yyval._$);
newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
stack.push(newState);
break;
case 3:
return true;
}
}
return true;
}
};
function stripFlags(open, close) {
return {
left: open[2] === '~',
right: close[0] === '~' || close[1] === '~'
};
}
/* Jison generated lexer */
var lexer = (function(){
var lexer = ({EOF:1,
parseError:function parseError(str, hash) {
if (this.yy.parser) {
this.yy.parser.parseError(str, hash);
} else {
throw new Error(str);
}
},
setInput:function (input) {
this._input = input;
this._more = this._less = this.done = false;
this.yylineno = this.yyleng = 0;
this.yytext = this.matched = this.match = '';
this.conditionStack = ['INITIAL'];
this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
if (this.options.ranges) this.yylloc.range = [0,0];
this.offset = 0;
return this;
},
input:function () {
var ch = this._input[0];
this.yytext += ch;
this.yyleng++;
this.offset++;
this.match += ch;
this.matched += ch;
var lines = ch.match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno++;
this.yylloc.last_line++;
} else {
this.yylloc.last_column++;
}
if (this.options.ranges) this.yylloc.range[1]++;
this._input = this._input.slice(1);
return ch;
},
unput:function (ch) {
var len = ch.length;
var lines = ch.split(/(?:\r\n?|\n)/g);
this._input = ch + this._input;
this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
//this.yyleng -= len;
this.offset -= len;
var oldLines = this.match.split(/(?:\r\n?|\n)/g);
this.match = this.match.substr(0, this.match.length-1);
this.matched = this.matched.substr(0, this.matched.length-1);
if (lines.length-1) this.yylineno -= lines.length-1;
var r = this.yylloc.range;
this.yylloc = {first_line: this.yylloc.first_line,
last_line: this.yylineno+1,
first_column: this.yylloc.first_column,
last_column: lines ?
(lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
this.yylloc.first_column - len
};
if (this.options.ranges) {
this.yylloc.range = [r[0], r[0] + this.yyleng - len];
}
return this;
},
more:function () {
this._more = true;
return this;
},
less:function (n) {
this.unput(this.match.slice(n));
},
pastInput:function () {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
},
upcomingInput:function () {
var next = this.match;
if (next.length < 20) {
next += this._input.substr(0, 20-next.length);
}
return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
},
showPosition:function () {
var pre = this.pastInput();
var c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c+"^";
},
next:function () {
if (this.done) {
return this.EOF;
}
if (!this._input) this.done = true;
var token,
match,
tempMatch,
index,
col,
lines;
if (!this._more) {
this.yytext = '';
this.match = '';
}
var rules = this._currentRules();
for (var i=0;i < rules.length; i++) {
tempMatch = this._input.match(this.rules[rules[i]]);
if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
match = tempMatch;
index = i;
if (!this.options.flex) break;
}
}
if (match) {
lines = match[0].match(/(?:\r\n?|\n).*/g);
if (lines) this.yylineno += lines.length;
this.yylloc = {first_line: this.yylloc.last_line,
last_line: this.yylineno+1,
first_column: this.yylloc.last_column,
last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
this.yytext += match[0];
this.match += match[0];
this.matches = match;
this.yyleng = this.yytext.length;
if (this.options.ranges) {
this.yylloc.range = [this.offset, this.offset += this.yyleng];
}
this._more = false;
this._input = this._input.slice(match[0].length);
this.matched += match[0];
token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
if (this.done && this._input) this.done = false;
if (token) return token;
else return;
}
if (this._input === "") {
return this.EOF;
} else {
return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
{text: "", token: null, line: this.yylineno});
}
},
lex:function lex() {
var r = this.next();
if (typeof r !== 'undefined') {
return r;
} else {
return this.lex();
}
},
begin:function begin(condition) {
this.conditionStack.push(condition);
},
popState:function popState() {
return this.conditionStack.pop();
},
_currentRules:function _currentRules() {
return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
},
topState:function () {
return this.conditionStack[this.conditionStack.length-2];
},
pushState:function begin(condition) {
this.begin(condition);
}});
lexer.options = {};
lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
function strip(start, end) {
return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end);
}
var YYSTATE=YY_START
switch($avoiding_name_collisions) {
case 0:
if(yy_.yytext.slice(-2) === "\\\\") {
strip(0,1);
this.begin("mu");
} else if(yy_.yytext.slice(-1) === "\\") {
strip(0,1);
this.begin("emu");
} else {
this.begin("mu");
}
if(yy_.yytext) return 14;
break;
case 1:return 14;
break;
case 2:
if(yy_.yytext.slice(-1) !== "\\") this.popState();
if(yy_.yytext.slice(-1) === "\\") strip(0,1);
return 14;
break;
case 3:strip(0,4); this.popState(); return 15;
break;
case 4:return 25;
break;
case 5:return 16;
break;
case 6:return 20;
break;
case 7:return 19;
break;
case 8:return 19;
break;
case 9:return 23;
break;
case 10:return 22;
break;
case 11:this.popState(); this.begin('com');
break;
case 12:strip(3,5); this.popState(); return 15;
break;
case 13:return 22;
break;
case 14:return 39;
break;
case 15:return 38;
break;
case 16:return 38;
break;
case 17:return 42;
break;
case 18:/*ignore whitespace*/
break;
case 19:this.popState(); return 24;
break;
case 20:this.popState(); return 18;
break;
case 21:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32;
break;
case 22:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32;
break;
case 23:return 40;
break;
case 24:return 34;
break;
case 25:return 34;
break;
case 26:return 33;
break;
case 27:return 38;
break;
case 28:yy_.yytext = strip(1,2); return 38;
break;
case 29:return 'INVALID';
break;
case 30:return 5;
break;
}
};
lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s])))/,/^(?:false(?=([~}\s])))/,/^(?:-?[0-9]+(?=([~}\s])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"INITIAL":{"rules":[0,1,30],"inclusive":true}};
return lexer;})()
parser.lexer = lexer;
function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
return new Parser;
})();__exports__ = handlebars;
return __exports__;
})();
// handlebars/compiler/base.js
var __module8__ = (function(__dependency1__, __dependency2__) {
"use strict";
var __exports__ = {};
var parser = __dependency1__;
var AST = __dependency2__;
__exports__.parser = parser;
function parse(input) {
// Just return if an already-compile AST was passed in.
if(input.constructor === AST.ProgramNode) { return input; }
parser.yy = AST;
return parser.parse(input);
}
__exports__.parse = parse;
return __exports__;
})(__module9__, __module7__);
// handlebars/compiler/javascript-compiler.js
var __module11__ = (function(__dependency1__) {
"use strict";
var __exports__;
var COMPILER_REVISION = __dependency1__.COMPILER_REVISION;
var REVISION_CHANGES = __dependency1__.REVISION_CHANGES;
var log = __dependency1__.log;
function Literal(value) {
this.value = value;
}
function JavaScriptCompiler() {}
JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name /* , type*/) {
var wrap,
ret;
if (parent.indexOf('depth') === 0) {
wrap = true;
}
if (/^[0-9]+$/.test(name)) {
ret = parent + "[" + name + "]";
} else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
ret = parent + "." + name;
}
else {
ret = parent + "['" + name + "']";
}
if (wrap) {
return '(' + parent + ' && ' + ret + ')';
} else {
return ret;
}
},
appendToBuffer: function(string) {
if (this.environment.isSimple) {
return "return " + string + ";";
} else {
return {
appendToBuffer: true,
content: string,
toString: function() { return "buffer += " + string + ";"; }
};
}
},
initializeBuffer: function() {
return this.quotedString("");
},
namespace: "Handlebars",
// END PUBLIC API
compile: function(environment, options, context, asObject) {
this.environment = environment;
this.options = options || {};
log('debug', this.environment.disassemble() + "\n\n");
this.name = this.environment.name;
this.isChild = !!context;
this.context = context || {
programs: [],
environments: [],
aliases: { }
};
this.preamble();
this.stackSlot = 0;
this.stackVars = [];
this.registers = { list: [] };
this.compileStack = [];
this.inlineStack = [];
this.compileChildren(environment, options);
var opcodes = environment.opcodes, opcode;
this.i = 0;
for(var l=opcodes.length; this.i<l; this.i++) {
opcode = opcodes[this.i];
if(opcode.opcode === 'DECLARE') {
this[opcode.name] = opcode.value;
} else {
this[opcode.opcode].apply(this, opcode.args);
}
// Reset the stripNext flag if it was not set by this operation.
if (opcode.opcode !== this.stripNext) {
this.stripNext = false;
}
}
// Flush any trailing content that might be pending.
this.pushSource('');
return this.createFunctionContext(asObject);
},
preamble: function() {
var out = [];
if (!this.isChild) {
var namespace = this.namespace;
var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
if (this.options.data) { copies = copies + " data = data || {};"; }
out.push(copies);
} else {
out.push('');
}
if (!this.environment.isSimple) {
out.push(", buffer = " + this.initializeBuffer());
} else {
out.push("");
}
// track the last context pushed into place to allow skipping the
// getContext opcode when it would be a noop
this.lastContext = 0;
this.source = out;
},
createFunctionContext: function(asObject) {
var locals = this.stackVars.concat(this.registers.list);
if(locals.length > 0) {
this.source[1] = this.source[1] + ", " + locals.join(", ");
}
// Generate minimizer alias mappings
if (!this.isChild) {
for (var alias in this.context.aliases) {
if (this.context.aliases.hasOwnProperty(alias)) {
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
}
}
}
if (this.source[1]) {
this.source[1] = "var " + this.source[1].substring(2) + ";";
}
// Merge children
if (!this.isChild) {
this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
}
if (!this.environment.isSimple) {
this.pushSource("return buffer;");
}
var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
params.push("depth" + this.environment.depths.list[i]);
}
// Perform a second pass over the output to merge content when possible
var source = this.mergeSource();
if (!this.isChild) {
var revision = COMPILER_REVISION,
versions = REVISION_CHANGES[revision];
source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
}
if (asObject) {
params.push(source);
return Function.apply(this, params);
} else {
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
log('debug', functionSource + "\n\n");
return functionSource;
}
},
mergeSource: function() {
// WARN: We are not handling the case where buffer is still populated as the source should
// not have buffer append operations as their final action.
var source = '',
buffer;
for (var i = 0, len = this.source.length; i < len; i++) {
var line = this.source[i];
if (line.appendToBuffer) {
if (buffer) {
buffer = buffer + '\n + ' + line.content;
} else {
buffer = line.content;
}
} else {
if (buffer) {
source += 'buffer += ' + buffer + ';\n ';
buffer = undefined;
}
source += line + '\n ';
}
}
return source;
},
// [blockValue]
//
// On stack, before: hash, inverse, program, value
// On stack, after: return value of blockHelperMissing
//
// The purpose of this opcode is to take a block of the form
// `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
// replace it on the stack with the result of properly
// invoking blockHelperMissing.
blockValue: function() {
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
var params = ["depth0"];
this.setupParams(0, params);
this.replaceStack(function(current) {
params.splice(1, 0, current);
return "blockHelperMissing.call(" + params.join(", ") + ")";
});
},
// [ambiguousBlockValue]
//
// On stack, before: hash, inverse, program, value
// Compiler value, before: lastHelper=value of last found helper, if any
// On stack, after, if no lastHelper: same as [blockValue]
// On stack, after, if lastHelper: value
ambiguousBlockValue: function() {
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
var params = ["depth0"];
this.setupParams(0, params);
var current = this.topStack();
params.splice(1, 0, current);
// Use the options value generated from the invocation
params[params.length-1] = 'options';
this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
},
// [appendContent]
//
// On stack, before: ...
// On stack, after: ...
//
// Appends the string value of `content` to the current buffer
appendContent: function(content) {
if (this.pendingContent) {
content = this.pendingContent + content;
}
if (this.stripNext) {
content = content.replace(/^\s+/, '');
}
this.pendingContent = content;
},
// [strip]
//
// On stack, before: ...
// On stack, after: ...
//
// Removes any trailing whitespace from the prior content node and flags
// the next operation for stripping if it is a content node.
strip: function() {
if (this.pendingContent) {
this.pendingContent = this.pendingContent.replace(/\s+$/, '');
}
this.stripNext = 'strip';
},
// [append]
//
// On stack, before: value, ...
// On stack, after: ...
//
// Coerces `value` to a String and appends it to the current buffer.
//
// If `value` is truthy, or 0, it is coerced into a string and appended
// Otherwise, the empty string is appended
append: function() {
// Force anything that is inlined onto the stack so we don't have duplication
// when we examine local
this.flushInline();
var local = this.popStack();
this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
if (this.environment.isSimple) {
this.pushSource("else { " + this.appendToBuffer("''") + " }");
}
},
// [appendEscaped]
//
// On stack, before: value, ...
// On stack, after: ...
//
// Escape `value` and append it to the buffer
appendEscaped: function() {
this.context.aliases.escapeExpression = 'this.escapeExpression';
this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
},
// [getContext]
//
// On stack, before: ...
// On stack, after: ...
// Compiler value, after: lastContext=depth
//
// Set the value of the `lastContext` compiler value to the depth
getContext: function(depth) {
if(this.lastContext !== depth) {
this.lastContext = depth;
}
},
// [lookupOnContext]
//
// On stack, before: ...
// On stack, after: currentContext[name], ...
//
// Looks up the value of `name` on the current context and pushes
// it onto the stack.
lookupOnContext: function(name) {
this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
},
// [pushContext]
//
// On stack, before: ...
// On stack, after: currentContext, ...
//
// Pushes the value of the current context onto the stack.
pushContext: function() {
this.pushStackLiteral('depth' + this.lastContext);
},
// [resolvePossibleLambda]
//
// On stack, before: value, ...
// On stack, after: resolved value, ...
//
// If the `value` is a lambda, replace it on the stack by
// the return value of the lambda
resolvePossibleLambda: function() {
this.context.aliases.functionType = '"function"';
this.replaceStack(function(current) {
return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
});
},
// [lookup]
//
// On stack, before: value, ...
// On stack, after: value[name], ...
//
// Replace the value on the stack with the result of looking
// up `name` on `value`
lookup: function(name) {
this.replaceStack(function(current) {
return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
});
},
// [lookupData]
//
// On stack, before: ...
// On stack, after: data, ...
//
// Push the data lookup operator
lookupData: function() {
this.push('data');
},
// [pushStringParam]
//
// On stack, before: ...
// On stack, after: string, currentContext, ...
//
// This opcode is designed for use in string mode, which
// provides the string value of a parameter along with its
// depth rather than resolving it immediately.
pushStringParam: function(string, type) {
this.pushStackLiteral('depth' + this.lastContext);
this.pushString(type);
if (typeof string === 'string') {
this.pushString(string);
} else {
this.pushStackLiteral(string);
}
},
emptyHash: function() {
this.pushStackLiteral('{}');
if (this.options.stringParams) {
this.register('hashTypes', '{}');
this.register('hashContexts', '{}');
}
},
pushHash: function() {
this.hash = {values: [], types: [], contexts: []};
},
popHash: function() {
var hash = this.hash;
this.hash = undefined;
if (this.options.stringParams) {
this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
this.register('hashTypes', '{' + hash.types.join(',') + '}');
}
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
},
// [pushString]
//
// On stack, before: ...
// On stack, after: quotedString(string), ...
//
// Push a quoted version of `string` onto the stack
pushString: function(string) {
this.pushStackLiteral(this.quotedString(string));
},
// [push]
//
// On stack, before: ...
// On stack, after: expr, ...
//
// Push an expression onto the stack
push: function(expr) {
this.inlineStack.push(expr);
return expr;
},
// [pushLiteral]
//
// On stack, before: ...
// On stack, after: value, ...
//
// Pushes a value onto the stack. This operation prevents
// the compiler from creating a temporary variable to hold
// it.
pushLiteral: function(value) {
this.pushStackLiteral(value);
},
// [pushProgram]
//
// On stack, before: ...
// On stack, after: program(guid), ...
//
// Push a program expression onto the stack. This takes
// a compile-time guid and converts it into a runtime-accessible
// expression.
pushProgram: function(guid) {
if (guid != null) {
this.pushStackLiteral(this.programExpression(guid));
} else {
this.pushStackLiteral(null);
}
},
// [invokeHelper]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of helper invocation
//
// Pops off the helper's parameters, invokes the helper,
// and pushes the helper's return value onto the stack.
//
// If the helper is not found, `helperMissing` is called.
invokeHelper: function(paramSize, name) {
this.context.aliases.helperMissing = 'helpers.helperMissing';
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
this.push(helper.name + ' || ' + nonHelper);
this.replaceStack(function(name) {
return name + ' ? ' + name + '.call(' +
helper.callParams + ") " + ": helperMissing.call(" +
helper.helperMissingParams + ")";
});
},
// [invokeKnownHelper]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of helper invocation
//
// This operation is used when the helper is known to exist,
// so a `helperMissing` fallback is not required.
invokeKnownHelper: function(paramSize, name) {
var helper = this.setupHelper(paramSize, name);
this.push(helper.name + ".call(" + helper.callParams + ")");
},
// [invokeAmbiguous]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of disambiguation
//
// This operation is used when an expression like `{{foo}}`
// is provided, but we don't know at compile-time whether it
// is a helper or a path.
//
// This operation emits more code than the other options,
// and can be avoided by passing the `knownHelpers` and
// `knownHelpersOnly` flags at compile-time.
invokeAmbiguous: function(name, helperCall) {
this.context.aliases.functionType = '"function"';
this.pushStackLiteral('{}'); // Hash value
var helper = this.setupHelper(0, name, helperCall);
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
var nextStack = this.nextStack();
this.pushSource('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
this.pushSource('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(' + helper.callParams + ') : ' + nextStack + '; }');
},
// [invokePartial]
//
// On stack, before: context, ...
// On stack after: result of partial invocation
//
// This operation pops off a context, invokes a partial with that context,
// and pushes the result of the invocation back.
invokePartial: function(name) {
var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
if (this.options.data) {
params.push("data");
}
this.context.aliases.self = "this";
this.push("self.invokePartial(" + params.join(", ") + ")");
},
// [assignToHash]
//
// On stack, before: value, hash, ...
// On stack, after: hash, ...
//
// Pops a value and hash off the stack, assigns `hash[key] = value`
// and pushes the hash back onto the stack.
assignToHash: function(key) {
var value = this.popStack(),
context,
type;
if (this.options.stringParams) {
type = this.popStack();
context = this.popStack();
}
var hash = this.hash;
if (context) {
hash.contexts.push("'" + key + "': " + context);
}
if (type) {
hash.types.push("'" + key + "': " + type);
}
hash.values.push("'" + key + "': (" + value + ")");
},
// HELPERS
compiler: JavaScriptCompiler,
compileChildren: function(environment, options) {
var children = environment.children, child, compiler;
for(var i=0, l=children.length; i<l; i++) {
child = children[i];
compiler = new this.compiler();
var index = this.matchExistingProgram(child);
if (index == null) {
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
index = this.context.programs.length;
child.index = index;
child.name = 'program' + index;
this.context.programs[index] = compiler.compile(child, options, this.context);
this.context.environments[index] = child;
} else {
child.index = index;
child.name = 'program' + index;
}
}
},
matchExistingProgram: function(child) {
for (var i = 0, len = this.context.environments.length; i < len; i++) {
var environment = this.context.environments[i];
if (environment && environment.equals(child)) {
return i;
}
}
},
programExpression: function(guid) {
this.context.aliases.self = "this";
if(guid == null) {
return "self.noop";
}
var child = this.environment.children[guid],
depths = child.depths.list, depth;
var programParams = [child.index, child.name, "data"];
for(var i=0, l = depths.length; i<l; i++) {
depth = depths[i];
if(depth === 1) { programParams.push("depth0"); }
else { programParams.push("depth" + (depth - 1)); }
}
return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
},
register: function(name, val) {
this.useRegister(name);
this.pushSource(name + " = " + val + ";");
},
useRegister: function(name) {
if(!this.registers[name]) {
this.registers[name] = true;
this.registers.list.push(name);
}
},
pushStackLiteral: function(item) {
return this.push(new Literal(item));
},
pushSource: function(source) {
if (this.pendingContent) {
this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
this.pendingContent = undefined;
}
if (source) {
this.source.push(source);
}
},
pushStack: function(item) {
this.flushInline();
var stack = this.incrStack();
if (item) {
this.pushSource(stack + " = " + item + ";");
}
this.compileStack.push(stack);
return stack;
},
replaceStack: function(callback) {
var prefix = '',
inline = this.isInline(),
stack;
// If we are currently inline then we want to merge the inline statement into the
// replacement statement via ','
if (inline) {
var top = this.popStack(true);
if (top instanceof Literal) {
// Literals do not need to be inlined
stack = top.value;
} else {
// Get or create the current stack name for use by the inline
var name = this.stackSlot ? this.topStackName() : this.incrStack();
prefix = '(' + this.push(name) + ' = ' + top + '),';
stack = this.topStack();
}
} else {
stack = this.topStack();
}
var item = callback.call(this, stack);
if (inline) {
if (this.inlineStack.length || this.compileStack.length) {
this.popStack();
}
this.push('(' + prefix + item + ')');
} else {
// Prevent modification of the context depth variable. Through replaceStack
if (!/^stack/.test(stack)) {
stack = this.nextStack();
}
this.pushSource(stack + " = (" + prefix + item + ");");
}
return stack;
},
nextStack: function() {
return this.pushStack();
},
incrStack: function() {
this.stackSlot++;
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
return this.topStackName();
},
topStackName: function() {
return "stack" + this.stackSlot;
},
flushInline: function() {
var inlineStack = this.inlineStack;
if (inlineStack.length) {
this.inlineStack = [];
for (var i = 0, len = inlineStack.length; i < len; i++) {
var entry = inlineStack[i];
if (entry instanceof Literal) {
this.compileStack.push(entry);
} else {
this.pushStack(entry);
}
}
}
},
isInline: function() {
return this.inlineStack.length;
},
popStack: function(wrapped) {
var inline = this.isInline(),
item = (inline ? this.inlineStack : this.compileStack).pop();
if (!wrapped && (item instanceof Literal)) {
return item.value;
} else {
if (!inline) {
this.stackSlot--;
}
return item;
}
},
topStack: function(wrapped) {
var stack = (this.isInline() ? this.inlineStack : this.compileStack),
item = stack[stack.length - 1];
if (!wrapped && (item instanceof Literal)) {
return item.value;
} else {
return item;
}
},
quotedString: function(str) {
return '"' + str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"';
},
setupHelper: function(paramSize, name, missingParams) {
var params = [];
this.setupParams(paramSize, params, missingParams);
var foundHelper = this.nameLookup('helpers', name, 'helper');
return {
params: params,
name: foundHelper,
callParams: ["depth0"].concat(params).join(", "),
helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
};
},
// the params and contexts arguments are passed in arrays
// to fill in
setupParams: function(paramSize, params, useRegister) {
var options = [], contexts = [], types = [], param, inverse, program;
options.push("hash:" + this.popStack());
inverse = this.popStack();
program = this.popStack();
// Avoid setting fn and inverse if neither are set. This allows
// helpers to do a check for `if (options.fn)`
if (program || inverse) {
if (!program) {
this.context.aliases.self = "this";
program = "self.noop";
}
if (!inverse) {
this.context.aliases.self = "this";
inverse = "self.noop";
}
options.push("inverse:" + inverse);
options.push("fn:" + program);
}
for(var i=0; i<paramSize; i++) {
param = this.popStack();
params.push(param);
if(this.options.stringParams) {
types.push(this.popStack());
contexts.push(this.popStack());
}
}
if (this.options.stringParams) {
options.push("contexts:[" + contexts.join(",") + "]");
options.push("types:[" + types.join(",") + "]");
options.push("hashContexts:hashContexts");
options.push("hashTypes:hashTypes");
}
if(this.options.data) {
options.push("data:data");
}
options = "{" + options.join(",") + "}";
if (useRegister) {
this.register('options', options);
params.push('options');
} else {
params.push(options);
}
return params.join(", ");
}
};
var reservedWords = (
"break else new var" +
" case finally return void" +
" catch for switch while" +
" continue function this with" +
" default if throw" +
" delete in try" +
" do instanceof typeof" +
" abstract enum int short" +
" boolean export interface static" +
" byte extends long super" +
" char final native synchronized" +
" class float package throws" +
" const goto private transient" +
" debugger implements protected volatile" +
" double import public let yield"
).split(" ");
var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
for(var i=0, l=reservedWords.length; i<l; i++) {
compilerWords[reservedWords[i]] = true;
}
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
return true;
}
return false;
};
__exports__ = JavaScriptCompiler;
return __exports__;
})(__module2__);
// handlebars/compiler/compiler.js
var __module10__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__) {
"use strict";
var __exports__ = {};
var Exception = __dependency1__;
var parse = __dependency2__.parse;
var JavaScriptCompiler = __dependency3__;
var AST = __dependency4__;
function Compiler() {}
__exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a
// function in a context. This is necessary for mustache compatibility, which
// requires that context functions in blocks are evaluated by blockHelperMissing,
// and then proceed as if the resulting value was provided to blockHelperMissing.
Compiler.prototype = {
compiler: Compiler,
disassemble: function() {
var opcodes = this.opcodes, opcode, out = [], params, param;
for (var i=0, l=opcodes.length; i<l; i++) {
opcode = opcodes[i];
if (opcode.opcode === 'DECLARE') {
out.push("DECLARE " + opcode.name + "=" + opcode.value);
} else {
params = [];
for (var j=0; j<opcode.args.length; j++) {
param = opcode.args[j];
if (typeof param === "string") {
param = "\"" + param.replace("\n", "\\n") + "\"";
}
params.push(param);
}
out.push(opcode.opcode + " " + params.join(" "));
}
}
return out.join("\n");
},
equals: function(other) {
var len = this.opcodes.length;
if (other.opcodes.length !== len) {
return false;
}
for (var i = 0; i < len; i++) {
var opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
return false;
}
for (var j = 0; j < opcode.args.length; j++) {
if (opcode.args[j] !== otherOpcode.args[j]) {
return false;
}
}
}
len = this.children.length;
if (other.children.length !== len) {
return false;
}
for (i = 0; i < len; i++) {
if (!this.children[i].equals(other.children[i])) {
return false;
}
}
return true;
},
guid: 0,
compile: function(program, options) {
this.opcodes = [];
this.children = [];
this.depths = {list: []};
this.options = options;
// These changes will propagate to the other compiler components
var knownHelpers = this.options.knownHelpers;
this.options.knownHelpers = {
'helperMissing': true,
'blockHelperMissing': true,
'each': true,
'if': true,
'unless': true,
'with': true,
'log': true
};
if (knownHelpers) {
for (var name in knownHelpers) {
this.options.knownHelpers[name] = knownHelpers[name];
}
}
return this.accept(program);
},
accept: function(node) {
var strip = node.strip || {},
ret;
if (strip.left) {
this.opcode('strip');
}
ret = this[node.type](node);
if (strip.right) {
this.opcode('strip');
}
return ret;
},
program: function(program) {
var statements = program.statements;
for(var i=0, l=statements.length; i<l; i++) {
this.accept(statements[i]);
}
this.isSimple = l === 1;
this.depths.list = this.depths.list.sort(function(a, b) {
return a - b;
});
return this;
},
compileProgram: function(program) {
var result = new this.compiler().compile(program, this.options);
var guid = this.guid++, depth;
this.usePartial = this.usePartial || result.usePartial;
this.children[guid] = result;
for(var i=0, l=result.depths.list.length; i<l; i++) {
depth = result.depths.list[i];
if(depth < 2) { continue; }
else { this.addDepth(depth - 1); }
}
return guid;
},
block: function(block) {
var mustache = block.mustache,
program = block.program,
inverse = block.inverse;
if (program) {
program = this.compileProgram(program);
}
if (inverse) {
inverse = this.compileProgram(inverse);
}
var type = this.classifyMustache(mustache);
if (type === "helper") {
this.helperMustache(mustache, program, inverse);
} else if (type === "simple") {
this.simpleMustache(mustache);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('blockValue');
} else {
this.ambiguousMustache(mustache, program, inverse);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('ambiguousBlockValue');
}
this.opcode('append');
},
hash: function(hash) {
var pairs = hash.pairs, pair, val;
this.opcode('pushHash');
for(var i=0, l=pairs.length; i<l; i++) {
pair = pairs[i];
val = pair[1];
if (this.options.stringParams) {
if(val.depth) {
this.addDepth(val.depth);
}
this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', val.stringModeValue, val.type);
} else {
this.accept(val);
}
this.opcode('assignToHash', pair[0]);
}
this.opcode('popHash');
},
partial: function(partial) {
var partialName = partial.partialName;
this.usePartial = true;
if(partial.context) {
this.ID(partial.context);
} else {
this.opcode('push', 'depth0');
}
this.opcode('invokePartial', partialName.name);
this.opcode('append');
},
content: function(content) {
this.opcode('appendContent', content.string);
},
mustache: function(mustache) {
var options = this.options;
var type = this.classifyMustache(mustache);
if (type === "simple") {
this.simpleMustache(mustache);
} else if (type === "helper") {
this.helperMustache(mustache);
} else {
this.ambiguousMustache(mustache);
}
if(mustache.escaped && !options.noEscape) {
this.opcode('appendEscaped');
} else {
this.opcode('append');
}
},
ambiguousMustache: function(mustache, program, inverse) {
var id = mustache.id,
name = id.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', id.depth);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('invokeAmbiguous', name, isBlock);
},
simpleMustache: function(mustache) {
var id = mustache.id;
if (id.type === 'DATA') {
this.DATA(id);
} else if (id.parts.length) {
this.ID(id);
} else {
// Simplified ID for `this`
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
this.opcode('pushContext');
}
this.opcode('resolvePossibleLambda');
},
helperMustache: function(mustache, program, inverse) {
var params = this.setupFullMustacheParams(mustache, program, inverse),
name = mustache.id.parts[0];
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) {
throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
} else {
this.opcode('invokeHelper', params.length, name);
}
},
ID: function(id) {
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
var name = id.parts[0];
if (!name) {
this.opcode('pushContext');
} else {
this.opcode('lookupOnContext', id.parts[0]);
}
for(var i=1, l=id.parts.length; i<l; i++) {
this.opcode('lookup', id.parts[i]);
}
},
DATA: function(data) {
this.options.data = true;
if (data.id.isScoped || data.id.depth) {
throw new Exception('Scoped data references are not supported: ' + data.original);
}
this.opcode('lookupData');
var parts = data.id.parts;
for(var i=0, l=parts.length; i<l; i++) {
this.opcode('lookup', parts[i]);
}
},
STRING: function(string) {
this.opcode('pushString', string.string);
},
INTEGER: function(integer) {
this.opcode('pushLiteral', integer.integer);
},
BOOLEAN: function(bool) {
this.opcode('pushLiteral', bool.bool);
},
comment: function() {},
// HELPERS
opcode: function(name) {
this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
},
declare: function(name, value) {
this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
},
addDepth: function(depth) {
if(isNaN(depth)) { throw new Error("EWOT"); }
if(depth === 0) { return; }
if(!this.depths[depth]) {
this.depths[depth] = true;
this.depths.list.push(depth);
}
},
classifyMustache: function(mustache) {
var isHelper = mustache.isHelper;
var isEligible = mustache.eligibleHelper;
var options = this.options;
// if ambiguous, we can possibly resolve the ambiguity now
if (isEligible && !isHelper) {
var name = mustache.id.parts[0];
if (options.knownHelpers[name]) {
isHelper = true;
} else if (options.knownHelpersOnly) {
isEligible = false;
}
}
if (isHelper) { return "helper"; }
else if (isEligible) { return "ambiguous"; }
else { return "simple"; }
},
pushParams: function(params) {
var i = params.length, param;
while(i--) {
param = params[i];
if(this.options.stringParams) {
if(param.depth) {
this.addDepth(param.depth);
}
this.opcode('getContext', param.depth || 0);
this.opcode('pushStringParam', param.stringModeValue, param.type);
} else {
this[param.type](param);
}
}
},
setupMustacheParams: function(mustache) {
var params = mustache.params;
this.pushParams(params);
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('emptyHash');
}
return params;
},
// this will replace setupMustacheParams when we're done
setupFullMustacheParams: function(mustache, program, inverse) {
var params = mustache.params;
this.pushParams(params);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('emptyHash');
}
return params;
}
};
function precompile(input, options) {
if (input == null || (typeof input !== 'string' && input.constructor !== AST.ProgramNode)) {
throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
}
options = options || {};
if (!('data' in options)) {
options.data = true;
}
var ast = parse(input);
var environment = new Compiler().compile(ast, options);
return new JavaScriptCompiler().compile(environment, options);
}
__exports__.precompile = precompile;function compile(input, options, env) {
if (input == null || (typeof input !== 'string' && input.constructor !== AST.ProgramNode)) {
throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
}
options = options || {};
if (!('data' in options)) {
options.data = true;
}
var compiled;
function compileInput() {
var ast = parse(input);
var environment = new Compiler().compile(ast, options);
var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true);
return env.template(templateSpec);
}
// Template is only compiled on first use and cached after that point.
return function(context, options) {
if (!compiled) {
compiled = compileInput();
}
return compiled.call(this, context, options);
};
}
__exports__.compile = compile;
return __exports__;
})(__module5__, __module8__, __module11__, __module7__);
// handlebars.js
var __module0__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
"use strict";
var __exports__;
var Handlebars = __dependency1__;
// Compiler imports
var AST = __dependency2__;
var Parser = __dependency3__.parser;
var parse = __dependency3__.parse;
var Compiler = __dependency4__.Compiler;
var compile = __dependency4__.compile;
var precompile = __dependency4__.precompile;
var JavaScriptCompiler = __dependency5__;
var _create = Handlebars.create;
var create = function() {
var hb = _create();
hb.compile = function(input, options) {
return compile(input, options, hb);
};
hb.precompile = precompile;
hb.AST = AST;
hb.Compiler = Compiler;
hb.JavaScriptCompiler = JavaScriptCompiler;
hb.Parser = Parser;
hb.parse = parse;
return hb;
};
Handlebars = create();
Handlebars.create = create;
__exports__ = Handlebars;
return __exports__;
})(__module1__, __module7__, __module8__, __module10__, __module11__);
return __module0__;
})();

View File

@@ -0,0 +1,104 @@
// Generated by CoffeeScript 1.6.3
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.Flagging = (function(_super) {
__extends(Flagging, _super);
//If you do not have options, delete next line and the parameters in the declaration function
Flagging.prototype.options = null;
//declaration function, remember to set up submit and/or update as necessary, if you don't have
//options, delete the options line below.
function Flagging(element,options) {
this.updateViewer = __bind(this.updateViewer, this);
this.flagAnnotation = __bind(this.flagAnnotation, this);
this.unflagAnnotation = __bind(this.unflagAnnotation, this);
this.options = options;
_ref = Flagging.__super__.constructor.apply(this, arguments);
return _ref;
}
//example variables to be used to receive input in the annotator view
Flagging.prototype.field = null;
Flagging.prototype.input = null;
Flagging.prototype.hasPressed = false;
//this function will initialize the plug in. Create your fields here in the editor and viewer.
Flagging.prototype.pluginInit = function() {
console.log("Flagging-pluginInit");
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
//-- Viewer
var newview = this.annotator.viewer.addField({
load: this.updateViewer,
});
return this.input = $(this.field).find(':input');
};
//The following allows you to edit the annotation popup when the viewer has already
//hit submit and is just viewing the annotation.
Flagging.prototype.updateViewer = function(field, annotation) {
$(field).remove();//remove the empty div created by annotator
var self = this;
this.hasPressed = false;
//perform routine to check if user has pressed the button before
var tags = typeof annotation.tags != 'undefined'?annotation.tags:[];
var user = this.annotator.plugins.Permissions.user.id;
tags.forEach(function(t){
if (t.indexOf("flagged")>=0) {
var usertest = t.replace('flagged-','');
if (usertest == user)
self.hasPressed = true;
}
});
var fieldControl = $(this.annotator.viewer.element.find('.annotator-controls')).parent();
if (this.hasPressed) {
fieldControl.prepend('<button title="You have already reported this annotation." class="flag-icon-used">');
var flagEl = fieldControl.find('.flag-icon-used'),
self = this;
flagEl.click(function(){self.unflagAnnotation(annotation,user,flagEl,field)});
} else{
fieldControl.prepend('<button title="Report annotation as inappropriate or offensive." class="flag-icon">');
var flagEl = fieldControl.find('.flag-icon'),
self = this;
flagEl.click(function(){self.flagAnnotation(annotation,user,flagEl,field)});
}
}
Flagging.prototype.flagAnnotation = function(annotation, userId, flagElement, field) {
flagElement.attr("class","flag-icon-used");
flagElement.attr("title","You have already reported this annotation.");
if (typeof annotation.tags == 'undefined') {
annotation.tags = ['flagged-'+userId];
} else{
annotation.tags.push("flagged-"+userId);
}
this.annotator.plugins['Store'].annotationUpdated(annotation);
this.annotator.publish("flaggedAnnotation",[field,annotation]);
}
Flagging.prototype.unflagAnnotation = function(annotation, userId, flagElement, field) {
flagElement.attr("class", "flag-icon");
flagElement.attr("title","Report annotation as inappropriate or offensive.");
annotation.tags.splice(annotation.tags.indexOf('flagged-'+userId));
this.annotator.plugins['Store'].annotationUpdated(annotation);
this.annotator.publish("flaggedAnnotation",[field,annotation]);
}
return Flagging;
})(Annotator.Plugin);

View File

@@ -0,0 +1,116 @@
/**
* jQuery Watch Plugin
*
* @author Darcy Clarke
* @version 2.0
*
* Copyright (c) 2012 Darcy Clarke
* Dual licensed under the MIT and GPL licenses.
*
* ADDS:
*
* - $.watch()
*
* USES:
*
* - DOMAttrModified event
*
* FALLBACKS:
*
* - propertychange event
* - setTimeout() with delay
*
* EXAMPLE:
*
* $('div').watch('width height', function(){
* console.log(this.style.width, this.style.height);
* });
*
* $('div').animate({width:'100px',height:'200px'}, 500);
*
*/
(function($){
$.extend($.fn, {
/**
* Watch Method
*
* @param {String} the name of the properties to watch
* @param {Object} options to overide defaults (only 'throttle' right now)
* @param {Function} callback function to be executed when attributes change
*
* @return {jQuery Object} returns the jQuery object for chainability
*/
watch : function(props, options, callback){
// Dummmy element
var element = document.createElement('div');
/**
* Checks Support for Event
*
* @param {String} the name of the event
* @param {Element Object} the element to test support against
*
* @return {Boolean} returns result of test (true/false)
*/
var isEventSupported = function(eventName, el) {
eventName = 'on' + eventName;
var supported = (eventName in el);
if(!supported){
el.setAttribute(eventName, 'return;');
supported = typeof el[eventName] == 'function';
}
return supported;
};
// Type check options
if(typeof(options) == 'function'){
callback = options;
options = {};
}
// Type check callback
if(typeof(callback) != 'function')
callback = function(){};
// Map options over defaults
options = $.extend({}, { throttle : 10 }, options);
/**
* Checks if properties have changed
*
* @param {Element Object} the element to watch
*
*/
var check = function(el) {
var data = el.data(),
changed = false,
temp;
// Loop through properties
var length = typeof data!='undefined' && typeof data.props!='undefined'?data.props.length:0;
for(var i=0;i < length; i++){
temp = el.css(data.props[i]);
if(data.vals[i] != temp){
data.vals[i] = temp;
changed = true;
break;
}
}
// Run callback if property has changed
if(changed && data.cb)
data.cb.call(el, data);
};
return this.each(function(){
var el = $(this),
cb = function(){ check.call(this, el) },
data = { props:props.split(','), cb:callback, vals: [] };
$.each(data.props, function(i){ data.vals[i] = el.css(data.props[i]); });
el.data(data);
if(isEventSupported('DOMAttrModified', element)){
el.on('DOMAttrModified', callback);
} else if(isEventSupported('propertychange', element)){
el.on('propertychange', callback);
} else {
setInterval(cb, options.throttle);
}
});
}
});
})(jQuery);

2553
common/static/js/vendor/ova/ova.js vendored Normal file
View File

@@ -0,0 +1,2553 @@
/*
Open Video Annotation v1.0 (http://openvideoannotation.org/)
Copyright (C) 2014 CHS (Harvard University), Daniel Cebri<72>n 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
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.
*/
//----------------Utilities----------------//
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; },
createDateFromISO8601 = function(string) {
var d, date, offset, regexp, time, _ref;
regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\\.([0-9]+))?)?" + "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
d = string.match(new RegExp(regexp));
offset = 0;
date = new Date(d[1], 0, 1);
if (d[3]) {
date.setMonth(d[3] - 1);
}
if (d[5]) {
date.setDate(d[5]);
}
if (d[7]) {
date.setHours(d[7]);
}
if (d[8]) {
date.setMinutes(d[8]);
}
if (d[10]) {
date.setSeconds(d[10]);
}
if (d[12]) {
date.setMilliseconds(Number("0." + d[12]) * 1000);
}
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= (_ref = d[15] === '-') != null ? _ref : {
1: -1
};
}
offset -= date.getTimezoneOffset();
time = Number(date) + (offset * 60 * 1000);
date.setTime(Number(time));
return date;
};
var Util = typeof Util !='undefined'? Util:{};
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
};
};
//----------------Load videojs-Annotation Plugin----------------//
(function (){
//-- Load Annotation plugin in videojs
function vjsAnnotation_(options){
var player = this;
//variables to know if it is ready
player.annotations=new vjsAnnotation(player, options);
//When the DOM, Range Slider and the video media is loaded
function initialVideoFinished(event) {
//-- wait for plugins --//
var wrapper = $('.annotator-wrapper').parent()[0],
annotator = $.data(wrapper, 'annotator');
//wait for Annotator and the Share plugin
if (typeof Annotator.Plugin["Share"] === 'function') {
if (typeof annotator.isShareLoaded!='undefined' && annotator.isShareLoaded){
annotator.unsubscribe('shareloaded',initialVideoFinished);
}else{
annotator.subscribe('shareloaded',initialVideoFinished);
return false;
}
}
var plugin = player.annotations;
//All components will be initialize after they have been loaded by videojs
for (var index in plugin.components) {
plugin.components[index].init_();
}
player.annotations.BigNewAn.show();
//set the position of the big buttom
plugin.setposBigNew(plugin.options.posBigNew);
if(!options.showDisplay) plugin.hideDisplay();
if(!options.showStatistics) plugin.hideStatistics();
//Get current instance of annotator
player.annotator = annotator;
plugin.annotator = annotator;
//get annotations
var allannotations = annotator.plugins['Store'].annotations;
plugin.refreshDisplay();
//-- Listener to Range Slider Plugin
player.rangeslider.rstb.on('mousedown', function(){plugin._onMouseDownRS(event)});
//Open the autoPlay from the API
if (player.autoPlayAPI){
var OnePlay = function (){
player.annotations.showAnnotation(player.autoPlayAPI);
$('html,body').animate({
scrollTop: $("#"+player.id_).offset().top},
'slow');
}
if (player.techName == 'Youtube')
setTimeout(OnePlay, 100);//fix the delay playing youtube
else
OnePlay();
}
//set the number of Annotations to display
plugin.refreshDesignPanel();
//check full-screen change
player.on('fullscreenchange',function() {
if (player.isFullScreen) {
$(player.annotator.wrapper[0]).addClass('vjs-fullscreen');
} else {
$(player.annotator.wrapper[0]).removeClass('vjs-fullscreen');
}
plugin.refreshDesignPanel();
});
//loaded plugin
plugin.loaded = true;
}
player.one('loadedRangeSlider', initialVideoFinished);//Loaded RangeSlider
console.log("Loaded Annotation Plugin");
}
videojs.plugin('annotations', vjsAnnotation_);
//-- Plugin
function vjsAnnotation(player,options){
var player = player || this;
this.player = player;
this.components = {}; // holds any custom components we add to the player
options = options || {}; // plugin options
if(!options.hasOwnProperty('posBigNew'))
options.posBigNew = 'none'; // ul = up left || ur = up right || bl = below left || br = below right || c = center
if(!options.hasOwnProperty('showDisplay'))
options.showDisplay = false;
/*if(!options.hasOwnProperty('NumAnnotations'))
options.NumAnnotations = 16; */
if(!options.hasOwnProperty('showStatistics'))
options.showStatistics = false;
this.options = options;
this.init();
}
//-- Methods
vjsAnnotation.prototype = {
/*Constructor*/
init:function(){
var player = this.player || {},
controlBar = player.controlBar,
seekBar = player.controlBar.progressControl.seekBar;
this.updatePrecision = 3;
//Components and Quick Aliases
this.BigNewAn = this.components.BigNewAnnotation = player.BigNewAnnotation;
this.AnConBut = this.components.AnContainerButtons = controlBar.AnContainerButtons;
this.ShowSt = this.components.ShowStatistics = this.AnConBut.ShowStatistics;
this.NewAn = this.components.NewAnnotation = this.AnConBut.NewAnnotation;
this.ShowAn =this.components.ShowAnnotations = this.AnConBut.ShowAnnotations;
this.BackAnDisplay = this.components.BackAnDisplay = controlBar.BackAnDisplay;//Background of the panel
this.AnDisplay = this.components.AnDisplay = controlBar.BackAnDisplay.AnDisplay;//Panel with all the annotations
this.AnStat = this.components.AnStat = controlBar.BackAnDisplay.AnStat;//Panel with statistics of the number of annotations
this.BackAnDisplayScroll = this.components.BackAnDisplayScroll = controlBar.BackAnDisplayScroll;//Back Panel with all the annotations
this.backDSBar = this.components.BackAnDisplayScrollBar = this.BackAnDisplayScroll.BackAnDisplayScrollBar;//Scroll Bar
this.backDSBarSel = this.components.ScrollBarSelector = this.backDSBar.ScrollBarSelector;//Scroll Bar Selector
this.backDSTime = this.components.BackAnDisplayScrollTime = this.BackAnDisplayScroll.BackAnDisplayScrollTime;//Back Panel with time of the annotations in the scroll
this.rsd = this.components.RangeSelectorDisplay = controlBar.BackAnDisplay.RangeSelectorDisplay;//Selection the time to display the annotations
this.rsdl = this.components.RangeSelectorLeft = this.rsd.RangeSelectorLeft;
this.rsdr = this.components.RangeSelectorRight = this.rsd.RangeSelectorRight;
this.rsdb = this.components.RangeSelectorBar = this.rsd.RangeSelectorBar;
this.rsdbl = this.components.RangeSelectorBarL = this.rsdb.RangeSelectorBarL;
this.rsdbr = this.components.RangeSelectorBarR = this.rsdb.RangeSelectorBarR;
this.rs = player.rangeslider;
//local variables
this.editing = false;
var wrapper = $('.annotator-wrapper').parent()[0],
annotator = $.data(wrapper, 'annotator'),
self = this;
//Subscribe to Annotator changes
annotator.subscribe("annotationsLoaded", function (annotations){
if(self.loaded)
self.refreshDisplay();
});
annotator.subscribe("annotationUpdated", function (annotation){
if(self.loaded)
self.refreshDisplay();
});
annotator.subscribe("annotationDeleted", function (annotation){
var annotations = annotator.plugins['Store'].annotations,
tot = typeof annotations !='undefined'?annotations.length:0,
attempts = 0; // max 100
//This is to watch the annotations object, to see when is deleted the annotation
var ischanged = function(){
var new_tot = annotator.plugins['Store'].annotations.length;
if (attempts<100)
setTimeout(function(){
if (new_tot != tot){
if(self.loaded)
self.refreshDisplay();
}else{
attempts++;
ischanged();
}
},100); //wait for the change in the annotations
};
ischanged();
});
this.BigNewAn.hide(); //Hide until the video is load
},
newan: function(start,end){
var player = this.player,
annotator = this.annotator,
sumPercent = 10,//percentage for the last mark
currentTime = player.currentTime(),
lastTime = this._sumPercent(currentTime,sumPercent);
var start = typeof start!='undefined'?start:currentTime,
end = typeof end!='undefined'?end:lastTime;
this._reset();
//set position RS and pause the player
player.showSlider();
player.pause();
player.setValueSlider(start,end);
//This variable is to say the editor that we want create a VideoJS annotation
annotator.editor.VideoJS = this.player.id_;
annotator.adder.show();
this._setOverRS(annotator.adder);
//Open a new annotator dialog
annotator.onAdderClick();
},
showDisplay: function(){
this._reset();
//show
this.BackAnDisplay.removeClass('disable');//show the Container
this.BackAnDisplayScroll.removeClass('disable');//show the scroll
//active button
this.ShowAn.addClass('active');
this.options.showDisplay =true;
},
hideDisplay: function(){
//hide
this.BackAnDisplay.addClass('disable');//hide the Container
this.BackAnDisplayScroll.addClass('disable');//hide the scroll
//no active button
videojs.removeClass(this.ShowAn.el_, 'active');
this.options.showDisplay =false;
},
showStatistics: function(){
this._reset();
//show
this.BackAnDisplay.removeClass('disable');//show the Container
this.AnStat.removeClass('disable');//show Statistics
//mode (this mode will hide the annotations to show the statistics in the container)
this.BackAnDisplay.addClass('statistics');//mode statistics
//paint
this.AnStat.paintCanvas();//refresh canvas
//active button
this.ShowSt.addClass('active');
this.options.showStatistics =true;
},
hideStatistics: function(){
//hide
this.BackAnDisplay.addClass('disable');//hide the Container
this.AnStat.addClass('disable');//hide Statistics
//remove mode statistics
this.BackAnDisplay.removeClass('statistics');
//no active button
this.ShowSt.removeClass('active');
this.options.showStatistics =false;
},
showAnnotation: function(annotation){
var isVideo = this._isVideoJS(annotation);
if (isVideo){
var start = annotation.rangeTime.start,
end = annotation.rangeTime.end,
duration = this.player.duration(),
isPoint = videojs.round(start,3)==videojs.round(end,3);
this._reset();
//show the range slider
this.rs.show();
//set the slider position
this.rs.setValues(start,end);
//lock the player
this.rs.lock();
//play
if(!isPoint)
this.rs.playBetween(start,end);
//fix small bar
var width = Math.min(1, Math.max(0.005, (this.rs._percent(end-start))))*100;
this.rs.bar.el_.style.width = width+'%';
//Add the annotation object to the bar
var bar = isPoint?this.rs[((duration-start)/duration<0.1)?'left':'right'].el_:this.rs.bar.el_,
holder = $(this.rs.left.el_).parent()[0];
$(holder).append('<span class="annotator-hl"></div>');
$(bar).appendTo( $(holder).find('.annotator-hl'));
var span = $(bar).parent()[0];
$.data(span, 'annotation', annotation);//Set the object in the span
//set the editor over the range slider
this._setOverRS(this.annotator.editor.element);
this.annotator.editor.checkOrientation();
//hide the panel
this.rs.hidePanel();
}
},
hideAnnotation: function(){
this.rs.hide();
this.rs.showPanel();
//remove the last single showed annotation
var holder = $(this.rs.left.el_).parent()[0];
var holderRight = $(this.rs.right.el_).parent()[0];
if ($(holder).find('.annotator-hl').length > 0){
$($(holder).find('.annotator-hl')[0].children[0]).appendTo(holder);
$(holder).find('.annotator-hl').remove();
}else if ($(holderRight).find('.annotator-hl').length > 0){
$($(holderRight).find('.annotator-hl')[0].children[0]).appendTo(holderRight);
$(holderRight).find('.annotator-hl').remove();
}
},
editAnnotation: function(annotation,editor){
//This will be usefull when we are going to edit an annotation.
if (this._isVideoJS(annotation)){
this.hideDisplay();
var player = this.player,
editor = editor || this.annotator.editor;
//show the slider and set in the position
player.showSlider();
player.unlockSlider();
player.setValueSlider(annotation.rangeTime.start,annotation.rangeTime.end);
//show the time panel
player.showSliderPanel();
//set the editor over the range slider
this._setOverRS(editor.element);
editor.checkOrientation();
//set the VideoJS variable
editor.VideoJS = player.id_;
}
},
refreshDisplay: function(){
console.log("loadingAnnotations");
var count = 0,
allannotations = this.annotator.plugins['Store'].annotations;
//Sort by date the Array
this._sortByDate(allannotations);
//reset the panel
$(this.AnDisplay.el_).find('span').remove();//remove the last html items
$(this.player.el_).find('.vjs-anpanel-annotation .annotation').remove();//remove a deleted annotation without span wrapper
for (var item in allannotations) {
var an = allannotations[item];
//check if the annotation is a video annotation
if (this._isVideoJS(an)){
var div = document.createElement('div'),
span = document.createElement('span'),
start = this.rs._percent(an.rangeTime.start) * 100,
end = this.rs._percent(an.rangeTime.end) * 100,
width;
span.appendChild(div);
span.className = "annotator-hl";
width = Math.min(100, Math.max(0.2, end - start));
div.className = "annotation";
div.id = count;
div.style.top = count+"em";
div.style.left = start+'%';
div.style.width = width+'%';
div.start = an.rangeTime.start;
div.end = an.rangeTime.end;
this.AnDisplay.el_.appendChild(span);
//detect point annotations
if(videojs.round(start,0)==videojs.round(end,0)){
$(div).css('width','');
$(div).addClass("point");
}
//Set the object in the div
$.data(span, 'annotation', an);
//Add the highlights to the annotation
an.highlights = $(span);
count++;
}
};
var start = this.rs._seconds(parseFloat(this.rsdl.el_.style.left)/100),
end = this.rs._seconds(parseFloat(this.rsdr.el_.style.left)/100);
this.showBetween(start,end,this.rsdl.include,this.rsdr.include);
},
showBetween: function (start,end,includeLeft,includeRight){
var duration = this.player.duration(),
start = start || 0,
end = end || duration,
annotationsHTML = $.makeArray($(this.player.el_).find('.vjs-anpanel-annotation .annotator-hl')),
count = 0;
for (var index in annotationsHTML){
var an = $.data(annotationsHTML[index], 'annotation'),
expressionLeft = includeLeft?(an.rangeTime.end >= start):(an.rangeTime.start >= start),
expressionRight = includeRight?(an.rangeTime.start <= end):(an.rangeTime.end <= end);
if (this._isVideoJS(an) && expressionLeft && expressionRight && typeof an.highlights[0]!='undefined'){
var annotationHTML = an.highlights[0].children[0];
annotationHTML.style.marginTop = (-1*parseFloat(annotationHTML.style.top)+count) + 'em';
$(an.highlights[0]).show();
count++;
}else if(this._isVideoJS(an) && typeof an.highlights[0]!='undefined'){
$(an.highlights[0]).hide();
an.highlights[0].children[0].style.marginTop = '';
}
}
//Set the times in the scroll time panel
this.backDSTime.setTimes();
},
setposBigNew: function(pos){
var pos = pos || 'ul',
el = this.player.BigNewAnnotation.el_;
videojs.removeClass(el, 'ul');
videojs.removeClass(el, 'ur');
videojs.removeClass(el, 'c');
videojs.removeClass(el, 'bl');
videojs.removeClass(el, 'br');
videojs.addClass(el, pos);
},
pressedKey: function (key){
var player = this.player,
rs = this.player.rs;
if (typeof key!='undefined' && key==73){ //-- i key
this._reset();
//show slider
this.rs.show();
//hide other elements
this.rs._reset();
this.rs.setValue(0,player.currentTime());
this.rs.right.el_.style.visibility = 'hidden';
this.rs.tpr.el_.style.visibility = 'hidden';
this.rs.ctpr.el_.style.visibility = 'hidden';
this.rs.bar.el_.style.visibility = 'hidden';
this.lastStartbyKey = player.currentTime();
}else if (typeof key!='undefined' && key==79){ //-- o key
if (this.rs.bar.el_.style.visibility == 'hidden'){//the last action was to type the i key
var start = this.lastStartbyKey!='undefined'?this.lastStartbyKey:0;
this.newan(start,player.currentTime());
}else{
this.newan(player.currentTime(),player.currentTime());
}
}
},
refreshDesignPanel: function(){
var player = this.player,
emtoPx = parseFloat($(this.backDSBar.el_).css('width')),
playerHeight = parseFloat($(player.el_).css('height')),
controlBarHeight = parseFloat($(player.controlBar.el_).css('height')),
newHeight = (playerHeight - controlBarHeight)/emtoPx-5;
/*this.BackAnDisplay.el_.style.height = this.backDSBar.el_.style.height = (this.options.NumAnnotations+'em');
this.BackAnDisplay.el_.style.top = this.backDSBar.el_.style.top = "-"+(this.options.NumAnnotations+3+'em');
this.BackAnDisplayScroll.el_.children[0].style.top = "-"+(this.options.NumAnnotations+5+'em');
this.backDSTime.el_.children[0].style.top = "-"+(this.options.NumAnnotations+5+'em');*/
this.BackAnDisplay.el_.style.height = this.backDSBar.el_.style.height = (newHeight+'em');
this.BackAnDisplay.el_.style.top = this.backDSBar.el_.style.top = "-"+(newHeight+3+'em');
this.BackAnDisplayScroll.el_.children[0].style.top = "-"+(newHeight+5+'em');
this.backDSTime.el_.children[0].style.top = "-"+(newHeight+5+'em');
},
_reset: function(){
//Hide all the components
this.hideDisplay();
this.hideAnnotation();
this.hideStatistics();
this.player.annotator.adder.hide();
this.player.annotator.editor.hide();
this.player.annotator.viewer.hide();
//make visible all the range slider element that maybe were hidden in pressedKey event
this.rs.right.el_.style.visibility = '';
this.rs.tpr.el_.style.visibility = '';
this.rs.ctpr.el_.style.visibility = '';
this.rs.bar.el_.style.visibility = '';
//by default the range slider must be unlocked
this.rs.unlock();
//whether there is a playing selection
this.rs.bar.suspendPlay();
//refresh the design
this.refreshDesignPanel();
},
_setOverRS: function(elem){
var annotator = this.player.annotator,
wrapper = $('.annotator-wrapper')[0],
positionLeft = videojs.findPosition(this.rs.left.el_),
positionRight = videojs.findPosition(this.rs.right.el_),
positionAnnotator = videojs.findPosition(wrapper),
positionAdder = {};
elem[0].style.display = 'block'; //Show the adder
if (this.player.isFullScreen){
positionAdder.top = positionLeft.top;
positionAdder.left = positionLeft.left + (positionRight.left - positionLeft.left) / 2;
}else{
positionAdder.left = positionLeft.left + (positionRight.left - positionLeft.left) / 2 - positionAnnotator.left;
positionAdder.top = positionLeft.top - positionAnnotator.top;
}
elem.css(positionAdder);
},
_onMouseDownRS: function(event){
event.preventDefault();
//videojs.blockTextSelection();
if(!this.rs.options.locked) {
videojs.on(document, "mousemove", videojs.bind(this,this._onMouseMoveRS));
videojs.on(document, "mouseup", videojs.bind(this,this._onMouseUpRS));
}
},
_onMouseMoveRS: function(event) {
var player = this.player,
annotator = player.annotator,
rs = player.rangeslider;
annotator.editor.element[0].style.display = 'none';
rs.show();
this._setOverRS(annotator.adder);
},
_onMouseUpRS: function(event){
videojs.off(document, "mousemove", this._onMouseMoveRS, false);
videojs.off(document, "mouseup", this._onMouseUpRS, false);
var player = this.player,
annotator = player.annotator,
rs = player.rangeslider;
annotator.editor.element[0].style.display = 'block';
this._setOverRS(annotator.editor.element);
},
_sumPercent: function(seconds,percent) {
//the percentage is in %
var duration = this.player.duration();
var seconds = seconds || 0;
var percent = percent || 10;
percent = Math.min(100, Math.max(0, percent));
if(isNaN(duration)) {
return 0;
}
return Math.min(duration, Math.max(0, seconds + duration * percent / 100));
},
//Detect if we are creating or editing a video-js annotation
_EditVideoAn: function (){
var annotator = this.annotator,
isOpenVideojs = (typeof this.player != 'undefined'),
VideoJS = annotator.editor.VideoJS;
return (isOpenVideojs && typeof VideoJS!='undefined' && VideoJS!==-1);
},
//Detect if the annotation is a video-js annotation
_isVideoJS: function (an){
var player = this.player,
rt = an.rangeTime,
isOpenVideojs = (typeof this.player != 'undefined'),
isVideo = (typeof an.media!='undefined' && (an.media=='video' || an.media=='audio')),
isContainer = (typeof an.target!='undefined' && an.target.container==player.id_ ),
isNumber = (typeof rt!='undefined' && !isNaN(parseFloat(rt.start)) && isFinite(rt.start) && !isNaN(parseFloat(rt.end)) && isFinite(rt.end)),
isSource = false;
if(isContainer){
//Compare without extension
var isYoutube = (isOpenVideojs && typeof this.player.techName!='undefined')?(this.player.techName == 'Youtube'):false,
targetSrc = isYoutube?an.target.src:an.target.src.substring(0,an.target.src.lastIndexOf(".")),
playerSrc = isYoutube?player.options_.sources[0].src:player.options_.sources[0].src.substring(0,player.options_.sources[0].src.lastIndexOf("."));
isSource = (targetSrc == playerSrc);
}
return (isOpenVideojs && isVideo && isContainer && isSource && isNumber);
},
_sortByDate: function (annotations,type){
var type = type || 'asc'; //asc => The value [0] will be the most recent date
annotations.sort(function(a,b){
a = new Date(typeof a.updated!='undefined'?createDateFromISO8601(a.updated):'');
b = new Date(typeof b.updated!='undefined'?createDateFromISO8601(b.updated):'');
if (type == 'asc')
return b<a?-1:b>a?1:0;
else
return a<b?-1:a>b?1:0;
});
}
};
//----------------CREATE new Components for video-js----------------//
//--Charge the new Component into videojs
videojs.ControlBar.prototype.options_.children.AnContainerButtons={}; //Container with the css for the buttons
videojs.ControlBar.prototype.options_.children.BackAnDisplay={}; //Range Slider Time Bar
videojs.ControlBar.prototype.options_.children.BackAnDisplayScroll={}; //Range Slider Time Bar
videojs.options.children.BigNewAnnotation={}; //Big Button New Annotation
//-- Player--> BigNewAnnotation
/**
* Create a New Annotation with big Button
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.BigNewAnnotation = videojs.Button.extend({
/** @constructor */
init: function(player, options){
videojs.Button.call(this, player, options);
}
});
videojs.BigNewAnnotation.prototype.init_ = function(){
this.an = this.player_.annotations;
//Hide Button if the user has selected readOnly in the Annotator options
var opts = this.an.options.optionsAnnotator;
if (typeof opts!='undefined' && typeof opts.readOnly!='undefined' && opts.readOnly)
this.hide();
};
videojs.BigNewAnnotation.prototype.createEl = function(){
return videojs.Button.prototype.createEl.call(this, 'div', {
className: 'vjs-big-new-annotation vjs-menu-button vjs-control',
innerHTML: '<div class="vjs-big-menu-button vjs-control">A</div>',
title: 'New Annotation',
});
};
videojs.BigNewAnnotation.prototype.onClick = function(){
this.an.newan();
};
//-- Player--> ControlBar--> AnContainerButtons
/**
* Container for the button CSS
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.AnContainerButtons = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.AnContainerButtons.prototype.init_ = function(){};
videojs.AnContainerButtons.prototype.options_ = {
children: {
'ShowStatistics': {},
'ShowAnnotations': {},
'NewAnnotation': {},
}
};
videojs.AnContainerButtons.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-container-button-annotation vjs-menu-button vjs-control',
});
};
//-- Player--> ControlBar--> AnContainerButtons--> ShowStatistics
/**
* Button for show/hide the chart with statistics of the annotation's number
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.ShowStatistics = videojs.Button.extend({
/** @constructor */
init: function(player, options){
videojs.Button.call(this, player, options);
}
});
videojs.ShowStatistics.prototype.init_ = function(){
this.an = this.player_.annotations;
};
videojs.ShowStatistics.prototype.createEl = function(){
return videojs.Button.prototype.createEl.call(this, 'div', {
className: 'vjs-statistics-annotation vjs-menu-button vjs-control',
title: 'Show the Statistics',
});
};
videojs.ShowStatistics.prototype.onClick = function(){
if (!this.an.options.showStatistics) this.an.showStatistics();
else this.an.hideStatistics();
};
//-- Player--> ControlBar--> AnContainerButtons--> ShowAnnotations
/**
* Button for show/hide the annotation panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.ShowAnnotations = videojs.Button.extend({
/** @constructor */
init: function(player, options){
videojs.Button.call(this, player, options);
}
});
videojs.ShowAnnotations.prototype.init_ = function(){
this.an = this.player_.annotations;
};
videojs.ShowAnnotations.prototype.createEl = function(){
return videojs.Button.prototype.createEl.call(this, 'div', {
className: 'vjs-showannotations-annotation vjs-menu-button vjs-control',
title: 'Show Annotations',
});
};
videojs.ShowAnnotations.prototype.onClick = function(){
if (!this.an.options.showDisplay) this.an.showDisplay();
else this.an.hideDisplay();
};
//-- Player--> ControlBar--> AnContainerButtons--> NewAnnotation
/**
* Create a New Annotation
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.NewAnnotation = videojs.Button.extend({
/** @constructor */
init: function(player, options){
videojs.Button.call(this, player, options);
}
});
videojs.NewAnnotation.prototype.init_ = function(){
this.an = this.player_.annotations;
//Hide Button if the user has selected readOnly in the Annotator options
var opts = this.an.options.optionsAnnotator;
if (typeof opts!='undefined' && typeof opts.readOnly!='undefined' && opts.readOnly)
this.hide();
};
videojs.NewAnnotation.prototype.createEl = function(){
return videojs.Button.prototype.createEl.call(this, 'div', {
className: 'vjs-new-annotation vjs-menu-button vjs-control',
title: 'New Annotation',
});
};
videojs.NewAnnotation.prototype.onClick = function(){
this.an.newan();
};
//-- Player--> ControlBar--> BackAnDisplay
/**
* The background annotations panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.BackAnDisplay = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.BackAnDisplay.prototype.init_ = function(){
this.an = this.player_.annotations
self = this;
//Fix error resizing the display panel. The scroll always went up.
$(this.el_).watch('font-size', function(){
self.an.backDSBarSel.setPosition(self.an.BackAnDisplayScroll.currentValue,false);
});
};
videojs.BackAnDisplay.prototype.options_ = {
children: {
'RangeSelectorDisplay': {},
'AnDisplay': {},
'AnStat': {},
}
};
videojs.BackAnDisplay.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-back-anpanel-annotation',
});
};
//-- Player--> ControlBar--> BackAnDisplay--> RangeSelectorDisplay
/**
* The selector to show the annotations in a time selection
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.RangeSelectorDisplay = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
}
});
videojs.RangeSelectorDisplay.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.an = this.player_.annotations;
var duration = this.an.player.duration();
this.start = 0;
this.end = duration;
//set the selection area in the extreme position
this.setPosition(0,0,false);
this.setPosition(1,this.rs._percent(duration),false);
};
videojs.RangeSelectorDisplay.prototype.options_ = {
children: {
'RangeSelectorLeft': {},
'RangeSelectorRight': {},
'RangeSelectorBar': {},
}
};
videojs.RangeSelectorDisplay.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-rangeselector-anpanel-annotation',
});
};
videojs.RangeSelectorDisplay.prototype.onMouseDown = function(event) {
event.preventDefault();
//videojs.blockTextSelection();
videojs.on(document, "mousemove", videojs.bind(this,this.onMouseMove));
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
videojs.removeClass(this.an.rsdb.el_,'disable');
};
videojs.RangeSelectorDisplay.prototype.onMouseUp = function(event) {
videojs.off(document, "mousemove", this.onMouseMove, false);
videojs.off(document, "mouseup", this.onMouseUp, false);
videojs.addClass(this.an.rsdb.el_,'disable');
};
videojs.RangeSelectorDisplay.prototype.onMouseMove = function(event) {
var left = this.calculateDistance(event);
if (this.an.rsdl.pressed)
this.setPosition(0,left);
else if (this.an.rsdr.pressed)
this.setPosition(1,left);
//move the frame to the position of the arrow
this.an.player.currentTime(this.rs._seconds(left));
};
videojs.RangeSelectorDisplay.prototype.calculateDistance = function(event){
var rstbX = this.getRSTBX();
var rstbW = this.getRSTBWidth();
var handleW = this.getWidth();
// Adjusted X and Width, so handle doesn't go outside the bar
rstbX = rstbX + (handleW / 2);
rstbW = rstbW - handleW;
// Percent that the click is through the adjusted area
return Math.max(0, Math.min(1, (event.pageX - rstbX) / rstbW));
};
videojs.RangeSelectorDisplay.prototype.getRSTBWidth = function() {
return this.el_.offsetWidth;
};
videojs.RangeSelectorDisplay.prototype.getRSTBX = function() {
return videojs.findPosition(this.el_).left;
};
videojs.RangeSelectorDisplay.prototype.getWidth = function() {
var arrow = $(this.an.rsdl.el_).find('.vjs-selector-arrow')[0];
return arrow.offsetWidth;//does not matter left or right
};
videojs.RangeSelectorDisplay.prototype.setPosition = function(index,left,changeTime) {
//index = 0 for left side, index = 1 for right side
var index = index || 0,
changeTime = typeof changeTime!='undefined'?changeTime:true;
// Check for invalid position
if(isNaN(left))
return false;
// Check index between 0 and 1
if(!(index === 0 || index === 1))
return false;
// Alias
var ObjLeft = this.an.rsdl.el_,
ObjRight = this.an.rsdr.el_,
Obj = this.an[index === 0 ? 'rsdl' : 'rsdr'].el_;
//Check if left arrow is over the right arrow
if ((index === 0 ?this.updateLeft(left):this.updateRight(left))){
if (index===1){//right
Obj.style.left = (left * 100)+'%';
Obj.style.width = ((1-left) * 100)+'%';
}else{//left
Obj.style.left = (left * 100)+'%';
Obj.style.width = ((left) * 100)+'%';
}
this[index === 0 ? 'start' : 'end'] = this.rs._seconds(left);
//Fix the problem when you press the button and the two arrow are underhand
//left.zIndex = 10 and right.zIndex=20. This is always less in this case:
if (index === 0 && (left * 100) >= 90)
$(ObjLeft).find('.vjs-selector-arrow')[0].style.zIndex = 25;
else
$(ObjLeft).find('.vjs-selector-arrow')[0].style.zIndex = 10;
//-- Panel
/*var MaxP,MinP,MaxDisP;
MaxP = this.player_.isFullScreen?96:92;
MinP = this.player_.isFullScreen?0.1:0.5;
MaxDisP = this.player_.isFullScreen?3.75:7.5;*/
var rsdbl = this.an.rsdbl.el_,
rsdbr = this.an.rsdbr.el_,
distance = parseFloat(ObjRight.style.left)-parseFloat(ObjLeft.style.left);
if (index===0)
rsdbl.children[0].innerHTML = videojs.formatTime(this.rs._seconds(left));
else
rsdbr.children[0].innerHTML = videojs.formatTime(this.rs._seconds(left));
if (typeof distance!='undefined' && distance <= 12.5){
if(parseFloat(ObjLeft.style.left)<7){
rsdbl.style.top = (-1.5) + 'em';rsdbl.style.left = 1 + 'em';
}else{
rsdbl.style.left = (-2.5) + 'em';rsdbl.style.top = '';
}
if(parseFloat(ObjRight.style.left)>93){
rsdbr.style.top = (-1.5) + 'em';rsdbr.style.right = 1 + 'em';
}else{
rsdbr.style.right = (-2.5) + 'em';rsdbr.style.top = '';
}
}else{
rsdbl.style.left = 1 + 'em';
rsdbr.style.right = 1 + 'em';
rsdbl.style.top = '';
rsdbr.style.top = '';
}
var start = this.rs._seconds(parseFloat(ObjLeft.style.left)/100),
end = this.rs._seconds(parseFloat(ObjRight.style.left)/100);
if(changeTime)
this.an.showBetween(start,end,this.an.rsdl.include,this.an.rsdr.include);
}
return true;
};
videojs.RangeSelectorDisplay.prototype.updateLeft = function(left) {
var rightVal = this.an.rsdr.el_.style.left!=''?this.an.rsdr.el_.style.left:100;
var right = parseFloat(rightVal) / 100,
bar = this.an.rsdb.el_;
var width = videojs.round((right - left),this.an.updatePrecision); //round necessary for not get 0.6e-7 for example that it's not able for the html css width
//(right+0.00001) is to fix the precision of the css in html
if(left <= (right+0.00001)) {
bar.style.left = (left * 100) + '%';
bar.style.width = (width * 100) + '%';
return true;
}
return false;
};
videojs.RangeSelectorDisplay.prototype.updateRight = function(right) {
var leftVal = this.an.rsdl.el_.style.left!=''?this.an.rsdl.el_.style.left:0;
var left = parseFloat(leftVal) / 100,
bar = this.an.rsdb.el_;
var width = videojs.round((right - left),this.an.updatePrecision); //round necessary for not get 0.6e-7 for example that it's not able for the html css width
//(right+0.00001) is to fix the precision of the css in html
if((right+0.00001) >= left) {
bar.style.width = (width * 100) + '%';
bar.style.left = ((right - width) * 100) + '%';
return true;
}
return false;
};
//-- Player--> ControlBar--> BackAnDisplay--> RangeSelectorDisplay--> RangeSelectorLeft
/**
* Left Time selector
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.RangeSelectorLeft = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
this.on('dblclick', this.ondblclick);
this.pressed = false;//to know when is mousedown
this.include = true;//to know when we want to include the boundary time in the selection or not
}
});
videojs.RangeSelectorLeft.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.an = this.player_.annotations;
videojs.addClass(this.el_, 'include');
};
videojs.RangeSelectorLeft.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-leftselector-anpanel-annotation',
innerHTML: '<div class="vjs-selector-arrow" title="Left Annotation Selector"></div><div class="vjs-leftselector-back"></div>'
});
};
videojs.RangeSelectorLeft.prototype.onMouseDown = function(event) {
event.preventDefault();
//videojs.blockTextSelection();
this.pressed = true;
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
videojs.addClass(this.el_, 'active');
videojs.addClass(this.el_.parentNode, 'active');
};
videojs.RangeSelectorLeft.prototype.onMouseUp = function(event) {
videojs.off(document, "mouseup", this.onMouseUp, false);
videojs.removeClass(this.el_, 'active');
videojs.removeClass(this.el_.parentNode, 'active');
this.pressed = false;
};
videojs.RangeSelectorLeft.prototype.ondblclick = function(event) {
if (this.include){
this.include = false;
videojs.removeClass(this.el_, 'include');
}else{
this.include = true;
videojs.addClass(this.el_, 'include');
}
var left = this.an.rsd.calculateDistance(event);
this.an.rsd.setPosition(0,left);
};
//-- Player--> ControlBar--> BackAnDisplay--> RangeSelectorDisplay--> RangeSelectorRight
/**
* Right Time selector
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.RangeSelectorRight = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
this.on('dblclick', this.ondblclick);
this.pressed = false;//to know when is mousedown
this.include = true;//to know when we want to include the boundary time in the selection or not
}
});
videojs.RangeSelectorRight.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.an = this.player_.annotations;
videojs.addClass(this.el_, 'include');
};
videojs.RangeSelectorRight.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-rightselector-anpanel-annotation',
innerHTML: '<div class="vjs-selector-arrow" title="Right Annotation Selector"></div><div class="vjs-rightselector-back"></div>'
});
};
videojs.RangeSelectorRight.prototype.onMouseDown = function(event) {
event.preventDefault();
//videojs.blockTextSelection();
this.pressed = true;
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
videojs.addClass(this.el_, 'active');
videojs.addClass(this.el_.parentNode, 'active');
};
videojs.RangeSelectorRight.prototype.onMouseUp = function(event) {
videojs.off(document, "mouseup", this.onMouseUp, false);
videojs.removeClass(this.el_, 'active');
videojs.removeClass(this.el_.parentNode, 'active');
this.pressed = false;
};
videojs.RangeSelectorRight.prototype.ondblclick = function(event) {
if (this.include){
this.include = false;
videojs.removeClass(this.el_, 'include');
}else{
this.include = true;
videojs.addClass(this.el_, 'include');
}
var left = this.an.rsd.calculateDistance(event);
this.an.rsd.setPosition(1,left);
};
//-- Player--> ControlBar--> BackAnDisplay--> RangeSelectorDisplay--> RangeSelectorBar
/**
* Bar to display the selected Time
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.RangeSelectorBar = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.RangeSelectorBar.prototype.init_ = function(){
videojs.addClass(this.el_,'disable');
};
videojs.RangeSelectorBar.prototype.options_ = {
children: {
'RangeSelectorBarL': {},
'RangeSelectorBarR': {},
}
};
videojs.RangeSelectorBar.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-barselector-anpanel-annotation',
});
};
//-- Player--> ControlBar--> BackAnDisplay--> RangeSelectorDisplay--> RangeSelectorBar--> RangeSelectorBarL
/**
* This is the left time panel for RangeSelectorBar
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.RangeSelectorBarL = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.RangeSelectorBarL.prototype.init_ = function(){};
videojs.RangeSelectorBarL.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-barselector-left',
innerHTML: '<span class="vjs-time-text">00:00</span>',
});
};
//-- Player--> ControlBar--> BackAnDisplay--> RangeSelectorDisplay--> RangeSelectorBar--> RangeSelectorBarR
/**
* This is the right time panel for RangeSelectorBar
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.RangeSelectorBarR = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.RangeSelectorBarR.prototype.init_ = function(){};
videojs.RangeSelectorBarR.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-barselector-right',
innerHTML: '<span class="vjs-time-text">00:00</span>'
});
};
//-- Player--> ControlBar--> BackAnDisplay--> AnDisplay
/**
* Show the annotations in a panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.AnDisplay = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
this.on('mouseover', this.onMouseOver);
}
});
videojs.AnDisplay.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.an = this.player_.annotations;
this.transition = false;
};
videojs.AnDisplay.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-anpanel-annotation',
});
};
videojs.AnDisplay.prototype.onMouseDown = function(event){
var elem = $(event.target).parents('.annotator-hl').andSelf(),
_self = this;
if (elem.hasClass("annotator-hl")){
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
//Clone the bar box to make the animation
var boxup = document.createElement('div'),
ElemTop = parseFloat(elem[1].style.top),
ElemMargin = parseFloat(elem[1].style.marginTop),
emtoPx = parseFloat($(elem[1]).css('height')),
isPoint = $(elem[1]).hasClass("point");
boxup.className = isPoint?"boxup-dashed-line point":"boxup-dashed-line";
boxup.style.left = elem[1].style.left;
boxup.style.width = elem[1].style.width;
boxup.style.top = (ElemTop+ElemMargin-this.el_.scrollTop/emtoPx)+'em';
elem[0].parentNode.parentNode.appendChild(boxup);
}
}
videojs.AnDisplay.prototype.onMouseUp = function(event){
if (typeof this.lastelem == 'undefined')
return false;
var elem = this.lastelem,
_self = this;
if (elem.hasClass("annotator-hl")){
var annotation = elem.map(function() {
return $(this).data("annotation");
})[0];
var displayHeight = (-1)*parseFloat($(this.el_).parent()[0].style.top),
emtoPx = parseFloat($(elem[1]).css('height'));
if (typeof $(elem).parent().parent().find('.boxup-dashed-line')[0]!='undefined'){
$(elem).parent().parent().find('.boxup-dashed-line')[0].style.top=(displayHeight-2)+'em';
}
this.an.player.pause();
this.transition = true;
window.setTimeout(function () {
_self.an.showAnnotation(annotation);
_self.transition = false;
_self.onCloseViewer();
}, 900);
}
videojs.off(document, "mouseup", this.onMouseUp, false);
};
videojs.AnDisplay.prototype.onMouseOver = function(event){
if (!this.transition && !this.an.rsdl.pressed && !this.an.rsdr.pressed){
var annotator = this.an.annotator;
var elem = $(event.target).parents('.annotator-hl').andSelf();
//if there is a opened annotation then show the new annotation mouse over
if (typeof annotator!='undefined' && annotator.viewer.isShown() && elem.hasClass("annotator-hl")){
//hide the last open viewer
annotator.viewer.hide();
//get the annotation over the mouse
var annotations = elem.map(function() {
return $(this).data("annotation");
});
//show the annotation in the viewer
annotator.showViewer($.makeArray(annotations), Util.mousePosition(event, annotator.wrapper[0]));
}
//create dashed line
elem.addClass('active');
if (typeof elem != 'undefined' && $(elem[1]).hasClass('annotation')){
//create dashed line under the bar
var dashed = document.createElement('div'),
boxdown = document.createElement('div'),
DisplayHeight = parseFloat(this.an.BackAnDisplay.el_.style.height),
ElemMarginTop = elem[1].style.marginTop!=''?parseFloat(elem[1].style.marginTop):0;
ElemTop = parseFloat(elem[1].style.top)+ElemMarginTop,
emtoPx = parseFloat($(elem[1]).css('height')),
isPoint = $(elem[1]).hasClass("point");
dashed.className = isPoint?'dashed-line point':'dashed-line';
boxdown.className = "box-dashed-line";
dashed.style.left = boxdown.style.left = elem[1].style.left;
dashed.style.width = boxdown.style.width = isPoint?'0':elem[1].style.width;
dashed.style.top = ((ElemTop+1)-this.el_.scrollTop/emtoPx)+'em';
dashed.style.height = ((DisplayHeight-ElemTop+2)+this.el_.scrollTop/emtoPx)+'em';//get the absolute value of the top to put in the height
boxdown.style.top = (DisplayHeight+2)+'em';
elem[0].parentNode.parentNode.appendChild(dashed);
elem[0].parentNode.parentNode.appendChild(boxdown);
$(this.player).find('.vjs-play-progress').css('z-index', 2);
$(this.player).find('.vjs-seek-handle').css('z-index', 2);
}
//store the last selected item
if (elem.hasClass("annotator-hl"))
this.lastelem = elem;
}
};
videojs.AnDisplay.prototype.onCloseViewer = function(){
if (!this.transition){
if (typeof this.lastelem != 'undefined')
this.lastelem.removeClass('active');
//remove dashed line
if (typeof this.lastelem != 'undefined' && this.lastelem.hasClass("annotator-hl")){
$(this.lastelem).parent().parent().find('.dashed-line').remove();
$(this.lastelem).parent().parent().find('.box-dashed-line').remove();
$(this.lastelem).parent().parent().find('.boxup-dashed-line').remove();
$(this.player).find('.vjs-play-progress').css('z-index', "");
$(this.player).find('.vjs-seek-handle').css('z-index', "");
}
}
};
videojs.AnDisplay.prototype.countVisibles = function() {
var AnArray = $.makeArray(this.el_.children);
//Count visible annotations in Panel
var count = 0;
for (var index in AnArray){
var an = AnArray[index];
if (an.style.display!='none'){
count++;
}
}
return count;
};
//-- Player--> ControlBar--> BackAnDisplay--> AnStat
/**
* Display with a chart with the statistics of the number of Annotations
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.AnStat = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.marginTop = 20;
this.marginBottom = 0;
}
});
videojs.AnStat.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.an = this.player_.annotations;
this.canvas = this.el_.children[0];
};
videojs.AnStat.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-anstat-annotation',
innerHTML: '<canvas class="vjs-char-anstat-annotation">Your browser does not support the HTML5 canvas tag.</canvas>',
});
};
videojs.AnStat.prototype.paintCanvas = function(){
var ctx=this.canvas.getContext("2d"),
points = this._getPoints(),
w = this._getWeights(points),
maxEn = this._getMaxArray(points,'entries'),
TotAn = this.an.AnDisplay.el_.children.length;
duration = this.an.player.duration();
//set the position of the canvas
this.canvas.style.marginTop = Math.round(this.marginTop)+'px';
//Add the Max Concentration and Number of annotations
if($(this.canvas).parent().find('.vjs-totan-anstat-annotation').length==0){
$(this.canvas).parent().append('<div class="vjs-totan-anstat-annotation">');
$(this.canvas).parent().append('<div class="vjs-maxcon-anstat-annotation">');
}
var textCanvas = $(this.canvas).parent().find('.vjs-totan-anstat-annotation')[0];
textCanvas.innerHTML = TotAn+' total annotations';
var textCanvas = $(this.canvas).parent().find('.vjs-maxcon-anstat-annotation')[0];
textCanvas.innerHTML = 'Max Annotations = '+maxEn;
//Added dashed line function to paint
if (window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype.lineTo) {
CanvasRenderingContext2D.prototype.dashedLine = function(x1, y1, x2, y2, dashLen) {
if (dashLen == undefined) dashLen = 2;
this.beginPath();
this.moveTo(x1, y1);
var dX = x2 - x1;
var dY = y2 - y1;
var dashes = Math.floor(Math.sqrt(dX * dX + dY * dY) / dashLen);
var dashX = dX / dashes;
var dashY = dY / dashes;
var q = 0;
while (q++ < dashes) {
x1 += dashX;
y1 += dashY;
this[q % 2 == 0 ? 'moveTo' : 'lineTo'](x1, y1);
}
this[q % 2 == 0 ? 'moveTo' : 'lineTo'](x2, y2);
this.stroke();
this.closePath();
};
};
//set the canvas size
this.canvas.height = this.an.AnDisplay.el_.offsetHeight-(this.marginTop+this.marginBottom);
this.canvas.width = this.an.AnDisplay.el_.offsetWidth;
ctx.beginPath();
ctx.strokeStyle="rgb(255, 163, 0)";
var lastSe = 0, lastEn = 0;
ctx.moveTo(0,maxEn*w.Y); //Move pointer to 0,0
for (var index in points){
var p = points[index],
x1 =lastSe*w.X, y1 =(maxEn-lastEn)*w.Y, // Old Point
x2 =p.second*w.X, y2 =(maxEn-p.entries)*w.Y; // New Point
//new line
ctx.lineTo(x2,y1); //move horizontally to the new point
ctx.moveTo(x2,y1); //Move pointer
ctx.lineTo(x2,y2); //move vertically to the new point height
ctx.moveTo(x2,y2); //Prepare pointer for a new instance
//new rectangle under the curve
ctx.fillStyle="rgba(0, 0, 0,0.5)";
ctx.fillRect(x1,y1,(x2-x1),(maxEn*w.Y-y1));
//store the last point
lastSe = p.second;
lastEn = p.entries;
}
//set the graphic to the end of the video
ctx.lineTo(lastSe*w.X,maxEn*w.Y);
ctx.moveTo(lastSe*w.X,maxEn*w.Y);
ctx.lineTo(duration*w.X,maxEn*w.Y);
ctx.stroke();
//dashed line down
ctx.beginPath();
ctx.dashedLine(0,maxEn*w.Y, duration*w.X, maxEn*w.Y, 8);
ctx.stroke();
//dashed line top
ctx.beginPath();
ctx.dashedLine(0,0, duration*w.X, 0, 8);
ctx.stroke();
};
videojs.AnStat.prototype._getWeights = function(points){
var weight = {},
panel = $(this.an.AnDisplay.el_),
maxSe = this.an.player.duration(),
maxEn = this._getMaxArray(points,'entries'),
panelW = parseFloat(panel.css('width')),
panelH = parseFloat(panel.css('height'))-(this.marginTop+this.marginBottom);
weight.X = maxSe != 0? (panelW / maxSe):0;
weight.Y = maxEn != 0? (panelH / maxEn):0;
return weight;
};
videojs.AnStat.prototype._getMaxArray = function(points,variable){
var highest = 0,
tmp;
for (var index in points){
tmp = points[index][variable];
if (tmp > highest) highest = tmp;
}
return highest;
};
videojs.AnStat.prototype._getPoints = function(){
var points = [],
allannotations = this.an.annotator.plugins['Store'].annotations;
for (var index in allannotations){
var an = allannotations[index],
start,end;
if (this.an._isVideoJS(an)){
start = an.rangeTime.start;
end = an.rangeTime.end;
//start
if(!this._isFound(points,start)){
points.push({
second:an.rangeTime.start,
entries:this._getNumberAnnotations(start)
});
if (an.rangeTime.start==an.rangeTime.end){//is a point
points.push({
second:an.rangeTime.end,
entries:this._getNumberAnnotations(end,true)
});
}
}
//end
if(!this._isFound(points,end)){
points.push({
second:an.rangeTime.end,
entries:this._getNumberAnnotations(end,true)
});
}
found = false;
}
}
points.sort(function(a,b) {return parseFloat(a.second) - parseFloat(b.second)});
return points;
};
videojs.AnStat.prototype._isFound = function(array,elem){
var found = false;
for (var indexA in array){
if(typeof array[indexA].second!='undefined' && array[indexA].second == elem)
found = true;
}
return found;
};
videojs.AnStat.prototype._getNumberAnnotations = function(time,end){
var num = (typeof end!='undefined' && end)?-1:0,
allannotations = this.an.annotator.plugins['Store'].annotations;
for (var index in allannotations){
var an = allannotations[index];
if (this.an._isVideoJS(an)){
if(an.rangeTime.start<=time && an.rangeTime.end>=time)
num++;
}
}
return num;
};
//-- Player--> ControlBar--> BackAnDisplayScroll
/**
* The background annotations panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.BackAnDisplayScroll = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
this.UpValue = 0.1;
this.currentValue = 0;
}
});
videojs.BackAnDisplayScroll.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.an = this.player_.annotations;
this.mousedownID = -1;
var self = this,
direction;
//Firefox
$(this.an.AnDisplay.el_).bind('DOMMouseScroll', function(e){
if(e.originalEvent.detail > 0)
direction = self.UpValue;
else
direction = -self.UpValue;
self.an.backDSBarSel.setPosition(self.getPercentScroll()+direction);
return false;
});
//IE, Opera, Safari
$(this.an.AnDisplay.el_).bind('mousewheel', function(e){
if(e.originalEvent.wheelDelta < 0)
direction = self.UpValue;
else
direction = -self.UpValue;
self.an.backDSBarSel.setPosition(self.getPercentScroll()+direction);
return false;
});
};
videojs.BackAnDisplayScroll.prototype.options_ = {
children: {
'BackAnDisplayScrollBar': {},
'BackAnDisplayScrollTime': {},
}
};
videojs.BackAnDisplayScroll.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-scroll-anpanel-annotation',
innerHTML: '<div class="vjs-up-scroll-annotation"></div><div class="vjs-down-scroll-annotation"></div>',
});
};
videojs.BackAnDisplayScroll.prototype.onMouseDown = function(event){
var self = this;
if (event.target.className == 'vjs-scrollbar-anpanel-annotation'){
//change position with a click in the scrollbar
this.an.backDSBarSel.onMouseMove(event);
return false;
}else if (event.target.className == 'vjs-scrollbar-selector'){
//change position with scrollbar
//this event is controlled by this.an.backDSBarSel
return false;
}else{
//change position with arrows
var direction = event.target.className=='vjs-down-scroll-annotation'?this.UpValue:-this.UpValue;
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
if(this.mousedownID==-1) //Prevent multimple loops!
this.mousedownID = setInterval(function () {
var pos = Math.max(0,Math.min(1,self.getPercentScroll()+direction));
self.an.backDSBarSel.setPosition(pos);
},100);
}
};
videojs.BackAnDisplayScroll.prototype.onMouseUp = function(event){
videojs.off(document, "mouseup", this.onMouseUp, false);
var self = this;
if(this.mousedownID!=-1) { //Only stop if exists
clearInterval(this.mousedownID);
self.mousedownID=-1;
}
};
videojs.BackAnDisplayScroll.prototype.getPercentScroll = function(){
var scroll = this.an.AnDisplay.el_,
maxscroll = scroll.scrollHeight-scroll.offsetHeight,
currentValue = scroll.scrollTop;
return Math.max(0, Math.min(1, maxscroll != 0 ?(currentValue / maxscroll):0));
};
videojs.BackAnDisplayScroll.prototype.setPercentScroll = function(percent){
var scroll = this.an.AnDisplay.el_,
maxscroll = scroll.scrollHeight-scroll.offsetHeight;
percent = Math.max(0, Math.min(1, percent?percent:0));
scroll.scrollTop = Math.round(maxscroll*percent);
};
//-- Player--> ControlBar--> BackAnDisplayScroll--> BackAnDisplayScrollBar
/**
* The Scroll bar for the display
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.BackAnDisplayScrollBar = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.BackAnDisplayScrollBar.prototype.init_ = function(){};
videojs.BackAnDisplayScrollBar.prototype.options_ = {
children: {
'ScrollBarSelector': {},
}
};
videojs.BackAnDisplayScrollBar.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-scrollbar-anpanel-annotation',
});
};
//-- Player--> ControlBar--> BackAnDisplayScroll--> BackAnDisplayScrollBar--> ScrollBarSelector
/**
* The Scroll bar for the display
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.ScrollBarSelector = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
}
});
videojs.ScrollBarSelector.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.an = this.player_.annotations;
videojs.addClass(this.an.backDSBar.el_, 'disable');
};
videojs.ScrollBarSelector.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-scrollbar-selector',
});
};
videojs.ScrollBarSelector.prototype.onMouseDown = function(event){
event.preventDefault();
//videojs.blockTextSelection();
videojs.on(document, "mousemove", videojs.bind(this,this.onMouseMove));
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
}
videojs.ScrollBarSelector.prototype.onMouseUp = function(event){
videojs.off(document, "mousemove", this.onMouseMove, false);
videojs.off(document, "mouseup", this.onMouseUp, false);
};
videojs.ScrollBarSelector.prototype.onMouseMove = function(event){
var top = this.calculateDistance(event);
top = this.parseMaxPercent(top); //set the max value fixing the height of the handle
this.setPosition(top);
}
videojs.ScrollBarSelector.prototype.calculateDistance = function(event){
var scrollY = this.getscrollY();
var scrollH = this.getscrollHeight();
var handleH = this.getHeight();
// Adjusted X and Width, so handle doesn't go outside the bar
scrollY = scrollY + (handleH);
scrollH = scrollH - (handleH);
// Adjusted X and Width, so handle doesn't go outside the bar
// Percent that the click is through the adjusted area
return Math.max(0, Math.min(1, (event.pageY - scrollY) / scrollH));
};
videojs.ScrollBarSelector.prototype.getscrollHeight = function() {
return this.el_.parentNode.offsetHeight;
};
videojs.ScrollBarSelector.prototype.getscrollY = function() {
return videojs.findPosition(this.el_.parentNode).top;
};
videojs.ScrollBarSelector.prototype.getHeight = function() {
return this.el_.offsetHeight;
};
videojs.ScrollBarSelector.prototype.parseMaxHeight = function(top){
var scrollH = this.getscrollHeight(),
handleH = this.getHeight(),
percent = handleH / scrollH;
return Math.max(0,Math.min(1-percent, top));
};
videojs.ScrollBarSelector.prototype.parseMaxPercent = function(top){
var scrollH = this.getscrollHeight(),
handleH = this.getHeight(),
percent = handleH / scrollH,
newTop = top;
if(top >= (1-percent))
newTop = 1;
return newTop;
};
videojs.ScrollBarSelector.prototype.setPosition = function(top,showBar){
var showBar = typeof showBar != 'undefined'?showBar:true;
// Check for invalid position
if(isNaN(top))
return false;
//Check if there is enough annotations to scroll
if(!this.isScrollable())
return false;
// Show the Scrollbar
if(showBar){
videojs.removeClass(this.an.backDSBar.el_, 'disable')
}
// Alias
var Obj = this.el_,
scroll = this.an.BackAnDisplayScroll,
scrollTime = this.an.backDSTime;
Obj.style.top = (this.parseMaxHeight(top) * 100) + '%';
scroll.setPercentScroll(top);
//Set the times in the scroll time panel
scrollTime.setTimes();
//Hide the Scrollbar in 1 sec
if(showBar){
var _self = this;
if (typeof this.Timeout!='undefined')
clearTimeout(this.Timeout);
this.Timeout = window.setTimeout(function () {
videojs.addClass(_self.an.backDSBar.el_, 'disable');
}, 1000);
}
//set current position
this.an.BackAnDisplayScroll.currentValue = top;
return true;
}
videojs.ScrollBarSelector.prototype.isScrollable = function(){
var scroll = this.an.AnDisplay.el_,
emtoPx = parseFloat($(scroll).find('.annotation').css('height')),
minTop = parseInt(scroll.offsetHeight/emtoPx);
//Count visible annotations in Panel
var count = this.an.AnDisplay.countVisibles();
return (count>minTop);
}
//-- Player--> ControlBar--> BackAnDisplayScroll--> BackAnDisplayScrollTime
videojs.BackAnDisplayScrollTime = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.BackAnDisplayScrollTime.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.an = this.player_.annotations;
};
videojs.BackAnDisplayScrollTime.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-scrolltime-anpanel-annotation',
innerHTML: '<div class="vjs-up-scrolltime-annotation"><span class="vjs-time-text"></span></div><div class="vjs-down-scrolltime-annotation"><span class="vjs-time-text"></span></div>',
});
};
videojs.BackAnDisplayScrollTime.prototype.setTimes = function(){
var AnPos = this.getAnnotationPosition(),
AnEl = this.getElements(AnPos),
AnTimes = this.getTimes(AnEl);
if(AnTimes.top!='Invalid Date'){
$(this.el_).find('.vjs-up-scrolltime-annotation')[0].style.visibility='';
$(this.el_).find('.vjs-up-scrolltime-annotation span')[0].innerHTML = AnTimes.top;
}else{
$(this.el_).find('.vjs-up-scrolltime-annotation')[0].style.visibility='hidden';
}
if(AnTimes.bottom!='Invalid Date'){
$(this.el_).find('.vjs-down-scrolltime-annotation')[0].style.visibility='';
$(this.el_).find('.vjs-down-scrolltime-annotation span')[0].innerHTML = AnTimes.bottom;
}else{
$(this.el_).find('.vjs-down-scrolltime-annotation')[0].style.visibility='hidden';
}
};
videojs.BackAnDisplayScrollTime.prototype.getAnnotationPosition = function(){
var backDSBarSel = this.an.backDSBarSel,
percent = backDSBarSel.parseMaxPercent(parseFloat(backDSBarSel.el_.style.top)/100),
scroll = this.an.AnDisplay.el_,
maxTop = scroll.scrollHeight,
minTop = scroll.offsetHeight,
maxBottom = maxTop-minTop,
minBottom = 0,
pos = {};
percent = percent || 0;
pos.top = Math.max(minTop, Math.min(maxTop,maxBottom*percent+scroll.offsetHeight));
pos.bottom = Math.max(minBottom, Math.min(maxBottom,maxBottom*percent));
return pos;
};
videojs.BackAnDisplayScrollTime.prototype.getElements = function(AnPos){
var AnPos = AnPos || {},
scroll = this.an.AnDisplay.el_,
emtoPx = parseFloat($(scroll).find('.annotation').css('height')),
maxTop = parseInt(scroll.scrollHeight/emtoPx),
minTop = parseInt(scroll.offsetHeight/emtoPx),
maxBottom = (maxTop-minTop),
minBottom = 0,
AnEl = {};
AnEl.top = Math.max(minTop, Math.min(maxTop,parseInt(AnPos.top/emtoPx)));
AnEl.bottom = Math.max(minBottom, Math.min(maxBottom,parseInt(AnPos.bottom/emtoPx)));
return AnEl;
};
videojs.BackAnDisplayScrollTime.prototype.getTimes = function(AnEl){
var AnEl = AnEl || {},
AnTimes = {},
TopEl, BottomEl, AnTop, AnBottom,
AnArray = $.makeArray(this.an.AnDisplay.el_.children);
AnEl.top = AnEl.top || 0;
AnEl.bottom = AnEl.bottom || 0;
//Get HTML Elements
var count = 0, lastEl;
for (var index in AnArray){
var an = AnArray[index];
if (an.style.display!='none'){
if(count == AnEl.bottom){
TopEl = an;
}else if(count == AnEl.top){
BottomEl = an;
}
lastEl = an;
count++;
}
}
if (typeof BottomEl=='undefined')
BottomEl = lastEl;
//Annotation Element
AnTop = typeof TopEl!='undefined'?$.data(TopEl, 'annotation'):undefined;
AnBottom = typeof BottomEl!='undefined'?$.data(BottomEl, 'annotation'):undefined;
//Update of the element
AnTimes.top = (typeof AnTop!='undefined' && typeof AnTop.updated!='undefined')?AnTop.updated:'';
AnTimes.bottom = (typeof AnBottom!='undefined' && typeof AnBottom.updated!='undefined')?AnBottom.updated:'';
//Format
AnTimes.top = new Date(AnTimes.top!=''?createDateFromISO8601(AnTimes.top):'');
AnTimes.bottom = new Date(AnTimes.bottom!=''?createDateFromISO8601(AnTimes.bottom):'');
return AnTimes;
};
})();
//----------------Plugin for Annotator to setup videojs----------------//
Annotator.Plugin.VideoJS = (function(_super) {
__extends(VideoJS, _super);
//constructor
function VideoJS() {
this.pluginSubmit = __bind(this.pluginSubmit, this);
_ref = VideoJS.__super__.constructor.apply(this, arguments);
this.__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }
return _ref;
}
VideoJS.prototype.field = null;
VideoJS.prototype.input = null;
VideoJS.prototype.pluginInit = function() {
console.log("VideoJS-pluginInit");
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
//-- Editor
this.field = this.annotator.editor.addField({
id: 'vjs-input-rangeTime-annotations',
type: 'input', //options (textarea,input,select,checkbox)
submit: this.pluginSubmit,
EditVideoAn: this.EditVideoAn
});
//Modify the element created with annotator to be an invisible span
var select = '<li><span id="vjs-input-rangeTime-annotations"></span></li>',
newfield = Annotator.$(select);
Annotator.$(this.field).replaceWith(newfield);
this.field=newfield[0];
//-- Listener for Open Video Annotator
this.initListeners();
return this.input = $(this.field).find(':input');
};
// New JSON for the database
VideoJS.prototype.pluginSubmit = function(field, annotation) {
console.log("Plug-pluginSubmit");
//Select the new JSON for the Object to save
if (this.EditVideoAn()){
var annotator = this.annotator,
index = annotator.editor.VideoJS,
player = annotator.mplayer[index],
rs = player.rangeslider,
time = rs.getValues(),
isYoutube = (player && typeof player.techName!='undefined')?(player.techName == 'Youtube'):false,
isNew = typeof annotation.media=='undefined',
ext,
type = player.options_.sources[0].type.split("/") || "";
if (typeof annotation.media == 'undefined') annotation.media = typeof type[0]!='undefined'?type[0]:"video"; // - media (by default: video)
annotation.target = annotation.target || {}; // - target
annotation.target.container = player.id_ || ""; // - target.container
annotation.target.src = player.options_.sources[0].src || ""; // - target.src (media source)
ext = (player.options_.sources[0].src.substring(player.options_.sources[0].src.lastIndexOf("."))).toLowerCase();
ext = isYoutube?'Youtube':ext; //The extension for youtube
annotation.target.ext = ext || ""; // - target.ext (extension)
annotation.rangeTime = annotation.rangeTime || {}; // - rangeTime
annotation.rangeTime.start = time.start || 0; // - rangeTime.start
annotation.rangeTime.end = time.end || 0; // - rangeTime.end
annotation.updated = new Date().toISOString(); // - updated
if (typeof annotation.created == 'undefined')
annotation.created = annotation.updated; // - created
//show the new annotation
var eventAn = isNew?"annotationCreated":"annotationUpdated";
function afterFinish(){
player.annotations.showAnnotation(annotation);
annotator.unsubscribe(eventAn, afterFinish);
};
annotator.subscribe(eventAn, afterFinish);//show after the annotation is in the back-end
}else{
if (typeof annotation.media == 'undefined')
annotation.media = "text"; // - media
annotation.updated = new Date().toISOString(); // - updated
if (typeof annotation.created == 'undefined')
annotation.created = annotation.updated; // - created
}
return annotation.media;
};
//------ Methods ------//
//Detect if we are creating or editing a video-js annotation
VideoJS.prototype.EditVideoAn = function (){
var wrapper = $('.annotator-wrapper').parent()[0],
annotator = window.annotator = $.data(wrapper, 'annotator'),
isOpenVideojs = (typeof annotator.mplayer != 'undefined'),
VideoJS = annotator.editor.VideoJS;
return (isOpenVideojs && typeof VideoJS!='undefined' && VideoJS!==-1);
};
//Detect if the annotation is a video-js annotation
VideoJS.prototype.isVideoJS = function (an){
var wrapper = $('.annotator-wrapper').parent()[0],
annotator = window.annotator = $.data(wrapper, 'annotator'),
rt = an.rangeTime,
isOpenVideojs = (typeof annotator.mplayer != 'undefined'),
isVideo = (typeof an.media!='undefined' && (an.media=='video' || an.media=='audio')),
isNumber = (typeof rt!='undefined' && !isNaN(parseFloat(rt.start)) && isFinite(rt.start) && !isNaN(parseFloat(rt.end)) && isFinite(rt.end));
return (isOpenVideojs && isVideo && isNumber);
};
//Delete Video Annotation
VideoJS.prototype._deleteAnnotation = function(an){
var target = an.target || {},
container = target.container || {},
player = this.annotator.mplayer[container];
var annotator = this.annotator,
annotations = annotator.plugins['Store'].annotations,
tot = typeof annotations !='undefined'?annotations.length:0,
attempts = 0; // max 100
//This is to watch the annotations object, to see when is deleted the annotation
var ischanged = function(){
var new_tot = annotator.plugins['Store'].annotations.length;
if (attempts<100)
setTimeout(function(){
if (new_tot != tot){
player.annotations.refreshDisplay(); //Reload the display of annotation
}else{
attempts++;
ischanged();
}
},100); //wait for the change in the annotations
};
ischanged();
player.rangeslider.hide(); //Hide Range Slider
};
//--Listeners
VideoJS.prototype.initListeners = function (){
var wrapper = $('.annotator-wrapper').parent()[0],
annotator = $.data(wrapper, 'annotator');
var EditVideoAn = this.EditVideoAn,
isVideoJS = this.isVideoJS,
self = this;
//local functions
//-- Editor
function annotationEditorHidden(editor) {
console.log("annotationEditorHidden");
if (EditVideoAn()){
var index = annotator.editor.VideoJS;
annotator.mplayer[index].rangeslider.hide(); //Hide Range Slider
annotator.an[index].refreshDisplay(); //Reload the display of annotations
}
annotator.editor.VideoJS=-1;
annotator.unsubscribe("annotationEditorHidden", annotationEditorHidden);
};
function annotationEditorShown(editor,annotation) {
console.log("annotationEditorShown");
for (var index in annotator.an){
annotator.an[index].editAnnotation(annotation,editor);
}
annotator.subscribe("annotationEditorHidden", annotationEditorHidden);
};
//-- Annotations
function annotationDeleted(annotation) {
console.log("annotationDeleted");
if (isVideoJS(annotation))
self._deleteAnnotation(annotation);
};
//-- Viewer
function hideViewer(){
for (var index in annotator.an){
annotator.an[index].AnDisplay.onCloseViewer();
}
annotator.viewer.unsubscribe("hide", hideViewer);
};
function annotationViewerShown(viewer,annotations) {
console.log("annotationViewerShown");
var separation = viewer.element.hasClass(viewer.classes.invert.y)?5:-5,
newpos = {
top: parseFloat(viewer.element[0].style.top)+separation,
left: parseFloat(viewer.element[0].style.left)
};
viewer.element.css(newpos);
//Remove the time to wait until disapear, to be more faster that annotator by default
viewer.element.find('.annotator-controls').removeClass(viewer.classes.showControls);
annotator.viewer.subscribe("hide", hideViewer);
};
//subscribe to Annotator
annotator.subscribe("annotationEditorShown", annotationEditorShown)
.subscribe("annotationDeleted", annotationDeleted)
.subscribe("annotationViewerShown", annotationViewerShown);
};
return VideoJS;
})(Annotator.Plugin);
//----------------PUBLIC OBJECT TO CONTROL THE ANNOTATIONS----------------//
//The name of the plugin that the user will write in the html
OpenVideoAnnotation = ("OpenVideoAnnotation" in window) ? OpenVideoAnnotation : {};
OpenVideoAnnotation.Annotator = function (element, options) {
//local variables
var $ = jQuery,
options = options || {};
options.optionsAnnotator = options.optionsAnnotator || {};
options.optionsVideoJS = options.optionsVideoJS || {};
options.optionsRS = options.optionsRS || {};
options.optionsOVA = options.optionsOVA || {};
//if there isn't store optinos it will create a uri and limit variables for the Back-end of Annotations
if (typeof options.optionsAnnotator.store=='undefined')
options.optionsAnnotator.store = {};
var store = options.optionsAnnotator.store;
if (typeof store.annotationData=='undefined')
store.annotationData = {};
if (typeof store.annotationData.uri=='undefined'){
var uri = location.protocol + '//' + location.host + location.pathname;
store.annotationData.store = {uri:uri};
}
if (typeof store.loadFromSearch=='undefined')
store.loadFromSearch={};
if (typeof store.loadFromSearch.uri=='undefined')
store.loadFromSearch.uri = uri;
if (typeof store.loadFromSearch.limit=='undefined')
store.loadFromSearch.limit = 10000;
//global variables
this.currentUser = null;
//-- Init all the classes --/
//Annotator
this.annotator = $(element).annotator(options.optionsAnnotator.annotator).data('annotator');
options.optionsOVA.optionsAnnotator = options.optionsAnnotator.annotator; //send the Annotator's options to OVA
//Video-JS
/*
mplayers -> Array with the html of all the video-js
mplayer -> Array with all the video-js that will be in the plugin
*/
var mplayers = $(element).find('div .video-js').toArray();
var mplayer = this.mplayer = {};
for (var index in mplayers){
var id = mplayers[index].id;
var mplayer_ = videojs(mplayers[index],options.optionsVideoJS);
//solve a problem with firefox. In Firefox the src() function is loaded before charge the optionsVideoJS, and the techOrder are not loaded
if (vjs.IS_FIREFOX && typeof options.optionsVideoJS.techOrder !='undefined'){
mplayer_.options_.techOrder = options.optionsVideoJS.techOrder;
mplayer_.src(mplayer_.options_['sources']);
}
this.mplayer[id] = mplayer_;
}
//Video-JS
this.annotator.an = {}; //annotations video-js plugin to annotator
for (var index in this.mplayer){
//to be their own options is necessary to extend deeply the options with all the childrens
this.mplayer[index].rangeslider($.extend(true, {}, options.optionsRS));
this.mplayer[index].annotations($.extend(true, {}, options.optionsOVA));
this.annotator.an[index]=this.mplayer[index].annotations;
}
//-- Experimental Global function for Open Video Annotator --//
this.setCurrentUser = function (user) {
this.currentUser = user;
this.annotator.plugins["Permissions"].setUser(user);
}
//Local function to setup the keyboard listener
var focusedPlayer = this.focusedPlayer = '',//variable to know the focused player
lastfocusPlayer = this.lastfocusPlayer = '';
function onKeyUp(e){
//skip the text areas
if (e.target.nodeName.toLowerCase()!='textarea')
mplayer[focusedPlayer].annotations.pressedKey(e.which);
};
;(this._setupKeyboard = function(){
$(document).mousedown(function(e) {
focusedPlayer = '';
//Detects if a player was click
for (var index in mplayer){
if($(mplayer[index].el_).find(e.target).length)
focusedPlayer = mplayer[index].id_;
/* Could Fix keyboard Problem in full-screen mode
if ($(e.target).hasClass('vjs-fullscreen-control'))
mplayer[index].el_.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}*/
}
//Enter if we change the focus between player or go out of the player
if(lastfocusPlayer != focusedPlayer){
$(document).off("keyup", onKeyUp);//Remove the last listener
//set the key listener
if(focusedPlayer!='')
$(document).on("keyup", onKeyUp);
}
lastfocusPlayer = focusedPlayer;
});
})(this);
//-- Activate all the plugins --//
// Annotator
if (typeof options.optionsAnnotator.auth!='undefined')
this.annotator.addPlugin('Auth', options.optionsAnnotator.auth);
if (typeof options.optionsAnnotator.permissions!='undefined')
this.annotator.addPlugin("Permissions", options.optionsAnnotator.permissions);
if (typeof options.optionsAnnotator.store!='undefined')
this.annotator.addPlugin("Store", options.optionsAnnotator.store);
if (typeof options.optionsAnnotator.highlightTags!='undefined')
this.annotator.addPlugin("HighlightTags", options.optionsAnnotator.highlightTags);
/*
this.annotator.addPlugin("Filter", {
filters: [
{
label: 'Media',
property: 'media'
}
]
});//it is obligatory to have
*/
if (typeof Annotator.Plugin["Geolocation"] === 'function')
this.annotator.addPlugin("Geolocation",options.optionsAnnotator.geolocation);
if (typeof Annotator.Plugin["Share"] === 'function')
this.annotator.addPlugin("Share",options.optionsAnnotator.share);
this.annotator.addPlugin("VideoJS"); //it is obligatory to have
if (typeof Annotator.Plugin["RichText"] === 'function')
this.annotator.addPlugin("RichText",options.optionsAnnotator.richText);
if (typeof Annotator.Plugin["Reply"] === 'function')
this.annotator.addPlugin("Reply");
if (typeof Annotator.Plugin["Flagging"] === 'function')
this.annotator.addPlugin("Flagging");
//Will be add the player and the annotations plugin for video-js in the annotator
this.annotator.mplayer = this.mplayer;
this.annotator.editor.VideoJS=-1;
this.options = options;
return this;
}
//----------------Local Functions for Open Video Annotator----------------//
//--local functions
//if the annotation is a video return true
OpenVideoAnnotation.Annotator.prototype._isVideo = function(an){
//Detect if the annotation is a Open Video Annotation
var an = an || {}
rt = an.rangeTime,
isVideo = (typeof an.media!='undefined' && (an.media=='video' || an.media=='audio')),
hasContainer = (typeof an.target!='undefined' && typeof an.target.container!='undefined' ),
isNumber = (typeof rt!='undefined' && !isNaN(parseFloat(rt.start)) && isFinite(rt.start) && !isNaN(parseFloat(rt.end)) && isFinite(rt.end));
return (isVideo && hasContainer && isNumber);
}
//if the ova has been loaded and the video is opened return true
OpenVideoAnnotation.Annotator.prototype._isloaded = function(idElem){
var loaded = typeof this.mplayer[idElem].annotations.loaded!='undefined'?true:false;
return loaded;
}
//----------------Public Functions for Open Video Annotator----------------//
//Create a new video annotation
OpenVideoAnnotation.Annotator.prototype.newVideoAn = function(idElem){
var player = this.mplayer[idElem];
if (typeof player.play!='undefined'){
player.play();
player.one('playing',function(){
player.annotations.newan();
$('html,body').animate({
scrollTop: $("#"+player.id_).offset().top},
'slow');
player.pause();
});
}
};
//Show the annotation display
OpenVideoAnnotation.Annotator.prototype.showDisplay = function(idElem){
if(this._isloaded(idElem))
return this.mplayer[idElem].annotations.showDisplay();
};
//Hide the annotation display
OpenVideoAnnotation.Annotator.prototype.hideDisplay = function(idElem){
if(this._isloaded(idElem))
return this.mplayer[idElem].annotations.hideDisplay();
};
//Refresh the annotation display
OpenVideoAnnotation.Annotator.prototype.refreshDisplay = function(idElem){
if(this._isloaded(idElem))
return this.mplayer[idElem].annotations.hideDisplay();
};
//Set the position of the big new annotation button
OpenVideoAnnotation.Annotator.prototype.setposBigNew = function(idElem,position){
if(this._isloaded(idElem))
return this.mplayer[idElem].annotations.setposBigNew(position);
};
OpenVideoAnnotation.Annotator.prototype.playTarget = function (annotationId){
var allannotations = this.annotator.plugins['Store'].annotations,
ovaId = annotationId,
mplayer = this.mplayer;
for (var item in allannotations) {
var an = allannotations[item];
if (typeof an.id!='undefined' && an.id == ovaId){//this is the annotation
if(this._isVideo(an)){//It is a video
for (var index in mplayer){
var player = mplayer[index];
if (player.id_ == an.target.container && player.tech.options_.source.src == an.target.src){
var anFound = an;
var playFunction = function(){
//Fix problem with youtube videos in the first play. The plugin don't have this trigger
if (player.techName == 'Youtube'){
var startAPI = function(){
player.annotations.showAnnotation(anFound);
}
if (player.annotations.loaded)
startAPI();
else
player.one('loadedRangeSlider', startAPI);//show Annotations once the RangeSlider is loaded
}else{
player.annotations.showAnnotation(anFound);
}
$('html,body').animate({
scrollTop: $("#"+player.id_).offset().top},
'slow');
};
if (player.paused()) {
player.play();
player.one('playing',playFunction);
}else{
playFunction();
}
return false;//this will stop the code to not set a new player.one.
}
}
}else{//It is a text
var hasRanges = typeof an.ranges!='undefined' && typeof an.ranges[0] !='undefined',
startOffset = hasRanges?an.ranges[0].startOffset:'',
endOffset = hasRanges?an.ranges[0].endOffset:'';
if(typeof startOffset!='undefined' && typeof endOffset!='undefined'){
$(an.highlights).parent().find('.annotator-hl').removeClass('api');
//change the color
$(an.highlights).addClass('api');
//animate to the annotation
$('html,body').animate({
scrollTop: $(an.highlights[0]).offset().top},
'slow');
}
}
}
}
}

1078
common/static/js/vendor/ova/rangeslider.js vendored Normal file
View File

@@ -0,0 +1,1078 @@
/*
RangeSlider v1.0 (https://github.com/danielcebrian/rangeslider-videojs)
Copyright (C) 2014 Daniel Cebri<72>n Robles
License: https://github.com/danielcebrian/rangeslider-videojs/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.
*/
//----------------Load Plugin----------------//
(function (){
//-- Load RangeSlider plugin in videojs
function RangeSlider_(options){
var player = this;
player.rangeslider=new RangeSlider(player, options);
//When the DOM and the video media is loaded
function initialVideoFinished(event) {
var plugin = player.rangeslider;
//All components will be initialize after they have been loaded by videojs
for (var index in plugin.components) {
plugin.components[index].init_();
}
if (plugin.options.hidden)
plugin.hide(); //Hide the Range Slider
if(plugin.options.locked)
plugin.lock(); //Lock the Range Slider
if(plugin.options.panel==false)
plugin.hidePanel(); //Hide the second Panel
if(plugin.options.controlTime==false)
plugin.hidecontrolTime(); //Hide the control time panel
plugin._reset();
player.trigger('loadedRangeSlider'); //Let know if the Range Slider DOM is ready
}
if (player.techName == 'Youtube'){
//Detect youtube problems
player.one('error', function(e){
switch (player.error) {
case 2:
alert("The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.");
case 5:
alert("The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.");
case 100:
alert("The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.");
break;
case 101:
alert("The owner of the requested video does not allow it to be played in embedded players.");
break;
case 150:
alert("The owner of the requested video does not allow it to be played in embedded players.");
break;
default:
alert("Unknown Error");
break;
}
});
player.on('firstplay', initialVideoFinished);
}else{
player.one('playing', initialVideoFinished);
}
console.log("Loaded Plugin RangeSlider");
}
videojs.plugin('rangeslider', RangeSlider_);
//-- Plugin
function RangeSlider(player,options){
var player = player || this;
this.player = player;
this.components = {}; // holds any custom components we add to the player
options = options || {}; // plugin options
if(!options.hasOwnProperty('locked'))
options.locked = false; // lock slider handles
if(!options.hasOwnProperty('hidden'))
options.hidden = true; // hide slider handles
if(!options.hasOwnProperty('panel'))
options.panel = true; // Show Second Panel
if(!options.hasOwnProperty('controlTime'))
options.controlTime = true; // Show Control Time to set the arrows in the edition
this.options = options;
this.init();
}
//-- Methods
RangeSlider.prototype = {
/*Constructor*/
init:function(){
var player = this.player || {};
this.updatePrecision = 3;
//position in second of the arrows
this.start = 0;
this.end = 0;
//components of the plugin
var controlBar = player.controlBar;
var seekBar = controlBar.progressControl.seekBar;
this.components.RSTimeBar = seekBar.RSTimeBar;
this.components.ControlTimePanel = controlBar.ControlTimePanel;
//Save local component
this.rstb = this.components.RSTimeBar;
this.box = this.components.SeekRSBar = this.rstb.SeekRSBar;
this.bar = this.components.SelectionBar = this.box.SelectionBar;
this.left = this.components.SelectionBarLeft = this.box.SelectionBarLeft;
this.right = this.components.SelectionBarRight = this.box.SelectionBarRight;
this.tp = this.components.TimePanel = this.box.TimePanel;
this.tpl = this.components.TimePanelLeft = this.tp.TimePanelLeft;
this.tpr = this.components.TimePanelRight = this.tp.TimePanelRight;
this.ctp = this.components.ControlTimePanel;
this.ctpl = this.components.ControlTimePanelLeft = this.ctp.ControlTimePanelLeft;
this.ctpr = this.components.ControlTimePanelRight = this.ctp.ControlTimePanelRight;
},
lock: function() {
this.options.locked = true;
this.ctp.enable(false);
if (typeof this.box != 'undefined')
videojs.addClass(this.box.el_, 'locked');
},
unlock: function() {
this.options.locked = false;
this.ctp.enable();
if (typeof this.box !='undefined')
videojs.removeClass(this.box.el_, 'locked');
},
show:function(){
this.options.hidden = false;
if (typeof this.rstb !='undefined'){
this.rstb.show();
if (this.options.controlTime)
this.showcontrolTime();
}
},
hide:function(){
this.options.hidden = true;
if (typeof this.rstb !='undefined'){
this.rstb.hide();
this.ctp.hide();
}
},
showPanel:function(){
this.options.panel = true;
if (typeof this.tp !='undefined')
videojs.removeClass(this.tp.el_, 'disable');
},
hidePanel:function(){
this.options.panel = false;
if (typeof this.tp !='undefined')
videojs.addClass(this.tp.el_, 'disable');
},
showcontrolTime:function(){
this.options.controlTime = true;
if (typeof this.ctp !='undefined')
this.ctp.show();
},
hidecontrolTime:function(){
this.options.controlTime = false;
if (typeof this.ctp !='undefined')
this.ctp.hide();
},
setValue: function(index, seconds, writeControlTime) {
//index = 0 for the left Arrow and 1 for the right Arrow. Value in seconds
var writeControlTime = typeof writeControlTime!='undefined'?writeControlTime:true;
var percent = this._percent(seconds);
var isValidIndex = (index === 0 || index === 1);
var isChangeable = !this.locked;
if(isChangeable && isValidIndex)
this.box.setPosition(index,percent,writeControlTime);
},
setValues: function(start, end, writeControlTime) {
//index = 0 for the left Arrow and 1 for the right Arrow. Value in seconds
var writeControlTime = typeof writeControlTime!='undefined'?writeControlTime:true;
this._reset();
this._setValuesLocked(start,end,writeControlTime);
},
getValues: function() { //get values in seconds
var values = {}, start, end;
start = this.start || this._getArrowValue(0);
end = this.end || this._getArrowValue(1);
return {start:start, end:end};
},
playBetween: function(start, end,showRS) {
showRS = typeof showRS == 'undefined'?true:showRS;
this.player.currentTime(start);
this.player.play();
if (showRS){
this.show();
this._reset();
}else{
this.hide();
}
this._setValuesLocked(start,end);
this.bar.activatePlay(start,end);
},
loop: function (start, end, show) {
var player = this.player;
if (player) {
player.on("pause", videojs.bind(this, function () {
this.looping = false;
}));
show = typeof show === 'undefined' ? true : show;
if (show) {
this.show();
this._reset();
}
else {
this.hide();
}
this._setValuesLocked(start, end);
this.timeStart = start;
this.timeEnd = end;
this.looping = true;
this.player.currentTime(start);
this.player.play();
this.player.on("timeupdate", videojs.bind(this, this.bar.process_loop));
}
},
_getArrowValue: function(index) {
var index = index || 0;
var duration = this.player.duration();
duration = typeof duration == 'undefined'? 0 : duration;
var percentage = this[index === 0? "left" : "right"].el_.style.left.replace("%","");
if (percentage == "")
percentage = index === 0? 0 : 100;
return videojs.round(this._seconds(percentage / 100),this.updatePrecision-1);
},
_percent: function(seconds) {
var duration = this.player.duration();
if(isNaN(duration)) {
return 0;
}
return Math.min(1, Math.max(0, seconds / duration));
},
_seconds: function(percent) {
var duration = this.player.duration();
if(isNaN(duration)) {
return 0;
}
return Math.min(duration, Math.max(0, percent * duration));
},
_reset: function() {
var duration = this.player.duration();
this.tpl.el_.style.left = '0%';
this.tpr.el_.style.left = '100%';
this._setValuesLocked(0,duration);
},
_setValuesLocked: function(start,end, writeControlTime){
var triggerSliderChange = typeof writeControlTime!='undefined';
var writeControlTime = typeof writeControlTime!='undefined'?writeControlTime:true;
if(this.options.locked) {
this.unlock();//It is unlocked to change the bar position. In the end it will return the value.
this.setValue(0,start,writeControlTime);
this.setValue(1,end,writeControlTime);
this.lock();
}else{
this.setValue(0,start,writeControlTime);
this.setValue(1,end,writeControlTime);
}
// Trigger slider change
if (triggerSliderChange) {
this._triggerSliderChange();
}
},
_checkControlTime: function(index,TextInput,timeOld){
var h = TextInput[0],
m = TextInput[1],
s = TextInput[2],
newHour = h.value,
newMin = m.value,
newSec = s.value,
obj, objNew, objOld;
index = index || 0;
if (newHour != timeOld[0]){
obj = h;
objNew = newHour;
objOld = timeOld[0];
}else if (newMin != timeOld[1]){
obj = m;
objNew = newMin;
objOld = timeOld[1];
}else if(newSec != timeOld[2]){
obj = s;
objNew = newSec;
objOld = timeOld[2];
}else{
return false;
}
var duration = this.player.duration() || 0,
durationSel;
var intRegex = /^\d+$/;//check if the objNew is an integer
if(!intRegex.test(objNew) || objNew>60){
objNew = objNew ==""?"":objOld;
}
newHour = newHour == ""?0:newHour;
newMin = newMin == ""?0:newMin;
newSec = newSec == ""?0:newSec;
durationSel = videojs.TextTrack.prototype.parseCueTime(newHour+":"+newMin+":"+newSec);
if (durationSel > duration){
obj.value = objOld;
obj.style.border = "1px solid red";
}else{
obj.value = objNew;
h.style.border = m.style.border = s.style.border = "1px solid transparent";
this.setValue(index,durationSel,false);
// Trigger slider change
this._triggerSliderChange();
}
if (index===1){
var oldTimeLeft = this.ctpl.el_.children,
durationSelLeft = videojs.TextTrack.prototype.parseCueTime(oldTimeLeft[0].value+":"+oldTimeLeft[1].value+":"+oldTimeLeft[2].value);
if (durationSel < durationSelLeft){
obj.style.border = "1px solid red";
}
}else{
var oldTimeRight = this.ctpr.el_.children,
durationSelRight = videojs.TextTrack.prototype.parseCueTime(oldTimeRight[0].value+":"+oldTimeRight[1].value+":"+oldTimeRight[2].value);
if (durationSel > durationSelRight){
obj.style.border = "1px solid red";
}
}
},
_triggerSliderChange: function(){
this.player.trigger("sliderchange");
}
};
//----------------Public Functions----------------//
//-- Public Functions added to video-js
//Lock the Slider bar and it will not be possible to change the arrow positions
videojs.Player.prototype.lockSlider = function(){
return this.rangeslider.lock();
};
//Unlock the Slider bar and it will be possible to change the arrow positions
videojs.Player.prototype.unlockSlider = function(){
return this.rangeslider.unlock();
};
//Show the Slider Bar Component
videojs.Player.prototype.showSlider = function(){
return this.rangeslider.show();
};
//Hide the Slider Bar Component
videojs.Player.prototype.hideSlider = function(){
return this.rangeslider.hide();
};
//Show the Panel with the seconds of the selection
videojs.Player.prototype.showSliderPanel = function(){
return this.rangeslider.showPanel();
};
//Hide the Panel with the seconds of the selection
videojs.Player.prototype.hideSliderPanel = function(){
return this.rangeslider.hidePanel();
};
//Show the control Time to edit the position of the arrows
videojs.Player.prototype.showControlTime = function(){
return this.rangeslider.showcontrolTime();
};
//Hide the control Time to edit the position of the arrows
videojs.Player.prototype.hideControlTime = function(){
return this.rangeslider.hidecontrolTime();
};
//Set a Value in second for both arrows
videojs.Player.prototype.setValueSlider = function(start, end){
return this.rangeslider.setValues(start, end);
};
//The video will be played in a selected section
videojs.Player.prototype.playBetween = function(start, end){
return this.rangeslider.playBetween(start, end);
};
//The video will loop between to values
videojs.Player.prototype.loopBetween = function (start, end) {
return this.rangeslider.loop(start, end);
};
//Set a Value in second for the arrows
videojs.Player.prototype.getValueSlider = function(){
return this.rangeslider.getValues();
};
//----------------Create new Components----------------//
//--Charge the new Component into videojs
videojs.SeekBar.prototype.options_.children.RSTimeBar={}; //Range Slider Time Bar
videojs.ControlBar.prototype.options_.children.ControlTimePanel={}; //Panel with the time of the range slider
//-- Design the new components
/**
* Range Slider Time Bar
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.RSTimeBar = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.RSTimeBar.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
};
videojs.RSTimeBar.prototype.options_ = {
children: {
'SeekRSBar': {}
}
};
videojs.RSTimeBar.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-timebar-RS',
innerHTML: ''
});
};
/**
* Seek Range Slider Bar and holder for the selection bars
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.SeekRSBar = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
}
});
videojs.SeekRSBar.prototype.init_ =function(){
this.rs = this.player_.rangeslider;
};
videojs.SeekRSBar.prototype.options_ = {
children: {
'SelectionBar': {},
'SelectionBarLeft': {},
'SelectionBarRight': {},
'TimePanel': {},
}
};
videojs.SeekRSBar.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-rangeslider-holder'
});
};
videojs.SeekRSBar.prototype.onMouseDown = function(event) {
event.preventDefault();
videojs.blockTextSelection();
if(!this.rs.options.locked) {
videojs.on(document, "mousemove", videojs.bind(this,this.onMouseMove));
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
}
};
videojs.SeekRSBar.prototype.onMouseUp = function(event) {
videojs.off(document, "mousemove", this.onMouseMove, false);
videojs.off(document, "mouseup", this.onMouseUp, false);
};
videojs.SeekRSBar.prototype.onMouseMove = function(event) {
var left = this.calculateDistance(event);
if (this.rs.left.pressed)
this.setPosition(0,left);
else if (this.rs.right.pressed)
this.setPosition(1,left);
//Fix a problem with the presition in the display time
var currentTimeDisplay = this.player_.controlBar.currentTimeDisplay.content;
currentTimeDisplay.innerHTML = '<span class="vjs-control-text">Current Time </span>'+vjs.formatTime(this.rs._seconds(left), this.player_.duration());
// Trigger slider change
if (this.rs.left.pressed||this.rs.right.pressed) {
this.rs._triggerSliderChange();
}
};
videojs.SeekRSBar.prototype.setPosition = function(index,left,writeControlTime) {
var writeControlTime = typeof writeControlTime!='undefined'?writeControlTime:true;
//index = 0 for left side, index = 1 for right side
var index = index || 0;
// Position shouldn't change when handle is locked
if(this.rs.options.locked)
return false;
// Check for invalid position
if(isNaN(left))
return false;
// Check index between 0 and 1
if(!(index === 0 || index === 1))
return false;
// Alias
var ObjLeft = this.rs.left.el_,
ObjRight = this.rs.right.el_,
Obj = this.rs[index === 0 ? 'left' : 'right'].el_,
tpr = this.rs.tpr.el_,
tpl = this.rs.tpl.el_,
bar = this.rs.bar,
ctp = this.rs[index === 0 ? 'ctpl' : 'ctpr'].el_;
//Check if left arrow is passing the right arrow
if ((index === 0 ?bar.updateLeft(left):bar.updateRight(left))){
Obj.style.left = (left * 100) + '%';
index === 0 ?bar.updateLeft(left):bar.updateRight(left);
this.rs[index === 0 ? 'start' : 'end'] = this.rs._seconds(left);
//Fix the problem when you press the button and the two arrow are underhand
//left.zIndex = 10 and right.zIndex=20. This is always less in this case:
if (index === 0){
if((left) >= 0.9)
ObjLeft.style.zIndex = 25;
else
ObjLeft.style.zIndex = 10;
}
//-- Panel
var TimeText = videojs.formatTime(this.rs._seconds(left)),
tplTextLegth = tpl.children[0].innerHTML.length;
var MaxP,MinP,MaxDisP;
if (tplTextLegth<=4) //0:00
MaxDisP = this.player_.isFullScreen?3.25:6.5;
else if(tplTextLegth<=5)//00:00
MaxDisP = this.player_.isFullScreen?4:8;
else//0:00:00
MaxDisP = this.player_.isFullScreen?5:10;
if(TimeText.length<=4){ //0:00
MaxP = this.player_.isFullScreen?97:93;
MinP = this.player_.isFullScreen?0.1:0.5;
}else if(TimeText.length<=5){//00:00
MaxP = this.player_.isFullScreen?96:92;
MinP = this.player_.isFullScreen?0.1:0.5;
}else{//0:00:00
MaxP = this.player_.isFullScreen?95:91;
MinP = this.player_.isFullScreen?0.1:0.5;
}
if (index===0){
tpl.style.left = Math.max(MinP,Math.min(MaxP,(left * 100 - MaxDisP/2))) + '%';
if ((tpr.style.left.replace("%","") - tpl.style.left.replace("%",""))<=MaxDisP)
tpl.style.left = Math.max(MinP,Math.min(MaxP,tpr.style.left.replace("%","")-MaxDisP)) + '%';
tpl.children[0].innerHTML = TimeText;
}else{
tpr.style.left = Math.max(MinP,Math.min(MaxP,(left * 100 - MaxDisP/2))) + '%';
if (((tpr.style.left.replace("%","")||100) - tpl.style.left.replace("%",""))<=MaxDisP)
tpr.style.left = Math.max(MinP,Math.min(MaxP,tpl.style.left.replace("%","")-0+MaxDisP)) + '%';
tpr.children[0].innerHTML = TimeText;
}
//-- Control Time
if(writeControlTime){
var time = TimeText.split(":"),
h,m,s;
if(time.length == 2){
h = 00;
m = time[0];
s = time[1];
}else{
h = time[0];
m = time[1];
s = time[2];
}
ctp.children[0].value = h;
ctp.children[1].value = m;
ctp.children[2].value = s;
}
}
return true;
};
videojs.SeekRSBar.prototype.calculateDistance = function(event){
var rstbX = this.getRSTBX();
var rstbW = this.getRSTBWidth();
var handleW = this.getWidth();
// Adjusted X and Width, so handle doesn't go outside the bar
rstbX = rstbX + (handleW / 2);
rstbW = rstbW - handleW;
// Percent that the click is through the adjusted area
return Math.max(0, Math.min(1, (event.pageX - rstbX) / rstbW));
};
videojs.SeekRSBar.prototype.getRSTBWidth = function() {
return this.el_.offsetWidth;
};
videojs.SeekRSBar.prototype.getRSTBX = function() {
return videojs.findPosition(this.el_).left;
};
videojs.SeekRSBar.prototype.getWidth = function() {
return this.rs.left.el_.offsetWidth;//does not matter left or right
};
/**
* This is the bar with the selection of the RangeSlider
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.SelectionBar = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mouseup', this.onMouseUp);
this.fired = false;
}
});
videojs.SelectionBar.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
};
videojs.SelectionBar.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-selectionbar-RS'
});
};
videojs.SelectionBar.prototype.onMouseUp = function(){
var start = this.rs.left.el_.style.left.replace("%",""),
end = this.rs.right.el_.style.left.replace("%",""),
duration = this.player_.duration(),
precision = this.rs.updatePrecision,
segStart = videojs.round(start * duration/100, precision),
segEnd = videojs.round(end * duration/100, precision);
this.player_.currentTime(segStart);
this.player_.play();
this.rs.bar.activatePlay(segStart,segEnd);
};
videojs.SelectionBar.prototype.updateLeft = function(left) {
var rightVal = this.rs.right.el_.style.left!=''?this.rs.right.el_.style.left:100;
var right = parseFloat(rightVal) / 100;
var width = videojs.round((right - left),this.rs.updatePrecision); //round necessary for not get 0.6e-7 for example that it's not able for the html css width
//(right+0.00001) is to fix the precision of the css in html
if(left <= (right+0.00001)) {
this.rs.bar.el_.style.left = (left * 100) + '%';
this.rs.bar.el_.style.width = (width * 100) + '%';
return true;
}
return false;
};
videojs.SelectionBar.prototype.updateRight = function(right) {
var leftVal = this.rs.left.el_.style.left!=''?this.rs.left.el_.style.left:0;
var left = parseFloat(leftVal) / 100;
var width = videojs.round((right - left),this.rs.updatePrecision);//round necessary for not get 0.6e-7 for example that it's not able for the html css width
//(right+0.00001) is to fix the precision of the css in html
if((right+0.00001) >= left) {
this.rs.bar.el_.style.width = (width * 100) + '%';
this.rs.bar.el_.style.left = ((right - width) * 100) + '%';
return true;
}
return false;
};
videojs.SelectionBar.prototype.activatePlay = function(start,end){
this.timeStart = start;
this.timeEnd = end;
this.suspendPlay();
this.player_.on("timeupdate", videojs.bind(this,this._processPlay));
};
videojs.SelectionBar.prototype.suspendPlay = function(){
this.fired = false;
this.player_.off("timeupdate", videojs.bind(this,this._processPlay));
};
videojs.SelectionBar.prototype._processPlay = function (){
//Check if current time is between start and end
if(this.player_.currentTime() >= this.timeStart && (this.timeEnd < 0 || this.player_.currentTime() < this.timeEnd)){
if(this.fired){ //Do nothing if start has already been called
return;
}
this.fired = true; //Set fired flag to true
}else{
if(!this.fired){ //Do nothing if end has already been called
return;
}
this.fired = false; //Set fired flat to false
this.player_.pause(); //Call end function
this.player_.currentTime(this.timeEnd);
this.suspendPlay();
}
};
videojs.SelectionBar.prototype.process_loop = function () {
var player = this.player;
if (player && this.looping) {
var current_time = player.currentTime();
if (current_time < this.timeStart || this.timeEnd > 0 && this.timeEnd < current_time) {
player.currentTime(this.timeStart);
}
}
};
/**
* This is the left arrow to select the RangeSlider
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.SelectionBarLeft = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
this.pressed = false;
}
});
videojs.SelectionBarLeft.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
};
videojs.SelectionBarLeft.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-rangeslider-handle vjs-selectionbar-left-RS',
innerHTML: '<div class="vjs-selectionbar-arrow-RS"></div><div class="vjs-selectionbar-line-RS"></div>'
});
};
videojs.SelectionBarLeft.prototype.onMouseDown = function(event) {
event.preventDefault();
videojs.blockTextSelection();
if(!this.rs.options.locked) {
this.pressed = true;
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
videojs.addClass(this.el_, 'active');
}
};
videojs.SelectionBarLeft.prototype.onMouseUp = function(event) {
videojs.off(document, "mouseup", this.onMouseUp, false);
videojs.removeClass(this.el_, 'active');
if(!this.rs.options.locked) {
this.pressed = false;
}
};
/**
* This is the right arrow to select the RangeSlider
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.SelectionBarRight = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('mousedown', this.onMouseDown);
this.pressed = false;
}
});
videojs.SelectionBarRight.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
};
videojs.SelectionBarRight.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-rangeslider-handle vjs-selectionbar-right-RS',
innerHTML: '<div class="vjs-selectionbar-arrow-RS"></div><div class="vjs-selectionbar-line-RS"></div>'
});
};
videojs.SelectionBarRight.prototype.onMouseDown = function(event) {
event.preventDefault();
videojs.blockTextSelection();
if(!this.rs.options.locked) {
this.pressed = true;
videojs.on(document, "mouseup", videojs.bind(this,this.onMouseUp));
videojs.addClass(this.el_, 'active');
}
};
videojs.SelectionBarRight.prototype.onMouseUp = function(event) {
videojs.off(document, "mouseup", this.onMouseUp, false);
videojs.removeClass(this.el_, 'active');
if(!this.rs.options.locked) {
this.pressed = false;
}
};
/**
* This is the time panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.TimePanel = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.TimePanel.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
};
videojs.TimePanel.prototype.options_ = {
children: {
'TimePanelLeft': {},
'TimePanelRight': {},
}
};
videojs.TimePanel.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-timepanel-RS'
});
};
/**
* This is the left time panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.TimePanelLeft = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.TimePanelLeft.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
};
videojs.TimePanelLeft.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-timepanel-left-RS',
innerHTML: '<span class="vjs-time-text">00:00</span>'
});
};
/**
* This is the right time panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.TimePanelRight = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.TimePanelRight.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
};
videojs.TimePanelRight.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-timepanel-right-RS',
innerHTML: '<span class="vjs-time-text">00:00</span>'
});
};
/**
* This is the control time panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.ControlTimePanel= videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
}
});
videojs.ControlTimePanel.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
};
videojs.ControlTimePanel.prototype.options_ = {
children: {
'ControlTimePanelLeft': {},
'ControlTimePanelRight': {},
}
};
videojs.ControlTimePanel.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-controltimepanel-RS vjs-control',
});
};
videojs.ControlTimePanel.prototype.enable = function(enable){
var enable = typeof enable != 'undefined'? enable:true;
this.rs.ctpl.el_.children[0].disabled = enable?"":"disabled";
this.rs.ctpl.el_.children[1].disabled = enable?"":"disabled";
this.rs.ctpl.el_.children[2].disabled = enable?"":"disabled";
this.rs.ctpr.el_.children[0].disabled = enable?"":"disabled";
this.rs.ctpr.el_.children[1].disabled = enable?"":"disabled";
this.rs.ctpr.el_.children[2].disabled = enable?"":"disabled";
};
/**
* This is the control left time panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.ControlTimePanelLeft = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('keyup', this.onKeyUp);
this.on('keydown', this.onKeyDown);
}
});
videojs.ControlTimePanelLeft.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.timeOld = {};
};
videojs.ControlTimePanelLeft.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-controltimepanel-left-RS',
innerHTML: 'Start: <input type="text" id="controltimepanel" maxlength="2" value="00"/>:<input type="text" id="controltimepanel" maxlength="2" value="00"/>:<input type="text" id="controltimepanel" maxlength="2" value="00"/>'
});
};
videojs.ControlTimePanelLeft.prototype.onKeyDown = function(event) {
this.timeOld[0] = this.el_.children[0].value;
this.timeOld[1] = this.el_.children[1].value;
this.timeOld[2] = this.el_.children[2].value;
};
videojs.ControlTimePanelLeft.prototype.onKeyUp = function(event) {
this.rs._checkControlTime(0,this.el_.children,this.timeOld);
};
/**
* This is the control right time panel
* @param {videojs.Player|Object} player
* @param {Object=} options
* @constructor
*/
videojs.ControlTimePanelRight = videojs.Component.extend({
/** @constructor */
init: function(player, options){
videojs.Component.call(this, player, options);
this.on('keyup', this.onKeyUp);
this.on('keydown', this.onKeyDown);
}
});
videojs.ControlTimePanelRight.prototype.init_ = function(){
this.rs = this.player_.rangeslider;
this.timeOld = {};
};
videojs.ControlTimePanelRight.prototype.createEl = function(){
return videojs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-controltimepanel-right-RS',
innerHTML: 'End: <input type="text" id="controltimepanel" maxlength="2" value="00"/>:<input type="text" id="controltimepanel" maxlength="2" value="00"/>:<input type="text" id="controltimepanel" maxlength="2" value="00"/>'
});
};
videojs.ControlTimePanelRight.prototype.onKeyDown = function(event) {
this.timeOld[0] = this.el_.children[0].value;
this.timeOld[1] = this.el_.children[1].value;
this.timeOld[2] = this.el_.children[2].value;
};
videojs.ControlTimePanelRight.prototype.onKeyUp = function(event) {
this.rs._checkControlTime(1,this.el_.children,this.timeOld);
};
})();

View File

@@ -0,0 +1,99 @@
/*
Reply Annotator Plugin v1.0 (https://github.com/danielcebrian/reply-annotator)
Copyright (C) 2014 Daniel Cebri<72>n Robles
License: https://github.com/danielcebrian/reply-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.
*/
// Generated by CoffeeScript 1.6.3
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.Reply = (function(_super) {
__extends(Reply, _super);
function Reply() {
this.pluginSubmit = __bind(this.pluginSubmit, this);
this.updateField = __bind(this.updateField, this);
_ref = Reply.__super__.constructor.apply(this, arguments);
return _ref;
}
Reply.prototype.field = null;
Reply.prototype.input = null;
Reply.prototype.pluginInit = function() {
console.log("Reply-pluginInit");
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
//-- Editor
this.field = this.annotator.editor.addField({
type: 'input', //options (textarea,input,select,checkbox)
load: this.updateField,
submit: this.pluginSubmit,
});
var newfield = Annotator.$('<li class="annotator-item reply-item" style="display:none"><span class="parent-annotation">0</span></li>');//reply-item is the parent value
Annotator.$(this.field).replaceWith(newfield);
this.field=newfield[0];
//-- Viewer
var newview = this.annotator.viewer.addField({
load: this.updateViewer,
});
return this.input = $(this.field).find(':input');
};
// New JSON for the database
Reply.prototype.pluginSubmit = function(field, annotation) {
var replyItem = $(this.annotator.editor.element).find(".reply-item span.parent-annotation"),
parent = replyItem.html()!=''?replyItem.html():'0';
console.log(parent);
console.log(replyItem.html());
if (parent!='0') annotation.media = 'comment';
annotation.parent = parent;//set 0, because it is not a reply
console.log(annotation.parent);
return annotation.parent;
};
Reply.prototype.updateViewer = function(field, annotation) {
var self = this,
field = $(field),
ret = field.addClass('reply-viewer-annotator').html(function() {
var string;
return self;
});
this.annotation = annotation;
//Create the actions for the buttons
return ret;
};
Reply.prototype.updateField = function(field, annotation) {
//reset parent value
var replyItem = $(this.annotator.editor.element).find(".reply-item span.parent-annotation");
return replyItem.html('0');
};
return Reply;
})(Annotator.Plugin);

View File

@@ -0,0 +1,142 @@
/*
Rich Text Annotator Plugin v1.0 (https://github.com/danielcebrian/richText-annotator)
Copyright (C) 2014 Daniel Cebri<72>n Robles
License: https://github.com/danielcebrian/richText-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.
*/
// Generated by CoffeeScript 1.6.3
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.RichText = (function(_super) {
__extends(RichText, _super);
//Default tinymce configuration
RichText.prototype.options = {
tinymce:{
selector: "li.annotator-item textarea",
plugins: "media image insertdatetime link code",
menubar: false,
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 ",
}
};
function RichText(element,options) {
_ref = RichText.__super__.constructor.apply(this, arguments);
return _ref;
};
RichText.prototype.pluginInit = function() {
console.log("RichText-pluginInit");
var annotator = this.annotator,
editor = this.annotator.editor;
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
//Editor Setup
annotator.editor.addField({
type: 'input',
load: this.updateEditor,
});
//Viewer setup
annotator.viewer.addField({
load: this.updateViewer,
});
annotator.subscribe("annotationEditorShown", function(){
$(annotator.editor.element).find('.mce-tinymce')[0].style.display='block';
$(annotator.editor.element).find('.mce-container').css('z-index',3000000000);
annotator.editor.checkOrientation();
});
annotator.subscribe("annotationEditorHidden", function(){
$(annotator.editor.element).find('.mce-tinymce')[0].style.display='none';
});
//set listener for tinymce;
this.options.tinymce.setup = function(ed) {
ed.on('change', function(e) {
//set the modification in the textarea of annotator
$(editor.element).find('textarea')[0].value = tinymce.activeEditor.getContent();
});
ed.on('Init', function(ed){
$('.mce-container').css('z-index','3090000000000000000');
});
//New button to add Rubrics of the url https://gteavirtual.org/rubric
ed.addButton('rubric', {
icon: 'rubric',
title : 'Insert a rubric',
onclick: function() {
ed.windowManager.open({
title: 'Insert a public rubric of the webside https://gteavirtual.org/rubric ',
body: [
{type: 'textbox', name: 'url', label: 'Url'}
],
onsubmit: function(e) {
// Insert content when the window form is submitted
var url = e.data.url,
name = 'irb',
irb;
//get the variable 'name' from the given url
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(url);
//the rubric id
irb = results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
if (irb==''){
ed.windowManager.alert('Error: The given webpage didn\'t have a irb variable in the url');
}else{
var iframeRubric = "<iframe src='https://gteavirtual.org/rubric/?mod=portal&scr=viewrb&evt=frame&irb="+irb+"' style='width:800px;height:600px;overflow-y: scroll;background:transparent' frameborder='0' ></iframe>";
ed.setContent(ed.getContent()+iframeRubric);
$(editor.element).find('textarea')[0].value = ed.getContent();
}
}
});
ed.insertContent('Main button');
ed.label = 'My Button';
}
});
};
tinymce.init(this.options.tinymce);
};
RichText.prototype.updateEditor = function(field, annotation) {
var text = typeof annotation.text!='undefined'?annotation.text:'';
tinymce.activeEditor.setContent(text);
$(field).remove(); //this is the auto create field by annotator and it is not necessary
}
RichText.prototype.updateViewer = function(field, annotation) {
var textDiv = $(field.parentNode).find('div:first-of-type')[0];
textDiv.innerHTML =annotation.text;
$(textDiv).addClass('richText-annotation');
$(field).remove(); //this is the auto create field by annotator and it is not necessary
}
return RichText;
})(Annotator.Plugin);

View File

@@ -0,0 +1,499 @@
/*
Share Annotation Plugin v1.0 (https://github.com/danielcebrian/share-annotator)
Copyright (C) 2014 Daniel Cebri<72>n Robles
License: https://github.com/danielcebrian/share-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.
*/
// Generated by CoffeeScript 1.6.3
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.Share = (function(_super) {
__extends(Share, _super);
//Default Share configuration
Share.prototype.options = {
shareIn:['facebook','twitter','email','google'],
getUrl:{
'facebook':function(title,link,noteText){
return 'https://www.facebook.com/sharer/sharer.php?s=100&p[url]='+link+'&p[title]='+encodeURIComponent('Open Video Annotation')+'&p[summary]='+noteText;
},
'twitter':function(title,link,noteText){
return 'https://twitter.com/intent/tweet?original_referer='+link+'&source=tweetbutton&url='+link+ "&via=OpenVideoAnnotation&text=" +encodeURIComponent('I want to share the next Open Video Annotation: ');
},
'google':function(title,link,noteText){
return 'https://plus.google.com/share?url='+link;
},
'email': function(title,link,noteText){
return 'mailto:?subject='+title+'&body='+link;
}
},
baseUrl:'', //baseUrl = the base url for all the shared annotations
};
function Share(element,options) {
if (typeof options!='undefined')
this.options.shareIn = typeof options.shareIn!='undefined'?options.shareIn:this.options.shareIn;
this.buildHTMLShareButton = __bind(this.buildHTMLShareButton, this);
this.runningAPI = __bind(this.runningAPI, this);
this.updateViewer = __bind(this.updateViewer, this);
_ref = Share.__super__.constructor.apply(this, arguments);
return _ref;
}
Share.prototype.field = null;
Share.prototype.input = null;
Share.prototype.pluginInit = function() {
console.log("Share-pluginInit");
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
//-- Editor
this.field = this.annotator.editor.addField({
type: 'input', //options (textarea,input,select,checkbox)
});
//Modify the element created with annotator to be an invisible span
var newfield = Annotator.$('<li class="annotator-item">'+this.buildHTMLShareButton('Share without saving:')+'</li>');
Annotator.$(this.field).replaceWith(newfield);
this.field=newfield[0];
//Create the actions for the buttons
this.buttonsActions(this.field,2,this.options.baseUrl); //2 is the method of the API that will be for share without saving
//Init the API plugin
var APIoptions = this.initAPI();
this.runAPI(APIoptions);
//-- Viewer
var newview = this.annotator.viewer.addField({
load: this.updateViewer,
});
return this.input = $(this.field).find(':input');
};
//Share button HTML
Share.prototype.buildHTMLShareButton = function(title,id) {
var title = title || '',
id = typeof id!='undefined'?'annotationId="'+id+'"':'',
titleText = title!=''?'<div class="share-text-annotator">'+title+'</div>':'',
shareButton = '<div class="share-button-annotator share-button" '+id+'></div>',
popup = '<div class="share-popup-overlay-bg" style="z-index:30000000000"><div class="share-popup"><div class="share-popup-items"></div><div class="close-btn">Close</div></div></div>';
return '<div class="share-container-annotator">'+titleText+shareButton+popup+'</div>';
}
//template for the design of the Share Plugin
Share.prototype.buildHTMLPopup = function(title) {
var buttons = '';
if (typeof this.options.shareIn!='undefined'){
this.options.shareIn.forEach(function(item) {
buttons += '<div class="share-'+item+'-annotator share-button">'+item.charAt(0).toUpperCase() + item.slice(1)+'</div>';
});
}
this.uri = typeof this.uri!='undefined'?this.uri:'';
var title = '<div class="share-popup-title">'+title.replace(":","")+'</div>',
copy = '<div class="share-popup-copy">Copy and Share:</div>',
uri = '<input type="text" class="share-popup-uri" onclick="javascript:this.select();" readonly="true" value="'+this.uri+'">',
popup = title + buttons + copy + uri;
return popup;
}
//Create the actions for the buttons
Share.prototype.buttonsActions = function(field,method,url) {
var share = this;
// hide popup when user clicks on close button
$(field).find('.close-btn').click(function() {
$('.share-popup-overlay-bg').hide();
});
// hides the popup if user clicks anywhere outside the container
$(field).find('.share-popup-overlay-bg').click(function() {
$('.share-popup-overlay-bg').hide();
});
// prevents the overlay from closing if user clicks inside the popup overlay
$(field).find('.share-popup').click(function() {
return false;
});
// Share button
$(field).find('.share-button-annotator.share-button').click(function() {
event.preventDefault(); // disable normal link function so that it doesn't refresh the page
var _field = this,
ovaId = $(this).attr('annotationId'),
title = method == 1?'Share':'Share without saving';
// share.uri will be useful for buildHTMLPopup functions
share.uri = share.createAPIURL(method,ovaId,url);
//display your popup
$(this).parent().find('.share-popup-overlay-bg').show();
//build buttons
$(this).parent().find('.share-popup-items').html(share.buildHTMLPopup(title));
//buttons actions
if (typeof share.options.shareIn!='undefined'){
share.options.shareIn.forEach(function(item) {
$(_field).parent().find('.share-'+item+'-annotator.share-button').click(function() {
var url = share.createAPIURL(method,ovaId,url),
title = "Sharing a annotation with Open Video Annotation";
link = encodeURIComponent(url),
noteText = share.getSource('ovaText'),
finalUrl = '';
if (method==1){
var viewer = share.annotator.viewer,
textarea = $(viewer.element).find('div:first').html();
noteText = encodeURIComponent(textarea);
}
finalUrl = typeof share.options.getUrl[item]!='undefined'?share.options.getUrl[item](title,link,noteText):'';
if(typeof share.options.getUrl[item]!='undefined')
window.open(finalUrl);
});
});
}
});
};
Share.prototype.createAPIURL = function(method,ovaId,url) {
var annotator = this.annotator,
editor = annotator.editor,
method = method || 1,
//url = location.protocol + '//' + location.host + location.pathname,
url = url || window.location.href;
url += (url.indexOf('?') >= 0)?'&':'?';
if (method === 1){
var ovaId = typeof ovaId!='undefined'?ovaId:'';
url += 'ovaId=' + ovaId;
}else if (method === 2){
var ovaStart = this.getSource('ovaStart'),
ovaEnd = this.getSource('ovaEnd'),
ovaText = this.getSource('ovaText');
url += 'ovaStart='+ ovaStart
+'&ovaEnd='+ ovaEnd
+'&ovaText='+ ovaText;
if(typeof editor.VideoJS!='undefined' && editor.VideoJS !== -1){//Video Annotation
var ovaContainer = this.getSource('ovaContainer'),
ovaSrc = this.getSource('ovaSrc');
url += '&ovaContainer='+ovaContainer
+'&ovaSrc='+ ovaSrc;
}else{//Text Annotation
var ovastartOffset = this.getSource('ovastartOffset'),
ovaendOffset = this.getSource('ovaendOffset');
url += '&ovastartOffset='+ovastartOffset
+'&ovaendOffset='+ ovaendOffset;
}
}
return url;
};
Share.prototype.getSource = function(source) {
var source = source || '';
if (source == 'ovaId') {//method 1
source=this.annotation.id;
}else{//method 2
var annotator = this.annotator,
editor = annotator.editor,
textarea = $(editor.element).find('textarea')[0];
if(source == 'ovaText')
source = textarea.value;
if (typeof editor.VideoJS!='undefined' && editor.VideoJS !== -1){//Video Annotation
if(source == 'ovaContainer')
source = editor.VideoJS;
else if(source == 'ovaSrc')
source = annotator.mplayer[editor.VideoJS].tech.options_.source.src;
else if(source == 'ovaStart')
source = annotator.mplayer[editor.VideoJS].rangeslider.getValues().start;
else if(source == 'ovaEnd')
source = annotator.mplayer[editor.VideoJS].rangeslider.getValues().end;
}else{//Text Annotation
var annotation = editor.annotation;
if(source == 'ovastartOffset')
source = annotation.ranges[0].startOffset;
else if(source == 'ovaendOffset')
source = annotation.ranges[0].endOffset;
else if(source == 'ovaStart')
source = annotation.ranges[0].start;
else if(source == 'ovaEnd')
source = annotation.ranges[0].end;
}
}
return encodeURIComponent(source);
};
Share.prototype.initAPI = function() {
console.log("initAPI");
// -- Detect API in the URL -- //
/*
The first option is to give a known id of an annotation
Example http://url.com/#id=rTcpOjIMT2aF1apDtboC-Q
*/
var API = {},
ovaId = this.getParameterByName('ovaId'), //Method 1 (Obligatory)
start = this.getParameterByName('ovaStart'), //Method 2 (Obligatory)
end = this.getParameterByName('ovaEnd'), //Method 2 (Obligatory)
container = this.getParameterByName('ovaContainer'), //Method 2 (Obligatory)
src = this.getParameterByName('ovaSrc'),//Method 2 (Obligatory)
text = this.getParameterByName('ovaText'),//Method 2
user = this.getParameterByName('ovaUser'),//Method 2
startOffset = this.getParameterByName('ovastartOffset'),//Method 2
endOffset = this.getParameterByName('ovaendOffset');//Method 2
//remove the variables from the url browser
var stripped_url = top.location.href;
if (ovaId != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovaId');
if (start != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovaStart');
if (end != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovaEnd');
if (container != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovaContainer');
if (src != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovaSrc');
if (text != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovaText');
if (user != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovaUser');
if (startOffset != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovastartOffset');
if (endOffset != '') stripped_url = this.removeVariableFromURL(stripped_url, 'ovaendOffset');
window.history.pushState("object or string", "Title", stripped_url);
// Method 1 API with the Id of the annotation
//Example: http://danielcebrian.com/annotations/demo.html?&ovaId=wtva_SjnQb2HtqppDihKug
if(ovaId != ''){
$.extend(API,{method:1,ovaId:ovaId});
}
//Method 2 API with all the parameter to load the annotation
//Example with video: http://danielcebrian.com/annotations/demo.html?ovaContainer=vid1&ovaSrc=http%3A%2F%2Fvideo-js.zencoder.com%2Foceans-clip.mp4&ovaStart=2&ovaEnd=10&ovaText=This%20is%20test&ovaUser=Test%20User
//Example with text: http://danielcebrian.com/annotations/demo.html?ovaStart=%2Fp%5B1%5D&ovaEnd=%2Fp%5B1%5D&ovastartOffset=542&ovaendOffset=572&ovaText=API
if(start!='' && end!='' && container!='' && src!=''){//video api
$.extend(API,{method:2,start:start,end:end,container:container,src:src,text:text,user:user});
}else if(start!='' && end!='' && startOffset!='' && endOffset!=''){//text api
$.extend(API,{method:2,start:start,end:end,startOffset:startOffset,endOffset:endOffset,text:text,user:user});
}
return API;
}
Share.prototype.runningAPI = function (annotations,API){
console.log("runningAPI");
var wrapper = $('.annotator-wrapper').parent()[0],
mplayer,
osda,
self=this;
//Set Annotator in wrapper to fix quick DOM
$.data(wrapper, 'annotator', self.annotator);//Set the object in the span
annotator = window.annotator = $.data(wrapper, 'annotator');
mplayer = typeof annotator.mplayer!='undefined'?annotator.mplayer:[];
osda = typeof annotator.osda!='undefined'?annotator.osda:[];
//Detect if the URL has an API element
if (typeof API!='undefined' && typeof API.method!='undefined' && (API.method=='1'||API.method=='2')) {
if(API.method=='1'){
var allannotations = annotator.plugins['Store'].annotations,
ovaId = decodeURIComponent(API.ovaId);
for (var item in allannotations) {
var an = allannotations[item];
if (typeof an.id!='undefined' && an.id == ovaId){//this is the annotation
if(self._isVideo(an)){//It is a video
if (typeof mplayer[an.target.container]!='undefined'){
var player = mplayer[an.target.container];
if (player.id_ == an.target.container){
var anFound = an;
videojs(player.id_).ready(function(){
if (player.techName != 'Youtube'){
player.preload('auto');
}
player.autoPlayAPI = anFound;
player.play();
});
}
}
}else if(self._isVideo(an)){//It is a OpenSeaDragon Annotation
if (typeof mplayer[an.target.container]!='undefined'){
var player = mplayer[an.target.container];
if (player.id_ == an.target.container){
var anFound = an;
videojs(player.id_).ready(function(){
if (player.techName != 'Youtube'){
player.preload('auto');
}
player.autoPlayAPI = anFound;
player.play();
});
}
}
}else{//It is a text
var hasRanges = typeof an.ranges!='undefined' && typeof an.ranges[0] !='undefined',
startOffset = hasRanges?an.ranges[0].startOffset:'',
endOffset = hasRanges?an.ranges[0].endOffset:'';
if(typeof startOffset!='undefined' && typeof endOffset!='undefined'){
//change the color
$(an.highlights).addClass('api');
//animate to the annotation
$('html,body').animate({
scrollTop: $(an.highlights[0]).offset().top},
'slow');
}
}
}
}
}else if (API.method=='2'){
if (typeof mplayer!='undefined'){
//variable for Video
var container = decodeURIComponent(API.container),
player = mplayer[container],
isVideo = (typeof player!='undefined' && container==player.id_),
isNumber = (!isNaN(parseFloat(API.start)) && isFinite(API.start) && !isNaN(parseFloat(API.end)) && isFinite(API.end)),
isSource = false;
if(isVideo){
//Compare without extension
var src = decodeURIComponent(API.src),
targetSrc = src.substring(0,src.lastIndexOf(".")),
playerSrc = player.tech.options_.source.src==''?player.tag.currentSrc:player.tech.options_.source.src;
playerSrc = playerSrc.substring(0,playerSrc.lastIndexOf("."))
isSource = (targetSrc == playerSrc);
}
//Open Video Annotation
if(isVideo && isNumber && isSource){
var annotation = {
rangeTime: {
start:API.start,
end:API.end
},
created: new Date().toISOString(),
updated: new Date().toISOString(),
target:{
container: container,
src: src
},
media: 'video',
text:decodeURIComponent(API.text),
user:decodeURIComponent(API.user)
};
videojs(player.id_).ready(function(){
if (player.techName != 'Youtube'){
player.preload('auto');
}
player.autoPlayAPI = annotation;
player.play();
});
}
}
//variable for text
var startOffset = API.startOffset,
endOffset = API.endOffset;
//Text Annotation
if(!isVideo && typeof startOffset!='undefined' && typeof endOffset!='undefined'){
var annotation = {
ranges: [{
start:decodeURIComponent(API.start),
end:decodeURIComponent(API.end),
startOffset:decodeURIComponent(API.startOffset),
endOffset:decodeURIComponent(API.endOffset),
}],
created: new Date().toISOString(),
updated: new Date().toISOString(),
media: 'text',
text:decodeURIComponent(API.text),
user:decodeURIComponent(API.user)
};
//show the annotation
annotator.setupAnnotation(annotation);
//to change the color
$(annotation.highlights).addClass('api');
//animate to the annotation
$('html,body').animate({
scrollTop: $(annotation.highlights[0]).offset().top},
'slow');
}
}
}
//Let know to others API that this plugin is loaded
annotator.isShareLoaded = true;
annotator.publish('shareloaded');
}
Share.prototype.runAPI = function(API) {
var self = this;
var func = function (annotations){
self.runningAPI(annotations,API);
self.annotator.unsubscribe("annotationsLoaded",func);
};
this.annotator
//-- Finished the Annotator DOM
.subscribe("annotationsLoaded",func);
}
Share.prototype._isVideo = function(an){
//Detect if the annotation is a Open Video Annotation
var an = an || {}
rt = an.rangeTime,
isVideo = (typeof an.media!='undefined' && an.media=='video'),
hasContainer = (typeof an.target!='undefined' && typeof an.target.container!='undefined' ),
isNumber = (typeof rt!='undefined' && !isNaN(parseFloat(rt.start)) && isFinite(rt.start) && !isNaN(parseFloat(rt.end)) && isFinite(rt.end));
return (isVideo && hasContainer && isNumber);
}
Share.prototype.getParameterByName = function(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
//results = regex.exec(location.search),
results = regex.exec('?'+window.location.href.split('?')[1]);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
};
Share.prototype.removeVariableFromURL = function(url_string, variable_name) {
var URL = String(url_string);
var regex = new RegExp( "\\?" + variable_name + "=[^&]*&?", "gi");
URL = URL.replace(regex,'?');
regex = new RegExp( "\\&" + variable_name + "=[^&]*&?", "gi");
URL = URL.replace(regex,'&');
URL = URL.replace(/(\?|&)$/,'');
regex = null;
return URL;
}
Share.prototype.updateViewer = function(field, annotation) {
this.annotation = annotation;
var self = this,
field = $(field),
ret = field.addClass('share-viewer-annotator').html(function() {
var string;
return self.buildHTMLShareButton('Share:',self.getSource('ovaId'));
});
//Create the actions for the buttons
this.buttonsActions(field[0],1,this.options.baseUrl); //1 is the method of the API that will be for share some annotation in the database
return ret;
};
return Share;
})(Annotator.Plugin);

View File

@@ -0,0 +1 @@
.mce-object{border:1px dotted #3a3a3a;background:#d5d5d5 url(img/object.gif) no-repeat center}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px!important;height:9px!important;border:1px dotted #3a3a3a;background:#d5d5d5 url(img/anchor.gif) no-repeat center}.mce-nbsp{background:#AAA}hr{cursor:default}.mce-match-marker{background:green;color:#fff}.mce-spellchecker-word{background:url(img/wline.gif) repeat-x bottom left;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #BBB}td.mce-item-selected,th.mce-item-selected{background-color:#39f!important}.mce-edit-focus{outline:1px dotted #333}

View File

@@ -0,0 +1 @@
body{background-color:#fff;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px;scrollbar-3dlight-color:#f0f0ee;scrollbar-arrow-color:#676662;scrollbar-base-color:#f0f0ee;scrollbar-darkshadow-color:#ddd;scrollbar-face-color:#e0e0dd;scrollbar-highlight-color:#f0f0ee;scrollbar-shadow-color:#f0f0ee;scrollbar-track-color:#f5f5f5}td,th{font-family:Verdana,Arial,Helvetica,sans-serif;font-size:11px}.mce-object{border:1px dotted #3a3a3a;background:#d5d5d5 url(img/object.gif) no-repeat center}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px!important;height:9px!important;border:1px dotted #3a3a3a;background:#d5d5d5 url(img/anchor.gif) no-repeat center}.mce-nbsp{background:#AAA}hr{cursor:default}.mce-match-marker{background:green;color:#fff}.mce-spellchecker-word{background:url(img/wline.gif) repeat-x bottom left;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #BBB}td.mce-item-selected,th.mce-item-selected{background-color:#39f!important}.mce-edit-focus{outline:1px dotted #333}

Binary file not shown.

View File

@@ -0,0 +1,175 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
This is a custom SVG font generated by IcoMoon.
<iconset grid="16"></iconset>
</metadata>
<defs>
<font id="icomoon-small" horiz-adv-x="512" >
<font-face units-per-em="512" ascent="480" descent="-32" />
<missing-glyph horiz-adv-x="512" />
<glyph class="hidden" unicode="&#xf000;" d="M0,480L 512 -32L0 -32 z" horiz-adv-x="0" />
<glyph unicode="&#xe020;" d="M 352,64l0,18.502 c 75.674,30.814, 128,96.91, 128,173.498c0,106.039-100.288,192-224,192S 32,362.039, 32,256
c0-76.588, 52.327-142.684, 128-173.498L 160,64 L 64,64 l-32,48l0-112 l 160,0 L 192,111.406 c-50.45,25.681-85.333,80.77-85.333,144.594
c0,88.366, 66.859,160, 149.333,160c 82.474,0, 149.333-71.634, 149.333-160c0-63.824-34.883-118.913-85.333-144.594L 320,0 l 160,0 L 480,112 l-32-48
L 352,64 z" />
<glyph unicode="&#xe013;" d="M 128,448l0-448 l 128,128l 128-128L 384,448 L 128,448 z M 352,85.255l-96,96l-96-96L 160,416 l 192,0 L 352,85.255 z" />
<glyph unicode="&#xe012;" d="M 463.637,364.892l-66.745,66.744C 386.34,442.188, 372.276,448, 357.293,448s-29.047-5.812-39.598-16.363l-82.746-82.745
c-21.834-21.834-21.834-57.362,0-79.196l 1.373-1.373l 33.941,33.941l-1.373,1.373c-3.066,3.066-3.066,8.247,0,11.313l 82.746,82.746
C 353.641,399.7, 356.040,400, 357.292,400s 3.651-0.299, 5.656-2.305l 66.745-66.744c 3.066-3.067, 3.066-8.249, 0.001-11.314l-82.747-82.747
c-2.004-2.004-4.403-2.304-5.655-2.304s-3.651,0.3-5.656,2.306l-1.373,1.373l-33.94-33.942l 1.371-1.371
c 10.553-10.554, 24.615-16.364, 39.6-16.364s 29.047,5.812, 39.598,16.363l 82.747,82.746C 485.47,307.53, 485.47,343.057, 463.637,364.892
zM 275.678,179.678l-33.941-33.941l 1.373-1.373c 2.004-2.004, 2.305-4.403, 2.305-5.655c0-1.253-0.299-3.651-2.303-5.657
l-82.747-82.745c-2.005-2.005-4.405-2.305-5.657-2.305s-3.652,0.3-5.657,2.305L 82.305,117.050C 80.3,119.055, 80,121.455, 80,122.707
s 0.299,3.65, 2.305,5.656l 82.745,82.744c 2.005,2.006, 4.405,2.306, 5.657,2.306s 3.652-0.3, 5.657-2.306l 1.373-1.371l 33.941,33.94
l-1.373,1.373c-10.552,10.552-24.615,16.363-39.598,16.363s-29.046-5.812-39.598-16.363l-82.744-82.743
C 37.812,151.754, 32,137.689, 32,122.707s 5.812-29.047, 16.363-39.599l 66.745-66.745C 125.661,5.812, 139.724,0, 154.707,0
s 29.046,5.812, 39.598,16.363l 82.747,82.746c 10.552,10.552, 16.361,24.615, 16.361,39.598s-5.812,29.047-16.363,39.598
L 275.678,179.678zM 400,61c-4.862,0-9.725,1.854-13.435,5.565l-64,63.999c-7.422,7.42-7.422,19.449,0,26.869
c 7.42,7.422, 19.448,7.422, 26.868,0l 64-64c 7.422-7.42, 7.422-19.448,0-26.868C 409.725,62.854, 404.862,61, 400,61zM 304,0c-8.837,0-16,7.163-16,16l0,64 c0,8.837, 7.163,16, 16,16s 16-7.163, 16-16l0-64 C 320,7.163, 312.837,0, 304,0zM 464,160l-64,0 c-8.837,0-16,7.163-16,16s 7.163,16, 16,16l 64,0 c 8.837,0, 16-7.163, 16-16S 472.837,160, 464,160zM 112,387c 4.862,0, 9.725-1.854, 13.435-5.565l 64-64c 7.421-7.42, 7.421-19.449,0-26.869c-7.42-7.422-19.449-7.422-26.869,0
l-64,64c-7.421,7.42-7.421,19.449,0,26.869C 102.275,385.146, 107.138,387, 112,387zM 208,448c 8.837,0, 16-7.163, 16-16l0-64 c0-8.837-7.163-16-16-16s-16,7.163-16,16L 192,432 C 192,440.837, 199.163,448, 208,448zM 48,288l 64,0 c 8.837,0, 16-7.163, 16-16s-7.163-16-16-16L 48,256 c-8.837,0-16,7.163-16,16S 39.163,288, 48,288z" />
<glyph unicode="&#xe011;" d="M 463.637,364.892l-66.745,66.744C 386.34,442.188, 372.276,448, 357.293,448s-29.047-5.812-39.598-16.363l-82.746-82.745
c-21.834-21.834-21.834-57.362,0-79.196l 1.373-1.373l 33.941,33.941l-1.373,1.373c-3.066,3.066-3.066,8.247,0,11.313l 82.746,82.746
C 353.641,399.7, 356.040,400, 357.292,400s 3.651-0.299, 5.656-2.305l 66.745-66.744c 3.066-3.067, 3.066-8.249, 0.001-11.314l-82.747-82.747
c-2.004-2.004-4.403-2.304-5.655-2.304s-3.651,0.3-5.656,2.306l-1.373,1.373l-33.94-33.942l 1.371-1.371
c 10.553-10.554, 24.615-16.364, 39.6-16.364s 29.047,5.812, 39.598,16.363l 82.747,82.746C 485.47,307.53, 485.47,343.057, 463.637,364.892
zM 275.678,179.678l-33.941-33.941l 1.373-1.373c 2.004-2.004, 2.305-4.403, 2.305-5.655c0-1.253-0.299-3.651-2.303-5.657
l-82.747-82.745c-2.005-2.005-4.405-2.305-5.657-2.305s-3.652,0.3-5.657,2.305L 82.305,117.050C 80.3,119.055, 80,121.455, 80,122.707
s 0.299,3.65, 2.305,5.656l 82.745,82.744c 2.005,2.006, 4.405,2.306, 5.657,2.306s 3.652-0.3, 5.657-2.306l 1.373-1.371l 33.941,33.94
l-1.373,1.373c-10.552,10.552-24.615,16.363-39.598,16.363s-29.046-5.812-39.598-16.363l-82.744-82.743
C 37.812,151.754, 32,137.689, 32,122.707s 5.812-29.047, 16.363-39.599l 66.745-66.745C 125.661,5.812, 139.724,0, 154.707,0
s 29.046,5.812, 39.598,16.363l 82.747,82.746c 10.552,10.552, 16.361,24.615, 16.361,39.598s-5.812,29.047-16.363,39.598
L 275.678,179.678zM 176,125c-4.862,0-9.725,1.855-13.435,5.564c-7.42,7.42-7.42,19.449,0,26.869l 160,160c 7.42,7.42, 19.448,7.42, 26.868,0
c 7.422-7.42, 7.422-19.45,0-26.87l-160-160C 185.725,126.855, 180.862,125, 176,125z" />
<glyph unicode="&#xe010;" d="M 288,339.337L 288,448 l 168.001-168L 288,112L 288,223.048 C 92.547,227.633, 130.5,99.5, 160,0C 16,160, 53.954,345.437, 288,339.337z" />
<glyph unicode="&#xe00f;" d="M 352,0c 29.5,99.5, 67.453,227.633-128,223.048L 224,112 L 55.999,280L 224,448l0-108.663 C 458.046,345.437, 496,160, 352,0z" />
<glyph unicode="&#xe00e;" d="M 128.214,267.637c 52.9,0, 95.786-45.585, 95.786-101.819C 224,109.586, 181.114,64, 128.214,64
c-52.901,0-95.786,45.585-95.786,101.818L 32,180.364C 32,292.829, 117.77,384, 223.572,384l0-58.182 c-36.55,0-70.913-15.13-96.758-42.602
c-4.977-5.289-9.517-10.917-13.612-16.828C 118.094,267.208, 123.105,267.637, 128.214,267.637zM 384.214,267.637c 52.9,0, 95.786-45.585, 95.786-101.819C 480,109.586, 437.114,64, 384.214,64
c-52.901,0-95.786,45.585-95.786,101.818L 288,180.364C 288,292.829, 373.77,384, 479.572,384l0-58.182 c-36.55,0-70.913-15.13-96.758-42.602
c-4.978-5.289-9.518-10.917-13.612-16.828C 374.094,267.208, 379.105,267.637, 384.214,267.637z" />
<glyph unicode="&#xe00c;" d="M 32,384L 480,384L 480,320L 32,320zM 192,192L 480,192L 480,128L 192,128zM 192,288L 480,288L 480,224L 192,224zM 32,96L 480,96L 480,32L 32,32zM 32,288L 144,208L 32,128 z" />
<glyph unicode="&#xe00d;" d="M 32,384L 480,384L 480,320L 32,320zM 32,192L 320,192L 320,128L 32,128zM 32,288L 320,288L 320,224L 32,224zM 32,96L 480,96L 480,32L 32,32zM 480,288L 368,208L 480,128 z" />
<glyph unicode="&#xe00b;" d="M 192,416L 480,416L 480,352L 192,352zM 192,256L 480,256L 480,192L 192,192zM 192,96L 480,96L 480,32L 192,32zM 160,215L 160,288L 128,288L 128,448L 64,448L 64,416L 96,416L 96,288L 64,288L 64,256L 128,256L 128,231L 64,201L 64,128L 128,128L 128,96L 64,96L 64,64L 128,64L 128,32L 64,32L 64,0L 160,0L 160,160L 96,160L 96,185 z" />
<glyph unicode="&#xe00a;" d="M 192,416L 480,416L 480,352L 192,352zM 192,256L 480,256L 480,192L 192,192zM 192,96L 480,96L 480,32L 192,32zM 64,384A32,32 1980 1 1 128,384A32,32 1980 1 1 64,384zM 64,224A32,32 1980 1 1 128,224A32,32 1980 1 1 64,224zM 64,64A32,32 1980 1 1 128,64A32,32 1980 1 1 64,64z" />
<glyph unicode="&#xe009;" d="M 444,288l-28,0 L 416,416 l 32,0 L 448,448 L 288,448 l0-32 l 32,0 l0-128 L 192,288 L 192,416 l 32,0 L 224,448 L 64,448 l0-32 l 32,0 l0-128 L 68,288 c-19.8,0-36-16.2-36-36l0-216 c0-19.8, 16.2-36, 36-36l 120,0
c 19.8,0, 36,16.2, 36,36L 224,192 l 64,0 l0-156 c0-19.8, 16.2-36, 36-36l 120,0 c 19.8,0, 36,16.2, 36,36L 480,252 C 480,271.8, 463.8,288, 444,288z M 174,32L 82,32
c-9.9,0-18,7.2-18,16s 8.1,16, 18,16l 92,0 c 9.9,0, 18-7.2, 18-16S 183.9,32, 174,32z M 272,224l-32,0 c-8.8,0-16,7.2-16,16s 7.2,16, 16,16l 32,0
c 8.8,0, 16-7.2, 16-16S 280.8,224, 272,224z M 430,32l-92,0 c-9.9,0-18,7.2-18,16s 8.1,16, 18,16l 92,0 c 9.9,0, 18-7.2, 18-16S 439.9,32, 430,32z" />
<glyph unicode="&#xe008;" d="M 352,288l0,80 c0,8.8-7.2,16-16,16l-80,0 L 256,416 c0,17.6-14.4,32-32,32l-64,0 c-17.602,0-32-14.4-32-32l0-32 L 48,384 c-8.801,0-16-7.2-16-16l0-256
c0-8.8, 7.199-16, 16-16l 112,0 l0-96 l 192,0 l 96,96L 448,288 L 352,288 z M 160,415.943c 0.017,0.019, 0.036,0.039, 0.057,0.057l 63.884,0
c 0.021-0.018, 0.041-0.038, 0.059-0.057L 224,384 l-64,0 L 160,415.943 L 160,415.943z M 96,320l0,32 l 192,0 l0-32 L 96,320 z M 352,45.255L 352,96 l 50.745,0 L 352,45.255z
M 416,128l-96,0 l0-96 L 192,32 L 192,256 l 224,0 L 416,128 z" />
<glyph unicode="&#xe031;" d="M 416,320l-96,0 l0,32 l-96,96L 32,448 l0-352 l 192,0 l0-96 l 288,0 L 512,224 L 416,320z M 416,274.745L 466.745,224L 416,224 L 416,274.745 z M 224,402.745L 274.745,352
L 224,352 L 224,402.745 z M 64,416l 128,0 l0-96 l 96,0 l0-192 L 64,128 L 64,416 z M 480,32L 256,32 l0,64 l 64,0 L 320,288 l 64,0 l0-96 l 96,0 L 480,32 z" />
<glyph unicode="&#xe007;" d="M 432.204,144.934c-23.235,23.235-53.469,34.002-80.541,31.403L 320,208l 96,96c0,0, 64,64,0,128L 256,272L 96,432
c-64-64,0-128,0-128l 96-96l-31.663-31.663c-27.072,2.599-57.305-8.169-80.54-31.403c-37.49-37.49-42.556-93.209-11.313-124.45
c 31.241-31.241, 86.96-26.177, 124.45,11.313c 23.235,23.234, 34.001,53.469, 31.403,80.54L 256,144l 31.664-31.664
c-2.598-27.072, 8.168-57.305, 31.403-80.539c 37.489-37.49, 93.209-42.556, 124.449-11.313
C 474.76,51.725, 469.694,107.443, 432.204,144.934z M 176.562,100.711c-1.106-12.166-7.51-24.913-17.57-34.973
C 147.886,54.631, 133.452,48, 120.383,48c-5.262,0-12.649,1.114-17.958,6.424c-10.703,10.702-8.688,36.566, 11.313,56.568
c 11.106,11.107, 25.54,17.738, 38.609,17.738c 5.262,0, 12.649-1.114, 17.958-6.424C 176.861,115.751, 177.040,105.962, 176.562,100.711z
M 256,176c-17.673,0-32,14.327-32,32s 14.327,32, 32,32s 32-14.327, 32-32S 273.673,176, 256,176z M 409.576,54.424
c-5.31-5.31-12.696-6.424-17.958-6.424c-13.069,0-27.503,6.631-38.609,17.738c-10.061,10.060-16.464,22.807-17.569,34.973
c-0.479,5.251-0.3,15.040, 6.257,21.596c 5.309,5.311, 12.695,6.424, 17.958,6.424c 13.068,0, 27.503-6.631, 38.608-17.737
C 418.265,90.99, 420.279,65.126, 409.576,54.424z" />
<glyph unicode="&#xe006;" d="M 32,384L 480,384L 480,320L 32,320zM 32,192L 480,192L 480,128L 32,128zM 32,288L 480,288L 480,224L 32,224zM 32,96L 480,96L 480,32L 32,32z" />
<glyph unicode="&#xe004;" d="M 32,384L 480,384L 480,320L 32,320zM 32,192L 480,192L 480,128L 32,128zM 128,288L 384,288L 384,224L 128,224zM 128,96L 384,96L 384,32L 128,32z" />
<glyph unicode="&#xe005;" d="M 32,384L 480,384L 480,320L 32,320zM 32,192L 480,192L 480,128L 32,128zM 192,288L 480,288L 480,224L 192,224zM 192,96L 480,96L 480,32L 192,32z" />
<glyph unicode="&#xe003;" d="M 32,384L 480,384L 480,320L 32,320zM 32,192L 480,192L 480,128L 32,128zM 32,288L 320,288L 320,224L 32,224zM 32,96L 320,96L 320,32L 32,32z" />
<glyph unicode="&#xe02d;" d="M 480,224l-4.571,0 L 347.062,224 c-25.039,17.71-57.215,27.43-91.062,27.43c-44.603,0-82.286,25.121-82.286,54.856
c0,29.735, 37.683,54.857, 82.286,54.857c 37.529,0, 70.154-17.788, 79.56-41.143l 56.508,0 c-3.965,25.322-18.79,48.984-42.029,66.413
C 324.599,405.493, 291.201,416, 256,416c-35.202,0-68.598-10.507-94.037-29.587c-27.394-20.545-43.106-49.751-43.106-80.127
s 15.712-59.582, 43.106-80.127c 0.978-0.733, 1.971-1.449, 2.973-2.158L 36.571,224.001 L 32,224.001 l0-32 l 256.266,0 c 29.104-8.553, 50.021-28.135, 50.021-50.286
c0-29.734-37.684-54.855-82.286-54.855c-37.53,0-70.154,17.787-79.559,41.143l-56.508,0 c 3.965-25.32, 18.791-48.984, 42.030-66.413
C 187.402,42.508, 220.798,32, 256,32c 35.201,0, 68.599,10.508, 94.037,29.587c 27.395,20.545, 43.104,49.751, 43.104,80.127
c0,17.649-5.327,34.896-15.147,50.286L 480,192 L 480,224 z" />
<glyph unicode="&#xe02c;" d="M 96,64l 288,0 l0-32 L 96,32 L 96,64 zM 320,416l0-192 c0-15.656-7.35-30.812-20.695-42.676C 283.834,167.573, 262.771,160, 240,160c-22.772,0-43.834,7.573-59.304,21.324
C 167.35,193.188, 160,208.344, 160,224L 160,416 L 96,416 l0-192 c0-70.691, 64.471-128, 144-128c 79.529,0, 144,57.309, 144,128L 384,416 L 320,416 z" />
<glyph unicode="&#xe02b;" d="M 416,416l0-32 l-72,0 L 216,64l 72,0 l0-32 L 64,32 l0,32 l 72,0 L 264,384l-72,0 L 192,416 L 416,416 z" />
<glyph unicode="&#xe02a;" d="M 312.721,232.909C 336.758,251.984, 352,280.337, 352,312c0,57.438-50.145,104-112,104L 128,416 l0-384 l 144,0
c 61.856,0, 112,46.562, 112,104C 384,180.098, 354.441,217.781, 312.721,232.909z M 192,328c0,13.255, 10.745,24, 24,24l 33.602,0
C 270.809,352, 288,330.51, 288,304s-17.191-48-38.398-48L 192,256 L 192,328 z M 273.6,96L 216,96 c-13.255,0-24,10.745-24,24l0,72 l 81.6,0
c 21.209,0, 38.4-21.49, 38.4-48S 294.809,96, 273.6,96z" />
<glyph unicode="&#xe001;" d="M 425.373,358.627l-66.746,66.745C 346.183,437.818, 321.6,448, 304,448L 96,448 c-17.6,0-32-14.4-32-32l0-384 c0-17.6, 14.4-32, 32-32l 320,0
c 17.6,0, 32,14.4, 32,32L 448,304 C 448,321.6, 437.817,346.182, 425.373,358.627z M 402.745,336.001c 3.396-3.398, 6.896-9.581, 9.447-16.001L 320,320
L 320,412.193 c 6.42-2.55, 12.602-6.050, 16-9.448L 402.745,336.001z M 415.942,32L 96.057,32 c-0.020,0.017-0.041,0.038-0.057,0.058L 96,415.943
c 0.017,0.020, 0.038,0.041, 0.057,0.057L 288,416 l0-128 l 128,0 l0-255.942 C 415.983,32.038, 415.962,32.017, 415.942,32z" />
<glyph unicode="&#xe000;" d="M 480,40L 480,335.969 L 368.031,448L 72,448 c-22.091,0-40-17.908-40-40l0-368 c0-22.092, 17.909-40, 40-40l 368,0
C 462.092,0, 480,17.908, 480,40z M 288,384l 32,0 l0-96 l-32,0 L 288,384 z M 352,64L 160,64 L 160,191.941 c 0.017,0.021, 0.038,0.041, 0.058,0.059l 191.885,0
c 0.020-0.018, 0.041-0.038, 0.058-0.059L 352,64L 352,64z M 416,64l-32,0 L 384,192 c0,17.6-14.4,32-32,32L 160,224 c-17.6,0-32-14.4-32-32l0-128 L 96,64 L 96,384
l 32,0 l0-96 c0-17.6, 14.4-32, 32-32l 160,0 c 17.6,0, 32,14.4, 32,32l0,85.505 l 64-64.036L 416,64 z" />
<glyph unicode="&#xe01b;" d="M 32,384l0-352 l 448,0 L 480,384 L 32,384 z M 192,160l0,64 l 128,0 l0-64 L 192,160 z M 320,128l0-64 L 192,64 l0,64 L 320,128 z M 320,320l0-64 L 192,256 l0,64 L 320,320 z M 160,320l0-64 L 64,256 l0,64 L 160,320
z M 64,224l 96,0 l0-64 L 64,160 L 64,224 z M 352,224l 96,0 l0-64 l-96,0 L 352,224 z M 352,256l0,64 l 96,0 l0-64 L 352,256 z M 64,128l 96,0 l0-64 L 64,64 L 64,128 z M 352,64l0,64 l 96,0 l0-64 L 352,64 z" />
<glyph unicode="&#xe021;" d="M 256,410c 49.683,0, 96.391-19.347, 131.521-54.478S 442,273.683, 442,224s-19.348-96.391-54.479-131.521S 305.683,38, 256,38
s-96.391,19.348-131.522,54.479S 70,174.317, 70,224s 19.347,96.391, 54.478,131.522S 206.317,410, 256,410 M 256,448
C 132.288,448, 32,347.712, 32,224s 100.288-224, 224-224s 224,100.288, 224,224S 379.712,448, 256,448L 256,448zM 160,288A32,32 1980 1 1 224,288A32,32 1980 1 1 160,288zM 288,288A32,32 1980 1 1 352,288A32,32 1980 1 1 288,288zM 256,152c-50.92,0-96.28,18.437-125.583,47.164C 141.98,140.36, 193.806,96, 256,96c 62.194,0, 114.020,44.36, 125.584,103.164
C 352.28,170.437, 306.92,152, 256,152z" />
<glyph unicode="&#xe023;" d="M 240,288L 144,384L 208,448L 32,448L 32,272L 96,336L 192,240 zM 320,240L 416,336L 480,272L 480,448L 304,448L 368,384L 272,288 zM 272,160L 368,64L 304,0L 480,0L 480,176L 416,112L 320,208 zM 192,208L 96,112L 32,176L 32,0L 208,0L 144,64L 240,160 z" />
<glyph unicode="&#xe01c;" d="M 32,256L 480,256L 480,192L 32,192z" />
<glyph unicode="&#xe01d;" d="M 32,96l 256,0 l0-64 L 32,32 L 32,96 z M 384,384L 273.721,384 l-91.883-256l-66.144,0 l 91.881,256L 96,384 L 96,448 l 288,0 L 384,384 z M 464.887,32L 400,96.887
L 335.113,32L 304,63.113L 368.887,128L 304,192.887L 335.113,224L 400,159.113L 464.887,224L 496,192.887L 431.113,128L 496,63.113
L 464.887,32z" />
<glyph unicode="&#xe022;" d="M 128,416l 256,0 l0-64 L 128,352 L 128,416 z M 448,320L 64,320 c-17.6,0-32-14.4-32-32l0-128 c0-17.6, 14.398-32, 32-32l 64,0 l0-96 l 256,0 l0,96 l 64,0
c 17.6,0, 32,14.4, 32,32L 480,288 C 480,305.6, 465.6,320, 448,320z M 352,64L 160,64 L 160,192 l 192,0 L 352,64 z M 455.2,272c0-12.813-10.387-23.2-23.199-23.2
S 408.8,259.187, 408.8,272s 10.389,23.2, 23.201,23.2C 444.814,295.2, 455.2,284.813, 455.2,272z" />
<glyph unicode="&#xe02e;" d="M 192,416c-61.856,0-112-50.144-112-112s 50.144-112, 112-112l0-160 l 64,0 L 256,352 l 32,0 l0-320 l 64,0 L 352,352 l 64,0 L 416,416 L 192,416 z" />
<glyph unicode="&#xe02f;" d="M 224,416c-61.856,0-112-50.144-112-112s 50.144-112, 112-112l0-160 l 64,0 L 288,352 l 32,0 l0-320 l 64,0 L 384,352 l 64,0 L 448,416 L 224,416 zM 32,32L 144,128L 32,224 z" />
<glyph unicode="&#xe030;" d="M 160,416C 98.144,416, 48,365.856, 48,304s 50.144-112, 112-112l0-160 l 64,0 L 224,352 l 32,0 l0-320 l 64,0 L 320,352 l 64,0 L 384,416 L 160,416 zM 480,224L 368,128L 480,32 z" />
<glyph unicode="&#xe026;" d="M 256,288L 320,288L 320,256L 256,256zM 256,96L 320,96L 320,64L 256,64zM 288,192L 352,192L 352,160L 288,160zM 384,192L 384,96L 352,96L 352,64L 416,64L 416,192 zM 192,192L 256,192L 256,160L 192,160zM 160,96L 224,96L 224,64L 160,64zM 160,288L 224,288L 224,256L 160,256zM 96,384L 96,256L 128,256L 128,352L 160,352L 160,384 zM 352,256L 416,256L 416,384L 384,384L 384,288L 352,288 zM 32,448l0-448 l 448,0 L 480,448 L 32,448 z M 448,32L 64,32 L 64,416 l 384,0 L 448,32 zM 96,192L 96,64L 128,64L 128,160L 160,160L 160,192 zM 288,384L 352,384L 352,352L 288,352zM 192,384L 256,384L 256,352L 192,352z" />
<glyph unicode="&#xe027;" d="M 408,448l 8-192L 96,256 l 8,192l 16,0 l 8-160l 256,0 l 8,160L 408,448 z M 104,0l-8,160l 320,0 l-8-160l-16,0 l-8,128L 128,128 l-8-128L 104,0 zM 32,224L 96,224L 96,192L 32,192zM 128,224L 192,224L 192,192L 128,192zM 224,224L 288,224L 288,192L 224,192zM 320,224L 384,224L 384,192L 320,192zM 416,224L 480,224L 480,192L 416,192z" />
<glyph unicode="&#xe024;" d="M 480,416L 480,448 l-96,0 c-17.601,0-32-14.4-32-32l0-160 c0-7.928, 2.929-15.201, 7.748-20.807L 208,105l-71,74l-41-35l 112-144l 208,224l 64,0
l0,32 l-96,0 L 384,416 L 480,416 zM 128,224l 32,0 L 160,416 c0,17.6-14.4,32-32,32L 64,448 c-17.6,0-32-14.4-32-32l0-192 l 32,0 l0,96 l 64,0 L 128,224 z M 64,352L 64,416 l 64,0 l0-64 L 64,352 zM 320,256l0,48 c0,17.6-4.4,32-22,32c 17.6,0, 22,14.4, 22,32L 320,416 c0,17.6-14.4,32-32,32l-96,0 l0-224 l 96,0 C 305.6,224, 320,238.4, 320,256z
M 224,416l 64,0 l0-64 l-64,0 L 224,416 z M 224,320l 64,0 l0-64 l-64,0 L 224,320 z" />
<glyph unicode="&#xe025;" d="M 224,224l-64,0 l0,64 l 64,0 l0,64 l 64,0 l0-64 l 64,0 l0-64 l-64,0 l0-64 l-64,0 L 224,224 z M 480,192l0-160 L 32,32 L 32,192 l 64,0 l0-96 l 320,0 l0,96 L 480,192 z" />
<glyph unicode="&#xe017;" d="M 208,128L 112,224L 208,320L 176,352L 48,224L 176,96 zM 336,352L 304,320L 400,224L 304,128L 336,96L 464,224 z" />
<glyph unicode="&#xe016;" d="M 224,128l 64,0 l0-64 l-64,0 L 224,128 z M 352,352c 17.673,0, 32-14.327, 32-32l0-83 l-114-77l-46,0 l0,32 l 96,64l0,32 L 160,288 l0,64 L 352,352 z M 256,448
c-59.833,0-116.083-23.3-158.392-65.608C 55.301,340.083, 32,283.833, 32,224c0-59.832, 23.301-116.084, 65.608-158.392
C 139.917,23.3, 196.167,0, 256,0c 59.832,0, 116.084,23.3, 158.392,65.608C 456.7,107.916, 480,164.168, 480,224
c0,59.833-23.3,116.083-65.608,158.392C 372.084,424.7, 315.832,448, 256,448z" />
<glyph unicode="&#xe014;" d="M 448,416L 64,416 c-17.6,0-32-14.4-32-32l0-320 c0-17.6, 14.4-32, 32-32l 384,0 c 17.6,0, 32,14.4, 32,32L 480,384 C 480,401.6, 465.6,416, 448,416z
M 448,64.058c-0.006-0.007-0.015-0.014-0.021-0.021L 352,224l-80-64L 160,304L 64.016,64.042c-0.005,0.005-0.011,0.011-0.016,0.016
L 64,383.943 c 0.017,0.020, 0.038,0.041, 0.057,0.057l 383.885,0 c 0.020-0.017, 0.041-0.038, 0.058-0.058L 448,64.058 zM 320,304A48,48 1980 1 1 416,304A48,48 1980 1 1 320,304z" />
<glyph unicode="&#xe015;" d="M 448,416L 64,416 c-17.6,0-32-14.4-32-32l0-320 c0-17.6, 14.4-32, 32-32l 384,0 c 17.6,0, 32,14.4, 32,32L 480,384 C 480,401.6, 465.6,416, 448,416z
M 128,64L 64,64 l0,64 l 64,0 L 128,64 z M 128,192L 64,192 l0,64 l 64,0 L 128,192 z M 128,320L 64,320 L 64,384 l 64,0 L 128,320 z M 352,64L 160,64 L 160,384 l 192,0 L 352,64 z M 448,64l-64,0 l0,64 l 64,0 L 448,64 z
M 448,192l-64,0 l0,64 l 64,0 L 448,192 z M 448,320l-64,0 L 384,384 l 64,0 L 448,320 zM 192,320L 192,128L 336,224 z" />
<glyph unicode="&#xe018;" d="M 38.899,327.688l 40.707-25.441C 105.007,342.804, 144,373.974, 190.21,389.37l-15.183,45.547
C 118.153,415.968, 70.163,377.604, 38.899,327.688zM 336.973,434.917L 321.79,389.37c 46.211-15.396, 85.202-46.566, 110.604-87.124l 40.706,25.441
C 441.837,377.604, 393.847,415.968, 336.973,434.917zM 303.987,127.996c-2.404,0-4.846,0.545-7.143,1.693L 224,166.111L 224,272 c0,8.836, 7.164,16, 16,16s 16-7.164, 16-16l0-86.111
l 55.155-27.578c 7.903-3.951, 11.107-13.562, 7.155-21.466C 315.508,131.238, 309.856,127.997, 303.987,127.996zM 256,384C 149.961,384, 64,298.039, 64,192c0-106.039, 85.961-192, 192-192c 106.039,0, 192,85.961, 192,192
C 448,298.039, 362.039,384, 256,384z M 256,48c-79.529,0-144,64.471-144,144c0,79.529, 64.471,144, 144,144c 79.529,0, 144-64.471, 144-144
C 400,112.471, 335.529,48, 256,48z" />
<glyph unicode="&#xe019;" d="M 32,252.127c 22.659,24.96, 48.581,46.18, 76.636,62.562C 153.802,341.061, 204.759,355, 256,355
c 51.24,0, 102.198-13.939, 147.363-40.312c 28.056-16.382, 53.978-37.602, 76.637-62.562l0,58.716
c-16.505,14.059-34.062,26.57-52.434,37.297C 375.063,378.796, 315.737,395, 256,395s-119.064-16.204-171.567-46.86
C 66.062,337.413, 48.505,324.901, 32,310.842L 32,252.127 zM 256,320c-91.598,0-172.919-50.278-224-128c 51.081-77.724, 132.402-128, 224-128c 91.598,0, 172.919,50.276, 224,128
C 428.919,269.722, 347.598,320, 256,320z M 256,224c0-17.673-14.327-32-32-32s-32,14.327-32,32c0,17.674, 14.327,32, 32,32
S 256,241.674, 256,224z M 364.033,131.669C 330.316,111.982, 293.969,102, 256,102s-74.316,9.982-108.033,29.669
C 122.19,146.721, 98.659,167.324, 78.91,192c 19.749,24.675, 43.28,45.279, 69.058,60.33c 6.638,3.876, 13.379,7.37, 20.213,10.491
C 162.925,250.95, 160,237.817, 160,224c0-53.020, 42.981-96, 96-96c 53.020,0, 96,42.98, 96,96c0,13.817-2.925,26.95-8.18,38.821
c 6.834-3.122, 13.575-6.615, 20.213-10.491c 25.777-15.051, 49.308-35.655, 69.058-60.33
C 413.342,167.324, 389.811,146.721, 364.033,131.669z" />
<glyph unicode="&#xe01a;" d="M 325.584,338.083C 313.278,379.064, 311.146,384, 272,384l-32,0 c-39.809,0-41.332-5.076-54.209-48c0-0.001,0-0.001-0.001-0.002
L 113.791,96l 56.818,0 l 28.8,96l 113.183,0 l 28.8-96l 56.815,0 L 325.584,338.083z M 218.609,256l 19.2,68c 5.043,16.809, 18.19,15, 18.19,15
s 13.147,1.809, 18.19-15l 0.002,0 l 19.2-68L 218.609,256 z" />
<glyph unicode="&#xe028;" d="M 288,448 C 411.712,448 512,347.712 512,224 C 512,100.288 411.712,0 288,0 L 288,48 C 335.012,48 379.209,66.307 412.451,99.549 C 445.693,132.791 464,176.988 464,224 C 464,271.011 445.693,315.209 412.451,348.451 C 379.209,381.693 335.012,400 288,400 C 240.989,400 196.791,381.693 163.549,348.451 C 137.979,322.882 121.258,290.828 114.896,256 L 208,256 L 96,128 L -16,256 L 66.285,256 C 81.815,364.551 175.154,448 288,448 ZM 384,256 L 384,192 L 256,192 L 256,352 L 320,352 L 320,256 Z" />
<glyph unicode="&#xe002;" d="M 512,183.771l0,80.458 l-79.572,7.957c-4.093,15.021-10.044,29.274-17.605,42.49l 52.298,63.919L 410.595,435.12l-63.918-52.298
c-13.217,7.562-27.471,13.513-42.491,17.604L 296.229,480l-80.458,0 l-7.957-79.573c-15.021-4.093-29.274-10.043-42.49-17.604
L 101.405,435.12L 44.88,378.595l 52.298-63.918c-7.562-13.216-13.513-27.47-17.605-42.49L0,264.229l0-80.458 l 79.573-7.957
c 4.093-15.021, 10.043-29.274, 17.605-42.491L 44.88,69.405l 56.524-56.524l 63.919,52.298c 13.216-7.562, 27.47-13.514, 42.49-17.605
L 215.771-32l 80.458,0 l 7.957,79.572c 15.021,4.093, 29.274,10.044, 42.491,17.605l 63.918-52.298l 56.524,56.524l-52.298,63.918
c 7.562,13.217, 13.514,27.471, 17.605,42.49L 512,183.771z M 352,192l-64-64l-64,0 l-64,64l0,64 l 64,64l 64,0 l 64-64L 352,192 z" />
<glyph unicode="&#xe01f;" d="M 384,377 L 384,352 L 448,352 L 448,320 L 352,320 L 352,393 L 416,423 L 416,448 L 352,448 L 352,480 L 448,480 L 448,407 ZM 338,352L 270,352L 176,258L 82,352L 14,352L 142,224L 14,96L 82,96L 176,190L 270,96L 338,96L 210,224 z" />
<glyph unicode="&#xe01e;" d="M 384,25 L 384,0 L 448,0 L 448-32 L 352-32 L 352,41 L 416,71 L 416,96 L 352,96 L 352,128 L 448,128 L 448,55 ZM 338,352L 270,352L 176,258L 82,352L 14,352L 142,224L 14,96L 82,96L 176,190L 270,96L 338,96L 210,224 z" />
<glyph unicode="&#xe035;" d="M 352,288l0,80 c0,8.8-7.2,16-16,16l-80,0 L 256,416 c0,17.6-14.4,32-32,32l-64,0 c-17.602,0-32-14.4-32-32l0-32 L 48,384 c-8.801,0-16-7.2-16-16
l0-256 c0-8.8, 7.199-16, 16-16l 112,0 l0-96 l 288,0 L 448,288 L 352,288 z M 160,415.943c 0.017,0.019, 0.036,0.039, 0.057,0.057l 63.884,0
c 0.021-0.018, 0.041-0.038, 0.059-0.057L 224,384 l-64,0 L 160,415.943 z M 96,320l0,32 l 192,0 l0-32 L 96,320 z M 416,32L 192,32 L 192,256 l 224,0 L 416,32 zM 224,224L 224,160L 240,160L 256,192L 288,192L 288,96L 264,96L 264,64L 344,64L 344,96L 320,96L 320,192L 352,192L 368,160L 384,160L 384,224 z" />
<glyph unicode="&#xe032;" d="M 384,352L 416,352L 416,320L 384,320zM 320,288L 352,288L 352,256L 320,256zM 320,224L 352,224L 352,192L 320,192zM 320,160L 352,160L 352,128L 320,128zM 256,224L 288,224L 288,192L 256,192zM 256,160L 288,160L 288,128L 256,128zM 192,160L 224,160L 224,128L 192,128zM 384,288L 416,288L 416,256L 384,256zM 384,224L 416,224L 416,192L 384,192zM 384,160L 416,160L 416,128L 384,128zM 384,96L 416,96L 416,64L 384,64zM 320,96L 352,96L 352,64L 320,64zM 256,96L 288,96L 288,64L 256,64zM 192,96L 224,96L 224,64L 192,64zM 128,96L 160,96L 160,64L 128,64z" />
<glyph unicode="&#xe034;" d="M 464,416L 256,416L 240,448L 64,448L 32,384L 480,384 zM 420.17,128L 464,128 l 16,224L 32,352 l 32-320l 178.040,0 C 189.599,50.888, 152,101.133, 152,160c0,74.991, 61.009,136, 136,136
c 74.99,0, 136-61.009, 136-136C 424,149.161, 422.689,138.425, 420.17,128zM 437.498,55.125l-67.248,55.346C 378.977,124.932, 384,141.878, 384,160c0,53.020-42.98,96-96,96s-96-42.98-96-96
s 42.98-96, 96-96c 18.122,0, 35.069,5.023, 49.529,13.75l 55.346-67.248c 11.481-13.339, 31.059-14.070, 43.503-1.626l 2.746,2.746
C 451.568,24.066, 450.837,43.644, 437.498,55.125z M 288,98c-34.242,0-62,27.758-62,62s 27.758,62, 62,62s 62-27.758, 62-62
S 322.242,98, 288,98z" />
<glyph unicode="&#x20;" horiz-adv-x="256" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
This is a custom SVG font generated by IcoMoon.
<iconset grid="16"></iconset>
</metadata>
<defs>
<font id="icomoon" horiz-adv-x="512" >
<font-face units-per-em="512" ascent="480" descent="-32" />
<missing-glyph horiz-adv-x="512" />
<glyph class="hidden" unicode="&#xf000;" d="M0,480L 512 -32L0 -32 z" horiz-adv-x="0" />
<glyph unicode="&#xe034;" d="M 464,416L 256,416L 240,448L 64,448L 32,384L 480,384 zM 452.17,128l 37.43,0 L 512,352L0,352 l 32-320l 242.040,0 C 221.599,50.888, 184,101.133, 184,160c0,74.991, 61.009,136, 136,136
c 74.99,0, 136-61.009, 136-136C 456,149.161, 454.689,138.425, 452.17,128zM 501.498,23.125l-99.248,87.346C 410.977,124.931, 416,141.878, 416,160c0,53.020-42.98,96-96,96s-96-42.98-96-96
s 42.98-96, 96-96c 18.122,0, 35.069,5.023, 49.529,13.75l 87.346-99.248c 11.481-13.339, 31.059-14.070, 43.503-1.626l 2.746,2.746
C 515.568-7.934, 514.837,11.644, 501.498,23.125z M 320,98c-34.242,0-62,27.758-62,62s 27.758,62, 62,62s 62-27.758, 62-62
S 354.242,98, 320,98z" />
<glyph unicode="&#xe032;" d="M 384,352L 416,352L 416,320L 384,320zM 320,288L 352,288L 352,256L 320,256zM 320,224L 352,224L 352,192L 320,192zM 320,160L 352,160L 352,128L 320,128zM 256,224L 288,224L 288,192L 256,192zM 256,160L 288,160L 288,128L 256,128zM 192,160L 224,160L 224,128L 192,128zM 384,288L 416,288L 416,256L 384,256zM 384,224L 416,224L 416,192L 384,192zM 384,160L 416,160L 416,128L 384,128zM 384,96L 416,96L 416,64L 384,64zM 320,96L 352,96L 352,64L 320,64zM 256,96L 288,96L 288,64L 256,64zM 192,96L 224,96L 224,64L 192,64zM 128,96L 160,96L 160,64L 128,64z" />
<glyph unicode="&#xe031;" d="M 416,352l-96,0 L 320,384 L 224,480L0,480 l0-384 l 192,0 l0-128 l 320,0 L 512,256 L 416,352z M 416,306.745L 466.745,256L 416,256 L 416,306.745 z M 224,434.745L 274.745,384L 224,384
L 224,434.745 z M 32,448l 160,0 l0-96 l 96,0 l0-224 L 32,128 L 32,448 z M 480,0L 224,0 l0,96 l 96,0 L 320,320 l 64,0 l0-96 l 96,0 L 480,0 z" />
<glyph unicode="&#xe030;" d="M 128,448 L 384,448 L 384,384 L 320,384 L 320,0 L 256,0 L 256,384 L 192,384 L 192,0 L 128,0 L 128,224 C 66.144,224 16,274.144 16,336 C 16,397.856 66.144,448 128,448 ZM 480,32L 352,144L 480,256 z" />
<glyph unicode="&#xe02f;" d="M 224,448 L 480,448 L 480,384 L 416,384 L 416,0 L 352,0 L 352,384 L 288,384 L 288,0 L 224,0 L 224,224 C 162.144,224 112,274.144 112,336 C 112,397.856 162.144,448 224,448 ZM 32,256L 160,144L 32,32 z" />
<glyph unicode="&#xe02e;" d="M 192,448 L 448,448 L 448,384 L 384,384 L 384,0 L 320,0 L 320,384 L 256,384 L 256,0 L 192,0 L 192,224 C 130.144,224 80,274.144 80,336 C 80,397.856 130.144,448 192,448 Z" />
<glyph unicode="&#xe02d;" d="M 365.71,221.482 C 397.67,197.513 416,163.439 416,128 C 416,92.561 397.67,58.487 365.71,34.518 C 336.031,12.259 297.068,0 256,0 C 214.931,0 175.969,12.259 146.29,34.518 C 114.33,58.487 96,92.561 96,128 L 160,128 C 160,93.309 203.963,64 256,64 C 308.037,64 352,93.309 352,128 C 352,162.691 308.037,192 256,192 C 214.931,192 175.969,204.259 146.29,226.518 C 114.33,250.488 96,284.561 96,320 C 96,355.439 114.33,389.512 146.29,413.482 C 175.969,435.741 214.931,448 256,448 C 297.068,448 336.031,435.741 365.71,413.482 C 397.67,389.512 416,355.439 416,320 L 352,320 C 352,354.691 308.037,384 256,384 C 203.963,384 160,354.691 160,320 C 160,285.309 203.963,256 256,256 C 297.068,256 336.031,243.741 365.71,221.482 ZM0,224L 512,224L 512,192L0,192z" />
<glyph unicode="&#xe02c;" d="M 352,448 L 416,448 L 416,240 C 416,160.471 344.366,96 256,96 C 167.635,96 96,160.471 96,240 L 96,448 L 160,448 L 160,240 C 160,219.917 169.119,200.648 185.677,185.747 C 204.125,169.145 229.1,160 256,160 C 282.9,160 307.875,169.145 326.323,185.747 C 342.881,200.648 352,219.917 352,240 L 352,448 ZM 96,64L 416,64L 416,0L 96,0z" />
<glyph unicode="&#xe02b;" d="M 448,448 L 448,416 L 384,416 L 224,32 L 288,32 L 288,0 L 64,0 L 64,32 L 128,32 L 288,416 L 224,416 L 224,448 Z" />
<glyph unicode="&#xe02a;" d="M 353.94,237.674C 372.689,259.945, 384,288.678, 384,320c0,70.58-57.421,128-128,128l-64,0 l-64,0 L 96,448 l0-448 l 32,0 l 64,0 l 96,0
c 70.579,0, 128,57.421, 128,128C 416,174.478, 391.101,215.248, 353.94,237.674z M 192,384l 50.75,0 c 27.984,0, 50.75-28.71, 50.75-64
s-22.766-64-50.75-64L 192,256 L 192,384 z M 271.5,64L 192,64 L 192,192 l 79.5,0 c 29.225,0, 53-28.71, 53-64S 300.725,64, 271.5,64z" />
<glyph unicode="&#xe029;" d="M 192,64L 288,64L 288-32L 192-32zM 400,448 C 426.51,448 448,426.51 448,400 L 448,256 L 288,160 L 288,96 L 192,96 L 192,192 L 352,288 L 352,352 L 96,352 L 96,448 L 400,448 Z" />
<glyph unicode="&#xe028;" d="M 288,448 C 411.712,448 512,347.712 512,224 C 512,100.288 411.712,0 288,0 L 288,48 C 335.012,48 379.209,66.307 412.451,99.549 C 445.693,132.791 464,176.988 464,224 C 464,271.011 445.693,315.209 412.451,348.451 C 379.209,381.693 335.012,400 288,400 C 240.989,400 196.791,381.693 163.549,348.451 C 137.979,322.882 121.258,290.828 114.896,256 L 208,256 L 96,128 L -16,256 L 66.285,256 C 81.815,364.551 175.154,448 288,448 ZM 384,256 L 384,192 L 256,192 L 256,352 L 320,352 L 320,256 Z" />
<glyph unicode="&#xe027;" d="M0,224L 64,224L 64,192L0,192zM 96,224L 192,224L 192,192L 96,192zM 224,224L 288,224L 288,192L 224,192zM 320,224L 416,224L 416,192L 320,192zM 448,224L 512,224L 512,192L 448,192zM 440,480 L 448,256 L 64,256 L 72,480 L 88,480 L 96,288 L 416,288 L 424,480 ZM 72-32 L 64,160 L 448,160 L 440-32 L 424-32 L 416,128 L 96,128 L 88-32 Z" />
<glyph unicode="&#xe026;" d="M 192,384L 256,384L 256,352L 192,352zM 288,384L 352,384L 352,352L 288,352zM 448,384 L 448,256 L 352,256 L 352,288 L 416,288 L 416,352 L 384,352 L 384,384 ZM 160,288L 224,288L 224,256L 160,256zM 256,288L 320,288L 320,256L 256,256zM 96,352 L 96,288 L 128,288 L 128,256 L 64,256 L 64,384 L 160,384 L 160,352 ZM 192,192L 256,192L 256,160L 192,160zM 288,192L 352,192L 352,160L 288,160zM 448,192 L 448,64 L 352,64 L 352,96 L 416,96 L 416,160 L 384,160 L 384,192 ZM 160,96L 224,96L 224,64L 160,64zM 256,96L 320,96L 320,64L 256,64zM 96,160 L 96,96 L 128,96 L 128,64 L 64,64 L 64,192 L 160,192 L 160,160 ZM 480,448 L 32,448 L 32,0 L 480,0 L 480,448 Z M 512,480 L 512,480 L 512-32 L 0-32 L 0,480 L 512,480 Z" />
<glyph unicode="&#xe025;" d="M 224,192 L 128,192 L 128,256 L 224,256 L 224,352 L 288,352 L 288,256 L 384,256 L 384,192 L 288,192 L 288,96 L 224,96 ZM 512,160 L 512-32 L 0-32 L 0,160 L 64,160 L 64,32 L 448,32 L 448,160 Z" />
<glyph unicode="&#xe024;" d="M 64,352l 64,0 l0-96 l 32,0 L 160,448 c0,17.6-14.4,32-32,32L 64,480 C 46.4,480, 32,465.6, 32,448l0-192 l 32,0 L 64,352 z M 64,448l 64,0 l0-64 L 64,384 L 64,448 z M 480,448L 480,480 l-96,0
c-17.601,0-32-14.4-32-32l0-160 c0-17.6, 14.399-32, 32-32l 96,0 l0,32 l-96,0 L 384,448 L 480,448 z M 320,400L 320,448 c0,17.6-14.4,32-32,32l-96,0 l0-224 l 96,0
c 17.6,0, 32,14.4, 32,32l0,48 c0,17.6-4.4,32-22,32C 315.6,368, 320,382.4, 320,400z M 288,288l-64,0 l0,64 l 64,0 L 288,288 z M 288,384l-64,0 L 224,448 l 64,0 L 288,384 zM 416,192 L 208-32 L 96,112 L 137,147 L 208,73 L 384,224 Z" />
<glyph unicode="&#xe023;" d="M 512,480 L 512,288 L 442.87,357.13 L 336.87,251.13 L 283.13,304.87 L 389.13,410.87 L 320,480 ZM 122.87,410.87 L 228.87,304.87 L 175.13,251.13 L 69.13,357.13 L 0,288 L 0,480 L 192,480 ZM 442.87,90.87 L 512,160 L 512-32 L 320-32 L 389.13,37.13 L 283.13,143.13 L 336.87,196.87 ZM 228.87,143.13 L 122.87,37.13 L 192-32 L 0-32 L 0,160 L 69.13,90.87 L 175.13,196.87 Z" />
<glyph unicode="&#xe022;" d="M 128,448L 384,448L 384,384L 128,384zM 480,352L 32,352 C 14.4,352,0,337.6,0,320l0-160 c0-17.6, 14.398-32, 32-32l 96,0 l0-128 l 256,0 L 384,128 l 96,0 c 17.6,0, 32,14.4, 32,32L 512,320
C 512,337.6, 497.6,352, 480,352z M 352,32L 160,32 L 160,192 l 192,0 L 352,32 z M 487.2,304c0-12.813-10.387-23.2-23.199-23.2
c-12.813,0-23.201,10.387-23.201,23.2s 10.388,23.2, 23.201,23.2C 476.814,327.2, 487.2,316.813, 487.2,304z" />
<glyph unicode="&#xe021;" d="M 256,480C 114.615,480,0,365.386,0,224c0-141.385, 114.614-256, 256-256c 141.385,0, 256,114.615, 256,256
C 512,365.386, 397.385,480, 256,480z M 256,8c-119.293,0-216,96.706-216,216c0,119.293, 96.707,216, 216,216c 119.295,0, 216-96.707, 216-216
C 472,104.706, 375.295,8, 256,8z M 192,320c0-17.673-14.327-32-32-32s-32,14.327-32,32s 14.327,32, 32,32S 192,337.673, 192,320z
M 384,320c0-17.673-14.326-32-32-32s-32,14.327-32,32s 14.326,32, 32,32S 384,337.673, 384,320zM 256,154 C 326.537,154 387.344,182.766 415.231,215.596 C 404.795,129.986 337.087,64 256,64 C 174.941,64 107.251,130.013 96.778,215.584 C 124.671,182.761 185.471,154 256,154 Z" />
<glyph unicode="&#xe020;" d="M 352,32 L 480,32 L 512,96 L 512-32 L 320-32 L 320,75.107 C 385.556,103.349 432,173.688 432,256 C 432,363.216 353.201,447.133 256,447.133 C 158.797,447.133 80,363.217 80,256 C 80,173.688 126.443,103.349 192,75.107 L 192-32 L 0-32 L 0,96 L 32,32 L 160,32 L 160,48.295 C 66.185,81.525 0,161.996 0,256 C 0,379.712 114.615,480 256,480 C 397.385,480 512,379.712 512,256 C 512,161.996 445.815,81.525 352,48.295 L 352,32 Z" />
<glyph unicode="&#xe01f;" d="M 384,377 L 384,352 L 448,352 L 448,320 L 352,320 L 352,393 L 416,423 L 416,448 L 352,448 L 352,480 L 448,480 L 448,407 ZM 338,352L 270,352L 176,258L 82,352L 14,352L 142,224L 14,96L 82,96L 176,190L 270,96L 338,96L 210,224 z" />
<glyph unicode="&#xe01e;" d="M 384,25 L 384,0 L 448,0 L 448-32 L 352-32 L 352,41 L 416,71 L 416,96 L 352,96 L 352,128 L 448,128 L 448,55 ZM 338,352L 270,352L 176,258L 82,352L 14,352L 142,224L 14,96L 82,96L 176,190L 270,96L 338,96L 210,224 z" />
<glyph unicode="&#xe01d;" d="M0,32L 288,32L 288-32L0-32zM 96,480L 448,480L 448,416L 96,416zM 138.694,64 L 241.038,456.082 L 302.963,439.918 L 204.838,64 ZM 464.887-32 L 400,32.887 L 335.113-32 L 304-0.887 L 368.887,64 L 304,128.887 L 335.113,160 L 400,95.113 L 464.887,160 L 496,128.887 L 431.113,64 L 496-0.887 Z" />
<glyph unicode="&#xe01c;" d="M0,256L 512,256L 512,192L0,192z" />
<glyph unicode="&#xe01b;" d="M0,448l0-448 l 512,0 L 512,448 L0,448 z M 192,160l0,96 l 128,0 l0-96 L 192,160 z M 320,128l0-96 L 192,32 l0,96 L 320,128 z M 320,384l0-96 L 192,288 L 192,384 L 320,384 z M 160,384l0-96 L 32,288 L 32,384 L 160,384 z
M 32,256l 128,0 l0-96 L 32,160 L 32,256 z M 352,256l 128,0 l0-96 L 352,160 L 352,256 z M 352,288L 352,384 l 128,0 l0-96 L 352,288 z M 32,128l 128,0 l0-96 L 32,32 L 32,128 z M 352,32l0,96 l 128,0 l0-96 L 352,32 z" />
<glyph unicode="&#xe01a;" d="M 161.009,64l 28.8,96l 132.382,0 l 28.8-96l 56.816,0 L 311.809,384L 200.191,384 l-96-320L 161.009,64 z M 237.809,320l 36.382,0 l 28.8-96l-93.982,0
L 237.809,320z" />
<glyph unicode="&#xe019;" d="M 256,320C 151.316,320, 58.378,269.722,0,192c 58.378-77.723, 151.316-128, 256-128c 104.684,0, 197.622,50.277, 256,128
C 453.622,269.722, 360.684,320, 256,320z M 224,256c 17.673,0, 32-14.327, 32-32s-14.327-32-32-32s-32,14.327-32,32S 206.327,256, 224,256z
M 386.808,127.352c-19.824-10.129-40.826-17.931-62.423-23.188C 302.141,98.746, 279.134,96, 256,96
c-23.133,0-46.141,2.746-68.384,8.162c-21.597,5.259-42.599,13.061-62.423,23.188c-31.51,16.101-60.111,38.205-83.82,64.649
c 23.709,26.444, 52.31,48.55, 83.82,64.649c 16.168,8.261, 33.121,14.973, 50.541,20.020C 165.79,261.547, 160,243.451, 160,224
c0-53.020, 42.981-96, 96-96c 53.019,0, 96,42.98, 96,96c0,19.451-5.791,37.547-15.733,52.67c 17.419-5.048, 34.372-11.76, 50.541-20.021
c 31.511-16.099, 60.109-38.204, 83.819-64.649C 446.917,165.557, 418.318,143.45, 386.808,127.352z M 430.459,358.139
C 376.099,385.916, 317.403,400, 256,400c-61.403,0-120.099-14.084-174.459-41.861C 52.155,343.123, 24.675,324.187,0,302.101l0-54.603
c 27.669,29.283, 60.347,53.877, 96.097,72.145C 145.907,345.095, 199.706,358, 256,358s 110.093-12.905, 159.902-38.358
c 35.751-18.268, 68.429-42.862, 96.098-72.145L 512,302.1 C 487.325,324.187, 459.846,343.123, 430.459,358.139z" />
<glyph unicode="&#xe018;" d="M 256,384C 149.962,384, 64,298.039, 64,192s 85.961-192, 192-192c 106.037,0, 192,85.961, 192,192S 362.037,384, 256,384z
M 357.822,90.177C 330.626,62.979, 294.464,48, 256,48s-74.625,14.979-101.823,42.177C 126.979,117.374, 112,153.536, 112,192
s 14.979,74.625, 42.177,101.823C 181.375,321.021, 217.536,336, 256,336s 74.626-14.979, 101.821-42.177
C 385.022,266.625, 400,230.464, 400,192S 385.021,117.374, 357.822,90.177zM 162.965,378.069l-21.47,42.939C 92.058,396.24, 51.76,355.942, 26.992,306.504l 42.938-21.47
C 90.054,325.202, 122.796,357.945, 162.965,378.069zM 442.067,285.035l 42.939,21.469C 460.24,355.942, 419.943,396.24, 370.504,421.008l-21.472-42.939
C 389.201,357.945, 421.944,325.203, 442.067,285.035zM 256,288l-32,0 l0-96 c0-5.055, 2.35-9.555, 6.011-12.486l-0.006-0.008l 80-64l 19.988,24.988L 256,199.689L 256,288 z" />
<glyph unicode="&#xe017;" d="M 160,352L 32,224L 160,96L 224,96L 96,224L 224,352 zM 352,352L 288,352L 416,224L 288,96L 352,96L 480,224 z" />
<glyph unicode="&#xe016;" d="M 224,128L 288,128L 288,64L 224,64zM 352,352 C 369.673,352 384,337.673 384,320 L 384,224 L 288,160 L 224,160 L 224,192 L 320,256 L 320,288 L 160,288 L 160,352 L 352,352 ZM 256,432 C 200.441,432 148.208,410.364 108.922,371.078 C 69.636,331.792 48,279.559 48,224 C 48,168.441 69.636,116.208 108.922,76.922 C 148.208,37.636 200.441,16 256,16 C 311.559,16 363.792,37.636 403.078,76.922 C 442.364,116.208 464,168.441 464,224 C 464,279.559 442.364,331.792 403.078,371.078 C 363.792,410.364 311.559,432 256,432 Z M 256,480 L 256,480 C 397.385,480 512,365.385 512,224 C 512,82.615 397.385-32 256-32 C 114.615-32 0,82.615 0,224 C 0,365.385 114.615,480 256,480 Z" />
<glyph unicode="&#xe015;" d="M0,416l0-384 l 512,0 L 512,416 L0,416 z M 96,64L 32,64 l0,64 l 64,0 L 96,64 z M 96,192L 32,192 l0,64 l 64,0 L 96,192 z M 96,320L 32,320 L 32,384 l 64,0 L 96,320 z M 384,64L 128,64 L 128,384 l 256,0 L 384,64 z
M 480,64l-64,0 l0,64 l 64,0 L 480,64 z M 480,192l-64,0 l0,64 l 64,0 L 480,192 z M 480,320l-64,0 L 416,384 l 64,0 L 480,320 zM 192,320L 192,128L 320,224 z" />
<glyph unicode="&#xe014;" d="M0,416l0-416 l 512,0 L 512,416 L0,416 z M 480,32L 32,32 L 32,384 l 448,0 L 480,32 zM 352,304A48,48 1980 1 0 448,304A48,48 1980 1 0 352,304zM 448,64 L 64,64 L 160,320 L 288,160 L 352,208 Z" />
<glyph unicode="&#xe013;" d="M 96,480l0-512 l 160,160l 160-160L 416,480 L 96,480 z M 384,45.255l-128,128l-128-128L 128,448 l 256,0 L 384,45.255 z" />
<glyph unicode="&#xe012;" d="M 238.444,142.443c 2.28-4.524, 3.495-9.579, 3.495-14.848c0-8.808-3.372-17.029-9.496-23.154l-81.69-81.69
c-6.124-6.124-14.348-9.496-23.154-9.496s-17.030,3.372-23.154,9.496l-49.69,49.69c-6.124,6.125-9.496,14.348-9.496,23.154
s 3.372,17.030, 9.496,23.154l 81.69,81.691c 6.124,6.123, 14.348,9.496, 23.154,9.496c 5.269,0, 10.322-1.215, 14.848-3.494l 32.669,32.668
c-13.935,10.705-30.72,16.080-47.517,16.080c-19.993,0-39.986-7.583-55.154-22.751l-81.69-81.691
c-30.335-30.335-30.335-79.975,0-110.309l 49.69-49.691c 15.167-15.166, 35.16-22.75, 55.153-22.75
c 19.994,0, 39.987,7.584, 55.154,22.751l 81.69,81.69c 27.91,27.91, 30.119,72.149, 6.672,102.673L 238.444,142.443zM 489.248,407.558l-49.69,49.691C 424.391,472.417, 404.398,480, 384.404,480c-19.993,0-39.985-7.583-55.153-22.751l-81.691-81.691
c-27.91-27.91-30.119-72.149-6.671-102.671l 32.669,32.67c-2.279,4.525-3.494,9.58-3.494,14.847c0,8.808, 3.372,17.030, 9.496,23.154
l 81.691,81.691c 6.123,6.124, 14.347,9.497, 23.153,9.497c 8.808,0, 17.030-3.373, 23.154-9.497l 49.69-49.691
c 6.124-6.124, 9.496-14.347, 9.496-23.154c0-8.807-3.372-17.030-9.496-23.154l-81.69-81.691c-6.124-6.124-14.347-9.496-23.154-9.496
c-5.268,0-10.322,1.215-14.848,3.495l-32.669-32.669c 13.936-10.705, 30.72-16.080, 47.517-16.080c 19.994,0, 39.987,7.584, 55.154,22.752
l 81.69,81.69C 519.584,327.584, 519.584,377.223, 489.248,407.558zM 116.684,340.688L 20.687,436.685L 43.315,459.313L 139.312,363.316zM 192,480L 224,480L 224,384L 192,384zM0,288L 96,288L 96,256L0,256zM 395.316,107.312L 491.314,11.314L 468.686-11.314L 372.688,84.684zM 288,64L 320,64L 320-32L 288-32zM 416,192L 512,192L 512,160L 416,160z" />
<glyph unicode="&#xe011;" d="M 160,128c 8.8-8.8, 23.637-8.363, 32.971,0.971L 351.030,287.029C 360.364,296.363, 360.8,311.2, 352,320
s-23.637,8.363-32.971-0.971L 160.971,160.971C 151.637,151.637, 151.2,136.8, 160,128zM 238.444,142.444c 2.28-4.525, 3.495-9.58, 3.495-14.848c0-8.808-3.372-17.030-9.496-23.154l-81.691-81.691
c-6.124-6.124-14.347-9.496-23.154-9.496s-17.030,3.372-23.154,9.496l-49.691,49.691c-6.124,6.124-9.496,14.347-9.496,23.154
s 3.372,17.030, 9.496,23.154l 81.691,81.691c 6.124,6.124, 14.347,9.497, 23.154,9.497c 5.268,0, 10.322-1.215, 14.848-3.495l 32.669,32.669
c-13.935,10.705-30.72,16.080-47.517,16.080c-19.993,0-39.986-7.583-55.154-22.751l-81.691-81.691
c-30.335-30.335-30.335-79.974,0-110.309l 49.691-49.691C 87.611-24.416, 107.604-32, 127.597-32
c 19.994,0, 39.987,7.584, 55.154,22.751l 81.691,81.691c 27.91,27.91, 30.119,72.149, 6.672,102.672L 238.444,142.444zM 489.249,407.558l-49.691,49.691C 424.391,472.417, 404.398,480, 384.404,480c-19.993,0-39.986-7.583-55.154-22.751l-81.691-81.691
c-27.91-27.91-30.119-72.149-6.671-102.671l 32.669,32.67c-2.279,4.525-3.494,9.58-3.494,14.847c0,8.808, 3.372,17.030, 9.496,23.154
l 81.691,81.691c 6.124,6.124, 14.347,9.497, 23.154,9.497s 17.030-3.373, 23.154-9.497l 49.691-49.691
c 6.124-6.124, 9.496-14.347, 9.496-23.154s-3.372-17.030-9.496-23.154l-81.691-81.691c-6.124-6.124-14.347-9.496-23.154-9.496
c-5.268,0-10.322,1.215-14.848,3.495l-32.669-32.669c 13.936-10.705, 30.72-16.080, 47.517-16.080c 19.994,0, 39.987,7.584, 55.154,22.751
l 81.691,81.691C 519.584,327.584, 519.584,377.223, 489.249,407.558z" />
<glyph unicode="&#xe010;" d="M 288,355.814L 288,480 l 192-192L 288,96L 288,222.912 C 64.625,228.153, 74.206,71.016, 131.070-32
C-9.286,119.707, 20.52,362.785, 288,355.814z" />
<glyph unicode="&#xe00f;" d="M 380.931-32C 437.794,71.016, 447.375,228.153, 224,222.912L 224,96 L 32,288L 224,480l0-124.186
C 491.481,362.785, 521.285,119.707, 380.931-32z" />
<glyph unicode="&#xe00e;" d="M 112.5,256 C 174.356,256 224.5,205.855 224.5,144 C 224.5,82.144 174.356,32 112.5,32 C 50.644,32 0.5,82.144 0.5,144 L 0,160 C 0,283.712 100.288,384 224,384 L 224,320 C 181.263,320 141.083,303.357 110.863,273.137 C 105.046,267.319 99.737,261.129 94.948,254.627 C 100.667,255.527 106.528,256 112.5,256 ZM 400.5,256 C 462.355,256 512.5,205.855 512.5,144 C 512.5,82.144 462.355,32 400.5,32 C 338.645,32 288.5,82.144 288.5,144 L 288,160 C 288,283.712 388.288,384 512,384 L 512,320 C 469.263,320 429.083,303.357 398.863,273.137 C 393.045,267.319 387.736,261.129 382.947,254.627 C 388.667,255.527 394.527,256 400.5,256 Z" />
<glyph unicode="&#xe00d;" d="M0,448L 512,448L 512,384L0,384zM 192,352L 512,352L 512,288L 192,288zM 192,256L 512,256L 512,192L 192,192zM 192,160L 512,160L 512,96L 192,96zM0,64L 512,64L 512,0L0,0zM 128,320 L 128,128 L 0,224 Z" />
<glyph unicode="&#xe00c;" d="M0,448L 512,448L 512,384L0,384zM 192,352L 512,352L 512,288L 192,288zM 192,256L 512,256L 512,192L 192,192zM 192,160L 512,160L 512,96L 192,96zM0,64L 512,64L 512,0L0,0zM 0,128 L 0,320 L 128,224 Z" />
<glyph unicode="&#xe00b;" d="M 192,64L 512,64L 512,0L 192,0zM 192,256L 512,256L 512,192L 192,192zM 192,448L 512,448L 512,384L 192,384zM 96,480 L 96,352 L 64,352 L 64,448 L 32,448 L 32,480 ZM 64,217 L 64,192 L 128,192 L 128,160 L 32,160 L 32,233 L 96,263 L 96,288 L 32,288 L 32,320 L 128,320 L 128,247 ZM 128,128 L 128-32 L 32-32 L 32,0 L 96,0 L 96,32 L 32,32 L 32,64 L 96,64 L 96,96 L 32,96 L 32,128 Z" />
<glyph unicode="&#xe00a;" d="M 192,448l 320,0 l0-64 L 192,384 L 192,448 z M 192,256l 320,0 l0-64 L 192,192 L 192,256 z M 192,64l 320,0 l0-64 L 192,0 L 192,64 zM0,416A64,64 1980 1 0 128,416A64,64 1980 1 0 0,416zM0,224A64,64 1980 1 0 128,224A64,64 1980 1 0 0,224zM0,32A64,64 1980 1 0 128,32A64,64 1980 1 0 0,32z" />
<glyph unicode="&#xe009;" d="M 32,480L 224,480L 224,448L 32,448zM 288,480L 480,480L 480,448L 288,448zM 476,320l-28,0 L 448,448 L 320,448 l0-128 L 192,320 L 192,448 L 64,448 l0-128 L 36,320 c-19.8,0-36-16.2-36-36l0-280 c0-19.8, 16.2-36, 36-36l 152,0 c 19.8,0, 36,16.2, 36,36L 224,192 l 64,0
l0-188 c0-19.8, 16.2-36, 36-36l 152,0 c 19.8,0, 36,16.2, 36,36L 512,284 C 512,303.8, 495.8,320, 476,320z M 174,0L 50,0 c-9.9,0-18,7.2-18,16
s 8.1,16, 18,16l 124,0 c 9.9,0, 18-7.2, 18-16S 183.9,0, 174,0z M 272,224l-32,0 c-8.8,0-16,7.2-16,16s 7.2,16, 16,16l 32,0 c 8.8,0, 16-7.2, 16-16
S 280.8,224, 272,224z M 462,0L 338,0 c-9.9,0-18,7.2-18,16s 8.1,16, 18,16l 124,0 c 9.9,0, 18-7.2, 18-16S 471.9,0, 462,0z" />
<glyph unicode="&#xe008;" d="M 416,320L 416,400 c0,8.8-7.2,16-16,16L 288,416 L 288,448 c0,17.6-14.4,32-32,32l-64,0 c-17.602,0-32-14.4-32-32l0-32 L 48,416 c-8.801,0-16-7.2-16-16l0-320
c0-8.8, 7.199-16, 16-16l 144,0 l0-96 l 224,0 l 96,96L 512,320 L 416,320 z M 192,447.943c 0.017,0.019, 0.036,0.039, 0.057,0.057l 63.884,0
c 0.021-0.018, 0.041-0.038, 0.059-0.057L 256,416 l-64,0 L 192,447.943 z M 96,352L 96,384 l 256,0 l0-32 L 96,352 z M 416,13.255L 416,64 l 50.745,0 L 416,13.255z M 480,96l-96,0 l0-96
L 224,0 L 224,288 l 256,0 L 480,96 z" />
<glyph unicode="&#xe007;" d="M 445.387,125.423c-22.827,22.778-51.864,34.536-78.973,34.536l-14.556,0 l-31.952,32.004l 127.81,128.019
c 31.952,32.005, 31.952,96.014,0,128.019L 256.001,255.973L 64.285,448c-31.952-32.004-31.952-96.014,0-128.019l 127.811-128.017
l-31.953-32.004l-14.557,0 c-27.11,0-56.146-11.759-78.974-34.538c-40.811-40.721-46.325-101.242-12.315-135.175
C 69.282-24.704, 89.441-32, 110.795-32c 27.108,0, 56.145,11.757, 78.973,34.536c 26.792,26.732, 38.371,62, 33.542,92.674l 32.692,32.744
l 32.688-32.744c-4.828-30.674, 6.753-65.941, 33.542-92.674C 345.063-20.243, 374.098-32, 401.206-32
c 21.354,0, 41.512,7.296, 56.497,22.248C 491.713,24.181, 486.197,84.702, 445.387,125.423z M 176.512,57.231
c-3.849-8.941-9.505-17.173-16.813-24.463c-7.318-7.302-15.586-12.959-24.574-16.812c-8.066-3.458-16.48-5.284-24.331-5.284
c-7.573,0-18.306,1.701-26.431,9.806c-8.068,8.052-9.76,18.659-9.76,26.144c0,7.771, 1.821,16.105, 5.263,24.106
c 3.85,8.942, 9.507,17.173, 16.813,24.463c 7.317,7.303, 15.586,12.957, 24.575,16.812c 8.067,3.457, 16.48,5.284, 24.332,5.284
c 7.573,0, 18.306-1.7, 26.429-9.807c 8.067-8.049, 9.761-18.658, 9.761-26.142C 181.777,73.567, 179.957,65.23, 176.512,57.231z
M 256.002,146.702c-24.957,0-45.188,20.266-45.188,45.263c0,24.996, 20.231,45.26, 45.188,45.26s 45.186-20.264, 45.186-45.26
C 301.188,166.966, 280.958,146.702, 256.002,146.702z M 427.636,20.479c-8.124-8.104-18.856-9.806-26.43-9.806
c-7.852,0-16.265,1.826-24.333,5.284c-8.986,3.853-17.254,9.51-24.571,16.812c-7.307,7.29-12.963,15.521-16.813,24.463
c-3.443,7.999-5.263,16.336-5.263,24.106c0,7.483, 1.692,18.094, 9.76,26.143c 8.123,8.104, 18.856,9.807, 26.43,9.807
c 7.85,0, 16.265-1.827, 24.33-5.284c 8.989-3.854, 17.258-9.509, 24.575-16.812c 7.305-7.29, 12.962-15.521, 16.813-24.463
c 3.442-7.999, 5.263-16.335, 5.263-24.106C 437.396,39.138, 435.702,28.53, 427.636,20.479z" />
<glyph unicode="&#xe006;" d="M0,448L 512,448L 512,384L0,384zM0,352L 512,352L 512,288L0,288zM0,256L 512,256L 512,192L0,192zM0,160L 512,160L 512,96L0,96zM0,64L 512,64L 512,0L0,0z" />
<glyph unicode="&#xe005;" d="M0,448L 512,448L 512,384L0,384zM 192,352L 512,352L 512,288L 192,288zM 192,160L 512,160L 512,96L 192,96zM0,256L 512,256L 512,192L0,192zM0,64L 512,64L 512,0L0,0z" />
<glyph unicode="&#xe004;" d="M0,448L 512,448L 512,384L0,384zM 96,352L 416,352L 416,288L 96,288zM 96,160L 416,160L 416,96L 96,96zM0,256L 512,256L 512,192L0,192zM0,64L 512,64L 512,0L0,0z" />
<glyph unicode="&#xe003;" d="M0,448L 512,448L 512,384L0,384zM0,352L 320,352L 320,288L0,288zM0,160L 320,160L 320,96L0,96zM0,256L 512,256L 512,192L0,192zM0,64L 512,64L 512,0L0,0z" />
<glyph unicode="&#xe002;" d="M 512,183.771l0,80.458 l-79.572,7.957c-4.093,15.021-10.044,29.274-17.605,42.49l 52.298,63.919L 410.595,435.12l-63.918-52.298
c-13.217,7.562-27.471,13.513-42.491,17.604L 296.229,480l-80.458,0 l-7.957-79.573c-15.021-4.093-29.274-10.043-42.49-17.604
L 101.405,435.12L 44.88,378.595l 52.298-63.918c-7.562-13.216-13.513-27.47-17.605-42.49L0,264.229l0-80.458 l 79.573-7.957
c 4.093-15.021, 10.043-29.274, 17.605-42.491L 44.88,69.405l 56.524-56.524l 63.919,52.298c 13.216-7.562, 27.47-13.514, 42.49-17.605
L 215.771-32l 80.458,0 l 7.957,79.572c 15.021,4.093, 29.274,10.044, 42.491,17.605l 63.918-52.298l 56.524,56.524l-52.298,63.918
c 7.562,13.217, 13.514,27.471, 17.605,42.49L 512,183.771z M 352,192l-64-64l-64,0 l-64,64l0,64 l 64,64l 64,0 l 64-64L 352,192 z" />
<glyph unicode="&#xe001;" d="M 451.716,380.285l-71.432,71.431C 364.728,467.272, 334,480, 312,480L 72,480 C 50,480, 32,462, 32,440l0-432 c0-22, 18-40, 40-40l 368,0 c 22,0, 40,18, 40,40
L 480,312 C 480,334, 467.272,364.729, 451.716,380.285z M 429.089,357.657c 1.565-1.565, 3.125-3.487, 4.64-5.657L 352,352 L 352,433.728
c 2.17-1.515, 4.092-3.075, 5.657-4.64L 429.089,357.657z M 448,8c0-4.336-3.664-8-8-8L 72,0 c-4.336,0-8,3.664-8,8L 64,440 c0,4.336, 3.664,8, 8,8
l 240,0 c 2.416,0, 5.127-0.305, 8-0.852L 320,320 l 127.148,0 c 0.547-2.873, 0.852-5.583, 0.852-8L 448,8 z" />
<glyph unicode="&#xe000;" d="M 448,480L0,480 l0-512 l 512,0 L 512,416 L 448,480z M 256,416l 64,0 l0-128 l-64,0 L 256,416 z M 448,32L 64,32 L 64,416 l 32,0 l0-160 l 288,0 L 384,416 l 37.489,0 L 448,389.491L 448,32 z" />
<glyph unicode="&#xe033;" d="M 64,208L 208,64L 448,304L 384,368L 208,192L 128,272 z" />
<glyph unicode="&#xe035;" d="M 256,224L 256,160L 272,160L 288,192L 320,192L 320,64L 296,64L 296,32L 408,32L 408,64L 384,64L 384,192L 416,192L 432,160L 448,160L 448,224 zM 416,320L 416,400 c0,8.8-7.2,16-16,16L 288,416 L 288,448 c0,17.6-14.4,32-32,32l-64,0 c-17.602,0-32-14.4-32-32l0-32 L 48,416 c-8.801,0-16-7.2-16-16l0-320
c0-8.8, 7.199-16, 16-16l 144,0 l0-96 l 320,0 L 512,320 L 416,320 z M 192,447.943c 0.017,0.019, 0.036,0.039, 0.057,0.057l 63.884,0
c 0.021-0.018, 0.041-0.038, 0.059-0.057L 256,416 l-64,0 L 192,447.943 z M 96,352L 96,384 l 256,0 l0-32 L 96,352 z M 480,0L 224,0 L 224,288 l 256,0 L 480,0 z" />
<glyph unicode="&#x20;" horiz-adv-x="256" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,1100 @@
/*
* jQuery Plugin: Tokenizing Autocomplete Text Entry
* Version 1.6.0
*
* Copyright (c) 2009 James Smith (http://loopj.com)
* Licensed jointly under the GPL and MIT licenses,
* choose which one suits your project best!
*
*/
(function ($) {
// Default settings
var DEFAULT_SETTINGS = {
// Search settings
method: "GET",
contentType: "json",
queryParam: "q",
searchDelay: 300,
minChars: 1,
propertyToSearch: "name",
jsonContainer: null,
// Display settings
hintText: "Type in a search term",
noResultsText: "No results",
searchingText: "Searching...",
deleteText: "&times;",
animateDropdown: true,
// Tokenization settings
tokenLimit: null,
tokenDelimiter: ",",
preventDuplicates: false,
// Output settings
tokenValue: "id",
// Prepopulation settings
prePopulate: null,
processPrePopulate: false,
// Manipulation settings
idPrefix: "token-input-",
// Formatters
resultsFormatter: function(item){ return "<li>" + item[this.propertyToSearch]+ "</li>" },
tokenFormatter: function(item) { return "<li><p>" + item[this.propertyToSearch] + "</p></li>" },
// Callbacks
onResult: null,
onAdd: null,
onDelete: null,
onReady: null
};
// Default classes to use when theming
var DEFAULT_CLASSES = {
tokenList: "token-input-list",
token: "token-input-token",
tokenDelete: "token-input-delete-token",
selectedToken: "token-input-selected-token",
highlightedToken: "token-input-highlighted-token",
dropdown: "token-input-dropdown",
dropdownItem: "token-input-dropdown-item",
dropdownItem2: "token-input-dropdown-item2",
selectedDropdownItem: "token-input-selected-dropdown-item",
inputToken: "token-input-input-token"
};
// Input box position "enum"
var POSITION = {
BEFORE: 0,
AFTER: 1,
END: 2
};
// Keys "enum"
var KEY = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
ESCAPE: 27,
SPACE: 32,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
NUMPAD_ENTER: 108,
COMMA: 188
};
// Additional public (exposed) methods
var methods = {
init: function(url_or_data_or_function, options) {
var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
return this.each(function () {
$(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
});
},
clear: function() {
this.data("tokenInputObject").clear();
return this;
},
add: function(item) {
this.data("tokenInputObject").add(item);
return this;
},
remove: function(item) {
this.data("tokenInputObject").remove(item);
return this;
},
get: function() {
return this.data("tokenInputObject").getTokens();
}
}
// Expose the .tokenInput function to jQuery as a plugin
$.fn.tokenInput = function (method) {
// Method calling and initialization logic
if(methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else {
return methods.init.apply(this, arguments);
}
};
// TokenList class for each input
$.TokenList = function (input, url_or_data, settings) {
//
// Initialization
//
// Configure the data source
if($.type(url_or_data) === "string" || $.type(url_or_data) === "function") {
// Set the url to query against
settings.url = url_or_data;
// If the URL is a function, evaluate it here to do our initalization work
var url = computeURL();
// Make a smart guess about cross-domain if it wasn't explicitly specified
if(settings.crossDomain === undefined) {
if(url.indexOf("://") === -1) {
settings.crossDomain = false;
} else {
settings.crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
}
}
} else if(typeof(url_or_data) === "object") {
// Set the local data to search through
settings.local_data = url_or_data;
}
// Build class names
if(settings.classes) {
// Use custom class names
settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes);
} else if(settings.theme) {
// Use theme-suffixed default class names
settings.classes = {};
$.each(DEFAULT_CLASSES, function(key, value) {
settings.classes[key] = value + "-" + settings.theme;
});
} else {
settings.classes = DEFAULT_CLASSES;
}
// Save the tokens
var saved_tokens = [];
// Keep track of the number of tokens in the list
var token_count = 0;
// Basic cache to save on db hits
var cache = new $.TokenList.Cache();
// Keep track of the timeout, old vals
var timeout;
var input_val;
// Create a new text input an attach keyup events
var input_box = $("<input type=\"text\" autocomplete=\"off\">")
.css({
outline: "none"
})
.attr("id", settings.idPrefix + input.id)
.focus(function () {
if (settings.tokenLimit === null || settings.tokenLimit !== token_count) {
show_dropdown_hint();
}
})
.blur(function () {
hide_dropdown();
$(this).val("");
})
.bind("keyup keydown blur update", resize_input)
.keydown(function (event) {
var previous_token;
var next_token;
switch(event.keyCode) {
case KEY.LEFT:
case KEY.RIGHT:
case KEY.UP:
case KEY.DOWN:
if(!$(this).val()) {
previous_token = input_token.prev();
next_token = input_token.next();
if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {
// Check if there is a previous/next token and it is selected
if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
deselect_token($(selected_token), POSITION.BEFORE);
} else {
deselect_token($(selected_token), POSITION.AFTER);
}
} else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
// We are moving left, select the previous token if it exists
select_token($(previous_token.get(0)));
} else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
// We are moving right, select the next token if it exists
select_token($(next_token.get(0)));
}
} else {
var dropdown_item = null;
if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
dropdown_item = $(selected_dropdown_item).next();
} else {
dropdown_item = $(selected_dropdown_item).prev();
}
if(dropdown_item.length) {
select_dropdown_item(dropdown_item);
}
return false;
}
break;
case KEY.BACKSPACE:
previous_token = input_token.prev();
if(!$(this).val().length) {
if(selected_token) {
delete_token($(selected_token));
hidden_input.change();
} else if(previous_token.length) {
select_token($(previous_token.get(0)));
}
return false;
} else if($(this).val().length === 1) {
hide_dropdown();
} else {
// set a timeout just long enough to let this function finish.
setTimeout(function(){do_search();}, 5);
}
break;
case KEY.TAB:
case KEY.ENTER:
case KEY.NUMPAD_ENTER:
case KEY.COMMA:
if(selected_dropdown_item) {
add_token($(selected_dropdown_item).data("tokeninput"));
hidden_input.change();
return false;
}
break;
case KEY.ESCAPE:
hide_dropdown();
return true;
default:
if(String.fromCharCode(event.which)) {
// set a timeout just long enough to let this function finish.
setTimeout(function(){do_search();}, 5);
}
break;
}
});
// Keep a reference to the original input box
var hidden_input = $(input)
.hide()
.val("")
.focus(function () {
input_box.focus();
})
.blur(function () {
input_box.blur();
});
// Keep a reference to the selected token and dropdown item
var selected_token = null;
var selected_token_index = 0;
var selected_dropdown_item = null;
// The list to store the token items in
var token_list = $("<ul />")
.addClass(settings.classes.tokenList)
.click(function (event) {
var li = $(event.target).closest("li");
if(li && li.get(0) && $.data(li.get(0), "tokeninput")) {
toggle_select_token(li);
} else {
// Deselect selected token
if(selected_token) {
deselect_token($(selected_token), POSITION.END);
}
// Focus input box
input_box.focus();
}
})
.mouseover(function (event) {
var li = $(event.target).closest("li");
if(li && selected_token !== this) {
li.addClass(settings.classes.highlightedToken);
}
})
.mouseout(function (event) {
var li = $(event.target).closest("li");
if(li && selected_token !== this) {
li.removeClass(settings.classes.highlightedToken);
}
})
.insertBefore(hidden_input);
// The token holding the input box
var input_token = $("<li />")
.addClass(settings.classes.inputToken)
.appendTo(token_list)
.append(input_box);
// The list to store the dropdown items in
var dropdown = $("<div>")
.addClass(settings.classes.dropdown)
.appendTo("body")
.hide();
// Magic element to help us resize the text input
var input_resizer = $("<tester/>")
.insertAfter(input_box)
.css({
position: "absolute",
top: -9999,
left: -9999,
width: "auto",
fontSize: input_box.css("fontSize"),
fontFamily: input_box.css("fontFamily"),
fontWeight: input_box.css("fontWeight"),
letterSpacing: input_box.css("letterSpacing"),
whiteSpace: "nowrap"
});
// Pre-populate list if items exist
hidden_input.val("");
var li_data = settings.prePopulate || hidden_input.data("pre");
if(settings.processPrePopulate && $.isFunction(settings.onResult)) {
li_data = settings.onResult.call(hidden_input, li_data);
}
if(li_data && li_data.length) {
$.each(li_data, function (index, value) {
insert_token(value);
checkTokenLimit();
});
}
// Initialization is done
if($.isFunction(settings.onReady)) {
settings.onReady.call();
}
//
// Public functions
//
this.clear = function() {
token_list.children("li").each(function() {
if ($(this).children("input").length === 0) {
delete_token($(this));
}
});
}
this.add = function(item) {
add_token(item);
}
this.remove = function(item) {
token_list.children("li").each(function() {
if ($(this).children("input").length === 0) {
var currToken = $(this).data("tokeninput");
var match = true;
for (var prop in item) {
if (item[prop] !== currToken[prop]) {
match = false;
break;
}
}
if (match) {
delete_token($(this));
}
}
});
}
this.getTokens = function() {
return saved_tokens;
}
//
// Private functions
//
function checkTokenLimit() {
if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
input_box.hide();
hide_dropdown();
return;
}
}
function resize_input() {
if(input_val === (input_val = input_box.val())) {return;}
// Enter new content into resizer and resize input accordingly
var escaped = input_val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
input_resizer.html(escaped);
input_box.width(input_resizer.width() + 30);
}
function is_printable_character(keycode) {
return ((keycode >= 48 && keycode <= 90) || // 0-1a-z
(keycode >= 96 && keycode <= 111) || // numpad 0-9 + - / * .
(keycode >= 186 && keycode <= 192) || // ; = , - . / ^
(keycode >= 219 && keycode <= 222)); // ( \ ) '
}
// Inner function to a token to the list
function insert_token(item) {
var this_token = settings.tokenFormatter(item);
this_token = $(this_token)
.addClass(settings.classes.token)
.insertBefore(input_token);
// The 'delete token' button
$("<span>" + settings.deleteText + "</span>")
.addClass(settings.classes.tokenDelete)
.appendTo(this_token)
.click(function () {
delete_token($(this).parent());
hidden_input.change();
return false;
});
// Store data on the token
var token_data = {"id": item.id};
token_data[settings.propertyToSearch] = item[settings.propertyToSearch];
$.data(this_token.get(0), "tokeninput", item);
// Save this token for duplicate checking
saved_tokens = saved_tokens.slice(0,selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
selected_token_index++;
// Update the hidden input
update_hidden_input(saved_tokens, hidden_input);
token_count += 1;
// Check the token limit
if(settings.tokenLimit !== null && token_count >= settings.tokenLimit) {
input_box.hide();
hide_dropdown();
}
return this_token;
}
// Add a token to the token list based on user input
function add_token (item) {
var callback = settings.onAdd;
// See if the token already exists and select it if we don't want duplicates
if(token_count > 0 && settings.preventDuplicates) {
var found_existing_token = null;
token_list.children().each(function () {
var existing_token = $(this);
var existing_data = $.data(existing_token.get(0), "tokeninput");
if(existing_data && existing_data.id === item.id) {
found_existing_token = existing_token;
return false;
}
});
if(found_existing_token) {
select_token(found_existing_token);
input_token.insertAfter(found_existing_token);
input_box.focus();
return;
}
}
// Insert the new tokens
if(settings.tokenLimit == null || token_count < settings.tokenLimit) {
insert_token(item);
checkTokenLimit();
}
// Clear input box
input_box.val("");
// Don't show the help dropdown, they've got the idea
hide_dropdown();
// Execute the onAdd callback if defined
if($.isFunction(callback)) {
callback.call(hidden_input,item);
}
}
// Select a token in the token list
function select_token (token) {
token.addClass(settings.classes.selectedToken);
selected_token = token.get(0);
// Hide input box
input_box.val("");
// Hide dropdown if it is visible (eg if we clicked to select token)
hide_dropdown();
}
// Deselect a token in the token list
function deselect_token (token, position) {
token.removeClass(settings.classes.selectedToken);
selected_token = null;
if(position === POSITION.BEFORE) {
input_token.insertBefore(token);
selected_token_index--;
} else if(position === POSITION.AFTER) {
input_token.insertAfter(token);
selected_token_index++;
} else {
input_token.appendTo(token_list);
selected_token_index = token_count;
}
// Show the input box and give it focus again
input_box.focus();
}
// Toggle selection of a token in the token list
function toggle_select_token(token) {
var previous_selected_token = selected_token;
if(selected_token) {
deselect_token($(selected_token), POSITION.END);
}
if(previous_selected_token === token.get(0)) {
deselect_token(token, POSITION.END);
} else {
select_token(token);
}
}
// Delete a token from the token list
function delete_token (token) {
// Remove the id from the saved list
var token_data = $.data(token.get(0), "tokeninput");
var callback = settings.onDelete;
var index = token.prevAll().length;
if(index > selected_token_index) index--;
// Delete the token
token.remove();
selected_token = null;
// Show the input box and give it focus again
input_box.focus();
// Remove this token from the saved list
saved_tokens = saved_tokens.slice(0,index).concat(saved_tokens.slice(index+1));
if(index < selected_token_index) selected_token_index--;
// Update the hidden input
update_hidden_input(saved_tokens, hidden_input);
token_count -= 1;
if(settings.tokenLimit !== null) {
input_box
.show()
.val("")
.focus();
}
// Execute the onDelete callback if defined
if($.isFunction(callback)) {
callback.call(hidden_input,token_data);
}
}
// Update the hidden input box value
function update_hidden_input(saved_tokens, hidden_input) {
var token_values = $.map(saved_tokens, function (el) {
return el[settings.tokenValue];
});
hidden_input.val(token_values.join(settings.tokenDelimiter));
}
// Hide and clear the results dropdown
function hide_dropdown () {
dropdown.hide().empty();
selected_dropdown_item = null;
}
function show_dropdown() {
dropdown
.css({
position: "absolute",
top: $(token_list).offset().top + $(token_list).outerHeight(),
left: $(token_list).offset().left,
zindex: 999
})
.show();
}
function show_dropdown_searching () {
if(settings.searchingText) {
dropdown.html("<p>"+settings.searchingText+"</p>");
show_dropdown();
}
}
function show_dropdown_hint () {
if(settings.hintText) {
dropdown.html("<p>"+settings.hintText+"</p>");
show_dropdown();
}
}
// Highlight the query part of the search term
function highlight_term(value, term) {
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");
}
function find_value_and_highlight_term(template, value, term) {
return template.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + value + ")(?![^<>]*>)(?![^&;]+;)", "g"), highlight_term(value, term));
}
// Populate the results dropdown with some results
function populate_dropdown (query, results) {
if(results && results.length) {
dropdown.empty();
var dropdown_ul = $("<ul>")
.appendTo(dropdown)
.mouseover(function (event) {
select_dropdown_item($(event.target).closest("li"));
})
.mousedown(function (event) {
add_token($(event.target).closest("li").data("tokeninput"));
hidden_input.change();
return false;
})
.hide();
$.each(results, function(index, value) {
var this_li = settings.resultsFormatter(value);
this_li = find_value_and_highlight_term(this_li ,value[settings.propertyToSearch], query);
this_li = $(this_li).appendTo(dropdown_ul);
if(index % 2) {
this_li.addClass(settings.classes.dropdownItem);
} else {
this_li.addClass(settings.classes.dropdownItem2);
}
if(index === 0) {
select_dropdown_item(this_li);
}
$.data(this_li.get(0), "tokeninput", value);
});
show_dropdown();
if(settings.animateDropdown) {
dropdown_ul.slideDown("fast");
} else {
dropdown_ul.show();
}
} else {
if(settings.noResultsText) {
dropdown.html("<p>"+settings.noResultsText+"</p>");
show_dropdown();
}
}
}
// Highlight an item in the results dropdown
function select_dropdown_item (item) {
if(item) {
if(selected_dropdown_item) {
deselect_dropdown_item($(selected_dropdown_item));
}
item.addClass(settings.classes.selectedDropdownItem);
selected_dropdown_item = item.get(0);
}
}
// Remove highlighting from an item in the results dropdown
function deselect_dropdown_item (item) {
item.removeClass(settings.classes.selectedDropdownItem);
selected_dropdown_item = null;
}
// Do a search and show the "searching" dropdown if the input is longer
// than settings.minChars
function do_search() {
var query = input_box.val().toLowerCase();
if(query && query.length) {
if(selected_token) {
deselect_token($(selected_token), POSITION.AFTER);
}
if(query.length >= settings.minChars) {
show_dropdown_searching();
clearTimeout(timeout);
timeout = setTimeout(function(){
run_search(query);
}, settings.searchDelay);
} else {
hide_dropdown();
}
}
}
// Do the actual search
function run_search(query) {
var cache_key = query + computeURL();
var cached_results = cache.get(cache_key);
if(cached_results) {
populate_dropdown(query, cached_results);
} else {
// Are we doing an ajax search or local data search?
if(settings.url) {
var url = computeURL();
// Extract exisiting get params
var ajax_params = {};
ajax_params.data = {};
if(url.indexOf("?") > -1) {
var parts = url.split("?");
ajax_params.url = parts[0];
var param_array = parts[1].split("&");
$.each(param_array, function (index, value) {
var kv = value.split("=");
ajax_params.data[kv[0]] = kv[1];
});
} else {
ajax_params.url = url;
}
// Prepare the request
ajax_params.data[settings.queryParam] = query;
ajax_params.type = settings.method;
ajax_params.dataType = settings.contentType;
if(settings.crossDomain) {
ajax_params.dataType = "jsonp";
}
// Attach the success callback
ajax_params.success = function(results) {
if($.isFunction(settings.onResult)) {
results = settings.onResult.call(hidden_input, results);
}
cache.add(cache_key, settings.jsonContainer ? results[settings.jsonContainer] : results);
// only populate the dropdown if the results are associated with the active search query
if(input_box.val().toLowerCase() === query) {
populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);
}
};
// Make the request
$.ajax(ajax_params);
} else if(settings.local_data) {
// Do the search through local data
var results = $.grep(settings.local_data, function (row) {
return row[settings.propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
});
if($.isFunction(settings.onResult)) {
results = settings.onResult.call(hidden_input, results);
}
cache.add(cache_key, results);
populate_dropdown(query, results);
}
}
}
// compute the dynamic URL
function computeURL() {
var url = settings.url;
if(typeof settings.url == 'function') {
url = settings.url.call();
}
return url;
}
};
// Really basic cache for the results
$.TokenList.Cache = function (options) {
var settings = $.extend({
max_size: 500
}, options);
var data = {};
var size = 0;
var flush = function () {
data = {};
size = 0;
};
this.add = function (query, results) {
if(size > settings.max_size) {
flush();
}
if(!data[query]) {
size += 1;
}
data[query] = results;
};
this.get = function (query) {
return data[query];
};
};
}(jQuery));
// Generated by CoffeeScript 1.6.3
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.HighlightTags = (function(_super) {
__extends(HighlightTags, _super);
HighlightTags.prototype.options = null;
function HighlightTags(element,options) {
this.pluginSubmit = __bind(this.pluginSubmit, this);
this.updateViewer = __bind(this.updateViewer, this);
this.colorize = __bind(this.colorize, this);
this.updateField = __bind(this.updateField, this);
this.options = options;
_ref = HighlightTags.__super__.constructor.apply(this, arguments);
return _ref;
}
//example variables to be used to receive input in the annotator view
HighlightTags.prototype.field = null;
HighlightTags.prototype.input = null;
HighlightTags.prototype.colors = null;
HighlightTags.prototype.isFirstTime = true;
//this function will initialize the plug in. Create your fields here in the editor and viewer.
HighlightTags.prototype.pluginInit = function() {
console.log("HighlightTags-pluginInit");
//Check that annotator is working
if (!Annotator.supported()) {
return;
}
this.field = this.annotator.editor.addField({
type: 'input',
load: this.updateField,
submit: this.pluginSubmit,
});
var self = this;
var newfield = Annotator.$('<li class="annotator-item">'+ "<div><input placeholder =\"Add tags\" type=\"text\" id=\"tag-input\" name=\"tags\" /></div>"+'</li>');
Annotator.$(self.field).replaceWith(newfield);
self.field=newfield[0];
//
//-- Viewer
var newview = this.annotator.viewer.addField({
load: this.updateViewer,
});
this.colors = this.getHighlightTags();
var self = this;
this.annotator.subscribe('annotationsLoaded', function(){setTimeout(function(){self.colorize()},1000)});
this.annotator.subscribe('annotationUpdated', this.colorize);
this.annotator.subscribe('flaggedAnnotation', this.updateViewer);
this.annotator.subscribe('annotationCreated', this.colorize);
};
HighlightTags.prototype.getHighlightTags = function(){
if (typeof this.options.tag != 'undefined') {
var self = this;
var final = {}, prelim = this.options.tag.split(",");
prelim.forEach(function(item){
var temp = item.split(":");
final[temp[0]] = self.getRGB(temp[1]);
});
return final;
}
return {};
}
HighlightTags.prototype.getRGB = function(item){
function getColorValues( color ){
var values = { red:null, green:null, blue:null, alpha:null };
if( typeof color == 'string' ){
/* hex */
if( color.indexOf('#') === 0 ){
color = color.substr(1)
if( color.length == 3 )
values = {
red: parseInt( color[0]+color[0], 16 ),
green: parseInt( color[1]+color[1], 16 ),
blue: parseInt( color[2]+color[2], 16 ),
alpha: .3
}
else
values = {
red: parseInt( color.substr(0,2), 16 ),
green: parseInt( color.substr(2,2), 16 ),
blue: parseInt( color.substr(4,2), 16 ),
alpha: .3
}
/* rgb */
}else if( color.indexOf('rgb(') === 0 ){
var pars = color.indexOf(',');
values = {
red: parseInt(color.substr(4,pars)),
green: parseInt(color.substr(pars+1,color.indexOf(',',pars))),
blue: parseInt(color.substr(color.indexOf(',',pars+1)+1,color.indexOf(')'))),
alpha: .3
}
/* rgba */
}else if( color.indexOf('rgba(') === 0 ){
var pars = color.indexOf(','),
repars = color.indexOf(',',pars+1);
values = {
red: parseInt(color.substr(5,pars)),
green: parseInt(color.substr(pars+1,repars)),
blue: parseInt(color.substr(color.indexOf(',',pars+1)+1,color.indexOf(',',repars))),
alpha: parseFloat(color.substr(color.indexOf(',',repars+1)+1,color.indexOf(')')))
}
/* verbous */
}else{
var stdCol = { acqua:'#0ff', teal:'#008080', blue:'#00f', navy:'#000080',
yellow:'#ff0', olive:'#808000', lime:'#0f0', green:'#008000',
fuchsia:'#f0f', purple:'#800080', red:'#f00', maroon:'#800000',
white:'#fff', gray:'#808080', silver:'#c0c0c0', black:'#000' };
if( stdCol[color]!=undefined )
values = getColorValues(stdCol[color]);
}
}
return values
}
return getColorValues(item)
}
HighlightTags.prototype.colorize = function(){
var annotations = Array.prototype.slice.call($(".annotator-hl"));
for (annNum = 0; annNum < annotations.length; ++annNum){
var anns = $.data(annotations[annNum],"annotation");
if (typeof anns.tags != "undefined" && anns.tags.length == 0) {
$(annotations[annNum]).css("background-color","");
}
if (typeof anns.tags != "undefined" && this.colors !== {}) {
for(var index = 0; index < anns.tags.length; ++index){
if (typeof this.colors[anns.tags[index]] != "undefined") {
var finalcolor = this.colors[anns.tags[index]];
$(annotations[annNum]).css("background","rgba("+finalcolor.red+","+finalcolor.green+","+finalcolor.blue+",0.3");
}else{
$(annotations[annNum]).css("background","");
}
}
}else{
$(annotations[annNum]).css("background","");
}
}
}
HighlightTags.prototype.updateField = function(field, annotation){
if(this.isFirstTime){
var tags = this.options.tag.split(",");
var tokensavailable = [];
tags.forEach (function(tagnames){
lonename = tagnames.split(":");
tokensavailable.push({'id': lonename[0], 'name':lonename[0]});
});
$("#tag-input").tokenInput(tokensavailable);
this.isFirstTime = false;
}
$('#token-input-tag-input').attr('placeholder','Add tags...');
$('#tag-input').tokenInput('clear');
if (typeof annotation.tags != "undefined") {
for (tagnum = 0; tagnum < annotation.tags.length; tagnum++){
var n = annotation.tags[tagnum];
if (typeof this.annotator.plugins["HighlightTags"] != 'undefined') {
if (annotation.tags[tagnum].indexOf("flagged-")==-1){
$('#tag-input').tokenInput('add',{'id':n,'name':n});
}
} else{
$('#tag-input').tokenInput('add',{'id':n,'name':n});
}
}
}
}
//The following function is run when a person hits submit.
HighlightTags.prototype.pluginSubmit = function(field, annotation) {
var tokens = Array.prototype.slice.call($(".token-input-input-token").parent().find('.token-input-token'));
var arr = [];
tokens.forEach(function(element){
tag = element.firstChild.firstChild;
arr.push(tag.nodeValue);
});
annotation.tags = arr;
}
//The following allows you to edit the annotation popup when the viewer has already
//hit submit and is just viewing the annotation.
HighlightTags.prototype.updateViewer = function(field, annotation) {
if (typeof annotation.tags != "undefined") {
if (annotation.tags.length == 0) {
$(field).remove();
return;
}
var nonFlagTags = true;
var tokenList = "<ul class=\"token-input-list\">";
for (tagnum = 0; tagnum < annotation.tags.length; ++tagnum){
if (typeof this.annotator.plugins["Flagging"] != 'undefined') {
if (annotation.tags[tagnum].indexOf("flagged-")==-1){
tokenList += "<li class=\"token-input-token\"><p>"+ annotation.tags[tagnum]+"</p></span></li>";
nonFlagTags = false;
}
} else{
tokenList += "<li class=\"token-input-token\"><p>"+ annotation.tags[tagnum]+"</p></span></li>";
}
}
tokenList += "</ul>";
$(field).append(tokenList);
if (nonFlagTags) {
$(field).remove();
}
} else{
$(field).remove();
}
this.annotator.publish("finishedDrawingTags");
}
return HighlightTags;
})(Annotator.Plugin);

File diff suppressed because one or more lines are too long

6461
common/static/js/vendor/ova/video.dev.js vendored Normal file
View File

@@ -0,0 +1,6461 @@
/**
* @fileoverview Main function src.
*/
// HTML5 Shiv. Must be in <head> to support older browsers.
document.createElement('video');
document.createElement('audio');
document.createElement('track');
/**
* Doubles as the main function for users to create a player instance and also
* the main library object.
*
* @param {String|Element} id Video element or video element ID
* @param {Object=} options Optional options object for config/settings
* @param {Function=} ready Optional ready callback
* @return {vjs.Player} A player instance
*/
var vjs = function(id, options, ready){
var tag; // Element of ID
// Allow for element or ID to be passed in
// String ID
if (typeof id === 'string') {
// Adjust for jQuery ID syntax
if (id.indexOf('#') === 0) {
id = id.slice(1);
}
// If a player instance has already been created for this ID return it.
if (vjs.players[id]) {
return vjs.players[id];
// Otherwise get element for ID
} else {
tag = vjs.el(id);
}
// ID is a media element
} else {
tag = id;
}
// Check for a useable element
if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also
throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
}
// Element may have a player attr referring to an already created player instance.
// If not, set up a new player and return the instance.
return tag['player'] || new vjs.Player(tag, options, ready);
};
// Extended name, also available externally, window.videojs
var videojs = vjs;
window.videojs = window.vjs = vjs;
// CDN Version. Used to target right flash swf.
vjs.CDN_VERSION = '4.2';
vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
/**
* Global Player instance options, surfaced from vjs.Player.prototype.options_
* vjs.options = vjs.Player.prototype.options_
* All options should use string keys so they avoid
* renaming by closure compiler
* @type {Object}
*/
vjs.options = {
// Default order of fallback technology
'techOrder': ['html5','flash','youtube'],
// techOrder: ['flash','html5'],
'html5': {},
'flash': {},
// Default of web browser is 300x150. Should rely on source width/height.
'width': 300,
'height': 150,
// defaultVolume: 0.85,
'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!
// Included control sets
'children': {
'mediaLoader': {},
'posterImage': {},
'textTrackDisplay': {},
'loadingSpinner': {},
'bigPlayButton': {},
'controlBar': {}
},
// Default message to show when a video cannot be played.
'notSupportedMessage': 'Sorry, no compatible source and playback ' +
'technology were found for this video. Try using another browser ' +
'like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the ' +
'latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
};
// Set CDN Version of swf
// The added (+) blocks the replace from changing this 4.2 string
if (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {
videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';
}
/**
* Global player list
* @type {Object}
*/
vjs.players = {};
/**
* Core Object/Class for objects that use inheritance + contstructors
* @constructor
*/
vjs.CoreObject = vjs['CoreObject'] = function(){};
// Manually exporting vjs['CoreObject'] here for Closure Compiler
// because of the use of the extend/create class methods
// If we didn't do this, those functions would get flattend to something like
// `a = ...` and `this.prototype` would refer to the global object instead of
// CoreObject
/**
* Create a new object that inherits from this Object
* @param {Object} props Functions and properties to be applied to the
* new object's prototype
* @return {vjs.CoreObject} Returns an object that inherits from CoreObject
* @this {*}
*/
vjs.CoreObject.extend = function(props){
var init, subObj;
props = props || {};
// Set up the constructor using the supplied init method
// or using the init of the parent object
// Make sure to check the unobfuscated version for external libs
init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){};
// In Resig's simple class inheritance (previously used) the constructor
// is a function that calls `this.init.apply(arguments)`
// However that would prevent us from using `ParentObject.call(this);`
// in a Child constuctor because the `this` in `this.init`
// would still refer to the Child and cause an inifinite loop.
// We would instead have to do
// `ParentObject.prototype.init.apply(this, argumnents);`
// Bleh. We're not creating a _super() function, so it's good to keep
// the parent constructor reference simple.
subObj = function(){
init.apply(this, arguments);
};
// Inherit from this object's prototype
subObj.prototype = vjs.obj.create(this.prototype);
// Reset the constructor property for subObj otherwise
// instances of subObj would have the constructor of the parent Object
subObj.prototype.constructor = subObj;
// Make the class extendable
subObj.extend = vjs.CoreObject.extend;
// Make a function for creating instances
subObj.create = vjs.CoreObject.create;
// Extend subObj's prototype with functions and other properties from props
for (var name in props) {
if (props.hasOwnProperty(name)) {
subObj.prototype[name] = props[name];
}
}
return subObj;
};
/**
* Create a new instace of this Object class
* @return {vjs.CoreObject} Returns an instance of a CoreObject subclass
* @this {*}
*/
vjs.CoreObject.create = function(){
// Create a new object that inherits from this object's prototype
var inst = vjs.obj.create(this.prototype);
// Apply this constructor function to the new object
this.apply(inst, arguments);
// Return the new object
return inst;
};
/**
* @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
* (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
* This should work very similarly to jQuery's events, however it's based off the book version which isn't as
* robust as jquery's, so there's probably some differences.
*/
/**
* Add an event listener to element
* It stores the handler function in a separate cache object
* and adds a generic handler to the element's event,
* along with a unique id (guid) to the element.
* @param {Element|Object} elem Element or object to bind listeners to
* @param {String} type Type of event to bind to.
* @param {Function} fn Event listener.
*/
vjs.on = function(elem, type, fn){
var data = vjs.getData(elem);
// We need a place to store all our handler data
if (!data.handlers) data.handlers = {};
if (!data.handlers[type]) data.handlers[type] = [];
if (!fn.guid) fn.guid = vjs.guid++;
data.handlers[type].push(fn);
if (!data.dispatcher) {
data.disabled = false;
data.dispatcher = function (event){
if (data.disabled) return;
event = vjs.fixEvent(event);
var handlers = data.handlers[event.type];
if (handlers) {
// Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
var handlersCopy = handlers.slice(0);
for (var m = 0, n = handlersCopy.length; m < n; m++) {
if (event.isImmediatePropagationStopped()) {
break;
} else {
handlersCopy[m].call(elem, event);
}
}
}
};
}
if (data.handlers[type].length == 1) {
if (document.addEventListener) {
elem.addEventListener(type, data.dispatcher, false);
} else if (document.attachEvent) {
elem.attachEvent('on' + type, data.dispatcher);
}
}
};
/**
* Removes event listeners from an element
* @param {Element|Object} elem Object to remove listeners from
* @param {String=} type Type of listener to remove. Don't include to remove all events from element.
* @param {Function} fn Specific listener to remove. Don't incldue to remove listeners for an event type.
*/
vjs.off = function(elem, type, fn) {
// Don't want to add a cache object through getData if not needed
if (!vjs.hasData(elem)) return;
var data = vjs.getData(elem);
// If no events exist, nothing to unbind
if (!data.handlers) { return; }
// Utility function
var removeType = function(t){
data.handlers[t] = [];
vjs.cleanUpEvents(elem,t);
};
// Are we removing all bound events?
if (!type) {
for (var t in data.handlers) removeType(t);
return;
}
var handlers = data.handlers[type];
// If no handlers exist, nothing to unbind
if (!handlers) return;
// If no listener was provided, remove all listeners for type
if (!fn) {
removeType(type);
return;
}
// We're only removing a single handler
if (fn.guid) {
for (var n = 0; n < handlers.length; n++) {
if (handlers[n].guid === fn.guid) {
handlers.splice(n--, 1);
}
}
}
vjs.cleanUpEvents(elem, type);
};
/**
* Clean up the listener cache and dispatchers
* @param {Element|Object} elem Element to clean up
* @param {String} type Type of event to clean up
*/
vjs.cleanUpEvents = function(elem, type) {
var data = vjs.getData(elem);
// Remove the events of a particular type if there are none left
if (data.handlers[type].length === 0) {
delete data.handlers[type];
// data.handlers[type] = null;
// Setting to null was causing an error with data.handlers
// Remove the meta-handler from the element
if (document.removeEventListener) {
elem.removeEventListener(type, data.dispatcher, false);
} else if (document.detachEvent) {
elem.detachEvent('on' + type, data.dispatcher);
}
}
// Remove the events object if there are no types left
if (vjs.isEmpty(data.handlers)) {
delete data.handlers;
delete data.dispatcher;
delete data.disabled;
// data.handlers = null;
// data.dispatcher = null;
// data.disabled = null;
}
// Finally remove the expando if there is no data left
if (vjs.isEmpty(data)) {
vjs.removeData(elem);
}
};
/**
* Fix a native event to have standard property values
* @param {Object} event Event object to fix
* @return {Object}
*/
vjs.fixEvent = function(event) {
function returnTrue() { return true; }
function returnFalse() { return false; }
// Test if fixing up is needed
// Used to check if !event.stopPropagation instead of isPropagationStopped
// But native events return true for stopPropagation, but don't have
// other expected methods like isPropagationStopped. Seems to be a problem
// with the Javascript Ninja code. So we're just overriding all events now.
if (!event || !event.isPropagationStopped) {
var old = event || window.event;
event = {};
// Clone the old object so that we can modify the values event = {};
// IE8 Doesn't like when you mess with native event properties
// Firefox returns false for event.hasOwnProperty('type') and other props
// which makes copying more difficult.
// TODO: Probably best to create a whitelist of event props
for (var key in old) {
// Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
if (key !== 'layerX' && key !== 'layerY') {
event[key] = old[key];
}
}
// The event occurred on this element
if (!event.target) {
event.target = event.srcElement || document;
}
// Handle which other element the event is related to
event.relatedTarget = event.fromElement === event.target ?
event.toElement :
event.fromElement;
// Stop the default browser action
event.preventDefault = function () {
if (old.preventDefault) {
old.preventDefault();
}
event.returnValue = false;
event.isDefaultPrevented = returnTrue;
};
event.isDefaultPrevented = returnFalse;
// Stop the event from bubbling
event.stopPropagation = function () {
if (old.stopPropagation) {
old.stopPropagation();
}
event.cancelBubble = true;
event.isPropagationStopped = returnTrue;
};
event.isPropagationStopped = returnFalse;
// Stop the event from bubbling and executing other handlers
event.stopImmediatePropagation = function () {
if (old.stopImmediatePropagation) {
old.stopImmediatePropagation();
}
event.isImmediatePropagationStopped = returnTrue;
event.stopPropagation();
};
event.isImmediatePropagationStopped = returnFalse;
// Handle mouse position
if (event.clientX != null) {
var doc = document.documentElement, body = document.body;
event.pageX = event.clientX +
(doc && doc.scrollLeft || body && body.scrollLeft || 0) -
(doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY +
(doc && doc.scrollTop || body && body.scrollTop || 0) -
(doc && doc.clientTop || body && body.clientTop || 0);
}
// Handle key presses
event.which = event.charCode || event.keyCode;
// Fix button for mouse clicks:
// 0 == left; 1 == middle; 2 == right
if (event.button != null) {
event.button = (event.button & 1 ? 0 :
(event.button & 4 ? 1 :
(event.button & 2 ? 2 : 0)));
}
}
// Returns fixed-up instance
return event;
};
/**
* Trigger an event for an element
* @param {Element|Object} elem Element to trigger an event on
* @param {String} event Type of event to trigger
*/
vjs.trigger = function(elem, event) {
// Fetches element data and a reference to the parent (for bubbling).
// Don't want to add a data object to cache for every parent,
// so checking hasData first.
var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {};
var parent = elem.parentNode || elem.ownerDocument;
// type = event.type || event,
// handler;
// If an event name was passed as a string, creates an event out of it
if (typeof event === 'string') {
event = { type:event, target:elem };
}
// Normalizes the event properties.
event = vjs.fixEvent(event);
// If the passed element has a dispatcher, executes the established handlers.
if (elemData.dispatcher) {
elemData.dispatcher.call(elem, event);
}
// Unless explicitly stopped or the event does not bubble (e.g. media events)
// recursively calls this function to bubble the event up the DOM.
if (parent && !event.isPropagationStopped() && event.bubbles !== false) {
vjs.trigger(parent, event);
// If at the top of the DOM, triggers the default action unless disabled.
} else if (!parent && !event.isDefaultPrevented()) {
var targetData = vjs.getData(event.target);
// Checks if the target has a default action for this event.
if (event.target[event.type]) {
// Temporarily disables event dispatching on the target as we have already executed the handler.
targetData.disabled = true;
// Executes the default action.
if (typeof event.target[event.type] === 'function') {
event.target[event.type]();
}
// Re-enables event dispatching.
targetData.disabled = false;
}
}
// Inform the triggerer if the default was prevented by returning false
return !event.isDefaultPrevented();
/* Original version of js ninja events wasn't complete.
* We've since updated to the latest version, but keeping this around
* for now just in case.
*/
// // Added in attion to book. Book code was broke.
// event = typeof event === 'object' ?
// event[vjs.expando] ?
// event :
// new vjs.Event(type, event) :
// new vjs.Event(type);
// event.type = type;
// if (handler) {
// handler.call(elem, event);
// }
// // Clean up the event in case it is being reused
// event.result = undefined;
// event.target = elem;
};
/**
* Trigger a listener only once for an event
* @param {Element|Object} elem Element or object to
* @param {[type]} type [description]
* @param {Function} fn [description]
* @return {[type]}
*/
vjs.one = function(elem, type, fn) {
var func = function(){
vjs.off(elem, type, func);
fn.apply(this, arguments);
};
func.guid = fn.guid = fn.guid || vjs.guid++;
vjs.on(elem, type, func);
};
var hasOwnProp = Object.prototype.hasOwnProperty;
/**
* Creates an element and applies properties.
* @param {String=} tagName Name of tag to be created.
* @param {Object=} properties Element properties to be applied.
* @return {Element}
*/
vjs.createEl = function(tagName, properties){
var el, propName;
el = document.createElement(tagName || 'div');
for (propName in properties){
if (hasOwnProp.call(properties, propName)) {
//el[propName] = properties[propName];
// Not remembering why we were checking for dash
// but using setAttribute means you have to use getAttribute
// The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.
// The additional check for "role" is because the default method for adding attributes does not
// add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
// browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
// http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
if (propName.indexOf('aria-') !== -1 || propName=='role') {
el.setAttribute(propName, properties[propName]);
} else {
el[propName] = properties[propName];
}
}
}
return el;
};
/**
* Uppercase the first letter of a string
* @param {String} string String to be uppercased
* @return {String}
*/
vjs.capitalize = function(string){
return string.charAt(0).toUpperCase() + string.slice(1);
};
/**
* Object functions container
* @type {Object}
*/
vjs.obj = {};
/**
* Object.create shim for prototypal inheritance.
* https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
* @param {Object} obj Object to use as prototype
*/
vjs.obj.create = Object.create || function(obj){
//Create a new function called 'F' which is just an empty object.
function F() {}
//the prototype of the 'F' function should point to the
//parameter of the anonymous function.
F.prototype = obj;
//create a new constructor function based off of the 'F' function.
return new F();
};
/**
* Loop through each property in an object and call a function
* whose arguments are (key,value)
* @param {Object} obj Object of properties
* @param {Function} fn Function to be called on each property.
* @this {*}
*/
vjs.obj.each = function(obj, fn, context){
for (var key in obj) {
if (hasOwnProp.call(obj, key)) {
fn.call(context || this, key, obj[key]);
}
}
};
/**
* Merge two objects together and return the original.
* @param {Object} obj1
* @param {Object} obj2
* @return {Object}
*/
vjs.obj.merge = function(obj1, obj2){
if (!obj2) { return obj1; }
for (var key in obj2){
if (hasOwnProp.call(obj2, key)) {
obj1[key] = obj2[key];
}
}
return obj1;
};
/**
* Merge two objects, and merge any properties that are objects
* instead of just overwriting one. Uses to merge options hashes
* where deeper default settings are important.
* @param {Object} obj1 Object to override
* @param {Object} obj2 Overriding object
* @return {Object} New object. Obj1 and Obj2 will be untouched.
*/
vjs.obj.deepMerge = function(obj1, obj2){
var key, val1, val2;
// make a copy of obj1 so we're not ovewriting original values.
// like prototype.options_ and all sub options objects
obj1 = vjs.obj.copy(obj1);
for (key in obj2){
if (hasOwnProp.call(obj2, key)) {
val1 = obj1[key];
val2 = obj2[key];
// Check if both properties are pure objects and do a deep merge if so
if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {
obj1[key] = vjs.obj.deepMerge(val1, val2);
} else {
obj1[key] = obj2[key];
}
}
}
return obj1;
};
/**
* Make a copy of the supplied object
* @param {Object} obj Object to copy
* @return {Object} Copy of object
*/
vjs.obj.copy = function(obj){
return vjs.obj.merge({}, obj);
};
/**
* Check if an object is plain, and not a dom node or any object sub-instance
* @param {Object} obj Object to check
* @return {Boolean} True if plain, false otherwise
*/
vjs.obj.isPlain = function(obj){
return !!obj
&& typeof obj === 'object'
&& obj.toString() === '[object Object]'
&& obj.constructor === Object;
};
/**
* Bind (a.k.a proxy or Context). A simple method for changing the context of a function
It also stores a unique id on the function so it can be easily removed from events
* @param {*} context The object to bind as scope
* @param {Function} fn The function to be bound to a scope
* @param {Number=} uid An optional unique ID for the function to be set
* @return {Function}
*/
vjs.bind = function(context, fn, uid) {
// Make sure the function has a unique ID
if (!fn.guid) { fn.guid = vjs.guid++; }
// Create the new function that changes the context
var ret = function() {
return fn.apply(context, arguments);
};
// Allow for the ability to individualize this function
// Needed in the case where multiple objects might share the same prototype
// IF both items add an event listener with the same function, then you try to remove just one
// it will remove both because they both have the same guid.
// when using this, you need to use the bind method when you remove the listener as well.
// currently used in text tracks
ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid;
return ret;
};
/**
* Element Data Store. Allows for binding data to an element without putting it directly on the element.
* Ex. Event listneres are stored here.
* (also from jsninja.com, slightly modified and updated for closure compiler)
* @type {Object}
*/
vjs.cache = {};
/**
* Unique ID for an element or function
* @type {Number}
*/
vjs.guid = 1;
/**
* Unique attribute name to store an element's guid in
* @type {String}
* @constant
*/
vjs.expando = 'vdata' + (new Date()).getTime();
/**
* Returns the cache object where data for an element is stored
* @param {Element} el Element to store data for.
* @return {Object}
*/
vjs.getData = function(el){
var id = el[vjs.expando];
if (!id) {
id = el[vjs.expando] = vjs.guid++;
vjs.cache[id] = {};
}
return vjs.cache[id];
};
/**
* Returns the cache object where data for an element is stored
* @param {Element} el Element to store data for.
* @return {Object}
*/
vjs.hasData = function(el){
var id = el[vjs.expando];
return !(!id || vjs.isEmpty(vjs.cache[id]));
};
/**
* Delete data for the element from the cache and the guid attr from getElementById
* @param {Element} el Remove data for an element
*/
vjs.removeData = function(el){
var id = el[vjs.expando];
if (!id) { return; }
// Remove all stored data
// Changed to = null
// http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
// vjs.cache[id] = null;
delete vjs.cache[id];
// Remove the expando property from the DOM node
try {
delete el[vjs.expando];
} catch(e) {
if (el.removeAttribute) {
el.removeAttribute(vjs.expando);
} else {
// IE doesn't appear to support removeAttribute on the document element
el[vjs.expando] = null;
}
}
};
vjs.isEmpty = function(obj) {
for (var prop in obj) {
// Inlude null properties as empty.
if (obj[prop] !== null) {
return false;
}
}
return true;
};
/**
* Add a CSS class name to an element
* @param {Element} element Element to add class name to
* @param {String} classToAdd Classname to add
*/
vjs.addClass = function(element, classToAdd){
if ((' '+element.className+' ').indexOf(' '+classToAdd+' ') == -1) {
element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;
}
};
/**
* Remove a CSS class name from an element
* @param {Element} element Element to remove from class name
* @param {String} classToAdd Classname to remove
*/
vjs.removeClass = function(element, classToRemove){
var classNames, i;
if (element.className.indexOf(classToRemove) == -1) { return; }
classNames = element.className.split(' ');
// no arr.indexOf in ie8, and we don't want to add a big shim
for (i = classNames.length - 1; i >= 0; i--) {
if (classNames[i] === classToRemove) {
classNames.splice(i,1);
}
}
element.className = classNames.join(' ');
};
/**
* Element for testing browser HTML5 video capabilities
* @type {Element}
* @constant
*/
vjs.TEST_VID = vjs.createEl('video');
/**
* Useragent for browser testing.
* @type {String}
* @constant
*/
vjs.USER_AGENT = navigator.userAgent;
/**
* Device is an iPhone
* @type {Boolean}
* @constant
*/
vjs.IS_IPHONE = (/iPhone/i).test(vjs.USER_AGENT);
vjs.IS_IPAD = (/iPad/i).test(vjs.USER_AGENT);
vjs.IS_IPOD = (/iPod/i).test(vjs.USER_AGENT);
vjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD;
vjs.IOS_VERSION = (function(){
var match = vjs.USER_AGENT.match(/OS (\d+)_/i);
if (match && match[1]) { return match[1]; }
})();
vjs.IS_ANDROID = (/Android/i).test(vjs.USER_AGENT);
vjs.ANDROID_VERSION = (function() {
// This matches Android Major.Minor.Patch versions
// ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
var match = vjs.USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
major,
minor;
if (!match) {
return null;
}
major = match[1] && parseFloat(match[1]);
minor = match[2] && parseFloat(match[2]);
if (major && minor) {
return parseFloat(match[1] + '.' + match[2]);
} else if (major) {
return major;
} else {
return null;
}
})();
// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
vjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.ANDROID_VERSION < 2.3;
vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);
vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
vjs.TOUCH_ENABLED = ('ontouchstart' in window);
/**
* Get an element's attribute values, as defined on the HTML tag
* Attributs are not the same as properties. They're defined on the tag
* or with setAttribute (which shouldn't be used with HTML)
* This will return true or false for boolean attributes.
* @param {Element} tag Element from which to get tag attributes
* @return {Object}
*/
vjs.getAttributeValues = function(tag){
var obj, knownBooleans, attrs, attrName, attrVal;
obj = {};
// known boolean attributes
// we can check for matching boolean properties, but older browsers
// won't know about HTML5 boolean attributes that we still read from
knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';
if (tag && tag.attributes && tag.attributes.length > 0) {
attrs = tag.attributes;
for (var i = attrs.length - 1; i >= 0; i--) {
attrName = attrs[i].name;
attrVal = attrs[i].value;
// check for known booleans
// the matching element property will return a value for typeof
if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {
// the value of an included boolean attribute is typically an empty
// string ('') which would equal false if we just check for a false value.
// we also don't want support bad code like autoplay='false'
attrVal = (attrVal !== null) ? true : false;
}
obj[attrName] = attrVal;
}
}
return obj;
};
/**
* Get the computed style value for an element
* From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
* @param {Element} el Element to get style value for
* @param {String} strCssRule Style name
* @return {String} Style value
*/
vjs.getComputedDimension = function(el, strCssRule){
var strValue = '';
if(document.defaultView && document.defaultView.getComputedStyle){
strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule);
} else if(el.currentStyle){
// IE8 Width/Height support
strValue = el['client'+strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1)] + 'px';
}
return strValue;
};
/**
* Insert an element as the first child node of another
* @param {Element} child Element to insert
* @param {[type]} parent Element to insert child into
*/
vjs.insertFirst = function(child, parent){
if (parent.firstChild) {
parent.insertBefore(child, parent.firstChild);
} else {
parent.appendChild(child);
}
};
/**
* Object to hold browser support information
* @type {Object}
*/
vjs.support = {};
/**
* Shorthand for document.getElementById()
* Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
* @param {String} id Element ID
* @return {Element} Element with supplied ID
*/
vjs.el = function(id){
if (id.indexOf('#') === 0) {
id = id.slice(1);
}
return document.getElementById(id);
};
/**
* Format seconds as a time string, H:MM:SS or M:SS
* Supplying a guide (in seconds) will force a number of leading zeros
* to cover the length of the guide
* @param {Number} seconds Number of seconds to be turned into a string
* @param {Number} guide Number (in seconds) to model the string after
* @return {String} Time formatted as H:MM:SS or M:SS
*/
vjs.formatTime = function(seconds, guide) {
// Default to using seconds as guide
guide = guide || seconds;
var s = Math.floor(seconds % 60),
m = Math.floor(seconds / 60 % 60),
h = Math.floor(seconds / 3600),
gm = Math.floor(guide / 60 % 60),
gh = Math.floor(guide / 3600);
// handle invalid times
if (isNaN(seconds) || seconds === Infinity) {
// '-' is false for all relational operators (e.g. <, >=) so this setting
// will add the minimum number of fields specified by the guide
h = m = s = '-';
}
// Check if we need to show hours
h = (h > 0 || gh > 0) ? h + ':' : '';
// If hours are showing, we may need to add a leading zero.
// Always show at least one digit of minutes.
m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
// Check if leading zero is need for seconds
s = (s < 10) ? '0' + s : s;
return h + m + s;
};
// Attempt to block the ability to select text while dragging controls
vjs.blockTextSelection = function(){
document.body.focus();
document.onselectstart = function () { return false; };
};
// Turn off text selection blocking
vjs.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };
/**
* Trim whitespace from the ends of a string.
* @param {String} string String to trim
* @return {String} Trimmed string
*/
vjs.trim = function(str){
return (str+'').replace(/^\s+|\s+$/g, '');
};
/**
* Should round off a number to a decimal place
* @param {Number} num Number to round
* @param {Number} dec Number of decimal places to round to
* @return {Number} Rounded number
*/
vjs.round = function(num, dec) {
if (!dec) { dec = 0; }
return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
};
/**
* Should create a fake TimeRange object
* Mimics an HTML5 time range instance, which has functions that
* return the start and end times for a range
* TimeRanges are returned by the buffered() method
* @param {Number} start Start time in seconds
* @param {Number} end End time in seconds
* @return {Object} Fake TimeRange object
*/
vjs.createTimeRange = function(start, end){
return {
length: 1,
start: function() { return start; },
end: function() { return end; }
};
};
/**
* Simple http request for retrieving external files (e.g. text tracks)
* @param {String} url URL of resource
* @param {Function=} onSuccess Success callback
* @param {Function=} onError Error callback
*/
vjs.get = function(url, onSuccess, onError){
var local, request;
if (typeof XMLHttpRequest === 'undefined') {
window.XMLHttpRequest = function () {
try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}
try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}
try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {}
throw new Error('This browser does not support XMLHttpRequest.');
};
}
request = new XMLHttpRequest();
try {
request.open('GET', url);
} catch(e) {
onError(e);
}
local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
request.onreadystatechange = function() {
if (request.readyState === 4) {
if (request.status === 200 || local && request.status === 0) {
onSuccess(request.responseText);
} else {
if (onError) {
onError();
}
}
}
};
try {
request.send();
} catch(e) {
if (onError) {
onError(e);
}
}
};
/* Local Storage
================================================================================ */
vjs.setLocalStorage = function(key, value){
try {
// IE was throwing errors referencing the var anywhere without this
var localStorage = window.localStorage || false;
if (!localStorage) { return; }
localStorage[key] = value;
} catch(e) {
if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
vjs.log('LocalStorage Full (VideoJS)', e);
} else {
if (e.code == 18) {
vjs.log('LocalStorage not allowed (VideoJS)', e);
} else {
vjs.log('LocalStorage Error (VideoJS)', e);
}
}
}
};
/**
* Get abosolute version of relative URL. Used to tell flash correct URL.
* http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
* @param {String} url URL to make absolute
* @return {String} Absolute URL
*/
vjs.getAbsoluteURL = function(url){
// Check if absolute URL
if (!url.match(/^https?:\/\//)) {
// Convert to absolute URL. Flash hosted off-site needs an absolute URL.
url = vjs.createEl('div', {
innerHTML: '<a href="'+url+'">x</a>'
}).firstChild.href;
}
return url;
};
// usage: log('inside coolFunc',this,arguments);
// http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
vjs.log = function(){
vjs.log.history = vjs.log.history || []; // store logs to an array for reference
vjs.log.history.push(arguments);
if(window.console){
window.console.log(Array.prototype.slice.call(arguments));
}
};
// Offset Left
// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
vjs.findPosition = function(el) {
var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
if (el.getBoundingClientRect && el.parentNode) {
box = el.getBoundingClientRect();
}
if (!box) {
return {
left: 0,
top: 0
};
}
docEl = document.documentElement;
body = document.body;
clientLeft = docEl.clientLeft || body.clientLeft || 0;
scrollLeft = window.pageXOffset || body.scrollLeft;
left = box.left + scrollLeft - clientLeft;
clientTop = docEl.clientTop || body.clientTop || 0;
scrollTop = window.pageYOffset || body.scrollTop;
top = box.top + scrollTop - clientTop;
return {
left: left,
top: top
};
};
/**
* @fileoverview Player Component - Base class for all UI objects
*
*/
/**
* Base UI Component class
* @param {Object} player Main Player
* @param {Object=} options
* @constructor
*/
vjs.Component = vjs.CoreObject.extend({
/** @constructor */
init: function(player, options, ready){
this.player_ = player;
// Make a copy of prototype.options_ to protect against overriding global defaults
this.options_ = vjs.obj.copy(this.options_);
// Updated options with supplied options
options = this.options(options);
// Get ID from options, element, or create using player ID and unique ID
this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );
this.name_ = options['name'] || null;
// Create element if one wasn't provided in options
this.el_ = options['el'] || this.createEl();
this.children_ = [];
this.childIndex_ = {};
this.childNameIndex_ = {};
// Add any child components in options
this.initChildren();
this.ready(ready);
// Don't want to trigger ready here or it will before init is actually
// finished for all children that run this constructor
}
});
/**
* Dispose of the component and all child components.
*/
vjs.Component.prototype.dispose = function(){
this.trigger('dispose');
// Dispose all children.
if (this.children_) {
for (var i = this.children_.length - 1; i >= 0; i--) {
if (this.children_[i].dispose) {
this.children_[i].dispose();
}
}
}
// Delete child references
this.children_ = null;
this.childIndex_ = null;
this.childNameIndex_ = null;
// Remove all event listeners.
this.off();
// Remove element from DOM
if (this.el_.parentNode) {
this.el_.parentNode.removeChild(this.el_);
}
vjs.removeData(this.el_);
this.el_ = null;
};
/**
* Reference to main player instance.
* @type {vjs.Player}
* @private
*/
vjs.Component.prototype.player_;
/**
* Return the component's player.
* @return {vjs.Player}
*/
vjs.Component.prototype.player = function(){
return this.player_;
};
/**
* Component options object.
* @type {Object}
* @private
*/
vjs.Component.prototype.options_;
/**
* Deep merge of options objects
* Whenever a property is an object on both options objects
* the two properties will be merged using vjs.obj.deepMerge.
*
* This is used for merging options for child components. We
* want it to be easy to override individual options on a child
* component without having to rewrite all the other default options.
*
* Parent.prototype.options_ = {
* children: {
* 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
* 'childTwo': {},
* 'childThree': {}
* }
* }
* newOptions = {
* children: {
* 'childOne': { 'foo': 'baz', 'abc': '123' }
* 'childTwo': null,
* 'childFour': {}
* }
* }
*
* this.options(newOptions);
*
* RESULT
*
* {
* children: {
* 'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },
* 'childTwo': null, // Disabled. Won't be initialized.
* 'childThree': {},
* 'childFour': {}
* }
* }
*
* @param {Object} obj Object whose values will be overwritten
* @return {Object} NEW merged object. Does not return obj1.
*/
vjs.Component.prototype.options = function(obj){
if (obj === undefined) return this.options_;
return this.options_ = vjs.obj.deepMerge(this.options_, obj);
};
/**
* The DOM element for the component.
* @type {Element}
* @private
*/
vjs.Component.prototype.el_;
/**
* Create the component's DOM element.
* @param {String=} tagName Element's node type. e.g. 'div'
* @param {Object=} attributes An object of element attributes that should be set on the element.
* @return {Element}
*/
vjs.Component.prototype.createEl = function(tagName, attributes){
return vjs.createEl(tagName, attributes);
};
/**
* Return the component's DOM element.
* @return {Element}
*/
vjs.Component.prototype.el = function(){
return this.el_;
};
/**
* An optional element where, if defined, children will be inserted
* instead of directly in el_
* @type {Element}
* @private
*/
vjs.Component.prototype.contentEl_;
/**
* Return the component's DOM element for embedding content.
* will either be el_ or a new element defined in createEl
* @return {Element}
*/
vjs.Component.prototype.contentEl = function(){
return this.contentEl_ || this.el_;
};
/**
* The ID for the component.
* @type {String}
* @private
*/
vjs.Component.prototype.id_;
/**
* Return the component's ID.
* @return {String}
*/
vjs.Component.prototype.id = function(){
return this.id_;
};
/**
* The name for the component. Often used to reference the component.
* @type {String}
* @private
*/
vjs.Component.prototype.name_;
/**
* Return the component's ID.
* @return {String}
*/
vjs.Component.prototype.name = function(){
return this.name_;
};
/**
* Array of child components
* @type {Array}
* @private
*/
vjs.Component.prototype.children_;
/**
* Returns array of all child components.
* @return {Array}
*/
vjs.Component.prototype.children = function(){
return this.children_;
};
/**
* Object of child components by ID
* @type {Object}
* @private
*/
vjs.Component.prototype.childIndex_;
/**
* Returns a child component with the provided ID.
* @return {Array}
*/
vjs.Component.prototype.getChildById = function(id){
return this.childIndex_[id];
};
/**
* Object of child components by Name
* @type {Object}
* @private
*/
vjs.Component.prototype.childNameIndex_;
/**
* Returns a child component with the provided ID.
* @return {Array}
*/
vjs.Component.prototype.getChild = function(name){
return this.childNameIndex_[name];
};
/**
* Adds a child component inside this component.
* @param {String|vjs.Component} child The class name or instance of a child to add.
* @param {Object=} options Optional options, including options to be passed to
* children of the child.
* @return {vjs.Component} The child component, because it might be created in this process.
* @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}
*/
vjs.Component.prototype.addChild = function(child, options){
var component, componentClass, componentName, componentId;
// If string, create new component with options
if (typeof child === 'string') {
componentName = child;
// Make sure options is at least an empty object to protect against errors
options = options || {};
// Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
componentClass = options['componentClass'] || vjs.capitalize(componentName);
// Set name through options
options['name'] = componentName;
// Create a new object & element for this controls set
// If there's no .player_, this is a player
// Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly.
// Every class should be exported, so this should never be a problem here.
component = new window['videojs'][componentClass](this.player_ || this, options);
// child is a component instance
} else {
component = child;
}
this.children_.push(component);
if (typeof component.id === 'function') {
this.childIndex_[component.id()] = component;
}
// If a name wasn't used to create the component, check if we can use the
// name function of the component
componentName = componentName || (component.name && component.name());
if (componentName) {
this.childNameIndex_[componentName] = component;
}
// Add the UI object's element to the container div (box)
// Having an element is not required
if (typeof component['el'] === 'function' && component['el']()) {
this.contentEl().appendChild(component['el']());
}
// Return so it can stored on parent object if desired.
return component;
};
vjs.Component.prototype.removeChild = function(component){
if (typeof component === 'string') {
component = this.getChild(component);
}
if (!component || !this.children_) return;
var childFound = false;
for (var i = this.children_.length - 1; i >= 0; i--) {
if (this.children_[i] === component) {
childFound = true;
this.children_.splice(i,1);
break;
}
}
if (!childFound) return;
this.childIndex_[component.id] = null;
this.childNameIndex_[component.name] = null;
var compEl = component.el();
if (compEl && compEl.parentNode === this.contentEl()) {
this.contentEl().removeChild(component.el());
}
};
/**
* Initialize default child components from options
*/
vjs.Component.prototype.initChildren = function(){
var options = this.options_;
if (options && options['children']) {
var self = this;
// Loop through components and add them to the player
vjs.obj.each(options['children'], function(name, opts){
// Allow for disabling default components
// e.g. vjs.options['children']['posterImage'] = false
if (opts === false) return;
// Allow waiting to add components until a specific event is called
var tempAdd = function(){
// Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
self[name] = self.addChild(name, opts);
};
if (opts['loadEvent']) {
// this.one(opts.loadEvent, tempAdd)
} else {
tempAdd();
}
});
}
};
vjs.Component.prototype.buildCSSClass = function(){
// Child classes can include a function that does:
// return 'CLASS NAME' + this._super();
return '';
};
/* Events
============================================================================= */
/**
* Add an event listener to this component's element. Context will be the component.
* @param {String} type Event type e.g. 'click'
* @param {Function} fn Event listener
* @return {vjs.Component}
*/
vjs.Component.prototype.on = function(type, fn){
vjs.on(this.el_, type, vjs.bind(this, fn));
return this;
};
/**
* Remove an event listener from the component's element
* @param {String=} type Optional event type. Without type it will remove all listeners.
* @param {Function=} fn Optional event listener. Without fn it will remove all listeners for a type.
* @return {vjs.Component}
*/
vjs.Component.prototype.off = function(type, fn){
vjs.off(this.el_, type, fn);
return this;
};
/**
* Add an event listener to be triggered only once and then removed
* @param {String} type Event type
* @param {Function} fn Event listener
* @return {vjs.Component}
*/
vjs.Component.prototype.one = function(type, fn) {
vjs.one(this.el_, type, vjs.bind(this, fn));
return this;
};
/**
* Trigger an event on an element
* @param {String} type Event type to trigger
* @param {Event|Object} event Event object to be passed to the listener
* @return {vjs.Component}
*/
vjs.Component.prototype.trigger = function(type, event){
vjs.trigger(this.el_, type, event);
return this;
};
/* Ready
================================================================================ */
/**
* Is the component loaded.
* @type {Boolean}
* @private
*/
vjs.Component.prototype.isReady_;
/**
* Trigger ready as soon as initialization is finished.
* Allows for delaying ready. Override on a sub class prototype.
* If you set this.isReadyOnInitFinish_ it will affect all components.
* Specially used when waiting for the Flash player to asynchrnously load.
* @type {Boolean}
* @private
*/
vjs.Component.prototype.isReadyOnInitFinish_ = true;
/**
* List of ready listeners
* @type {Array}
* @private
*/
vjs.Component.prototype.readyQueue_;
/**
* Bind a listener to the component's ready state.
* Different from event listeners in that if the ready event has already happend
* it will trigger the function immediately.
* @param {Function} fn Ready listener
* @return {vjs.Component}
*/
vjs.Component.prototype.ready = function(fn){
if (fn) {
if (this.isReady_) {
fn.call(this);
} else {
if (this.readyQueue_ === undefined) {
this.readyQueue_ = [];
}
this.readyQueue_.push(fn);
}
}
return this;
};
/**
* Trigger the ready listeners
* @return {vjs.Component}
*/
vjs.Component.prototype.triggerReady = function(){
this.isReady_ = true;
var readyQueue = this.readyQueue_;
if (readyQueue && readyQueue.length > 0) {
for (var i = 0, j = readyQueue.length; i < j; i++) {
readyQueue[i].call(this);
}
// Reset Ready Queue
this.readyQueue_ = [];
// Allow for using event listeners also, in case you want to do something everytime a source is ready.
this.trigger('ready');
}
};
/* Display
============================================================================= */
/**
* Add a CSS class name to the component's element
* @param {String} classToAdd Classname to add
* @return {vjs.Component}
*/
vjs.Component.prototype.addClass = function(classToAdd){
vjs.addClass(this.el_, classToAdd);
return this;
};
/**
* Remove a CSS class name from the component's element
* @param {String} classToRemove Classname to remove
* @return {vjs.Component}
*/
vjs.Component.prototype.removeClass = function(classToRemove){
vjs.removeClass(this.el_, classToRemove);
return this;
};
/**
* Show the component element if hidden
* @return {vjs.Component}
*/
vjs.Component.prototype.show = function(){
this.el_.style.display = 'block';
return this;
};
/**
* Hide the component element if hidden
* @return {vjs.Component}
*/
vjs.Component.prototype.hide = function(){
this.el_.style.display = 'none';
return this;
};
/**
* Lock an item in its visible state. To be used with fadeIn/fadeOut.
* @return {vjs.Component}
*/
vjs.Component.prototype.lockShowing = function(){
this.addClass('vjs-lock-showing');
return this;
};
/**
* Unlock an item to be hidden. To be used with fadeIn/fadeOut.
* @return {vjs.Component}
*/
vjs.Component.prototype.unlockShowing = function(){
this.removeClass('vjs-lock-showing');
return this;
};
/**
* Disable component by making it unshowable
*/
vjs.Component.prototype.disable = function(){
this.hide();
this.show = function(){};
};
/**
* If a value is provided it will change the width of the player to that value
* otherwise the width is returned
* http://dev.w3.org/html5/spec/dimension-attributes.html#attr-dim-height
* Video tag width/height only work in pixels. No percents.
* But allowing limited percents use. e.g. width() will return number+%, not computed width
* @param {Number|String=} num Optional width number
* @param {[type]} skipListeners Skip the 'resize' event trigger
* @return {vjs.Component|Number|String} Returns 'this' if dimension was set.
* Otherwise it returns the dimension.
*/
vjs.Component.prototype.width = function(num, skipListeners){
return this.dimension('width', num, skipListeners);
};
/**
* Get or set the height of the player
* @param {Number|String=} num Optional new player height
* @param {Boolean=} skipListeners Optional skip resize event trigger
* @return {vjs.Component|Number|String} The player, or the dimension
*/
vjs.Component.prototype.height = function(num, skipListeners){
return this.dimension('height', num, skipListeners);
};
/**
* Set both width and height at the same time.
* @param {Number|String} width
* @param {Number|String} height
* @return {vjs.Component} The player.
*/
vjs.Component.prototype.dimensions = function(width, height){
// Skip resize listeners on width for optimization
return this.width(width, true).height(height);
};
/**
* Get or set width or height.
* All for an integer, integer + 'px' or integer + '%';
* Known issue: hidden elements. Hidden elements officially have a width of 0.
* So we're defaulting to the style.width value and falling back to computedStyle
* which has the hidden element issue.
* Info, but probably not an efficient fix:
* http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
* @param {String=} widthOrHeight 'width' or 'height'
* @param {Number|String=} num New dimension
* @param {Boolean=} skipListeners Skip resize event trigger
* @return {vjs.Component|Number|String} Return the player if setting a dimension.
* Otherwise it returns the dimension.
*/
vjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){
if (num !== undefined) {
// Check if using css width/height (% or px) and adjust
if ((''+num).indexOf('%') !== -1 || (''+num).indexOf('px') !== -1) {
this.el_.style[widthOrHeight] = num;
} else if (num === 'auto') {
this.el_.style[widthOrHeight] = '';
} else {
this.el_.style[widthOrHeight] = num+'px';
}
// skipListeners allows us to avoid triggering the resize event when setting both width and height
if (!skipListeners) { this.trigger('resize'); }
// Return component
return this;
}
// Not setting a value, so getting it
// Make sure element exists
if (!this.el_) return 0;
// Get dimension value from style
var val = this.el_.style[widthOrHeight];
var pxIndex = val.indexOf('px');
if (pxIndex !== -1) {
// Return the pixel value with no 'px'
return parseInt(val.slice(0,pxIndex), 10);
// No px so using % or no style was set, so falling back to offsetWidth/height
// If component has display:none, offset will return 0
// TODO: handle display:none and no dimension style using px
} else {
return parseInt(this.el_['offset'+vjs.capitalize(widthOrHeight)], 10);
// ComputedStyle version.
// Only difference is if the element is hidden it will return
// the percent value (e.g. '100%'')
// instead of zero like offsetWidth returns.
// var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);
// var pxIndex = val.indexOf('px');
// if (pxIndex !== -1) {
// return val.slice(0, pxIndex);
// } else {
// return val;
// }
}
};
/**
* Emit 'tap' events when touch events are supported. We're requireing them to
* be enabled because otherwise every component would have this extra overhead
* unnecessarily, on mobile devices where extra overhead is especially bad.
*
* This is being implemented so we can support taps on the video element
* toggling the controls.
*/
vjs.Component.prototype.emitTapEvents = function(){
var touchStart, touchTime, couldBeTap, noTap;
// Track the start time so we can determine how long the touch lasted
touchStart = 0;
this.on('touchstart', function(event) {
// Record start time so we can detect a tap vs. "touch and hold"
touchStart = new Date().getTime();
// Reset couldBeTap tracking
couldBeTap = true;
});
noTap = function(){
couldBeTap = false;
};
// TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
this.on('touchmove', noTap);
this.on('touchleave', noTap);
this.on('touchcancel', noTap);
// When the touch ends, measure how long it took and trigger the appropriate
// event
this.on('touchend', function() {
// Proceed only if the touchmove/leave/cancel event didn't happen
if (couldBeTap === true) {
// Measure how long the touch lasted
touchTime = new Date().getTime() - touchStart;
// The touch needs to be quick in order to consider it a tap
if (touchTime < 250) {
this.trigger('tap');
// It may be good to copy the touchend event object and change the
// type to tap, if the other event properties aren't exact after
// vjs.fixEvent runs (e.g. event.target)
}
}
});
};
/* Button - Base class for all buttons
================================================================================ */
/**
* Base class for all buttons
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Button = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
var touchstart = false;
this.on('touchstart', function(event) {
// Stop click and other mouse events from triggering also
event.preventDefault();
touchstart = true;
});
this.on('touchmove', function() {
touchstart = false;
});
var self = this;
this.on('touchend', function(event) {
if (touchstart) {
self.onClick(event);
}
event.preventDefault();
});
this.on('click', this.onClick);
this.on('focus', this.onFocus);
this.on('blur', this.onBlur);
}
});
vjs.Button.prototype.createEl = function(type, props){
// Add standard Aria and Tabindex info
props = vjs.obj.merge({
className: this.buildCSSClass(),
innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + (this.buttonText || 'Need Text') + '</span></div>',
role: 'button',
'aria-live': 'polite', // let the screen reader user know that the text of the button may change
tabIndex: 0
}, props);
return vjs.Component.prototype.createEl.call(this, type, props);
};
vjs.Button.prototype.buildCSSClass = function(){
// TODO: Change vjs-control to vjs-button?
return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);
};
// Click - Override with specific functionality for button
vjs.Button.prototype.onClick = function(){};
// Focus - Add keyboard functionality to element
vjs.Button.prototype.onFocus = function(){
vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
// KeyPress (document level) - Trigger click when keys are pressed
vjs.Button.prototype.onKeyPress = function(event){
// Check for space bar (32) or enter (13) keys
if (event.which == 32 || event.which == 13) {
event.preventDefault();
this.onClick();
}
};
// Blur - Remove keyboard triggers
vjs.Button.prototype.onBlur = function(){
vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
/* Slider
================================================================================ */
/**
* Parent for seek bar and volume slider
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Slider = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
// Set property names to bar and handle to match with the child Slider class is looking for
this.bar = this.getChild(this.options_['barName']);
this.handle = this.getChild(this.options_['handleName']);
player.on(this.playerEvent, vjs.bind(this, this.update));
this.on('mousedown', this.onMouseDown);
this.on('touchstart', this.onMouseDown);
this.on('focus', this.onFocus);
this.on('blur', this.onBlur);
this.on('click', this.onClick);
this.player_.on('controlsvisible', vjs.bind(this, this.update));
// This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
// this.player_.one('timeupdate', vjs.bind(this, this.update));
player.ready(vjs.bind(this, this.update));
this.boundEvents = {};
}
});
vjs.Slider.prototype.createEl = function(type, props) {
props = props || {};
// Add the slider element class to all sub classes
props.className = props.className + ' vjs-slider';
props = vjs.obj.merge({
role: 'slider',
'aria-valuenow': 0,
'aria-valuemin': 0,
'aria-valuemax': 100,
tabIndex: 0
}, props);
return vjs.Component.prototype.createEl.call(this, type, props);
};
vjs.Slider.prototype.onMouseDown = function(event){
event.preventDefault();
vjs.blockTextSelection();
this.boundEvents.move = vjs.bind(this, this.onMouseMove);
this.boundEvents.end = vjs.bind(this, this.onMouseUp);
vjs.on(document, 'mousemove', this.boundEvents.move);
vjs.on(document, 'mouseup', this.boundEvents.end);
vjs.on(document, 'touchmove', this.boundEvents.move);
vjs.on(document, 'touchend', this.boundEvents.end);
this.onMouseMove(event);
};
vjs.Slider.prototype.onMouseUp = function() {
vjs.unblockTextSelection();
vjs.off(document, 'mousemove', this.boundEvents.move, false);
vjs.off(document, 'mouseup', this.boundEvents.end, false);
vjs.off(document, 'touchmove', this.boundEvents.move, false);
vjs.off(document, 'touchend', this.boundEvents.end, false);
this.update();
};
vjs.Slider.prototype.update = function(){
// In VolumeBar init we have a setTimeout for update that pops and update to the end of the
// execution stack. The player is destroyed before then update will cause an error
if (!this.el_) return;
// If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
// On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
// var progress = (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
var barProgress,
progress = this.getPercent(),
handle = this.handle,
bar = this.bar;
// Protect against no duration and other division issues
if (isNaN(progress)) { progress = 0; }
barProgress = progress;
// If there is a handle, we need to account for the handle in our calculation for progress bar
// so that it doesn't fall short of or extend past the handle.
if (handle) {
var box = this.el_,
boxWidth = box.offsetWidth,
handleWidth = handle.el().offsetWidth,
// The width of the handle in percent of the containing box
// In IE, widths may not be ready yet causing NaN
handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
// Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
// There is a margin of half the handle's width on both sides.
boxAdjustedPercent = 1 - handlePercent,
// Adjust the progress that we'll use to set widths to the new adjusted box width
adjustedProgress = progress * boxAdjustedPercent;
// The bar does reach the left side, so we need to account for this in the bar's width
barProgress = adjustedProgress + (handlePercent / 2);
// Move the handle from the left based on the adjected progress
handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';
}
// Set the new bar width
bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
};
vjs.Slider.prototype.calculateDistance = function(event){
var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;
el = this.el_;
box = vjs.findPosition(el);
boxW = boxH = el.offsetWidth;
handle = this.handle;
if (this.options_.vertical) {
boxY = box.top;
if (event.changedTouches) {
pageY = event.changedTouches[0].pageY;
} else {
pageY = event.pageY;
}
if (handle) {
var handleH = handle.el().offsetHeight;
// Adjusted X and Width, so handle doesn't go outside the bar
boxY = boxY + (handleH / 2);
boxH = boxH - handleH;
}
// Percent that the click is through the adjusted area
return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
} else {
boxX = box.left;
if (event.changedTouches) {
pageX = event.changedTouches[0].pageX;
} else {
pageX = event.pageX;
}
if (handle) {
var handleW = handle.el().offsetWidth;
// Adjusted X and Width, so handle doesn't go outside the bar
boxX = boxX + (handleW / 2);
boxW = boxW - handleW;
}
// Percent that the click is through the adjusted area
return Math.max(0, Math.min(1, (pageX - boxX) / boxW));
}
};
vjs.Slider.prototype.onFocus = function(){
vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
vjs.Slider.prototype.onKeyPress = function(event){
if (event.which == 37) { // Left Arrow
event.preventDefault();
this.stepBack();
} else if (event.which == 39) { // Right Arrow
event.preventDefault();
this.stepForward();
}
};
vjs.Slider.prototype.onBlur = function(){
vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
};
/**
* Listener for click events on slider, used to prevent clicks
* from bubbling up to parent elements like button menus.
* @param {Object} event Event object
*/
vjs.Slider.prototype.onClick = function(event){
event.stopImmediatePropagation();
event.preventDefault();
};
/**
* SeekBar Behavior includes play progress bar, and seek handle
* Needed so it can determine seek position based on handle position/size
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.SliderHandle = vjs.Component.extend();
/**
* Default value of the slider
* @type {Number}
*/
vjs.SliderHandle.prototype.defaultValue = 0;
/** @inheritDoc */
vjs.SliderHandle.prototype.createEl = function(type, props) {
props = props || {};
// Add the slider element class to all sub classes
props.className = props.className + ' vjs-slider-handle';
props = vjs.obj.merge({
innerHTML: '<span class="vjs-control-text">'+this.defaultValue+'</span>'
}, props);
return vjs.Component.prototype.createEl.call(this, 'div', props);
};
/* Menu
================================================================================ */
/**
* The base for text track and settings menu buttons.
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.Menu = vjs.Component.extend();
/**
* Add a menu item to the menu
* @param {Object|String} component Component or component type to add
*/
vjs.Menu.prototype.addItem = function(component){
this.addChild(component);
component.on('click', vjs.bind(this, function(){
this.unlockShowing();
}));
};
/** @inheritDoc */
vjs.Menu.prototype.createEl = function(){
var contentElType = this.options().contentElType || 'ul';
this.contentEl_ = vjs.createEl(contentElType, {
className: 'vjs-menu-content'
});
var el = vjs.Component.prototype.createEl.call(this, 'div', {
append: this.contentEl_,
className: 'vjs-menu'
});
el.appendChild(this.contentEl_);
// Prevent clicks from bubbling up. Needed for Menu Buttons,
// where a click on the parent is significant
vjs.on(el, 'click', function(event){
event.preventDefault();
event.stopImmediatePropagation();
});
return el;
};
/**
* Menu item
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.MenuItem = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
this.selected(options['selected']);
}
});
/** @inheritDoc */
vjs.MenuItem.prototype.createEl = function(type, props){
return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({
className: 'vjs-menu-item',
innerHTML: this.options_['label']
}, props));
};
/** @inheritDoc */
vjs.MenuItem.prototype.onClick = function(){
this.selected(true);
};
/**
* Set this menu item as selected or not
* @param {Boolean} selected
*/
vjs.MenuItem.prototype.selected = function(selected){
if (selected) {
this.addClass('vjs-selected');
this.el_.setAttribute('aria-selected',true);
} else {
this.removeClass('vjs-selected');
this.el_.setAttribute('aria-selected',false);
}
};
/**
* A button class with a popup menu
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.MenuButton = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
this.menu = this.createMenu();
// Add list to element
this.addChild(this.menu);
// Automatically hide empty menu buttons
if (this.items && this.items.length === 0) {
this.hide();
}
this.on('keyup', this.onKeyPress);
this.el_.setAttribute('aria-haspopup', true);
this.el_.setAttribute('role', 'button');
}
});
/**
* Track the state of the menu button
* @type {Boolean}
*/
vjs.MenuButton.prototype.buttonPressed_ = false;
vjs.MenuButton.prototype.createMenu = function(){
var menu = new vjs.Menu(this.player_);
// Add a title list item to the top
if (this.options().title) {
menu.el().appendChild(vjs.createEl('li', {
className: 'vjs-menu-title',
innerHTML: vjs.capitalize(this.kind_),
tabindex: -1
}));
}
this.items = this['createItems']();
if (this.items) {
// Add menu items to the menu
for (var i = 0; i < this.items.length; i++) {
menu.addItem(this.items[i]);
}
}
return menu;
};
/**
* Create the list of menu items. Specific to each subclass.
*/
vjs.MenuButton.prototype.createItems = function(){};
/** @inheritDoc */
vjs.MenuButton.prototype.buildCSSClass = function(){
return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);
};
// Focus - Add keyboard functionality to element
// This function is not needed anymore. Instead, the keyboard functionality is handled by
// treating the button as triggering a submenu. When the button is pressed, the submenu
// appears. Pressing the button again makes the submenu disappear.
vjs.MenuButton.prototype.onFocus = function(){};
// Can't turn off list display that we turned on with focus, because list would go away.
vjs.MenuButton.prototype.onBlur = function(){};
vjs.MenuButton.prototype.onClick = function(){
// When you click the button it adds focus, which will show the menu indefinitely.
// So we'll remove focus when the mouse leaves the button.
// Focus is needed for tab navigation.
this.one('mouseout', vjs.bind(this, function(){
this.menu.unlockShowing();
this.el_.blur();
}));
if (this.buttonPressed_){
this.unpressButton();
} else {
this.pressButton();
}
};
vjs.MenuButton.prototype.onKeyPress = function(event){
event.preventDefault();
// Check for space bar (32) or enter (13) keys
if (event.which == 32 || event.which == 13) {
if (this.buttonPressed_){
this.unpressButton();
} else {
this.pressButton();
}
// Check for escape (27) key
} else if (event.which == 27){
if (this.buttonPressed_){
this.unpressButton();
}
}
};
vjs.MenuButton.prototype.pressButton = function(){
this.buttonPressed_ = true;
this.menu.lockShowing();
this.el_.setAttribute('aria-pressed', true);
if (this.items && this.items.length > 0) {
this.items[0].el().focus(); // set the focus to the title of the submenu
}
};
vjs.MenuButton.prototype.unpressButton = function(){
this.buttonPressed_ = false;
this.menu.unlockShowing();
this.el_.setAttribute('aria-pressed', false);
};
/**
* Main player class. A player instance is returned by _V_(id);
* @param {Element} tag The original video tag used for configuring options
* @param {Object=} options Player options
* @param {Function=} ready Ready callback function
* @constructor
*/
vjs.Player = vjs.Component.extend({
/** @constructor */
init: function(tag, options, ready){
this.tag = tag; // Store the original tag used to set options
// Set Options
// The options argument overrides options set in the video tag
// which overrides globally set options.
// This latter part coincides with the load order
// (tag must exist before Player)
options = vjs.obj.merge(this.getTagSettings(tag), options);
// Cache for video property values.
this.cache_ = {};
// Set poster
this.poster_ = options['poster'];
// Set controls
this.controls_ = options['controls'];
// Original tag settings stored in options
// now remove immediately so native controls don't flash.
// May be turned back on by HTML5 tech if nativeControlsForTouch is true
tag.controls = false;
// Run base component initializing with new options.
// Builds the element through createEl()
// Inits and embeds any child components in opts
vjs.Component.call(this, this, options, ready);
// Update controls className. Can't do this when the controls are initially
// set because the element doesn't exist yet.
if (this.controls()) {
this.addClass('vjs-controls-enabled');
} else {
this.addClass('vjs-controls-disabled');
}
// TODO: Make this smarter. Toggle user state between touching/mousing
// using events, since devices can have both touch and mouse events.
// if (vjs.TOUCH_ENABLED) {
// this.addClass('vjs-touch-enabled');
// }
// Firstplay event implimentation. Not sold on the event yet.
// Could probably just check currentTime==0?
this.one('play', function(e){
var fpEvent = { type: 'firstplay', target: this.el_ };
// Using vjs.trigger so we can check if default was prevented
var keepGoing = vjs.trigger(this.el_, fpEvent);
if (!keepGoing) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
});
this.on('ended', this.onEnded);
this.on('play', this.onPlay);
this.on('firstplay', this.onFirstPlay);
this.on('pause', this.onPause);
this.on('progress', this.onProgress);
this.on('durationchange', this.onDurationChange);
this.on('error', this.onError);
this.on('fullscreenchange', this.onFullscreenChange);
// Make player easily findable by ID
vjs.players[this.id_] = this;
if (options['plugins']) {
vjs.obj.each(options['plugins'], function(key, val){
this[key](val);
}, this);
}
this.listenForUserActivity();
}
});
/**
* Player instance options, surfaced using vjs.options
* vjs.options = vjs.Player.prototype.options_
* Make changes in vjs.options, not here.
* All options should use string keys so they avoid
* renaming by closure compiler
* @type {Object}
* @private
*/
vjs.Player.prototype.options_ = vjs.options;
vjs.Player.prototype.dispose = function(){
this.trigger('dispose');
// prevent dispose from being called twice
this.off('dispose');
// Kill reference to this player
vjs.players[this.id_] = null;
if (this.tag && this.tag['player']) { this.tag['player'] = null; }
if (this.el_ && this.el_['player']) { this.el_['player'] = null; }
// Ensure that tracking progress and time progress will stop and plater deleted
this.stopTrackingProgress();
this.stopTrackingCurrentTime();
if (this.tech) { this.tech.dispose(); }
// Component dispose
vjs.Component.prototype.dispose.call(this);
};
vjs.Player.prototype.getTagSettings = function(tag){
var options = {
'sources': [],
'tracks': []
};
vjs.obj.merge(options, vjs.getAttributeValues(tag));
// Get tag children settings
if (tag.hasChildNodes()) {
var children, child, childName, i, j;
children = tag.childNodes;
for (i=0,j=children.length; i<j; i++) {
child = children[i];
// Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
childName = child.nodeName.toLowerCase();
if (childName === 'source') {
options['sources'].push(vjs.getAttributeValues(child));
} else if (childName === 'track') {
options['tracks'].push(vjs.getAttributeValues(child));
}
}
}
return options;
};
vjs.Player.prototype.createEl = function(){
var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
var tag = this.tag;
// Remove width/height attrs from tag so CSS can make it 100% width/height
tag.removeAttribute('width');
tag.removeAttribute('height');
// Empty video tag sources and tracks so the built-in player doesn't use them also.
// This may not be fast enough to stop HTML5 browsers from reading the tags
// so we'll need to turn off any default tracks if we're manually doing
// captions and subtitles. videoElement.textTracks
if (tag.hasChildNodes()) {
var nodes, nodesLength, i, node, nodeName, removeNodes;
nodes = tag.childNodes;
nodesLength = nodes.length;
removeNodes = [];
while (nodesLength--) {
node = nodes[nodesLength];
nodeName = node.nodeName.toLowerCase();
if (nodeName === 'source' || nodeName === 'track') {
removeNodes.push(node);
}
}
for (i=0; i<removeNodes.length; i++) {
tag.removeChild(removeNodes[i]);
}
}
// Make sure tag ID exists
tag.id = tag.id || 'vjs_video_' + vjs.guid++;
// Give video tag ID and class to player div
// ID will now reference player box, not the video tag
el.id = tag.id;
el.className = tag.className;
// Update tag id/class for use as HTML5 playback tech
// Might think we should do this after embedding in container so .vjs-tech class
// doesn't flash 100% width/height, but class only applies with .video-js parent
tag.id += '_html5_api';
tag.className = 'vjs-tech';
// Make player findable on elements
tag['player'] = el['player'] = this;
// Default state of video is paused
this.addClass('vjs-paused');
// Make box use width/height of tag, or rely on default implementation
// Enforce with CSS since width/height attrs don't work on divs
this.width(this.options_['width'], true); // (true) Skip resize listener on load
this.height(this.options_['height'], true);
// Wrap video tag in div (el/box) container
if (tag.parentNode) {
tag.parentNode.insertBefore(el, tag);
}
vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
return el;
};
// /* Media Technology (tech)
// ================================================================================ */
// Load/Create an instance of playback technlogy including element and API methods
// And append playback element in player div.
vjs.Player.prototype.loadTech = function(techName, source){
// Pause and remove current playback technology
if (this.tech) {
this.unloadTech();
// If the first time loading, HTML5 tag will exist but won't be initialized
// So we need to remove it if we're not loading HTML5
} else if (techName !== 'Html5' && this.tag) {
this.el_.removeChild(this.tag);
this.tag['player'] = null;
this.tag = null;
}
this.techName = techName;
// Turn off API access because we're loading a new tech that might load asynchronously
this.isReady_ = false;
var techReady = function(){
this.player_.triggerReady();
// Manually track progress in cases where the browser/flash player doesn't report it.
if (!this.features['progressEvents']) {
this.player_.manualProgressOn();
}
// Manually track timeudpates in cases where the browser/flash player doesn't report it.
if (!this.features['timeupdateEvents']) {
this.player_.manualTimeUpdatesOn();
}
};
// Grab tech-specific options from player options and add source and parent element to use.
var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);
if (source) {
if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
techOptions['startTime'] = this.cache_.currentTime;
}
this.cache_.src = source.src;
}
// Initialize tech instance
this.tech = new window['videojs'][techName](this, techOptions);
this.tech.ready(techReady);
};
vjs.Player.prototype.unloadTech = function(){
this.isReady_ = false;
this.tech.dispose();
// Turn off any manual progress or timeupdate tracking
if (this.manualProgress) { this.manualProgressOff(); }
if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }
this.tech = false;
};
// There's many issues around changing the size of a Flash (or other plugin) object.
// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
// reloadTech: function(betweenFn){
// vjs.log('unloadingTech')
// this.unloadTech();
// vjs.log('unloadedTech')
// if (betweenFn) { betweenFn.call(); }
// vjs.log('LoadingTech')
// this.loadTech(this.techName, { src: this.cache_.src })
// vjs.log('loadedTech')
// },
/* Fallbacks for unsupported event types
================================================================================ */
// Manually trigger progress events based on changes to the buffered amount
// Many flash players and older HTML5 browsers don't send progress or progress-like events
vjs.Player.prototype.manualProgressOn = function(){
this.manualProgress = true;
// Trigger progress watching when a source begins loading
this.trackProgress();
// Watch for a native progress event call on the tech element
// In HTML5, some older versions don't support the progress event
// So we're assuming they don't, and turning off manual progress if they do.
// As opposed to doing user agent detection
this.tech.one('progress', function(){
// Update known progress support for this playback technology
this.features['progressEvents'] = true;
// Turn off manual progress tracking
this.player_.manualProgressOff();
});
};
vjs.Player.prototype.manualProgressOff = function(){
this.manualProgress = false;
this.stopTrackingProgress();
};
vjs.Player.prototype.trackProgress = function(){
this.progressInterval = setInterval(vjs.bind(this, function(){
// Don't trigger unless buffered amount is greater than last time
// log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
/* TODO: update for multiple buffered regions */
if (this.cache_.bufferEnd < this.buffered().end(0)) {
this.trigger('progress');
} else if (this.bufferedPercent() == 1) {
this.stopTrackingProgress();
this.trigger('progress'); // Last update
}
}), 500);
};
vjs.Player.prototype.stopTrackingProgress = function(){ clearInterval(this.progressInterval); };
/* Time Tracking -------------------------------------------------------------- */
vjs.Player.prototype.manualTimeUpdatesOn = function(){
this.manualTimeUpdates = true;
this.on('play', this.trackCurrentTime);
this.on('pause', this.stopTrackingCurrentTime);
// timeupdate is also called by .currentTime whenever current time is set
// Watch for native timeupdate event
this.tech.one('timeupdate', function(){
// Update known progress support for this playback technology
this.features['timeupdateEvents'] = true;
// Turn off manual progress tracking
this.player_.manualTimeUpdatesOff();
});
};
vjs.Player.prototype.manualTimeUpdatesOff = function(){
this.manualTimeUpdates = false;
this.stopTrackingCurrentTime();
this.off('play', this.trackCurrentTime);
this.off('pause', this.stopTrackingCurrentTime);
};
vjs.Player.prototype.trackCurrentTime = function(){
if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }
this.currentTimeInterval = setInterval(vjs.bind(this, function(){
this.trigger('timeupdate');
}), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
};
// Turn off play progress tracking (when paused or dragging)
vjs.Player.prototype.stopTrackingCurrentTime = function(){ clearInterval(this.currentTimeInterval); };
// /* Player event handlers (how the player reacts to certain events)
// ================================================================================ */
vjs.Player.prototype.onEnded = function(){
if (this.options_['loop']) {
this.currentTime(0);
this.play();
}
};
vjs.Player.prototype.onPlay = function(){
vjs.removeClass(this.el_, 'vjs-paused');
vjs.addClass(this.el_, 'vjs-playing');
};
vjs.Player.prototype.onFirstPlay = function(){
//If the first starttime attribute is specified
//then we will start at the given offset in seconds
if(this.options_['starttime']){
this.currentTime(this.options_['starttime']);
}
this.addClass('vjs-has-started');
};
vjs.Player.prototype.onPause = function(){
vjs.removeClass(this.el_, 'vjs-playing');
vjs.addClass(this.el_, 'vjs-paused');
};
vjs.Player.prototype.onProgress = function(){
// Add custom event for when source is finished downloading.
if (this.bufferedPercent() == 1) {
this.trigger('loadedalldata');
}
};
// Update duration with durationchange event
// Allows for cacheing value instead of asking player each time.
vjs.Player.prototype.onDurationChange = function(){
this.duration(this.techGet('duration'));
};
vjs.Player.prototype.onError = function(e) {
vjs.log('Video Error', e);
};
vjs.Player.prototype.onFullscreenChange = function() {
if (this.isFullScreen) {
this.addClass('vjs-fullscreen');
} else {
this.removeClass('vjs-fullscreen');
}
};
// /* Player API
// ================================================================================ */
/**
* Object for cached values.
* @private
*/
vjs.Player.prototype.cache_;
vjs.Player.prototype.getCache = function(){
return this.cache_;
};
// Pass values to the playback tech
vjs.Player.prototype.techCall = function(method, arg){
// If it's not ready yet, call method when it is
if (this.tech && !this.tech.isReady_) {
this.tech.ready(function(){
this[method](arg);
});
// Otherwise call method now
} else {
try {
this.tech[method](arg);
} catch(e) {
vjs.log(e);
throw e;
}
}
};
// Get calls can't wait for the tech, and sometimes don't need to.
vjs.Player.prototype.techGet = function(method){
if (this.tech && this.tech.isReady_) {
// Flash likes to die and reload when you hide or reposition it.
// In these cases the object methods go away and we get errors.
// When that happens we'll catch the errors and inform tech that it's not ready any more.
try {
return this.tech[method]();
} catch(e) {
// When building additional tech libs, an expected method may not be defined yet
if (this.tech[method] === undefined) {
vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);
} else {
// When a method isn't available on the object it throws a TypeError
if (e.name == 'TypeError') {
vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);
this.tech.isReady_ = false;
} else {
vjs.log(e);
}
}
throw e;
}
}
return;
};
/**
* Start media playback
* http://dev.w3.org/html5/spec/video.html#dom-media-play
* We're triggering the 'play' event here instead of relying on the
* media element to allow using event.preventDefault() to stop
* play from happening if desired. Usecase: preroll ads.
*/
vjs.Player.prototype.play = function(){
this.techCall('play');
return this;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-pause
vjs.Player.prototype.pause = function(){
this.techCall('pause');
return this;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-paused
// The initial state of paused should be true (in Safari it's actually false)
vjs.Player.prototype.paused = function(){
return (this.techGet('paused') === false) ? false : true;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-currenttime
vjs.Player.prototype.currentTime = function(seconds){
if (seconds !== undefined) {
// Cache the last set value for smoother scrubbing.
this.cache_.lastSetCurrentTime = seconds;
this.techCall('setCurrentTime', seconds);
// Improve the accuracy of manual timeupdates
if (this.manualTimeUpdates) { this.trigger('timeupdate'); }
return this;
}
// Cache last currentTime and return
// Default to 0 seconds
return this.cache_.currentTime = (this.techGet('currentTime') || 0);
};
// http://dev.w3.org/html5/spec/video.html#dom-media-duration
// Duration should return NaN if not available. ParseFloat will turn false-ish values to NaN.
vjs.Player.prototype.duration = function(seconds){
if (seconds !== undefined) {
// Cache the last set value for optimiized scrubbing (esp. Flash)
this.cache_.duration = parseFloat(seconds);
return this;
}
return this.cache_.duration;
};
// Calculates how much time is left. Not in spec, but useful.
vjs.Player.prototype.remainingTime = function(){
return this.duration() - this.currentTime();
};
// http://dev.w3.org/html5/spec/video.html#dom-media-buffered
// Buffered returns a timerange object.
// Kind of like an array of portions of the video that have been downloaded.
// So far no browsers return more than one range (portion)
vjs.Player.prototype.buffered = function(){
var buffered = this.techGet('buffered'),
start = 0,
buflast = buffered.length - 1,
// Default end to 0 and store in values
end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {
end = buffered.end(buflast);
// Storing values allows them be overridden by setBufferedFromProgress
this.cache_.bufferEnd = end;
}
return vjs.createTimeRange(start, end);
};
// Calculates amount of buffer is full. Not in spec but useful.
vjs.Player.prototype.bufferedPercent = function(){
return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-volume
vjs.Player.prototype.volume = function(percentAsDecimal){
var vol;
if (percentAsDecimal !== undefined) {
vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
this.cache_.volume = vol;
this.techCall('setVolume', vol);
vjs.setLocalStorage('volume', vol);
return this;
}
// Default to 1 when returning current volume.
vol = parseFloat(this.techGet('volume'));
return (isNaN(vol)) ? 1 : vol;
};
// http://dev.w3.org/html5/spec/video.html#attr-media-muted
vjs.Player.prototype.muted = function(muted){
if (muted !== undefined) {
this.techCall('setMuted', muted);
return this;
}
return this.techGet('muted') || false; // Default to false
};
// Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
vjs.Player.prototype.supportsFullScreen = function(){ return this.techGet('supportsFullScreen') || false; };
// Turn on fullscreen (or window) mode
vjs.Player.prototype.requestFullScreen = function(){
var requestFullScreen = vjs.support.requestFullScreen;
this.isFullScreen = true;
if (requestFullScreen) {
// the browser supports going fullscreen at the element level so we can
// take the controls fullscreen as well as the video
// Trigger fullscreenchange event after change
// We have to specifically add this each time, and remove
// when cancelling fullscreen. Otherwise if there's multiple
// players on a page, they would all be reacting to the same fullscreen
// events
vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(e){
this.isFullScreen = document[requestFullScreen.isFullScreen];
// If cancelling fullscreen, remove event listener.
if (this.isFullScreen === false) {
vjs.off(document, requestFullScreen.eventName, arguments.callee);
}
this.trigger('fullscreenchange');
}));
this.el_[requestFullScreen.requestFn]();
} else if (this.tech.supportsFullScreen()) {
// we can't take the video.js controls fullscreen but we can go fullscreen
// with native controls
this.techCall('enterFullScreen');
} else {
// fullscreen isn't supported so we'll just stretch the video element to
// fill the viewport
this.enterFullWindow();
this.trigger('fullscreenchange');
}
return this;
};
vjs.Player.prototype.cancelFullScreen = function(){
var requestFullScreen = vjs.support.requestFullScreen;
this.isFullScreen = false;
// Check for browser element fullscreen support
if (requestFullScreen) {
document[requestFullScreen.cancelFn]();
} else if (this.tech.supportsFullScreen()) {
this.techCall('exitFullScreen');
} else {
this.exitFullWindow();
this.trigger('fullscreenchange');
}
return this;
};
// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
vjs.Player.prototype.enterFullWindow = function(){
this.isFullWindow = true;
// Storing original doc overflow value to return to when fullscreen is off
this.docOrigOverflow = document.documentElement.style.overflow;
// Add listener for esc key to exit fullscreen
vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
// Hide any scroll bars
document.documentElement.style.overflow = 'hidden';
// Apply fullscreen styles
vjs.addClass(document.body, 'vjs-full-window');
this.trigger('enterFullWindow');
};
vjs.Player.prototype.fullWindowOnEscKey = function(event){
if (event.keyCode === 27) {
if (this.isFullScreen === true) {
this.cancelFullScreen();
} else {
this.exitFullWindow();
}
}
};
vjs.Player.prototype.exitFullWindow = function(){
this.isFullWindow = false;
vjs.off(document, 'keydown', this.fullWindowOnEscKey);
// Unhide scroll bars.
document.documentElement.style.overflow = this.docOrigOverflow;
// Remove fullscreen styles
vjs.removeClass(document.body, 'vjs-full-window');
// Resize the box, controller, and poster to original sizes
// this.positionAll();
this.trigger('exitFullWindow');
};
vjs.Player.prototype.selectSource = function(sources){
// Loop through each playback technology in the options order
for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {
var techName = vjs.capitalize(j[i]),
tech = window['videojs'][techName];
// Check if the browser supports this technology
if (tech.isSupported()) {
// Loop through each source object
for (var a=0,b=sources;a<b.length;a++) {
var source = b[a];
// Check if source can be played with this technology
if (tech['canPlaySource'](source)) {
return { source: source, tech: techName };
}
}
}
}
return false;
};
// src is a pretty powerful function
// If you pass it an array of source objects, it will find the best source to play and use that object.src
// If the new source requires a new playback technology, it will switch to that.
// If you pass it an object, it will set the source to object.src
// If you pass it anything else (url string) it will set the video source to that
vjs.Player.prototype.src = function(source){
// Case: Array of source objects to choose from and pick the best to play
if (source instanceof Array) {
var sourceTech = this.selectSource(source),
techName;
if (sourceTech) {
source = sourceTech.source;
techName = sourceTech.tech;
// If this technology is already loaded, set source
if (techName == this.techName) {
this.src(source); // Passing the source object
// Otherwise load this technology with chosen source
} else {
this.loadTech(techName, source);
}
} else {
this.el_.appendChild(vjs.createEl('p', {
innerHTML: this.options()['notSupportedMessage']
}));
}
// Case: Source object { src: '', type: '' ... }
} else if (source instanceof Object) {
if (window['videojs'][this.techName]['canPlaySource'](source)) {
this.src(source.src);
} else {
// Send through tech loop to check for a compatible technology.
this.src([source]);
}
// Case: URL String (http://myvideo...)
} else {
// Cache for getting last set source
this.cache_.src = source;
if (!this.isReady_) {
this.ready(function(){
this.src(source);
});
} else {
this.techCall('src', source);
if (this.options_['preload'] == 'auto') {
this.load();
}
if (this.options_['autoplay']) {
this.play();
}
}
}
return this;
};
// Begin loading the src data
// http://dev.w3.org/html5/spec/video.html#dom-media-load
vjs.Player.prototype.load = function(){
this.techCall('load');
return this;
};
// http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
vjs.Player.prototype.currentSrc = function(){
return this.techGet('currentSrc') || this.cache_.src || '';
};
// Attributes/Options
vjs.Player.prototype.preload = function(value){
if (value !== undefined) {
this.techCall('setPreload', value);
this.options_['preload'] = value;
return this;
}
return this.techGet('preload');
};
vjs.Player.prototype.autoplay = function(value){
if (value !== undefined) {
this.techCall('setAutoplay', value);
this.options_['autoplay'] = value;
return this;
}
return this.techGet('autoplay', value);
};
vjs.Player.prototype.loop = function(value){
if (value !== undefined) {
this.techCall('setLoop', value);
this.options_['loop'] = value;
return this;
}
return this.techGet('loop');
};
/**
* The url of the poster image source.
* @type {String}
* @private
*/
vjs.Player.prototype.poster_;
/**
* Get or set the poster image source url.
* @param {String} src Poster image source URL
* @return {String} Poster image source URL or null
*/
vjs.Player.prototype.poster = function(src){
if (src !== undefined) {
this.poster_ = src;
}
return this.poster_;
};
/**
* Whether or not the controls are showing
* @type {Boolean}
* @private
*/
vjs.Player.prototype.controls_;
/**
* Get or set whether or not the controls are showing.
* @param {Boolean} controls Set controls to showing or not
* @return {Boolean} Controls are showing
*/
vjs.Player.prototype.controls = function(bool){
if (bool !== undefined) {
bool = !!bool; // force boolean
// Don't trigger a change event unless it actually changed
if (this.controls_ !== bool) {
this.controls_ = bool;
if (bool) {
this.removeClass('vjs-controls-disabled');
this.addClass('vjs-controls-enabled');
this.trigger('controlsenabled');
} else {
this.removeClass('vjs-controls-enabled');
this.addClass('vjs-controls-disabled');
this.trigger('controlsdisabled');
}
}
return this;
}
return this.controls_;
};
vjs.Player.prototype.usingNativeControls_;
/**
* Toggle native controls on/off. Native controls are the controls built into
* devices (e.g. default iPhone controls), Flash, or other techs
* (e.g. Vimeo Controls)
*
* **This should only be set by the current tech, because only the tech knows
* if it can support native controls**
*
* @param {Boolean} bool True signals that native controls are on
* @return {vjs.Player} Returns the player
*/
vjs.Player.prototype.usingNativeControls = function(bool){
if (bool !== undefined) {
bool = !!bool; // force boolean
// Don't trigger a change event unless it actually changed
if (this.usingNativeControls_ !== bool) {
this.usingNativeControls_ = bool;
if (bool) {
this.addClass('vjs-using-native-controls');
this.trigger('usingnativecontrols');
} else {
this.removeClass('vjs-using-native-controls');
this.trigger('usingcustomcontrols');
}
}
return this;
}
return this.usingNativeControls_;
};
vjs.Player.prototype.error = function(){ return this.techGet('error'); };
vjs.Player.prototype.ended = function(){ return this.techGet('ended'); };
vjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };
// When the player is first initialized, trigger activity so components
// like the control bar show themselves if needed
vjs.Player.prototype.userActivity_ = true;
vjs.Player.prototype.reportUserActivity = function(event){
this.userActivity_ = true;
};
vjs.Player.prototype.userActive_ = true;
vjs.Player.prototype.userActive = function(bool){
if (bool !== undefined) {
bool = !!bool;
if (bool !== this.userActive_) {
this.userActive_ = bool;
if (bool) {
// If the user was inactive and is now active we want to reset the
// inactivity timer
this.userActivity_ = true;
this.removeClass('vjs-user-inactive');
this.addClass('vjs-user-active');
this.trigger('useractive');
} else {
// We're switching the state to inactive manually, so erase any other
// activity
this.userActivity_ = false;
// Chrome/Safari/IE have bugs where when you change the cursor it can
// trigger a mousemove event. This causes an issue when you're hiding
// the cursor when the user is inactive, and a mousemove signals user
// activity. Making it impossible to go into inactive mode. Specifically
// this happens in fullscreen when we really need to hide the cursor.
//
// When this gets resolved in ALL browsers it can be removed
// https://code.google.com/p/chromium/issues/detail?id=103041
this.tech.one('mousemove', function(e){
e.stopPropagation();
e.preventDefault();
});
this.removeClass('vjs-user-active');
this.addClass('vjs-user-inactive');
this.trigger('userinactive');
}
}
return this;
}
return this.userActive_;
};
vjs.Player.prototype.listenForUserActivity = function(){
var onMouseActivity, onMouseDown, mouseInProgress, onMouseUp,
activityCheck, inactivityTimeout;
onMouseActivity = this.reportUserActivity;
onMouseDown = function() {
onMouseActivity();
// For as long as the they are touching the device or have their mouse down,
// we consider them active even if they're not moving their finger or mouse.
// So we want to continue to update that they are active
clearInterval(mouseInProgress);
// Setting userActivity=true now and setting the interval to the same time
// as the activityCheck interval (250) should ensure we never miss the
// next activityCheck
mouseInProgress = setInterval(vjs.bind(this, onMouseActivity), 250);
};
onMouseUp = function(event) {
onMouseActivity();
// Stop the interval that maintains activity if the mouse/touch is down
clearInterval(mouseInProgress);
};
// Any mouse movement will be considered user activity
this.on('mousedown', onMouseDown);
this.on('mousemove', onMouseActivity);
this.on('mouseup', onMouseUp);
// Listen for keyboard navigation
// Shouldn't need to use inProgress interval because of key repeat
this.on('keydown', onMouseActivity);
this.on('keyup', onMouseActivity);
// Consider any touch events that bubble up to be activity
// Certain touches on the tech will be blocked from bubbling because they
// toggle controls
this.on('touchstart', onMouseDown);
this.on('touchmove', onMouseActivity);
this.on('touchend', onMouseUp);
this.on('touchcancel', onMouseUp);
// Run an interval every 250 milliseconds instead of stuffing everything into
// the mousemove/touchmove function itself, to prevent performance degradation.
// `this.reportUserActivity` simply sets this.userActivity_ to true, which
// then gets picked up by this loop
// http://ejohn.org/blog/learning-from-twitter/
activityCheck = setInterval(vjs.bind(this, function() {
// Check to see if mouse/touch activity has happened
if (this.userActivity_) {
// Reset the activity tracker
this.userActivity_ = false;
// If the user state was inactive, set the state to active
this.userActive(true);
// Clear any existing inactivity timeout to start the timer over
clearTimeout(inactivityTimeout);
// In X seconds, if no more activity has occurred the user will be
// considered inactive
inactivityTimeout = setTimeout(vjs.bind(this, function() {
// Protect against the case where the inactivityTimeout can trigger just
// before the next user activity is picked up by the activityCheck loop
// causing a flicker
if (!this.userActivity_) {
this.userActive(false);
}
}), 2000);
}
}), 250);
// Clean up the intervals when we kill the player
this.on('dispose', function(){
clearInterval(activityCheck);
clearTimeout(inactivityTimeout);
});
};
// Methods to add support for
// networkState: function(){ return this.techCall('networkState'); },
// readyState: function(){ return this.techCall('readyState'); },
// seeking: function(){ return this.techCall('seeking'); },
// initialTime: function(){ return this.techCall('initialTime'); },
// startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
// played: function(){ return this.techCall('played'); },
// seekable: function(){ return this.techCall('seekable'); },
// videoTracks: function(){ return this.techCall('videoTracks'); },
// audioTracks: function(){ return this.techCall('audioTracks'); },
// videoWidth: function(){ return this.techCall('videoWidth'); },
// videoHeight: function(){ return this.techCall('videoHeight'); },
// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
// playbackRate: function(){ return this.techCall('playbackRate'); },
// mediaGroup: function(){ return this.techCall('mediaGroup'); },
// controller: function(){ return this.techCall('controller'); },
// defaultMuted: function(){ return this.techCall('defaultMuted'); }
// TODO
// currentSrcList: the array of sources including other formats and bitrates
// playList: array of source lists in order of playback
// RequestFullscreen API
(function(){
var prefix, requestFS, div;
div = document.createElement('div');
requestFS = {};
// Current W3C Spec
// http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
// Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
// New: https://dvcs.w3.org/hg/fullscreen/raw-file/529a67b8d9f3/Overview.html
if (div.cancelFullscreen !== undefined) {
requestFS.requestFn = 'requestFullscreen';
requestFS.cancelFn = 'exitFullscreen';
requestFS.eventName = 'fullscreenchange';
requestFS.isFullScreen = 'fullScreen';
// Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations
// that use prefixes and vary slightly from the new W3C spec. Specifically,
// using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.
// Other browsers don't have any hints of which version they might follow yet,
// so not going to try to predict by looping through all prefixes.
} else {
if (document.mozCancelFullScreen) {
prefix = 'moz';
requestFS.isFullScreen = prefix + 'FullScreen';
} else {
prefix = 'webkit';
requestFS.isFullScreen = prefix + 'IsFullScreen';
}
if (div[prefix + 'RequestFullScreen']) {
requestFS.requestFn = prefix + 'RequestFullScreen';
requestFS.cancelFn = prefix + 'CancelFullScreen';
}
requestFS.eventName = prefix + 'fullscreenchange';
}
if (document[requestFS.cancelFn]) {
vjs.support.requestFullScreen = requestFS;
}
})();
/**
* Container of main controls
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.ControlBar = vjs.Component.extend();
vjs.ControlBar.prototype.options_ = {
loadEvent: 'play',
children: {
'playToggle': {},
'currentTimeDisplay': {},
'timeDivider': {},
'durationDisplay': {},
'remainingTimeDisplay': {},
'progressControl': {},
'fullscreenToggle': {},
'volumeControl': {},
'muteToggle': {}
// 'volumeMenuButton': {}
}
};
vjs.ControlBar.prototype.createEl = function(){
return vjs.createEl('div', {
className: 'vjs-control-bar'
});
};
/**
* Button to toggle between play and pause
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.PlayToggle = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
player.on('play', vjs.bind(this, this.onPlay));
player.on('pause', vjs.bind(this, this.onPause));
}
});
vjs.PlayToggle.prototype.buttonText = 'Play';
vjs.PlayToggle.prototype.buildCSSClass = function(){
return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);
};
// OnClick - Toggle between play and pause
vjs.PlayToggle.prototype.onClick = function(){
if (this.player_.paused()) {
this.player_.play();
} else {
this.player_.pause();
}
};
// OnPlay - Add the vjs-playing class to the element so it can change appearance
vjs.PlayToggle.prototype.onPlay = function(){
vjs.removeClass(this.el_, 'vjs-paused');
vjs.addClass(this.el_, 'vjs-playing');
this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to "Pause"
};
// OnPause - Add the vjs-paused class to the element so it can change appearance
vjs.PlayToggle.prototype.onPause = function(){
vjs.removeClass(this.el_, 'vjs-playing');
vjs.addClass(this.el_, 'vjs-paused');
this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to "Play"
};/**
* Displays the current time
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.CurrentTimeDisplay = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('timeupdate', vjs.bind(this, this.updateContent));
}
});
vjs.CurrentTimeDisplay.prototype.createEl = function(){
var el = vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-current-time vjs-time-controls vjs-control'
});
this.content = vjs.createEl('div', {
className: 'vjs-current-time-display',
innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', // label the current time for screen reader users
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
});
el.appendChild(vjs.createEl('div').appendChild(this.content));
return el;
};
vjs.CurrentTimeDisplay.prototype.updateContent = function(){
// Allows for smooth scrubbing, when player can't keep up.
var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
this.content.innerHTML = '<span class="vjs-control-text">Current Time </span>' + vjs.formatTime(time, this.player_.duration());
};
/**
* Displays the duration
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.DurationDisplay = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('timeupdate', vjs.bind(this, this.updateContent)); // this might need to be changes to 'durationchange' instead of 'timeupdate' eventually, however the durationchange event fires before this.player_.duration() is set, so the value cannot be written out using this method. Once the order of durationchange and this.player_.duration() being set is figured out, this can be updated.
}
});
vjs.DurationDisplay.prototype.createEl = function(){
var el = vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-duration vjs-time-controls vjs-control'
});
this.content = vjs.createEl('div', {
className: 'vjs-duration-display',
innerHTML: '<span class="vjs-control-text">Duration Time </span>' + '0:00', // label the duration time for screen reader users
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
});
el.appendChild(vjs.createEl('div').appendChild(this.content));
return el;
};
vjs.DurationDisplay.prototype.updateContent = function(){
var duration = this.player_.duration();
if (duration) {
this.content.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(duration); // label the duration time for screen reader users
}
};
/**
* Time Separator (Not used in main skin, but still available, and could be used as a 'spare element')
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.TimeDivider = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.TimeDivider.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-time-divider',
innerHTML: '<div><span>/</span></div>'
});
};
/**
* Displays the time left in the video
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.RemainingTimeDisplay = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('timeupdate', vjs.bind(this, this.updateContent));
}
});
vjs.RemainingTimeDisplay.prototype.createEl = function(){
var el = vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-remaining-time vjs-time-controls vjs-control'
});
this.content = vjs.createEl('div', {
className: 'vjs-remaining-time-display',
innerHTML: '<span class="vjs-control-text">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users
'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
});
el.appendChild(vjs.createEl('div').appendChild(this.content));
return el;
};
vjs.RemainingTimeDisplay.prototype.updateContent = function(){
if (this.player_.duration()) {
this.content.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());
}
// Allows for smooth scrubbing, when player can't keep up.
// var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
// this.content.innerHTML = vjs.formatTime(time, this.player_.duration());
};
/**
* Toggle fullscreen video
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.FullscreenToggle = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
}
});
vjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';
vjs.FullscreenToggle.prototype.buildCSSClass = function(){
return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);
};
vjs.FullscreenToggle.prototype.onClick = function(){
if (!this.player_.isFullScreen) {
this.player_.requestFullScreen();
this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to "Non-Fullscreen"
} else {
this.player_.cancelFullScreen();
this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to "Fullscreen"
}
};/**
* Seek, Load Progress, and Play Progress
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.ProgressControl = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.ProgressControl.prototype.options_ = {
children: {
'seekBar': {}
}
};
vjs.ProgressControl.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-progress-control vjs-control'
});
};
/**
* Seek Bar and holder for the progress bars
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.SeekBar = vjs.Slider.extend({
/** @constructor */
init: function(player, options){
vjs.Slider.call(this, player, options);
player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));
player.ready(vjs.bind(this, this.updateARIAAttributes));
}
});
vjs.SeekBar.prototype.options_ = {
children: {
'loadProgressBar': {},
'playProgressBar': {},
'seekHandle': {}
},
'barName': 'playProgressBar',
'handleName': 'seekHandle'
};
vjs.SeekBar.prototype.playerEvent = 'timeupdate';
vjs.SeekBar.prototype.createEl = function(){
return vjs.Slider.prototype.createEl.call(this, 'div', {
className: 'vjs-progress-holder',
'aria-label': 'video progress bar'
});
};
vjs.SeekBar.prototype.updateARIAAttributes = function(){
// Allows for smooth scrubbing, when player can't keep up.
var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
this.el_.setAttribute('aria-valuenow',vjs.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)
this.el_.setAttribute('aria-valuetext',vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
};
vjs.SeekBar.prototype.getPercent = function(){
var currentTime;
// Flash RTMP provider will not report the correct time
// immediately after a seek. This isn't noticeable if you're
// seeking while the video is playing, but it is if you seek
// while the video is paused.
if (this.player_.techName === 'Flash' && this.player_.seeking()) {
var cache = this.player_.getCache();
if (cache.lastSetCurrentTime) {
currentTime = cache.lastSetCurrentTime;
}
else {
currentTime = this.player_.currentTime();
}
}
else {
currentTime = this.player_.currentTime();
}
return currentTime / this.player_.duration();
};
vjs.SeekBar.prototype.onMouseDown = function(event){
vjs.Slider.prototype.onMouseDown.call(this, event);
this.player_.scrubbing = true;
this.videoWasPlaying = !this.player_.paused();
this.player_.pause();
};
vjs.SeekBar.prototype.onMouseMove = function(event){
var newTime = this.calculateDistance(event) * this.player_.duration();
// Don't let video end while scrubbing.
if (newTime == this.player_.duration()) { newTime = newTime - 0.1; }
// Set new time (tell player to seek to new time)
this.player_.currentTime(newTime);
};
vjs.SeekBar.prototype.onMouseUp = function(event){
vjs.Slider.prototype.onMouseUp.call(this, event);
this.player_.scrubbing = false;
if (this.videoWasPlaying) {
this.player_.play();
}
};
vjs.SeekBar.prototype.stepForward = function(){
this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
};
vjs.SeekBar.prototype.stepBack = function(){
this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
};
/**
* Shows load progres
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.LoadProgressBar = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('progress', vjs.bind(this, this.update));
}
});
vjs.LoadProgressBar.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-load-progress',
innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>'
});
};
vjs.LoadProgressBar.prototype.update = function(){
if (this.el_.style) { this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%'; }
};
/**
* Shows play progress
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.PlayProgressBar = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.PlayProgressBar.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-play-progress',
innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
});
};
/**
* SeekBar component includes play progress bar, and seek handle
* Needed so it can determine seek position based on handle position/size
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.SeekHandle = vjs.SliderHandle.extend();
/** @inheritDoc */
vjs.SeekHandle.prototype.defaultValue = '00:00';
/** @inheritDoc */
vjs.SeekHandle.prototype.createEl = function(){
return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
className: 'vjs-seek-handle'
});
};/**
* Control the volume
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.VolumeControl = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
// hide volume controls when they're not supported by the current tech
if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {
this.addClass('vjs-hidden');
}
player.on('loadstart', vjs.bind(this, function(){
if (player.tech.features && player.tech.features['volumeControl'] === false) {
this.addClass('vjs-hidden');
} else {
this.removeClass('vjs-hidden');
}
}));
}
});
vjs.VolumeControl.prototype.options_ = {
children: {
'volumeBar': {}
}
};
vjs.VolumeControl.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-volume-control vjs-control'
});
};
/**
* Contains volume level
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.VolumeBar = vjs.Slider.extend({
/** @constructor */
init: function(player, options){
vjs.Slider.call(this, player, options);
player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));
player.ready(vjs.bind(this, this.updateARIAAttributes));
setTimeout(vjs.bind(this, this.update), 0); // update when elements is in DOM
}
});
vjs.VolumeBar.prototype.updateARIAAttributes = function(){
// Current value of volume bar as a percentage
this.el_.setAttribute('aria-valuenow',vjs.round(this.player_.volume()*100, 2));
this.el_.setAttribute('aria-valuetext',vjs.round(this.player_.volume()*100, 2)+'%');
};
vjs.VolumeBar.prototype.options_ = {
children: {
'volumeLevel': {},
'volumeHandle': {}
},
'barName': 'volumeLevel',
'handleName': 'volumeHandle'
};
vjs.VolumeBar.prototype.playerEvent = 'volumechange';
vjs.VolumeBar.prototype.createEl = function(){
return vjs.Slider.prototype.createEl.call(this, 'div', {
className: 'vjs-volume-bar',
'aria-label': 'volume level'
});
};
vjs.VolumeBar.prototype.onMouseMove = function(event) {
if (this.player_.muted()) {
this.player_.muted(false);
}
this.player_.volume(this.calculateDistance(event));
};
vjs.VolumeBar.prototype.getPercent = function(){
if (this.player_.muted()) {
return 0;
} else {
return this.player_.volume();
}
};
vjs.VolumeBar.prototype.stepForward = function(){
this.player_.volume(this.player_.volume() + 0.1);
};
vjs.VolumeBar.prototype.stepBack = function(){
this.player_.volume(this.player_.volume() - 0.1);
};
/**
* Shows volume level
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.VolumeLevel = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
}
});
vjs.VolumeLevel.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-volume-level',
innerHTML: '<span class="vjs-control-text"></span>'
});
};
/**
* Change volume level
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.VolumeHandle = vjs.SliderHandle.extend();
/** @inheritDoc */
vjs.VolumeHandle.prototype.defaultValue = '00:00';
/** @inheritDoc */
vjs.VolumeHandle.prototype.createEl = function(){
return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
className: 'vjs-volume-handle'
});
};
/**
* Mute the audio
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.MuteToggle = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
player.on('volumechange', vjs.bind(this, this.update));
// hide mute toggle if the current tech doesn't support volume control
if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {
this.addClass('vjs-hidden');
}
player.on('loadstart', vjs.bind(this, function(){
if (player.tech.features && player.tech.features['volumeControl'] === false) {
this.addClass('vjs-hidden');
} else {
this.removeClass('vjs-hidden');
}
}));
}
});
vjs.MuteToggle.prototype.createEl = function(){
return vjs.Button.prototype.createEl.call(this, 'div', {
className: 'vjs-mute-control vjs-control',
innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
});
};
vjs.MuteToggle.prototype.onClick = function(){
this.player_.muted( this.player_.muted() ? false : true );
};
vjs.MuteToggle.prototype.update = function(){
var vol = this.player_.volume(),
level = 3;
if (vol === 0 || this.player_.muted()) {
level = 0;
} else if (vol < 0.33) {
level = 1;
} else if (vol < 0.67) {
level = 2;
}
// Don't rewrite the button text if the actual text doesn't change.
// This causes unnecessary and confusing information for screen reader users.
// This check is needed because this function gets called every time the volume level is changed.
if(this.player_.muted()){
if(this.el_.children[0].children[0].innerHTML!='Unmute'){
this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to "Unmute"
}
} else {
if(this.el_.children[0].children[0].innerHTML!='Mute'){
this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to "Mute"
}
}
/* TODO improve muted icon classes */
for (var i = 0; i < 4; i++) {
vjs.removeClass(this.el_, 'vjs-vol-'+i);
}
vjs.addClass(this.el_, 'vjs-vol-'+level);
};
/**
* Menu button with a popup for showing the volume slider.
* @constructor
*/
vjs.VolumeMenuButton = vjs.MenuButton.extend({
/** @constructor */
init: function(player, options){
vjs.MenuButton.call(this, player, options);
// Same listeners as MuteToggle
player.on('volumechange', vjs.bind(this, this.update));
// hide mute toggle if the current tech doesn't support volume control
if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
this.addClass('vjs-hidden');
}
player.on('loadstart', vjs.bind(this, function(){
if (player.tech.features && player.tech.features.volumeControl === false) {
this.addClass('vjs-hidden');
} else {
this.removeClass('vjs-hidden');
}
}));
this.addClass('vjs-menu-button');
}
});
vjs.VolumeMenuButton.prototype.createMenu = function(){
var menu = new vjs.Menu(this.player_, {
contentElType: 'div'
});
var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({vertical: true}, this.options_.volumeBar));
menu.addChild(vc);
return menu;
};
vjs.VolumeMenuButton.prototype.onClick = function(){
vjs.MuteToggle.prototype.onClick.call(this);
vjs.MenuButton.prototype.onClick.call(this);
};
vjs.VolumeMenuButton.prototype.createEl = function(){
return vjs.Button.prototype.createEl.call(this, 'div', {
className: 'vjs-volume-menu-button vjs-menu-button vjs-control',
innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
});
};
vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
/* Poster Image
================================================================================ */
/**
* Poster image. Shows before the video plays.
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.PosterImage = vjs.Button.extend({
/** @constructor */
init: function(player, options){
vjs.Button.call(this, player, options);
if (!player.poster() || !player.controls()) {
this.hide();
}
player.on('play', vjs.bind(this, this.hide));
}
});
vjs.PosterImage.prototype.createEl = function(){
var el = vjs.createEl('div', {
className: 'vjs-poster',
// Don't want poster to be tabbable.
tabIndex: -1
}),
poster = this.player_.poster();
if (poster) {
if ('backgroundSize' in el.style) {
el.style.backgroundImage = 'url("' + poster + '")';
} else {
el.appendChild(vjs.createEl('img', { src: poster }));
}
}
return el;
};
vjs.PosterImage.prototype.onClick = function(){
// Only accept clicks when controls are enabled
if (this.player().controls()) {
this.player_.play();
}
};
/* Loading Spinner
================================================================================ */
/**
* Loading spinner for waiting events
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.LoadingSpinner = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
player.on('canplay', vjs.bind(this, this.hide));
player.on('canplaythrough', vjs.bind(this, this.hide));
player.on('playing', vjs.bind(this, this.hide));
player.on('seeked', vjs.bind(this, this.hide));
player.on('seeking', vjs.bind(this, this.show));
// in some browsers seeking does not trigger the 'playing' event,
// so we also need to trap 'seeked' if we are going to set a
// 'seeking' event
player.on('seeked', vjs.bind(this, this.hide));
player.on('error', vjs.bind(this, this.show));
// Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
// Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing
// player.on('stalled', vjs.bind(this, this.show));
player.on('waiting', vjs.bind(this, this.show));
}
});
vjs.LoadingSpinner.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-loading-spinner'
});
};
/* Big Play Button
================================================================================ */
/**
* Initial play button. Shows before the video has played. The hiding of the
* big play button is done via CSS and player states.
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.BigPlayButton = vjs.Button.extend();
vjs.BigPlayButton.prototype.createEl = function(){
return vjs.Button.prototype.createEl.call(this, 'div', {
className: 'vjs-big-play-button',
innerHTML: '<span></span>',
'aria-label': 'play video'
});
};
vjs.BigPlayButton.prototype.onClick = function(){
this.player_.play();
};
/**
* @fileoverview Media Technology Controller - Base class for media playback
* technology controllers like Flash and HTML5
*/
/**
* Base class for media (HTML5 Video, Flash) controllers
* @param {vjs.Player|Object} player Central player instance
* @param {Object=} options Options object
* @constructor
*/
vjs.MediaTechController = vjs.Component.extend({
/** @constructor */
init: function(player, options, ready){
vjs.Component.call(this, player, options, ready);
this.initControlsListeners();
}
});
/**
* Set up click and touch listeners for the playback element
* On desktops, a click on the video itself will toggle playback,
* on a mobile device a click on the video toggles controls.
* (toggling controls is done by toggling the user state between active and
* inactive)
*
* A tap can signal that a user has become active, or has become inactive
* e.g. a quick tap on an iPhone movie should reveal the controls. Another
* quick tap should hide them again (signaling the user is in an inactive
* viewing state)
*
* In addition to this, we still want the user to be considered inactive after
* a few seconds of inactivity.
*
* Note: the only part of iOS interaction we can't mimic with this setup
* is a touch and hold on the video element counting as activity in order to
* keep the controls showing, but that shouldn't be an issue. A touch and hold on
* any controls will still keep the user active
*/
vjs.MediaTechController.prototype.initControlsListeners = function(){
var player, tech, activateControls, deactivateControls;
tech = this;
player = this.player();
var activateControls = function(){
if (player.controls() && !player.usingNativeControls()) {
tech.addControlsListeners();
}
};
deactivateControls = vjs.bind(tech, tech.removeControlsListeners);
// Set up event listeners once the tech is ready and has an element to apply
// listeners to
this.ready(activateControls);
player.on('controlsenabled', activateControls);
player.on('controlsdisabled', deactivateControls);
};
vjs.MediaTechController.prototype.addControlsListeners = function(){
var preventBubble, userWasActive;
// Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
// trigger mousedown/up.
// http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
// Any touch events are set to block the mousedown event from happening
this.on('mousedown', this.onClick);
// We need to block touch events on the video element from bubbling up,
// otherwise they'll signal activity prematurely. The specific use case is
// when the video is playing and the controls have faded out. In this case
// only a tap (fast touch) should toggle the user active state and turn the
// controls back on. A touch and move or touch and hold should not trigger
// the controls (per iOS as an example at least)
//
// We always want to stop propagation on touchstart because touchstart
// at the player level starts the touchInProgress interval. We can still
// report activity on the other events, but won't let them bubble for
// consistency. We don't want to bubble a touchend without a touchstart.
this.on('touchstart', function(event) {
// Stop the mouse events from also happening
event.preventDefault();
event.stopPropagation();
// Record if the user was active now so we don't have to keep polling it
userWasActive = this.player_.userActive();
});
preventBubble = function(event){
event.stopPropagation();
if (userWasActive) {
this.player_.reportUserActivity();
}
};
// Treat all touch events the same for consistency
this.on('touchmove', preventBubble);
this.on('touchleave', preventBubble);
this.on('touchcancel', preventBubble);
this.on('touchend', preventBubble);
// Turn on component tap events
this.emitTapEvents();
// The tap listener needs to come after the touchend listener because the tap
// listener cancels out any reportedUserActivity when setting userActive(false)
this.on('tap', this.onTap);
};
/**
* Remove the listeners used for click and tap controls. This is needed for
* toggling to controls disabled, where a tap/touch should do nothing.
*/
vjs.MediaTechController.prototype.removeControlsListeners = function(){
// We don't want to just use `this.off()` because there might be other needed
// listeners added by techs that extend this.
this.off('tap');
this.off('touchstart');
this.off('touchmove');
this.off('touchleave');
this.off('touchcancel');
this.off('touchend');
this.off('click');
this.off('mousedown');
};
/**
* Handle a click on the media element. By default will play/pause the media.
*/
vjs.MediaTechController.prototype.onClick = function(event){
// We're using mousedown to detect clicks thanks to Flash, but mousedown
// will also be triggered with right-clicks, so we need to prevent that
if (event.button !== 0) return;
// When controls are disabled a click should not toggle playback because
// the click is considered a control
if (this.player().controls()) {
if (this.player().paused()) {
this.player().play();
} else {
this.player().pause();
}
}
};
/**
* Handle a tap on the media element. By default it will toggle the user
* activity state, which hides and shows the controls.
*/
vjs.MediaTechController.prototype.onTap = function(){
this.player().userActive(!this.player().userActive());
};
vjs.MediaTechController.prototype.features = {
'volumeControl': true,
// Resizing plugins using request fullscreen reloads the plugin
'fullscreenResize': false,
// Optional events that we can manually mimic with timers
// currently not triggered by video-js-swf
'progressEvents': false,
'timeupdateEvents': false
};
vjs.media = {};
/**
* List of default API methods for any MediaTechController
* @type {String}
*/
vjs.media.ApiMethods = 'play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted'.split(',');
// Create placeholder methods for each that warn when a method isn't supported by the current playback technology
function createMethod(methodName){
return function(){
throw new Error('The "'+methodName+'" method is not available on the playback technology\'s API');
};
}
for (var i = vjs.media.ApiMethods.length - 1; i >= 0; i--) {
var methodName = vjs.media.ApiMethods[i];
vjs.MediaTechController.prototype[vjs.media.ApiMethods[i]] = createMethod(methodName);
}
/**
* @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
*/
/**
* HTML5 Media Controller - Wrapper for HTML5 Media API
* @param {vjs.Player|Object} player
* @param {Object=} options
* @param {Function=} ready
* @constructor
*/
vjs.Html5 = vjs.MediaTechController.extend({
/** @constructor */
init: function(player, options, ready){
// volume cannot be changed from 1 on iOS
this.features['volumeControl'] = vjs.Html5.canControlVolume();
// In iOS, if you move a video element in the DOM, it breaks video playback.
this.features['movingMediaElementInDOM'] = !vjs.IS_IOS;
// HTML video is able to automatically resize when going to fullscreen
this.features['fullscreenResize'] = true;
vjs.MediaTechController.call(this, player, options, ready);
var source = options['source'];
// If the element source is already set, we may have missed the loadstart event, and want to trigger it.
// We don't want to set the source again and interrupt playback.
if (source && this.el_.currentSrc == source.src) {
player.trigger('loadstart');
// Otherwise set the source if one was provided.
} else if (source) {
this.el_.src = source.src;
}
// Determine if native controls should be used
// Our goal should be to get the custom controls on mobile solid everywhere
// so we can remove this all together. Right now this will block custom
// controls on touch enabled laptops like the Chrome Pixel
if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] !== false) {
this.useNativeControls();
}
// Chrome and Safari both have issues with autoplay.
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
// This fixes both issues. Need to wait for API, so it updates displays correctly
player.ready(function(){
if (this.tag && this.options_['autoplay'] && this.paused()) {
delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.
this.play();
}
});
this.setupTriggers();
this.triggerReady();
}
});
vjs.Html5.prototype.dispose = function(){
vjs.MediaTechController.prototype.dispose.call(this);
};
vjs.Html5.prototype.createEl = function(){
var player = this.player_,
// If possible, reuse original tag for HTML5 playback technology element
el = player.tag,
newEl;
// Check if this browser supports moving the element into the box.
// On the iPhone video will break if you move the element,
// So we have to create a brand new element.
if (!el || this.features['movingMediaElementInDOM'] === false) {
// If the original tag is still there, remove it.
if (el) {
el['player'] = null;
player.tag = null;
player.el().removeChild(el);
el = el.cloneNode(false);
} else {
el = vjs.createEl('video', {
id:player.id() + '_html5_api',
className:'vjs-tech'
});
}
// associate the player with the new tag
el['player'] = player;
vjs.insertFirst(el, player.el());
}
// Update specific tag settings, in case they were overridden
var attrs = ['autoplay','preload','loop','muted'];
for (var i = attrs.length - 1; i >= 0; i--) {
var attr = attrs[i];
if (player.options_[attr] !== null) {
el[attr] = player.options_[attr];
}
}
return el;
// jenniisawesome = true;
};
// Make video events trigger player events
// May seem verbose here, but makes other APIs possible.
vjs.Html5.prototype.setupTriggers = function(){
for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this.player_, this.eventHandler));
}
};
// Triggers removed using this.off when disposed
vjs.Html5.prototype.eventHandler = function(e){
this.trigger(e);
// No need for media events to bubble up.
e.stopPropagation();
};
vjs.Html5.prototype.useNativeControls = function(){
var tech, player, controlsOn, controlsOff, cleanUp;
tech = this;
player = this.player();
// If the player controls are enabled turn on the native controls
tech.setControls(player.controls());
// Update the native controls when player controls state is updated
controlsOn = function(){
tech.setControls(true);
};
controlsOff = function(){
tech.setControls(false);
};
player.on('controlsenabled', controlsOn);
player.on('controlsdisabled', controlsOff);
// Clean up when not using native controls anymore
cleanUp = function(){
player.off('controlsenabled', controlsOn);
player.off('controlsdisabled', controlsOff);
};
tech.on('dispose', cleanUp);
player.on('usingcustomcontrols', cleanUp);
// Update the state of the player to using native controls
player.usingNativeControls(true);
};
vjs.Html5.prototype.play = function(){ this.el_.play(); };
vjs.Html5.prototype.pause = function(){ this.el_.pause(); };
vjs.Html5.prototype.paused = function(){ return this.el_.paused; };
vjs.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };
vjs.Html5.prototype.setCurrentTime = function(seconds){
try {
this.el_.currentTime = seconds;
} catch(e) {
vjs.log(e, 'Video is not ready. (Video.js)');
// this.warning(VideoJS.warnings.videoNotReady);
}
};
vjs.Html5.prototype.duration = function(){ return this.el_.duration || 0; };
vjs.Html5.prototype.buffered = function(){ return this.el_.buffered; };
vjs.Html5.prototype.volume = function(){ return this.el_.volume; };
vjs.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };
vjs.Html5.prototype.muted = function(){ return this.el_.muted; };
vjs.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };
vjs.Html5.prototype.width = function(){ return this.el_.offsetWidth; };
vjs.Html5.prototype.height = function(){ return this.el_.offsetHeight; };
vjs.Html5.prototype.supportsFullScreen = function(){
if (typeof this.el_.webkitEnterFullScreen == 'function') {
// Seems to be broken in Chromium/Chrome && Safari in Leopard
if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {
return true;
}
}
return false;
};
vjs.Html5.prototype.enterFullScreen = function(){
var video = this.el_;
if (video.paused && video.networkState <= video.HAVE_METADATA) {
// attempt to prime the video element for programmatic access
// this isn't necessary on the desktop but shouldn't hurt
this.el_.play();
// playing and pausing synchronously during the transition to fullscreen
// can get iOS ~6.1 devices into a play/pause loop
setTimeout(function(){
video.pause();
video.webkitEnterFullScreen();
}, 0);
} else {
video.webkitEnterFullScreen();
}
};
vjs.Html5.prototype.exitFullScreen = function(){
this.el_.webkitExitFullScreen();
};
vjs.Html5.prototype.src = function(src){ this.el_.src = src; };
vjs.Html5.prototype.load = function(){ this.el_.load(); };
vjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
vjs.Html5.prototype.preload = function(){ return this.el_.preload; };
vjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };
vjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };
vjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };
vjs.Html5.prototype.controls = function(){ return this.el_.controls; }
vjs.Html5.prototype.setControls = function(val){ this.el_.controls = !!val; }
vjs.Html5.prototype.loop = function(){ return this.el_.loop; };
vjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };
vjs.Html5.prototype.error = function(){ return this.el_.error; };
vjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };
vjs.Html5.prototype.ended = function(){ return this.el_.ended; };
vjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
/* HTML5 Support Testing ---------------------------------------------------- */
vjs.Html5.isSupported = function(){
return !!vjs.TEST_VID.canPlayType;
};
vjs.Html5.canPlaySource = function(srcObj){
// IE9 on Windows 7 without MediaPlayer throws an error here
// https://github.com/videojs/video.js/issues/519
try {
return !!vjs.TEST_VID.canPlayType(srcObj.type);
} catch(e) {
return '';
}
// TODO: Check Type
// If no Type, check ext
// Check Media Type
};
vjs.Html5.canControlVolume = function(){
var volume = vjs.TEST_VID.volume;
vjs.TEST_VID.volume = (volume / 2) + 0.1;
return volume !== vjs.TEST_VID.volume;
};
// List of all HTML5 events (various uses).
vjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');
// HTML5 Feature detection and Device Fixes --------------------------------- //
// Override Android 2.2 and less canPlayType method which is broken
if (vjs.IS_OLD_ANDROID) {
document.createElement('video').constructor.prototype.canPlayType = function(type){
return (type && type.toLowerCase().indexOf('video/mp4') != -1) ? 'maybe' : '';
};
}
/**
* @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API
* https://github.com/zencoder/video-js-swf
* Not using setupTriggers. Using global onEvent func to distribute events
*/
/**
* HTML5 Media Controller - Wrapper for HTML5 Media API
* @param {vjs.Player|Object} player
* @param {Object=} options
* @param {Function=} ready
* @constructor
*/
vjs.Flash = vjs.MediaTechController.extend({
/** @constructor */
init: function(player, options, ready){
vjs.MediaTechController.call(this, player, options, ready);
var source = options['source'],
// Which element to embed in
parentEl = options['parentEl'],
// Create a temporary element to be replaced by swf object
placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),
// Generate ID for swf object
objId = player.id()+'_flash_api',
// Store player options in local var for optimization
// TODO: switch to using player methods instead of options
// e.g. player.autoplay();
playerOptions = player.options_,
// Merge default flashvars with ones passed in to init
flashVars = vjs.obj.merge({
// SWF Callback Functions
'readyFunction': 'videojs.Flash.onReady',
'eventProxyFunction': 'videojs.Flash.onEvent',
'errorEventProxyFunction': 'videojs.Flash.onError',
// Player Settings
'autoplay': playerOptions.autoplay,
'preload': playerOptions.preload,
'loop': playerOptions.loop,
'muted': playerOptions.muted
}, options['flashVars']),
// Merge default parames with ones passed in
params = vjs.obj.merge({
'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
}, options['params']),
// Merge default attributes with ones passed in
attributes = vjs.obj.merge({
'id': objId,
'name': objId, // Both ID and Name needed or swf to identifty itself
'class': 'vjs-tech'
}, options['attributes'])
;
// If source was supplied pass as a flash var.
if (source) {
if (source.type && vjs.Flash.isStreamingType(source.type)) {
var parts = vjs.Flash.streamToParts(source.src);
flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);
flashVars['rtmpStream'] = encodeURIComponent(parts.stream);
}
else {
flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
}
}
// Add placeholder to player div
vjs.insertFirst(placeHolder, parentEl);
// Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
// This allows resetting the playhead when we catch the reload
if (options['startTime']) {
this.ready(function(){
this.load();
this.play();
this.currentTime(options['startTime']);
});
}
// Flash iFrame Mode
// In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
// - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
// - Webkit when hiding the plugin
// - Webkit and Firefox when using requestFullScreen on a parent element
// Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
// Issues that remain include hiding the element and requestFullScreen in Firefox specifically
// There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.
// Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.
// I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.
// Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe
// In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.
// The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.
// NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
// Firefox 9 throws a security error, unleess you call location.href right before doc.write.
// Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
// Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
// Create iFrame with vjs-tech class so it's 100% width/height
var iFrm = vjs.createEl('iframe', {
'id': objId + '_iframe',
'name': objId + '_iframe',
'className': 'vjs-tech',
'scrolling': 'no',
'marginWidth': 0,
'marginHeight': 0,
'frameBorder': 0
});
// Update ready function names in flash vars for iframe window
flashVars['readyFunction'] = 'ready';
flashVars['eventProxyFunction'] = 'events';
flashVars['errorEventProxyFunction'] = 'errors';
// Tried multiple methods to get this to work in all browsers
// Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.
// The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error
// var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
// (in onload)
// var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
// iDoc.body.appendChild(temp);
// Tried embedding the flash object through javascript in the iframe source.
// This works in webkit but still triggers the firefox security error
// iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
// Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe
// We should add an option to host the iframe locally though, because it could help a lot of issues.
// iFrm.src = "iframe.html";
// Wait until iFrame has loaded to write into it.
vjs.on(iFrm, 'load', vjs.bind(this, function(){
var iDoc,
iWin = iFrm.contentWindow;
// The one working method I found was to use the iframe's document.write() to create the swf object
// This got around the security issue in all browsers except firefox.
// I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
// However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
// Plus Firefox 3.6 didn't work no matter what I tried.
// if (vjs.USER_AGENT.match('Firefox')) {
// iWin.location.href = '';
// }
// Get the iFrame's document depending on what the browser supports
iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
// Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
// Even tried adding /. that was mentioned in a browser security writeup
// document.domain = document.domain+'/.';
// iDoc.domain = document.domain+'/.';
// Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
// iDoc.body.innerHTML = swfObjectHTML;
// Tried appending the object to the iframe doc's body. Security error in all browsers.
// iDoc.body.appendChild(swfObject);
// Using document.write actually got around the security error that browsers were throwing.
// Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
// Not sure why that's a security issue, but apparently it is.
iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
// Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
// So far no issues with swf ready event being called before it's set on the window.
iWin['player'] = this.player_;
// Create swf ready function for iFrame window
iWin['ready'] = vjs.bind(this.player_, function(currSwf){
var el = iDoc.getElementById(currSwf),
player = this,
tech = player.tech;
// Update reference to playback technology element
tech.el_ = el;
// Make sure swf is actually ready. Sometimes the API isn't actually yet.
vjs.Flash.checkReady(tech);
});
// Create event listener for all swf events
iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){
var player = this;
if (player && player.techName === 'flash') {
player.trigger(eventName);
}
});
// Create error listener for all swf errors
iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){
vjs.log('Flash Error', eventName);
});
}));
// Replace placeholder with iFrame (it will load now)
placeHolder.parentNode.replaceChild(iFrm, placeHolder);
// If not using iFrame mode, embed as normal object
} else {
vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
}
}
});
vjs.Flash.prototype.dispose = function(){
vjs.MediaTechController.prototype.dispose.call(this);
};
vjs.Flash.prototype.play = function(){
this.el_.vjs_play();
};
vjs.Flash.prototype.pause = function(){
this.el_.vjs_pause();
};
vjs.Flash.prototype.src = function(src){
if (vjs.Flash.isStreamingSrc(src)) {
src = vjs.Flash.streamToParts(src);
this.setRtmpConnection(src.connection);
this.setRtmpStream(src.stream);
}
else {
// Make sure source URL is abosolute.
src = vjs.getAbsoluteURL(src);
this.el_.vjs_src(src);
}
// Currently the SWF doesn't autoplay if you load a source later.
// e.g. Load player w/ no source, wait 2s, set src.
if (this.player_.autoplay()) {
var tech = this;
setTimeout(function(){ tech.play(); }, 0);
}
};
vjs.Flash.prototype.currentSrc = function(){
var src = this.el_.vjs_getProperty('currentSrc');
// no src, check and see if RTMP
if (src == null) {
var connection = this.rtmpConnection(),
stream = this.rtmpStream();
if (connection && stream) {
src = vjs.Flash.streamFromParts(connection, stream);
}
}
return src;
};
vjs.Flash.prototype.load = function(){
this.el_.vjs_load();
};
vjs.Flash.prototype.poster = function(){
this.el_.vjs_getProperty('poster');
};
vjs.Flash.prototype.buffered = function(){
return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));
};
vjs.Flash.prototype.supportsFullScreen = function(){
return false; // Flash does not allow fullscreen through javascript
};
vjs.Flash.prototype.enterFullScreen = function(){
return false;
};
// Create setters and getters for attributes
var api = vjs.Flash.prototype,
readWrite = 'rtmpConnection,rtmpStream,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
// Overridden: buffered
/**
* @this {*}
*/
var createSetter = function(attr){
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };
};
/**
* @this {*}
*/
var createGetter = function(attr){
api[attr] = function(){ return this.el_.vjs_getProperty(attr); };
};
(function(){
var i;
// Create getter and setters for all read/write attributes
for (i = 0; i < readWrite.length; i++) {
createGetter(readWrite[i]);
createSetter(readWrite[i]);
}
// Create getters for read-only attributes
for (i = 0; i < readOnly.length; i++) {
createGetter(readOnly[i]);
}
})();
/* Flash Support Testing -------------------------------------------------------- */
vjs.Flash.isSupported = function(){
return vjs.Flash.version()[0] >= 10;
// return swfobject.hasFlashPlayerVersion('10');
};
vjs.Flash.canPlaySource = function(srcObj){
if (srcObj.type in vjs.Flash.formats || srcObj.type in vjs.Flash.streamingFormats) { return 'maybe'; }
};
vjs.Flash.formats = {
'video/flv': 'FLV',
'video/x-flv': 'FLV',
'video/mp4': 'MP4',
'video/m4v': 'MP4'
};
vjs.Flash.streamingFormats = {
'rtmp/mp4': 'MP4',
'rtmp/flv': 'FLV'
};
vjs.Flash['onReady'] = function(currSwf){
var el = vjs.el(currSwf);
// Get player from box
// On firefox reloads, el might already have a player
var player = el['player'] || el.parentNode['player'],
tech = player.tech;
// Reference player on tech element
el['player'] = player;
// Update reference to playback technology element
tech.el_ = el;
vjs.Flash.checkReady(tech);
};
// The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.
// If it's not ready, we set a timeout to check again shortly.
vjs.Flash.checkReady = function(tech){
// Check if API property exists
if (tech.el().vjs_getProperty) {
// If so, tell tech it's ready
tech.triggerReady();
// Otherwise wait longer.
} else {
setTimeout(function(){
vjs.Flash.checkReady(tech);
}, 50);
}
};
// Trigger events from the swf on the player
vjs.Flash['onEvent'] = function(swfID, eventName){
var player = vjs.el(swfID)['player'];
player.trigger(eventName);
};
// Log errors from the swf
vjs.Flash['onError'] = function(swfID, err){
var player = vjs.el(swfID)['player'];
player.trigger('error');
vjs.log('Flash Error', err, swfID);
};
// Flash Version Check
vjs.Flash.version = function(){
var version = '0,0,0';
// IE
try {
version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
// other browsers
} catch(e) {
try {
if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){
version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
}
} catch(err) {}
}
return version.split(',');
};
// Flash embedding method. Only used in non-iframe mode
vjs.Flash.embed = function(swf, placeHolder, flashVars, params, attributes){
var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),
// Get element by embedding code and retrieving created element
obj = vjs.createEl('div', { innerHTML: code }).childNodes[0],
par = placeHolder.parentNode
;
placeHolder.parentNode.replaceChild(obj, placeHolder);
// IE6 seems to have an issue where it won't initialize the swf object after injecting it.
// This is a dumb fix
var newObj = par.childNodes[0];
setTimeout(function(){
newObj.style.display = 'block';
}, 1000);
return obj;
};
vjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){
var objTag = '<object type="application/x-shockwave-flash"',
flashVarsString = '',
paramsString = '',
attrsString = '';
// Convert flash vars to string
if (flashVars) {
vjs.obj.each(flashVars, function(key, val){
flashVarsString += (key + '=' + val + '&amp;');
});
}
// Add swf, flashVars, and other default params
params = vjs.obj.merge({
'movie': swf,
'flashvars': flashVarsString,
'allowScriptAccess': 'always', // Required to talk to swf
'allowNetworking': 'all' // All should be default, but having security issues.
}, params);
// Create param tags string
vjs.obj.each(params, function(key, val){
paramsString += '<param name="'+key+'" value="'+val+'" />';
});
attributes = vjs.obj.merge({
// Add swf to attributes (need both for IE and Others to work)
'data': swf,
// Default to 100% width/height
'width': '100%',
'height': '100%'
}, attributes);
// Create Attributes string
vjs.obj.each(attributes, function(key, val){
attrsString += (key + '="' + val + '" ');
});
return objTag + attrsString + '>' + paramsString + '</object>';
};
vjs.Flash.streamFromParts = function(connection, stream) {
return connection + '&' + stream;
};
vjs.Flash.streamToParts = function(src) {
var parts = {
connection: '',
stream: ''
};
if (! src) {
return parts;
}
// Look for the normal URL separator we expect, '&'.
// If found, we split the URL into two pieces around the
// first '&'.
var connEnd = src.indexOf('&');
var streamBegin;
if (connEnd !== -1) {
streamBegin = connEnd + 1;
}
else {
// If there's not a '&', we use the last '/' as the delimiter.
connEnd = streamBegin = src.lastIndexOf('/') + 1;
if (connEnd === 0) {
// really, there's not a '/'?
connEnd = streamBegin = src.length;
}
}
parts.connection = src.substring(0, connEnd);
parts.stream = src.substring(streamBegin, src.length);
return parts;
};
vjs.Flash.isStreamingType = function(srcType) {
return srcType in vjs.Flash.streamingFormats;
};
// RTMP has four variations, any string starting
// with one of these protocols should be valid
vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
vjs.Flash.isStreamingSrc = function(src) {
return vjs.Flash.RTMP_RE.test(src);
};
/**
* @constructor
*/
vjs.MediaLoader = vjs.Component.extend({
/** @constructor */
init: function(player, options, ready){
vjs.Component.call(this, player, options, ready);
// If there are no sources when the player is initialized,
// load the first supported playback technology.
if (!player.options_['sources'] || player.options_['sources'].length === 0) {
for (var i=0,j=player.options_['techOrder']; i<j.length; i++) {
var techName = vjs.capitalize(j[i]),
tech = window['videojs'][techName];
// Check if the browser supports this technology
if (tech && tech.isSupported()) {
player.loadTech(techName);
break;
}
}
} else {
// // Loop through playback technologies (HTML5, Flash) and check for support.
// // Then load the best source.
// // A few assumptions here:
// // All playback technologies respect preload false.
player.src(player.options_['sources']);
}
}
});/**
* @fileoverview Text Tracks
* Text tracks are tracks of timed text events.
* Captions - text displayed over the video for the hearing impared
* Subtitles - text displayed over the video for those who don't understand langauge in the video
* Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
* Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
*/
// Player Additions - Functions add to the player object for easier access to tracks
/**
* List of associated text tracks
* @type {Array}
* @private
*/
vjs.Player.prototype.textTracks_;
/**
* Get an array of associated text tracks. captions, subtitles, chapters, descriptions
* http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
* @return {Array} Array of track objects
*/
vjs.Player.prototype.textTracks = function(){
this.textTracks_ = this.textTracks_ || [];
return this.textTracks_;
};
/**
* Add a text track
* In addition to the W3C settings we allow adding additional info through options.
* http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
* @param {String} kind Captions, subtitles, chapters, descriptions, or metadata
* @param {String=} label Optional label
* @param {String=} language Optional language
* @param {Object=} options Additional track options, like src
*/
vjs.Player.prototype.addTextTrack = function(kind, label, language, options){
var tracks = this.textTracks_ = this.textTracks_ || [];
options = options || {};
options['kind'] = kind;
options['label'] = label;
options['language'] = language;
// HTML5 Spec says default to subtitles.
// Uppercase first letter to match class names
var Kind = vjs.capitalize(kind || 'subtitles');
// Create correct texttrack class. CaptionsTrack, etc.
var track = new window['videojs'][Kind + 'Track'](this, options);
tracks.push(track);
// If track.dflt() is set, start showing immediately
// TODO: Add a process to deterime the best track to show for the specific kind
// Incase there are mulitple defaulted tracks of the same kind
// Or the user has a set preference of a specific language that should override the default
// if (track.dflt()) {
// this.ready(vjs.bind(track, track.show));
// }
return track;
};
/**
* Add an array of text tracks. captions, subtitles, chapters, descriptions
* Track objects will be stored in the player.textTracks() array
* @param {Array} trackList Array of track elements or objects (fake track elements)
*/
vjs.Player.prototype.addTextTracks = function(trackList){
var trackObj;
for (var i = 0; i < trackList.length; i++) {
trackObj = trackList[i];
this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);
}
return this;
};
// Show a text track
// disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
vjs.Player.prototype.showTextTrack = function(id, disableSameKind){
var tracks = this.textTracks_,
i = 0,
j = tracks.length,
track, showTrack, kind;
// Find Track with same ID
for (;i<j;i++) {
track = tracks[i];
if (track.id() === id) {
track.show();
showTrack = track;
// Disable tracks of the same kind
} else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {
track.disable();
}
}
// Get track kind from shown track or disableSameKind
kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);
// Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.
if (kind) {
this.trigger(kind+'trackchange');
}
return this;
};
/**
* Track Class
* Contains track methods for loading, showing, parsing cues of tracks
* @param {vjs.Player|Object} player
* @param {Object=} options
* @constructor
*/
vjs.TextTrack = vjs.Component.extend({
/** @constructor */
init: function(player, options){
vjs.Component.call(this, player, options);
// Apply track info to track object
// Options will often be a track element
// Build ID if one doesn't exist
this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
this.src_ = options['src'];
// 'default' is a reserved keyword in js so we use an abbreviated version
this.dflt_ = options['default'] || options['dflt'];
this.title_ = options['title'];
this.language_ = options['srclang'];
this.label_ = options['label'];
this.cues_ = [];
this.activeCues_ = [];
this.readyState_ = 0;
this.mode_ = 0;
this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));
}
});
/**
* Track kind value. Captions, subtitles, etc.
* @private
*/
vjs.TextTrack.prototype.kind_;
/**
* Get the track kind value
* @return {String}
*/
vjs.TextTrack.prototype.kind = function(){
return this.kind_;
};
/**
* Track src value
* @private
*/
vjs.TextTrack.prototype.src_;
/**
* Get the track src value
* @return {String}
*/
vjs.TextTrack.prototype.src = function(){
return this.src_;
};
/**
* Track default value
* If default is used, subtitles/captions to start showing
* @private
*/
vjs.TextTrack.prototype.dflt_;
/**
* Get the track default value
* 'default' is a reserved keyword
* @return {Boolean}
*/
vjs.TextTrack.prototype.dflt = function(){
return this.dflt_;
};
/**
* Track title value
* @private
*/
vjs.TextTrack.prototype.title_;
/**
* Get the track title value
* @return {String}
*/
vjs.TextTrack.prototype.title = function(){
return this.title_;
};
/**
* Language - two letter string to represent track language, e.g. 'en' for English
* Spec def: readonly attribute DOMString language;
* @private
*/
vjs.TextTrack.prototype.language_;
/**
* Get the track language value
* @return {String}
*/
vjs.TextTrack.prototype.language = function(){
return this.language_;
};
/**
* Track label e.g. 'English'
* Spec def: readonly attribute DOMString label;
* @private
*/
vjs.TextTrack.prototype.label_;
/**
* Get the track label value
* @return {String}
*/
vjs.TextTrack.prototype.label = function(){
return this.label_;
};
/**
* All cues of the track. Cues have a startTime, endTime, text, and other properties.
* Spec def: readonly attribute TextTrackCueList cues;
* @private
*/
vjs.TextTrack.prototype.cues_;
/**
* Get the track cues
* @return {Array}
*/
vjs.TextTrack.prototype.cues = function(){
return this.cues_;
};
/**
* ActiveCues is all cues that are currently showing
* Spec def: readonly attribute TextTrackCueList activeCues;
* @private
*/
vjs.TextTrack.prototype.activeCues_;
/**
* Get the track active cues
* @return {Array}
*/
vjs.TextTrack.prototype.activeCues = function(){
return this.activeCues_;
};
/**
* ReadyState describes if the text file has been loaded
* const unsigned short NONE = 0;
* const unsigned short LOADING = 1;
* const unsigned short LOADED = 2;
* const unsigned short ERROR = 3;
* readonly attribute unsigned short readyState;
* @private
*/
vjs.TextTrack.prototype.readyState_;
/**
* Get the track readyState
* @return {Number}
*/
vjs.TextTrack.prototype.readyState = function(){
return this.readyState_;
};
/**
* Mode describes if the track is showing, hidden, or disabled
* const unsigned short OFF = 0;
* const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)
* const unsigned short SHOWING = 2;
* attribute unsigned short mode;
* @private
*/
vjs.TextTrack.prototype.mode_;
/**
* Get the track mode
* @return {Number}
*/
vjs.TextTrack.prototype.mode = function(){
return this.mode_;
};
/**
* Change the font size of the text track to make it larger when playing in fullscreen mode
* and restore it to its normal size when not in fullscreen mode.
*/
vjs.TextTrack.prototype.adjustFontSize = function(){
if (this.player_.isFullScreen) {
// Scale the font by the same factor as increasing the video width to the full screen window width.
// Additionally, multiply that factor by 1.4, which is the default font size for
// the caption track (from the CSS)
this.el_.style.fontSize = screen.width / this.player_.width() * 1.4 * 100 + '%';
} else {
// Change the font size of the text track back to its original non-fullscreen size
this.el_.style.fontSize = '';
}
};
/**
* Create basic div to hold cue text
* @return {Element}
*/
vjs.TextTrack.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-' + this.kind_ + ' vjs-text-track'
});
};
/**
* Show: Mode Showing (2)
* Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
* The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
* In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;
* for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a non-visual fashion;
* and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.
* The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.
* This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
*/
vjs.TextTrack.prototype.show = function(){
this.activate();
this.mode_ = 2;
// Show element.
vjs.Component.prototype.show.call(this);
};
/**
* Hide: Mode Hidden (1)
* Indicates that the text track is active, but that the user agent is not actively displaying the cues.
* If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
* The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
*/
vjs.TextTrack.prototype.hide = function(){
// When hidden, cues are still triggered. Disable to stop triggering.
this.activate();
this.mode_ = 1;
// Hide element.
vjs.Component.prototype.hide.call(this);
};
/**
* Disable: Mode Off/Disable (0)
* Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.
* No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
*/
vjs.TextTrack.prototype.disable = function(){
// If showing, hide.
if (this.mode_ == 2) { this.hide(); }
// Stop triggering cues
this.deactivate();
// Switch Mode to Off
this.mode_ = 0;
};
/**
* Turn on cue tracking. Tracks that are showing OR hidden are active.
*/
vjs.TextTrack.prototype.activate = function(){
// Load text file if it hasn't been yet.
if (this.readyState_ === 0) { this.load(); }
// Only activate if not already active.
if (this.mode_ === 0) {
// Update current cue on timeupdate
// Using unique ID for bind function so other tracks don't remove listener
this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));
// Reset cue time on media end
this.player_.on('ended', vjs.bind(this, this.reset, this.id_));
// Add to display
if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {
this.player_.getChild('textTrackDisplay').addChild(this);
}
}
};
/**
* Turn off cue tracking.
*/
vjs.TextTrack.prototype.deactivate = function(){
// Using unique ID for bind function so other tracks don't remove listener
this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));
this.player_.off('ended', vjs.bind(this, this.reset, this.id_));
this.reset(); // Reset
// Remove from display
this.player_.getChild('textTrackDisplay').removeChild(this);
};
// A readiness state
// One of the following:
//
// Not loaded
// Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained.
//
// Loading
// Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track.
//
// Loaded
// Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object.
//
// Failed to load
// Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.
vjs.TextTrack.prototype.load = function(){
// Only load if not loaded yet.
if (this.readyState_ === 0) {
this.readyState_ = 1;
vjs.get(this.src_, vjs.bind(this, this.parseCues), vjs.bind(this, this.onError));
}
};
vjs.TextTrack.prototype.onError = function(err){
this.error = err;
this.readyState_ = 3;
this.trigger('error');
};
// Parse the WebVTT text format for cue times.
// TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
vjs.TextTrack.prototype.parseCues = function(srcContent) {
var cue, time, text,
lines = srcContent.split('\n'),
line = '', id;
for (var i=1, j=lines.length; i<j; i++) {
// Line 0 should be 'WEBVTT', so skipping i=0
line = vjs.trim(lines[i]); // Trim whitespace and linebreaks
if (line) { // Loop until a line with content
// First line could be an optional cue ID
// Check if line has the time separator
if (line.indexOf('-->') == -1) {
id = line;
// Advance to next line for timing.
line = vjs.trim(lines[++i]);
} else {
id = this.cues_.length;
}
// First line - Number
cue = {
id: id, // Cue Number
index: this.cues_.length // Position in Array
};
// Timing line
time = line.split(' --> ');
cue.startTime = this.parseCueTime(time[0]);
cue.endTime = this.parseCueTime(time[1]);
// Additional lines - Cue Text
text = [];
// Loop until a blank line or end of lines
// Assumeing trim('') returns false for blank lines
while (lines[++i] && (line = vjs.trim(lines[i]))) {
text.push(line);
}
cue.text = text.join('<br/>');
// Add this cue
this.cues_.push(cue);
}
}
this.readyState_ = 2;
this.trigger('loaded');
};
vjs.TextTrack.prototype.parseCueTime = function(timeText) {
var parts = timeText.split(':'),
time = 0,
hours, minutes, other, seconds, ms;
// Check if optional hours place is included
// 00:00:00.000 vs. 00:00.000
if (parts.length == 3) {
hours = parts[0];
minutes = parts[1];
other = parts[2];
} else {
hours = 0;
minutes = parts[0];
other = parts[1];
}
// Break other (seconds, milliseconds, and flags) by spaces
// TODO: Make additional cue layout settings work with flags
other = other.split(/\s+/);
// Remove seconds. Seconds is the first part before any spaces.
seconds = other.splice(0,1)[0];
// Could use either . or , for decimal
seconds = seconds.split(/\.|,/);
// Get milliseconds
ms = parseFloat(seconds[1]);
seconds = seconds[0];
// hours => seconds
time += parseFloat(hours) * 3600;
// minutes => seconds
time += parseFloat(minutes) * 60;
// Add seconds
time += parseFloat(seconds);
// Add milliseconds
if (ms) { time += ms/1000; }
return time;
};
// Update active cues whenever timeupdate events are triggered on the player.
vjs.TextTrack.prototype.update = function(){
if (this.cues_.length > 0) {
// Get curent player time
var time = this.player_.currentTime();
// Check if the new time is outside the time box created by the the last update.
if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
var cues = this.cues_,
// Create a new time box for this state.
newNextChange = this.player_.duration(), // Start at beginning of the timeline
newPrevChange = 0, // Start at end
reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.
newCues = [], // Store new active cues.
// Store where in the loop the current active cues are, to provide a smart starting point for the next loop.
firstActiveIndex, lastActiveIndex,
cue, i; // Loop vars
// Check if time is going forwards or backwards (scrubbing/rewinding)
// If we know the direction we can optimize the starting position and direction of the loop through the cues array.
if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen
// Forwards, so start at the index of the first active cue and loop forward
i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;
} else {
// Backwards, so start at the index of the last active cue and loop backward
reverse = true;
i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;
}
while (true) { // Loop until broken
cue = cues[i];
// Cue ended at this point
if (cue.endTime <= time) {
newPrevChange = Math.max(newPrevChange, cue.endTime);
if (cue.active) {
cue.active = false;
}
// No earlier cues should have an active start time.
// Nevermind. Assume first cue could have a duration the same as the video.
// In that case we need to loop all the way back to the beginning.
// if (reverse && cue.startTime) { break; }
// Cue hasn't started
} else if (time < cue.startTime) {
newNextChange = Math.min(newNextChange, cue.startTime);
if (cue.active) {
cue.active = false;
}
// No later cues should have an active start time.
if (!reverse) { break; }
// Cue is current
} else {
if (reverse) {
// Add cue to front of array to keep in time order
newCues.splice(0,0,cue);
// If in reverse, the first current cue is our lastActiveCue
if (lastActiveIndex === undefined) { lastActiveIndex = i; }
firstActiveIndex = i;
} else {
// Add cue to end of array
newCues.push(cue);
// If forward, the first current cue is our firstActiveIndex
if (firstActiveIndex === undefined) { firstActiveIndex = i; }
lastActiveIndex = i;
}
newNextChange = Math.min(newNextChange, cue.endTime);
newPrevChange = Math.max(newPrevChange, cue.startTime);
cue.active = true;
}
if (reverse) {
// Reverse down the array of cues, break if at first
if (i === 0) { break; } else { i--; }
} else {
// Walk up the array fo cues, break if at last
if (i === cues.length - 1) { break; } else { i++; }
}
}
this.activeCues_ = newCues;
this.nextChange = newNextChange;
this.prevChange = newPrevChange;
this.firstActiveIndex = firstActiveIndex;
this.lastActiveIndex = lastActiveIndex;
this.updateDisplay();
this.trigger('cuechange');
}
}
};
// Add cue HTML to display
vjs.TextTrack.prototype.updateDisplay = function(){
var cues = this.activeCues_,
html = '',
i=0,j=cues.length;
for (;i<j;i++) {
html += '<span class="vjs-tt-cue">'+cues[i].text+'</span>';
}
this.el_.innerHTML = html;
};
// Set all loop helper values back
vjs.TextTrack.prototype.reset = function(){
this.nextChange = 0;
this.prevChange = this.player_.duration();
this.firstActiveIndex = 0;
this.lastActiveIndex = 0;
};
// Create specific track types
/**
* @constructor
*/
vjs.CaptionsTrack = vjs.TextTrack.extend();
vjs.CaptionsTrack.prototype.kind_ = 'captions';
// Exporting here because Track creation requires the track kind
// to be available on global object. e.g. new window['videojs'][Kind + 'Track']
/**
* @constructor
*/
vjs.SubtitlesTrack = vjs.TextTrack.extend();
vjs.SubtitlesTrack.prototype.kind_ = 'subtitles';
/**
* @constructor
*/
vjs.ChaptersTrack = vjs.TextTrack.extend();
vjs.ChaptersTrack.prototype.kind_ = 'chapters';
/* Text Track Display
============================================================================= */
// Global container for both subtitle and captions text. Simple div container.
/**
* @constructor
*/
vjs.TextTrackDisplay = vjs.Component.extend({
/** @constructor */
init: function(player, options, ready){
vjs.Component.call(this, player, options, ready);
// This used to be called during player init, but was causing an error
// if a track should show by default and the display hadn't loaded yet.
// Should probably be moved to an external track loader when we support
// tracks that don't need a display.
if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
this.player_.addTextTracks(player.options_['tracks']);
}
}
});
vjs.TextTrackDisplay.prototype.createEl = function(){
return vjs.Component.prototype.createEl.call(this, 'div', {
className: 'vjs-text-track-display'
});
};
/* Text Track Menu Items
============================================================================= */
/**
* @constructor
*/
vjs.TextTrackMenuItem = vjs.MenuItem.extend({
/** @constructor */
init: function(player, options){
var track = this.track = options['track'];
// Modify options for parent MenuItem class's init.
options['label'] = track.label();
options['selected'] = track.dflt();
vjs.MenuItem.call(this, player, options);
this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));
}
});
vjs.TextTrackMenuItem.prototype.onClick = function(){
vjs.MenuItem.prototype.onClick.call(this);
this.player_.showTextTrack(this.track.id_, this.track.kind());
};
vjs.TextTrackMenuItem.prototype.update = function(){
this.selected(this.track.mode() == 2);
};
/**
* @constructor
*/
vjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({
/** @constructor */
init: function(player, options){
// Create pseudo track info
// Requires options['kind']
options['track'] = {
kind: function() { return options['kind']; },
player: player,
label: function(){ return options['kind'] + ' off'; },
dflt: function(){ return false; },
mode: function(){ return false; }
};
vjs.TextTrackMenuItem.call(this, player, options);
this.selected(true);
}
});
vjs.OffTextTrackMenuItem.prototype.onClick = function(){
vjs.TextTrackMenuItem.prototype.onClick.call(this);
this.player_.showTextTrack(this.track.id_, this.track.kind());
};
vjs.OffTextTrackMenuItem.prototype.update = function(){
var tracks = this.player_.textTracks(),
i=0, j=tracks.length, track,
off = true;
for (;i<j;i++) {
track = tracks[i];
if (track.kind() == this.track.kind() && track.mode() == 2) {
off = false;
}
}
this.selected(off);
};
/* Captions Button
================================================================================ */
/**
* @constructor
*/
vjs.TextTrackButton = vjs.MenuButton.extend({
/** @constructor */
init: function(player, options){
vjs.MenuButton.call(this, player, options);
if (this.items.length <= 1) {
this.hide();
}
}
});
// vjs.TextTrackButton.prototype.buttonPressed = false;
// vjs.TextTrackButton.prototype.createMenu = function(){
// var menu = new vjs.Menu(this.player_);
// // Add a title list item to the top
// // menu.el().appendChild(vjs.createEl('li', {
// // className: 'vjs-menu-title',
// // innerHTML: vjs.capitalize(this.kind_),
// // tabindex: -1
// // }));
// this.items = this.createItems();
// // Add menu items to the menu
// for (var i = 0; i < this.items.length; i++) {
// menu.addItem(this.items[i]);
// }
// // Add list to element
// this.addChild(menu);
// return menu;
// };
// Create a menu item for each text track
vjs.TextTrackButton.prototype.createItems = function(){
var items = [], track;
// Add an OFF menu item to turn all tracks off
items.push(new vjs.OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
for (var i = 0; i < this.player_.textTracks().length; i++) {
track = this.player_.textTracks()[i];
if (track.kind() === this.kind_) {
items.push(new vjs.TextTrackMenuItem(this.player_, {
'track': track
}));
}
}
return items;
};
/**
* @constructor
*/
vjs.CaptionsButton = vjs.TextTrackButton.extend({
/** @constructor */
init: function(player, options, ready){
vjs.TextTrackButton.call(this, player, options, ready);
this.el_.setAttribute('aria-label','Captions Menu');
}
});
vjs.CaptionsButton.prototype.kind_ = 'captions';
vjs.CaptionsButton.prototype.buttonText = 'Captions';
vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
/**
* @constructor
*/
vjs.SubtitlesButton = vjs.TextTrackButton.extend({
/** @constructor */
init: function(player, options, ready){
vjs.TextTrackButton.call(this, player, options, ready);
this.el_.setAttribute('aria-label','Subtitles Menu');
}
});
vjs.SubtitlesButton.prototype.kind_ = 'subtitles';
vjs.SubtitlesButton.prototype.buttonText = 'Subtitles';
vjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';
// Chapters act much differently than other text tracks
// Cues are navigation vs. other tracks of alternative languages
/**
* @constructor
*/
vjs.ChaptersButton = vjs.TextTrackButton.extend({
/** @constructor */
init: function(player, options, ready){
vjs.TextTrackButton.call(this, player, options, ready);
this.el_.setAttribute('aria-label','Chapters Menu');
}
});
vjs.ChaptersButton.prototype.kind_ = 'chapters';
vjs.ChaptersButton.prototype.buttonText = 'Chapters';
vjs.ChaptersButton.prototype.className = 'vjs-chapters-button';
// Create a menu item for each text track
vjs.ChaptersButton.prototype.createItems = function(){
var items = [], track;
for (var i = 0; i < this.player_.textTracks().length; i++) {
track = this.player_.textTracks()[i];
if (track.kind() === this.kind_) {
items.push(new vjs.TextTrackMenuItem(this.player_, {
'track': track
}));
}
}
return items;
};
vjs.ChaptersButton.prototype.createMenu = function(){
var tracks = this.player_.textTracks(),
i = 0,
j = tracks.length,
track, chaptersTrack,
items = this.items = [];
for (;i<j;i++) {
track = tracks[i];
if (track.kind() == this.kind_ && track.dflt()) {
if (track.readyState() < 2) {
this.chaptersTrack = track;
track.on('loaded', vjs.bind(this, this.createMenu));
return;
} else {
chaptersTrack = track;
break;
}
}
}
var menu = this.menu = new vjs.Menu(this.player_);
menu.el_.appendChild(vjs.createEl('li', {
className: 'vjs-menu-title',
innerHTML: vjs.capitalize(this.kind_),
tabindex: -1
}));
if (chaptersTrack) {
var cues = chaptersTrack.cues_, cue, mi;
i = 0;
j = cues.length;
for (;i<j;i++) {
cue = cues[i];
mi = new vjs.ChaptersTrackMenuItem(this.player_, {
'track': chaptersTrack,
'cue': cue
});
items.push(mi);
menu.addChild(mi);
}
}
if (this.items.length > 0) {
this.show();
}
return menu;
};
/**
* @constructor
*/
vjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({
/** @constructor */
init: function(player, options){
var track = this.track = options['track'],
cue = this.cue = options['cue'],
currentTime = player.currentTime();
// Modify options for parent MenuItem class's init.
options['label'] = cue.text;
options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
vjs.MenuItem.call(this, player, options);
track.on('cuechange', vjs.bind(this, this.update));
}
});
vjs.ChaptersTrackMenuItem.prototype.onClick = function(){
vjs.MenuItem.prototype.onClick.call(this);
this.player_.currentTime(this.cue.startTime);
this.update(this.cue.startTime);
};
vjs.ChaptersTrackMenuItem.prototype.update = function(){
var cue = this.cue,
currentTime = this.player_.currentTime();
// vjs.log(currentTime, cue.startTime);
this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
};
// Add Buttons to controlBar
vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
'subtitlesButton': {},
'captionsButton': {},
'chaptersButton': {}
});
// vjs.Cue = vjs.Component.extend({
// /** @constructor */
// init: function(player, options){
// vjs.Component.call(this, player, options);
// }
// });
/**
* @fileoverview Add JSON support
* @suppress {undefinedVars}
* (Compiler doesn't like JSON not being declared)
*/
/**
* Javascript JSON implementation
* (Parse Method Only)
* https://github.com/douglascrockford/JSON-js/blob/master/json2.js
* Only using for parse method when parsing data-setup attribute JSON.
* @type {Object}
* @suppress {undefinedVars}
*/
vjs.JSON;
/**
* @suppress {undefinedVars}
*/
if (typeof window.JSON !== 'undefined' && window.JSON.parse === 'function') {
vjs.JSON = window.JSON;
} else {
vjs.JSON = {};
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
vjs.JSON.parse = function (text, reviver) {
var j;
function walk(holder, key) {
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
j = eval('(' + text + ')');
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
throw new SyntaxError('JSON.parse(): invalid or malformed JSON data');
};
}
/**
* @fileoverview Functions for automatically setting up a player
* based on the data-setup attribute of the video tag
*/
// Automatically set up any tags that have a data-setup attribute
vjs.autoSetup = function(){
var options, vid, player,
vids = document.getElementsByTagName('video');
// Check if any media elements exist
if (vids && vids.length > 0) {
for (var i=0,j=vids.length; i<j; i++) {
vid = vids[i];
// Check if element exists, has getAttribute func.
// IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
if (vid && vid.getAttribute) {
// Make sure this player hasn't already been set up.
if (vid['player'] === undefined) {
options = vid.getAttribute('data-setup');
// Check if data-setup attr exists.
// We only auto-setup if they've added the data-setup attr.
if (options !== null) {
// Parse options JSON
// If empty string, make it a parsable json object.
options = vjs.JSON.parse(options || '{}');
// Create new video.js instance.
player = videojs(vid, options);
}
}
// If getAttribute isn't defined, we need to wait for the DOM.
} else {
vjs.autoSetupTimeout(1);
break;
}
}
// No videos were found, so keep looping unless page is finisehd loading.
} else if (!vjs.windowLoaded) {
vjs.autoSetupTimeout(1);
}
};
// Pause to let the DOM keep processing
vjs.autoSetupTimeout = function(wait){
setTimeout(vjs.autoSetup, wait);
};
if (document.readyState === 'complete') {
vjs.windowLoaded = true;
} else {
vjs.one(window, 'load', function(){
vjs.windowLoaded = true;
});
}
// Run Auto-load players
// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
vjs.autoSetupTimeout(1);
vjs.plugin = function(name, init){
vjs.Player.prototype[name] = init;
};

View File

@@ -0,0 +1,605 @@
/**
* @fileoverview YouTube Media Controller - Wrapper for YouTube Media API
*/
/**
* YouTube Media Controller - Wrapper for YouTube Media API
* @param {videojs.Player|Object} player
* @param {Object=} options
* @param {Function=} ready
* @constructor
*/
videojs.Youtube = videojs.MediaTechController.extend({
/** @constructor */
init: function(player, options, ready){
videojs.MediaTechController.call(this, player, options, ready);
// No event is triggering this for YouTube
this.features['progressEvents'] = false;
this.features['timeupdateEvents'] = false;
// Copy the JavaScript options if they exists
if (typeof options['source'] != 'undefined') {
for (var key in options['source']) {
player.options()[key] = options['source'][key];
}
}
this.userQuality = videojs.Youtube.convertQualityName(player.options()['quality']);
// Save those for internal usage
this.player_ = player;
this.player_el_ = document.getElementById(player.id());
this.player_el_.className += ' vjs-youtube';
// Mobile devices are using their own native players
if (!!navigator.userAgent.match(/iPhone/i) || !!navigator.userAgent.match(/iPad/i) || !!navigator.userAgent.match(/iPod/i) || !!navigator.userAgent.match(/Android.*AppleWebKit/i)) {
player.options()['ytcontrols'] = true;
}
// Create the Quality button
this.qualityButton = document.createElement('div');
this.qualityButton.setAttribute('class', 'vjs-quality-button vjs-menu-button vjs-control');
this.qualityButton.setAttribute('tabindex', 0);
var qualityContent = document.createElement('div');
this.qualityButton.appendChild(qualityContent);
this.qualityTitle = document.createElement('span');
qualityContent.appendChild(this.qualityTitle);
var qualityMenu = document.createElement('div');
qualityMenu.setAttribute('class', 'vjs-menu');
this.qualityButton.appendChild(qualityMenu);
this.qualityMenuContent = document.createElement('ul');
this.qualityMenuContent.setAttribute('class', 'vjs-menu-content');
qualityMenu.appendChild(this.qualityMenuContent);
this.id_ = this.player_.id() + '_youtube_api';
this.el_ = videojs.Component.prototype.createEl('iframe', {
id: this.id_,
className: 'vjs-tech',
scrolling: 'no',
marginWidth: 0,
marginHeight: 0,
frameBorder: 0,
webkitAllowFullScreen: 'true',
mozallowfullscreen: 'true',
allowFullScreen: 'true'
});
// This makes sure the mousemove is not lost within the iframe
// Only way to make sure the control bar shows when we come back in the video player
this.iframeblocker = videojs.Component.prototype.createEl('div', {
className: 'iframeblocker'
});
// Make sure to not block the play or pause
var self = this;
var toggleThis = function() {
if (self.paused()) {
self.play();
} else {
self.pause();
}
};
this.iframeblocker.addEventListener('click', toggleThis);
this.iframeblocker.addEventListener('mousemove', function(e) {
if (!self.player_.userActive()) {
self.player_.userActive(true);
}
e.stopPropagation();
e.preventDefault();
});
if (!this.player_.options()['ytcontrols']) {
// Before the tech is ready, we have to take care of the play action
this.iframeblocker.style.display = 'block';
}
this.player_el_.insertBefore(this.iframeblocker, this.player_el_.firstChild);
this.player_el_.insertBefore(this.el_, this.iframeblocker);
this.parseSrc(player.options()['src']);
this.playOnReady = this.player_.options()['autoplay'] || false;
var params = {
enablejsapi: 1,
iv_load_policy: 3,
playerapiid: this.id(),
disablekb: 1,
wmode: 'transparent',
controls: (this.player_.options()['ytcontrols'])?1:0,
showinfo: 0,
modestbranding: 1,
rel: 0,
autoplay: (this.playOnReady)?1:0,
loop: (this.player_.options()['loop'])?1:0,
list: this.playlistId,
vq: this.userQuality
};
if (typeof params.list == 'undefined') {
delete params.list;
}
// If we are not on a server, don't specify the origin (it will crash)
if (window.location.protocol != 'file:'){
params.origin = window.location.protocol + '//' + window.location.host;
this.el_.src = window.location.protocol + '//www.youtube.com/embed/' + this.videoId + '?' + videojs.Youtube.makeQueryString(params);
} else {
this.el_.src = 'https://www.youtube.com/embed/' + this.videoId + '?' + videojs.Youtube.makeQueryString(params);
}
var self = this;
player.ready(function(){
var controlBar = self.player_el_.getElementsByClassName('vjs-control-bar')[0];
controlBar.appendChild(self.qualityButton);
if (self.playOnReady && !self.player_.options()['ytcontrols']) {
self.player_.loadingSpinner.show();
self.player_.bigPlayButton.hide();
}
});
if (this.player_.options()['ytcontrols']){
// Disable the video.js controls if we use the YouTube controls
this.player_.controls(false);
} else {
// Show the YouTube poster if their is no custom poster
if (!this.player_.poster()) {
if (this.videoId == null) {
// Set the black background if their is no video initially
this.iframeblocker.style.backgroundColor = 'black';
} else {
this.player_.poster('https://img.youtube.com/vi/' + this.videoId + '/0.jpg');
}
}
}
if (videojs.Youtube.apiReady){
this.loadYoutube();
} else {
// Add to the queue because the YouTube API is not ready
videojs.Youtube.loadingQueue.push(this);
// Load the YouTube API if it is the first YouTube video
if(!videojs.Youtube.apiLoading){
var tag = document.createElement('script');
tag.src = '//www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
videojs.Youtube.apiLoading = true;
}
}
this.on('dispose', function() {
// Get rid of the created DOM elements
this.el_.parentNode.removeChild(this.el_);
this.iframeblocker.parentNode.removeChild(this.iframeblocker);
this.qualityButton.parentNode.removeChild(this.qualityButton);
this.player_.loadingSpinner.hide();
this.player_.bigPlayButton.hide();
});
}
});
videojs.Youtube.prototype.parseSrc = function(src){
this.srcVal = src;
if (src) {
// Regex to parse the video ID
var regId = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
var match = src.match(regId);
if (match && match[2].length == 11){
this.videoId = match[2];
} else {
this.videoId = null;
}
// Regex to parse the playlist ID
var regPlaylist = /[?&]list=([^#\&\?]+)/;
match = src.match(regPlaylist);
if (match != null && match.length > 1) {
this.playlistId = match[1];
} else {
// Make sure their is no playlist
if (this.playlistId) {
delete this.playlistId;
}
}
// Parse video quality option
var regVideoQuality = /[?&]vq=([^#\&\?]+)/;
match = src.match(regVideoQuality);
if (match != null && match.length > 1) {
this.userQuality = match[1];
}
}
};
videojs.Youtube.prototype.src = function(src){
if (src) {
this.parseSrc(src);
if (this.videoId == null) {
// Set the black background if the URL isn't valid
this.iframeblocker.style.backgroundColor = 'black';
this.iframeblocker.style.display = 'block';
} else {
this.ytplayer.loadVideoById({
videoId: this.videoId,
suggestedQuality: this.userQuality
});
// Update the poster
this.player_el_.getElementsByClassName('vjs-poster')[0].style.backgroundImage = 'url(https://img.youtube.com/vi/' + this.videoId + '/0.jpg)';
this.iframeblocker.style.backgroundColor = '';
this.iframeblocker.style.display = '';
this.player_.poster('https://img.youtube.com/vi/' + this.videoId + '/0.jpg');
}
}
return this.srcVal;
};
videojs.Youtube.prototype.load = function(){};
videojs.Youtube.prototype.play = function(){
if (this.videoId != null) {
// Make sure to not display the spinner for mobile
if (!this.player_.options()['ytcontrols']) {
// Display the spinner until the video is playing by YouTube
this.player_.trigger('waiting');
}
if (this.isReady_){
this.ytplayer.playVideo();
} else {
this.playOnReady = true;
}
}
};
videojs.Youtube.prototype.pause = function(){ this.ytplayer.pauseVideo(); };
videojs.Youtube.prototype.paused = function(){ return (this.ytplayer)?(this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING):true; };
videojs.Youtube.prototype.currentTime = function(){ return (this.ytplayer && this.ytplayer.getCurrentTime)?this.ytplayer.getCurrentTime():0; };
videojs.Youtube.prototype.setCurrentTime = function(seconds){ this.ytplayer.seekTo(seconds, true); this.player_.trigger('timeupdate'); };
videojs.Youtube.prototype.duration = function(){ return (this.ytplayer && this.ytplayer.getDuration)?this.ytplayer.getDuration():0; };
videojs.Youtube.prototype.volume = function() {
if (this.ytplayer && isNaN(this.volumeVal)) {
this.volumeVal = this.ytplayer.getVolume() / 100.0;
}
return this.volumeVal;
};
videojs.Youtube.prototype.setVolume = function(percentAsDecimal){
if (percentAsDecimal && percentAsDecimal != this.volumeVal) {
this.ytplayer.setVolume(percentAsDecimal * 100.0);
this.volumeVal = percentAsDecimal;
this.player_.trigger('volumechange');
}
};
videojs.Youtube.prototype.muted = function() { return this.mutedVal; };
videojs.Youtube.prototype.setMuted = function(muted) {
if (muted) {
this.ytplayer.mute();
} else {
this.ytplayer.unMute();
}
this.mutedVal = muted;
this.player_.trigger('volumechange');
};
videojs.Youtube.prototype.buffered = function(){
if (this.ytplayer && this.ytplayer.getVideoBytesLoaded) {
var loadedBytes = this.ytplayer.getVideoBytesLoaded();
var totalBytes = this.ytplayer.getVideoBytesTotal();
if (!loadedBytes || !totalBytes) return 0;
var duration = this.ytplayer.getDuration();
var secondsBuffered = (loadedBytes / totalBytes) * duration;
var secondsOffset = (this.ytplayer.getVideoStartBytes() / totalBytes) * duration;
return videojs.createTimeRange(secondsOffset, secondsOffset + secondsBuffered);
} else {
return videojs.createTimeRange(0, 0);
}
};
videojs.Youtube.prototype.supportsFullScreen = function(){ return true; };
// YouTube is supported on all platforms
videojs.Youtube.isSupported = function(){ return true; };
// You can use video/youtube as a media in your HTML5 video to specify the source
videojs.Youtube.canPlaySource = function(srcObj){
return (srcObj.type == 'video/youtube');
};
// Always can control the volume
videojs.Youtube.canControlVolume = function(){ return true; };
////////////////////////////// YouTube specific functions //////////////////////////////
// All videos created before YouTube API is loaded
videojs.Youtube.loadingQueue = [];
// Create the YouTube player
videojs.Youtube.prototype.loadYoutube = function(){
this.ytplayer = new YT.Player(this.id_, {
events: {
onReady: function(e) { e.target.vjsTech.onReady(); },
onStateChange: function(e) { e.target.vjsTech.onStateChange(e.data); },
onPlaybackQualityChange: function(e){ e.target.vjsTech.onPlaybackQualityChange(e.data); },
onError: function(e){ e.target.vjsTech.onError(e.data); }
}
});
this.ytplayer.vjsTech = this;
};
// Transform a JavaScript object into URL params
videojs.Youtube.makeQueryString = function(args){
var array = [];
for (var key in args){
if (args.hasOwnProperty(key)){
array.push(encodeURIComponent(key) + '=' + encodeURIComponent(args[key]));
}
}
return array.join('&');
};
// Called when YouTube API is ready to be used
window.onYouTubeIframeAPIReady = function(){
var yt;
while ((yt = videojs.Youtube.loadingQueue.shift())){
yt.loadYoutube();
}
videojs.Youtube.loadingQueue = [];
videojs.Youtube.apiReady = true;
};
videojs.Youtube.prototype.onReady = function(){
this.isReady_ = true;
this.triggerReady();
// Let the player take care of itself as soon as the YouTube is ready
// The loading spinner while waiting for the tech would be impossible otherwise
this.iframeblocker.style.display = '';
this.player_.loadingSpinner.hide();
if (this.player_.options()['muted']) {
this.setMuted(true);
}
// Play ASAP if they clicked play before it's ready
if (this.playOnReady) {
this.playOnReady = false;
this.play();
}
};
videojs.Youtube.prototype.updateQualities = function(){
var qualities = this.ytplayer.getAvailableQualityLevels();
if (qualities.length == 0) {
this.qualityButton.style.display = 'none';
} else {
this.qualityButton.style.display = '';
while (this.qualityMenuContent.hasChildNodes()) {
this.qualityMenuContent.removeChild(this.qualityMenuContent.lastChild);
}
for (var i = 0; i < qualities.length; ++i) {
var el = document.createElement('li');
el.setAttribute('class', 'vjs-menu-item');
setInnerText(el, videojs.Youtube.parseQualityName(qualities[i]));
el.setAttribute('data-val', qualities[i]);
if (qualities[i] == this.quality) el.classList.add('vjs-selected');
var self = this;
el.addEventListener('click', function() {
var quality = this.getAttribute('data-val');
self.ytplayer.setPlaybackQuality(quality);
setInnerText(self.qualityTitle, videojs.Youtube.parseQualityName(quality));
var selected = self.qualityMenuContent.querySelector('.vjs-selected');
if (selected) selected.classList.remove('vjs-selected');
this.classList.add('vjs-selected');
});
this.qualityMenuContent.appendChild(el);
}
}
};
videojs.Youtube.prototype.onStateChange = function(state){
if (state != this.lastState){
switch(state){
case -1:
this.player_.trigger('durationchange');
break;
case YT.PlayerState.ENDED:
// Replace YouTube play button by our own
if (!this.player_.options()['ytcontrols']) {
this.player_el_.getElementsByClassName('vjs-poster')[0].style.display = 'block';
this.player_.bigPlayButton.show();
}
this.player_.trigger('ended');
break;
case YT.PlayerState.PLAYING:
// Make sure the big play is not there
this.player_.bigPlayButton.hide();
this.updateQualities();
this.player_.trigger('timeupdate');
this.player_.trigger('durationchange');
this.player_.trigger('playing');
this.player_.trigger('play');
break;
case YT.PlayerState.PAUSED:
this.player_.trigger('pause');
break;
case YT.PlayerState.BUFFERING:
this.player_.trigger('timeupdate');
// Make sure to not display the spinner for mobile
if (!this.player_.options()['ytcontrols']) {
this.player_.trigger('waiting');
}
break;
case YT.PlayerState.CUED:
break;
}
this.lastState = state;
}
};
videojs.Youtube.convertQualityName = function(name) {
switch (name) {
case '144p':
return 'tiny';
case '240p':
return 'small';
case '360p':
return 'medium';
case '480p':
return 'large';
case '720p':
return 'hd720';
case '1080p':
return 'hd1080';
}
return name;
};
videojs.Youtube.parseQualityName = function(name) {
switch (name) {
case 'tiny':
return '144p';
case 'small':
return '240p';
case 'medium':
return '360p';
case 'large':
return '480p';
case 'hd720':
return '720p';
case 'hd1080':
return '1080p';
}
return name;
};
videojs.Youtube.prototype.onPlaybackQualityChange = function(quality){
this.quality = quality;
setInnerText(this.qualityTitle, videojs.Youtube.parseQualityName(quality));
switch(quality){
case 'medium':
this.player_.videoWidth = 480;
this.player_.videoHeight = 360;
break;
case 'large':
this.player_.videoWidth = 640;
this.player_.videoHeight = 480;
break;
case 'hd720':
this.player_.videoWidth = 960;
this.player_.videoHeight = 720;
break;
case 'hd1080':
this.player_.videoWidth = 1440;
this.player_.videoHeight = 1080;
break;
case 'highres':
this.player_.videoWidth = 1920;
this.player_.videoHeight = 1080;
break;
case 'small':
this.player_.videoWidth = 320;
this.player_.videoHeight = 240;
break;
case 'tiny':
this.player_.videoWidth = 144;
this.player_.videoHeight = 108;
break;
default:
this.player_.videoWidth = 0;
this.player_.videoHeight = 0;
break;
}
this.player_.trigger('ratechange');
};
videojs.Youtube.prototype.onError = function(error){
this.player_.error = error;
this.player_.trigger('error');
};
//Cross browser solution to add text content to an element
function setInnerText(element, text) {
var textProperty = ('innerText' in element)? 'innerText' : 'textContent';
element[textProperty] = text;
}
// Stretch the YouTube poster
// Keep the iframeblocker in front of the player when the user is inactive
// (ONLY way because the iframe is so selfish with events)
(function() {
var style = document.createElement("style");
style.type = 'text/css';
var css = " .vjs-youtube .vjs-poster { background-size: cover; }.iframeblocker { display:none;position:absolute;top:0;left:0;width:100%;height:100%;cursor:pointer;z-index:2; }.vjs-youtube.vjs-user-inactive .iframeblocker { display:block; } .vjs-quality-button > div:first-child > span:first-child { position:relative;top:7px }";
setInnerText(style, css);
document.getElementsByTagName("head")[0].appendChild(style);
})();