├── .gitignore ├── requirements.txt ├── digitalpaper ├── __init__.py ├── templatetags │ ├── __init__.py │ └── reader_tags.py ├── templates │ └── digitalpaper │ │ ├── reader.html │ │ ├── _jquery.html │ │ └── base.html ├── static │ └── digitalpaper │ │ ├── img │ │ ├── zoom.cur │ │ ├── loading.gif │ │ ├── warning.png │ │ ├── button-first.png │ │ ├── button-last.png │ │ ├── button-next.png │ │ ├── button-prev.png │ │ ├── button-zoom.png │ │ ├── left-corner.png │ │ ├── right-corner.png │ │ ├── jdpicker │ │ │ ├── bg_hover.png │ │ │ ├── bg_selected.png │ │ │ └── bg_selectable.png │ │ ├── page-only-subscribers.png │ │ ├── page-under-construction.png │ │ └── page-under-construction_smallest.png │ │ ├── js │ │ ├── init.js │ │ ├── jquery.mousewheel.js │ │ ├── slideshow.js │ │ ├── config.js │ │ ├── page.js │ │ ├── jquery.DOMWindow.js │ │ ├── jquery.jdpicker.js │ │ └── reader.js │ │ └── css │ │ ├── jdpicker.css │ │ └── reader.css ├── urls.py ├── constants.py ├── views.py ├── models.py └── utils.py ├── example_project ├── digitalpaper ├── example_digitalpaper_project │ ├── __init__.py │ ├── example_app │ │ ├── __init__.py │ │ ├── templates │ │ │ └── registration │ │ │ │ └── login.html │ │ ├── admin.py │ │ ├── urls.py │ │ ├── tests.py │ │ ├── models.py │ │ └── views.py │ ├── urls.py │ ├── wsgi.py │ └── settings.py ├── example_digitalpaper_project.db ├── paper_page │ └── 2012 │ │ └── 07 │ │ └── 03 │ │ ├── digitalpaper1.pdf │ │ ├── digitalpaper2.pdf │ │ ├── digitalpaper3.pdf │ │ └── digitalpaper4.pdf ├── cache │ └── paper_page │ │ └── 2012 │ │ └── 07 │ │ └── 03 │ │ ├── digitalpaper1_2000.png │ │ ├── digitalpaper1_x500.jpg │ │ ├── digitalpaper2_2000.png │ │ ├── digitalpaper2_x500.jpg │ │ ├── digitalpaper3_2000.png │ │ ├── digitalpaper3_x500.jpg │ │ ├── digitalpaper4_2000.png │ │ └── digitalpaper4_x500.jpg └── manage.py ├── MANIFEST.in ├── LICENSE ├── setup.py └── README /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyPdf -------------------------------------------------------------------------------- /digitalpaper/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /digitalpaper/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example_project/digitalpaper: -------------------------------------------------------------------------------- 1 | ../digitalpaper -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/example_app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /digitalpaper/templates/digitalpaper/reader.html: -------------------------------------------------------------------------------- 1 | {% extends "digitalpaper/base.html" %} 2 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/zoom.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/zoom.cur -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/loading.gif -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/warning.png -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/example_digitalpaper_project.db -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/button-first.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/button-first.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/button-last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/button-last.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/button-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/button-next.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/button-prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/button-prev.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/button-zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/button-zoom.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/left-corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/left-corner.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/right-corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/right-corner.png -------------------------------------------------------------------------------- /example_project/paper_page/2012/07/03/digitalpaper1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/paper_page/2012/07/03/digitalpaper1.pdf -------------------------------------------------------------------------------- /example_project/paper_page/2012/07/03/digitalpaper2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/paper_page/2012/07/03/digitalpaper2.pdf -------------------------------------------------------------------------------- /example_project/paper_page/2012/07/03/digitalpaper3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/paper_page/2012/07/03/digitalpaper3.pdf -------------------------------------------------------------------------------- /example_project/paper_page/2012/07/03/digitalpaper4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/paper_page/2012/07/03/digitalpaper4.pdf -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/jdpicker/bg_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/jdpicker/bg_hover.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/jdpicker/bg_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/jdpicker/bg_selected.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/jdpicker/bg_selectable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/jdpicker/bg_selectable.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/page-only-subscribers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/page-only-subscribers.png -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/page-under-construction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/page-under-construction.png -------------------------------------------------------------------------------- /example_project/cache/paper_page/2012/07/03/digitalpaper1_2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/cache/paper_page/2012/07/03/digitalpaper1_2000.png -------------------------------------------------------------------------------- /example_project/cache/paper_page/2012/07/03/digitalpaper1_x500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/cache/paper_page/2012/07/03/digitalpaper1_x500.jpg -------------------------------------------------------------------------------- /example_project/cache/paper_page/2012/07/03/digitalpaper2_2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/cache/paper_page/2012/07/03/digitalpaper2_2000.png -------------------------------------------------------------------------------- /example_project/cache/paper_page/2012/07/03/digitalpaper2_x500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/cache/paper_page/2012/07/03/digitalpaper2_x500.jpg -------------------------------------------------------------------------------- /example_project/cache/paper_page/2012/07/03/digitalpaper3_2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/cache/paper_page/2012/07/03/digitalpaper3_2000.png -------------------------------------------------------------------------------- /example_project/cache/paper_page/2012/07/03/digitalpaper3_x500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/cache/paper_page/2012/07/03/digitalpaper3_x500.jpg -------------------------------------------------------------------------------- /example_project/cache/paper_page/2012/07/03/digitalpaper4_2000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/cache/paper_page/2012/07/03/digitalpaper4_2000.png -------------------------------------------------------------------------------- /example_project/cache/paper_page/2012/07/03/digitalpaper4_x500.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/example_project/cache/paper_page/2012/07/03/digitalpaper4_x500.jpg -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/img/page-under-construction_smallest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/django-digitalpaper/master/digitalpaper/static/digitalpaper/img/page-under-construction_smallest.png -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/example_app/templates/registration/login.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | {{ form.as_table }} 3 |

4 |
5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README 2 | include LICENSE 3 | recursive-include docs * 4 | recursive-include digitalpaper/fixtures *.json 5 | recursive-include digitalpaper/locale * 6 | recursive-include digitalpaper/static * 7 | recursive-include digitalpaper/templates *.txt *.html *.xml *.js 8 | prune docs/build 9 | -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/example_app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from example_digitalpaper_project.example_app import models 4 | 5 | admin.site.register(models.Publication) 6 | admin.site.register(models.Book) 7 | admin.site.register(models.PaperPage) 8 | admin.site.register(models.Article) -------------------------------------------------------------------------------- /example_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_digitalpaper_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /digitalpaper/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls.defaults import * 3 | 4 | urlpatterns = patterns('digitalpaper.views', 5 | url(r'^reader/(?P[\d]+)/$', 'reader', name='digitalpaper_reader'), 6 | url(r'^reader/date/(?P\d{4}-\d{2}-\d{2})/$', 'reader_date', name='digitalpaper_reader_date'), 7 | url(r'^reader/$', 'reader_latest', name='digitalpaper_reader_latest'), 8 | ) 9 | -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/example_app/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('example_digitalpaper_project.example_app.views', 4 | url(r'^settings/', 'reader_settings', name='reader_settings'), 5 | url(r'^token/', 'reader_token', name='reader_token'), 6 | url(r'^publication/(?P\d+)', 'reader_publication', name='reader_publication'), 7 | url(r'^reader_page_resized/(?P\d+)', 'reader_page_resized', name='reader_page_resized'), 8 | ) -------------------------------------------------------------------------------- /digitalpaper/templates/digitalpaper/_jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/example_app/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | from django.contrib import admin 3 | from django.conf import settings 4 | 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | url(r'^digitalpaper/', include('digitalpaper.urls')), 9 | url(r'^app/', include('example_digitalpaper_project.example_app.urls')), 10 | 11 | # auth 12 | url(r'^auth/', include('django.contrib.auth.urls')), 13 | 14 | # admin stuff 15 | url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 16 | url(r'^admin/', include(admin.site.urls)), 17 | ) 18 | 19 | if settings.DEBUG: 20 | urlpatterns += patterns( 21 | 'django.views.static', 22 | url(r'^media/(?P.*)$', 'serve', 23 | {'document_root': settings.MEDIA_ROOT}), 24 | url(r'^static/(?P.*)$', 'serve', 25 | {'document_root': settings.STATIC_ROOT}), 26 | ) -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/example_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from digitalpaper.models import AbstractDigitalPaperPage, AbstractDigitalPublication 4 | 5 | # Create your models here. 6 | 7 | class Publication(AbstractDigitalPublication): 8 | original_file = models.FileField(upload_to="publication/%Y/%m/%d", max_length=255, verbose_name=u"PDF File") 9 | publication_date = models.DateField(db_index=True, auto_now_add=True) 10 | 11 | class Book(models.Model): 12 | publication = models.ForeignKey(Publication) 13 | pagination = models.PositiveSmallIntegerField(verbose_name="Number of pages in the book") 14 | 15 | class PaperPage(AbstractDigitalPaperPage): 16 | original_file = models.FileField(upload_to="paper_page/%Y/%m/%d", max_length=255, verbose_name=u"PDF File") 17 | paper_channel = models.CharField(blank=True, max_length=255) 18 | page_number = models.PositiveSmallIntegerField() 19 | book = models.ForeignKey(Book) 20 | 21 | class Article(models.Model): 22 | pass 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Setup script for django-digitalpaper""" 2 | from setuptools import setup 3 | from setuptools import find_packages 4 | 5 | 6 | setup( 7 | name='django-digitalpaper', 8 | version='0.1', 9 | packages=find_packages(exclude=['test', 'tests', 10 | 'example_project']), 11 | include_package_data=True, 12 | license='BSD License', 13 | description='A complete Newspaper viewer in Javascript, canvas and CSS', 14 | long_description=open('README').read(), 15 | author='Djaz Team', 16 | author_email='devweb@liberation.fr', 17 | url='http://www.liberation.fr/', 18 | classifiers=[ 19 | 'Environment :: Web Environment', 20 | 'Framework :: Django', 21 | 'Intended Audience :: Developers', 22 | 'License :: OSI Approved :: BSD License', 23 | 'Operating System :: OS Independent', 24 | 'Programming Language :: Python', 25 | 'Topic :: Internet :: WWW/HTTP', 26 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 27 | ]) 28 | -------------------------------------------------------------------------------- /digitalpaper/constants.py: -------------------------------------------------------------------------------- 1 | PAPERPAGE_IMAGE_HEIGHT = 2000 # Height used for 'big' paperpage preview (zoomed) 2 | PAPERPAGE_IMAGE_PREVIEW_HEIGHT = 500 # Height used for 'normal' paperpage preview 3 | PAPERPAGE_IMAGE_MEDIUM_HEIGHT = 250 # Height used for 'medium' paperpage preview (miniatures of books) 4 | PAPERPAGE_IMAGE_SMALL_HEIGHT = 148 # Height used for 'small' paperpage preview (miniatures) 5 | PAPERPAGE_IMAGE_EXTRASMALL_HEIGHT = 50 # Height used for 'extra small' paperpage preview (small miniatures) 6 | ACCEPTED_THUMB_SIZE = ('x%s' % PAPERPAGE_IMAGE_EXTRASMALL_HEIGHT, 7 | 'x%s' % PAPERPAGE_IMAGE_SMALL_HEIGHT, 8 | 'x%s' % PAPERPAGE_IMAGE_MEDIUM_HEIGHT, 9 | 'x%s' % PAPERPAGE_IMAGE_PREVIEW_HEIGHT) # Accepted thumbnails size (imagemagick format, we specify the height, so there is an "x" prefix) 10 | PAPERPAGE_CROP_IMAGES_PER_COLUMN = 4 # Number of columns when generating multiple images for zoomed paperpages 11 | PAPERPAGE_CROP_IMAGES_PER_ROW = 4 # Number of rows when generating multiple images for zoomed paperpages 12 | PAPERPAGE_CROPPED_ZOOM_FACTOR = PAPERPAGE_IMAGE_HEIGHT / 500 # Zoom factor 13 | -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example_digitalpaper_project project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_digitalpaper_project.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Django Digitalpaper is a newspaper viewer in Django, JavaScript, CSS and HTML5. 2 | 3 | It needs better documentation to explain how it works, what you need to get it 4 | to run, etc. The few things you should know: 5 | 6 | - Its goal is to emulate the look & feel of a newspaper on the web. 7 | - Although it's not meant to, it can easily run without django. Simply 8 | replace the views and template by your own. What's really important is giving 9 | it access to an API that follows the same principles the app is expecting. 10 | - It should be compatible with all modern browsers, and some older ones (IE7). 11 | - It was initially developped by and for Liberation.fr , and you can find it in 12 | production at http://journal.liberation.fr/publication/liberation/ 13 | 14 | The toplevel directory of the repository contains an example_project which 15 | implements the basic views you need to use the app. There is a preloaded 16 | sqlite db (*) in it containing basic data, as well as some medias, but you 17 | can also use your own if you want. 18 | 19 | (*) Should you want to log in, use demo/demo or change that user using a shell. 20 | 21 | If you want to use the python code in utils.py (or the example_project, which 22 | depends on it), you'll need to install the python requirements by doing 23 | pip install -r requirements.txt and have the following binaries in your PATH: 24 | - convert (from imagemagick) 25 | - gs (from ghostscript) -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/js/init.js: -------------------------------------------------------------------------------- 1 | var accessLevels = { 2 | 'BAS' : 0, 3 | 'ESS' : 10, 4 | 'PRE' : 20 5 | }; // FIXME: those level names shouldn't exist, shouldn't be hardcoded in the app 6 | 7 | var contentmapping = { 8 | 'default' : 'iframe', 9 | 'paperad' : 'link' 10 | }; 11 | 12 | jQuery(document).ready(function () { 13 | if (libeConfig.pageHeight) { 14 | jQuery('#bookPages').height(libeConfig.pageHeight); 15 | } 16 | 17 | jQuery('#calendarButton').val(startDate); 18 | jQuery('#calendarButton').bind('change', function(e) { 19 | var val = jQuery(this).val(); 20 | val = val.replace(/\//gi, '-'); 21 | location.href = libeConfig.webservices.reader_by_date.replace('{date}', val); 22 | }); 23 | 24 | jQuery('#calendarButton').jdPicker({ 25 | 'date_max' : lastDate, 26 | 'date_min' : firstDate 27 | }); 28 | 29 | function readerInitCallback(data, textStatus, xhrobject) { 30 | try { 31 | var level = accessLevels[data['access_level']]; 32 | libeConfig.changeAuthStatus(data['authenticated']); 33 | libeConfig.changeAccessLevel(level); 34 | libeReader.init(publicationId); 35 | } catch (e) { 36 | 37 | } 38 | } 39 | 40 | jQuery.ajax({ 41 | 'url': libeConfig.webservices.token.replace('{format}', 'json'), 42 | 'type': 'post', 43 | 'data' : {'use_session' : 1 }, 44 | 'dataType': 'json', 45 | 'success': readerInitCallback, 46 | 'error': readerInitCallback 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /digitalpaper/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.shortcuts import render_to_response, get_object_or_404, redirect 4 | from django.http import Http404 5 | from django.core.urlresolvers import reverse 6 | from django.template import RequestContext 7 | from datetime import datetime 8 | 9 | from digitalpaper.utils import get_model_for_publication, \ 10 | get_manager_for_publication, \ 11 | get_manager_method_for_publication_by_date, \ 12 | get_publication_date_field \ 13 | 14 | def reader_latest(request): 15 | publication_model = get_model_for_publication() 16 | pub = get_manager_for_publication(publication_model).latest() 17 | return redirect('digitalpaper_reader', publication_id=pub.pk) 18 | 19 | def reader_date(request, date): 20 | publication_model = get_model_for_publication() 21 | method = get_manager_method_for_publication_by_date(publication_model) 22 | filters = {} 23 | filters[get_publication_date_field()] = datetime.strptime(date, '%Y-%m-%d') 24 | pub = method(**filters) 25 | if not pub: 26 | raise Http404 27 | 28 | return redirect('digitalpaper_reader', publication_id=pub.pk) 29 | 30 | def reader(request, publication_id): 31 | publication_model = get_model_for_publication() 32 | manager = get_manager_for_publication(publication_model) 33 | pub = get_object_or_404(manager, pk=int(publication_id)) 34 | context = { 35 | 'publication': pub, 36 | 'latest_publication' : get_manager_for_publication(publication_model).latest(), 37 | 'first_publication' : get_manager_for_publication(publication_model).reverse().latest() 38 | } 39 | return render_to_response('digitalpaper/reader.html', context, context_instance=RequestContext(request)) 40 | -------------------------------------------------------------------------------- /digitalpaper/templatetags/reader_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django import template 4 | from django.utils.html import conditional_escape 5 | 6 | from digitalpaper.utils import get_publication_date_field, get_model_for_paperpage 7 | 8 | 9 | register = template.Library() 10 | 11 | 12 | @register.filter 13 | def get_publication_date(o): 14 | return getattr(o, get_publication_date_field()) 15 | 16 | 17 | @register.simple_tag 18 | def paperpage_preview_absolute_url(paperpage, size='x148'): 19 | """ 20 | Returns a correctly formatted absolute_url in order to print a paperpage preview. 21 | """ 22 | if not paperpage: 23 | url = get_model_for_paperpage().get_page_under_construction_preview_url(size=size) 24 | else: 25 | url = paperpage.get_preview_absolute_url(size=size) 26 | return conditional_escape(url) 27 | 28 | 29 | @register.simple_tag 30 | def book_preview_absolute_url(book, size='x148'): 31 | """ 32 | Returns a correctly formatted absolute_url in order to print a book preview. 33 | """ 34 | if not book: 35 | url = get_model_for_paperpage().get_page_under_construction_preview_url(size=size) 36 | else: 37 | url = book.get_preview_absolute_url(size=size) 38 | return conditional_escape(url) 39 | 40 | 41 | @register.simple_tag 42 | def publication_preview_absolute_url(publication, size='x148'): 43 | """ 44 | Returns a correctly formatted absolute_url in order to print a publication preview. 45 | """ 46 | if not publication: 47 | # publication.get_preview_absolute_url knows how to handle a publication 48 | # with no PaperPage attached, but here we don't even have a Publication, 49 | # so take a shortcut and use PaperPage.get_page_under_construction_preview_url 50 | url = get_model_for_paperpage().get_page_under_construction_preview_url(size=size) 51 | else: 52 | url = publication.get_preview_absolute_url(size=size) 53 | return conditional_escape(url) 54 | -------------------------------------------------------------------------------- /digitalpaper/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf import settings 3 | from django.db import models 4 | 5 | 6 | class AbstractDigitalPaperPage(models.Model): 7 | class Meta: 8 | abstract = True 9 | get_latest_by = ('id') 10 | 11 | def get_preview_absolute_url(self, size='x148'): 12 | """ 13 | Return the url corresponding to the preview image for the PaperPage 14 | at the given size. 15 | """ 16 | pass 17 | 18 | @classmethod 19 | def get_page_under_construction_preview_url(cls, size="x148"): 20 | """ 21 | Return the image corresponding to a page under construction 22 | """ 23 | if size == 'x50': 24 | img = 'digitalpaper/img/page_under_construction_smallest.png' 25 | else: 26 | img = 'digitalpaper/img/page_under_construction.png' 27 | return '%s%s' % (settings.STATIC_URL, img) 28 | 29 | @classmethod 30 | def get_page_restricted_to_subscribers_preview_url(cls, size="x148"): 31 | """ 32 | Return the image corresponding to a page restricted for subscribers 33 | """ 34 | img = 'digitalpaper/img/page-only-subscribers.png' 35 | return '%s%s' % (settings.STATIC_URL, img) 36 | 37 | 38 | class AbstractDigitalBook(models.Model): 39 | class Meta: 40 | abstract = True 41 | get_latest_by = ('id') 42 | 43 | def get_preview_absolute_url(self, size='x148'): 44 | """ 45 | Return the url corresponding to the preview image for the Book 46 | at the given size. 47 | """ 48 | pass 49 | 50 | 51 | class AbstractDigitalPublication(models.Model): 52 | class Meta: 53 | abstract = True 54 | get_latest_by = ('id') 55 | 56 | def get_preview_absolute_url(self, size='x250'): 57 | """ 58 | Return the url corresponding to the preview image for the Publication 59 | at the given size. Usually, it will be the preview for the first PaperPage 60 | attached to that publication. 61 | """ 62 | pass 63 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/js/jquery.mousewheel.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net) 2 | * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 3 | * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. 4 | * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. 5 | * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. 6 | * 7 | * Patched by Mathieu Pillard to add horizontal scrolling support 8 | * 9 | * Version: 3.0.2patched 10 | * 11 | * Requires: 1.2.2+ 12 | */ 13 | 14 | (function($) { 15 | 16 | var types = ['DOMMouseScroll', 'mousewheel']; 17 | 18 | $.event.special.mousewheel = { 19 | setup: function() { 20 | if ( this.addEventListener ) 21 | for ( var i=types.length; i; ) 22 | this.addEventListener( types[--i], handler, false ); 23 | else 24 | this.onmousewheel = handler; 25 | }, 26 | 27 | teardown: function() { 28 | if ( this.removeEventListener ) 29 | for ( var i=types.length; i; ) 30 | this.removeEventListener( types[--i], handler, false ); 31 | else 32 | this.onmousewheel = null; 33 | } 34 | }; 35 | 36 | $.fn.extend({ 37 | mousewheel: function(fn) { 38 | return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); 39 | }, 40 | 41 | unmousewheel: function(fn) { 42 | return this.unbind("mousewheel", fn); 43 | } 44 | }); 45 | 46 | 47 | function handler(event) { 48 | var args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true; 49 | var deltaX = 0; 50 | var deltaY = 0; 51 | 52 | event = $.event.fix(event || window.event); 53 | event.type = "mousewheel"; 54 | 55 | if ( event.wheelDelta ) deltaY = event.wheelDelta / 120; 56 | if ( event.detail ) deltaY = -event.detail / 3; 57 | 58 | // Horizontal scrolling support in Webkit browsers. Combination 59 | // of horizontal and vertical is possible, set both 60 | if (typeof event.originalEvent['wheelDeltaX'] !== 'undefined' 61 | && typeof event.originalEvent['wheelDeltaY'] !== 'undefined') { 62 | deltaY = event.originalEvent['wheelDeltaY'] / 120; 63 | deltaX = event.originalEvent['wheelDeltaX'] / 120; 64 | } 65 | // Horizontal scrolling support in Gecko browsers. Combinaison isn't 66 | // possible, only set variables if an horizontal scroll is detected 67 | else if (typeof event.originalEvent['axis'] !== 'undefined' 68 | && typeof event.originalEvent['HORIZONTAL_AXIS'] !== 'undefined' 69 | && event.originalEvent['axis'] === event.originalEvent['HORIZONTAL_AXIS']) 70 | { 71 | deltaY = 0; 72 | deltaX = -event.detail / 3; 73 | } 74 | 75 | // Add events and delta to the front of the arguments 76 | args.unshift(event, deltaX, deltaY); 77 | 78 | return $.event.handle.apply(this, args); 79 | } 80 | 81 | })(jQuery); 82 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/js/slideshow.js: -------------------------------------------------------------------------------- 1 | var readerSlider = function() { 2 | var iSlideSize = 150; 3 | var sSlideSpeed = 'slow'; 4 | var continueMoving = false; 5 | 6 | function prev(e) { 7 | continueMoving = true; 8 | moveBy(iSlideSize); 9 | e.preventDefault(); 10 | } 11 | 12 | function next(e) { 13 | continueMoving = true; 14 | moveBy(-iSlideSize); 15 | e.preventDefault(); 16 | } 17 | 18 | function cancel() { 19 | continueMoving = false; 20 | } 21 | 22 | function moveBy(value) { 23 | if (continueMoving) { 24 | var currentPosition = parseInt(jQuery('#pagesList').css('left'), 10); 25 | moveTo(currentPosition + value, function() { moveBy(value); }); 26 | } else { 27 | jQuery('#pagesList').clearQueue(); 28 | } 29 | } 30 | 31 | function moveIntoView(itemNumber) { 32 | if (itemNumber < 1) { 33 | // The page "0" doesn't exist, but can be passed as an argument when 34 | // we are viewing the first page 35 | itemNumber = 1; 36 | } 37 | var child = jQuery('#pagesList').children()[itemNumber - 1]; 38 | if (child) { 39 | var childPosition = jQuery(child).position()['left']; 40 | var outerWidth = jQuery('#innerPagesSlider').outerWidth(); 41 | moveTo(-(childPosition - outerWidth + outerWidth / 2 + jQuery(child).outerWidth())); 42 | } 43 | } 44 | 45 | function restrictPositionValue(value) { 46 | var minPosition = 0; 47 | var maxPosition = jQuery('#pagesList').outerWidth() - jQuery('#innerPagesSlider').outerWidth(); 48 | 49 | if (value < -maxPosition) { 50 | value = -maxPosition; 51 | continueMoving = false; 52 | return value; 53 | } 54 | 55 | if (value > minPosition) { 56 | value = minPosition; 57 | continueMoving = false; 58 | return value; 59 | } 60 | 61 | return value; 62 | } 63 | 64 | function moveTo(value, callback) { 65 | value = restrictPositionValue(value); 66 | jQuery('#pagesList').animate({ left: value }, sSlideSpeed, 'linear', function() { 67 | if (typeof callback != 'undefined') { 68 | callback(); 69 | } 70 | }); 71 | } 72 | 73 | return { 74 | 'prev' : prev, 75 | 'next' : next, 76 | 'cancel' : cancel, 77 | 'moveIntoView' : moveIntoView 78 | }; 79 | }(); 80 | 81 | jQuery(document).ready(function() { 82 | jQuery('#sliderPrev').bind('mousedown', readerSlider.prev); 83 | jQuery('#sliderNext').bind('mousedown', readerSlider.next); 84 | jQuery('#sliderPrev').bind('click', function(e) { e.preventDefault(); }); 85 | jQuery('#sliderNext').bind('click', function(e) { e.preventDefault(); }); 86 | jQuery(document).bind('mouseup', readerSlider.cancel); 87 | }); 88 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/js/config.js: -------------------------------------------------------------------------------- 1 | var libeConfigFunc = function (data) { 2 | 3 | var evenSideElement = document.getElementById('evenSide'); 4 | var oddSideElement = document.getElementById('oddSide'); 5 | var accessLevel = 0; 6 | var accessLevelNeeded = 20; 7 | var authenticated = false; 8 | 9 | var canAccess = function(pageNumber, pageId) { 10 | return settings.accessLevel >= settings.accessLevelNeeded || jQuery.inArray(pageNumber, settings.pagesFree) >= 0; 11 | }; 12 | 13 | var canZoom = function() { 14 | return settings.accessLevel >= settings.accessLevelNeeded && settings.pageWidth && settings.pageHeight; 15 | }; 16 | 17 | var canUseMap = function(pageNumber, pageId) { 18 | return settings.accessLevel >= settings.accessLevelNeeded && settings.pageWidth && settings.pageHeight; 19 | }; 20 | 21 | var changeAccessLevelNeeded = function(newneededlevel) { 22 | settings.accessLevelNeeded = newneededlevel; 23 | }; 24 | 25 | var changeAccessLevel = function(newlevel) { 26 | settings.accessLevel = newlevel; 27 | }; 28 | 29 | var changeAuthStatus = function(newstatus) { 30 | settings.authenticated = newstatus; 31 | }; 32 | 33 | var restrictedAccess = function() { 34 | jQuery(document).trigger('show-restricted-access'); 35 | jQuery.openDOMWindow({ 36 | windowSourceID: '#restrictedAccess', 37 | width: 760, 38 | height: 480, 39 | windowPadding: 0 40 | }); 41 | return false; 42 | }; 43 | 44 | var setSize = function(w, h) { 45 | // dynamic config should set height 46 | if (w) { 47 | libeConfig.pageWidth = w; 48 | // libeConfig.pageHeight = h 49 | // libeConfig.pageThumbnailHeight = 50 | libeConfig.pageThumbnailWidth = parseInt(w * libeConfig.pageThumbnailHeight / libeConfig.pageHeight, 10); 51 | jQuery(window).trigger('size-known'); 52 | return true; 53 | } 54 | return false; 55 | }; 56 | 57 | var defaultError = function(xhr, status) { 58 | if (jQuery('#errorPopin').length <= 0) { 59 | jQuery('#main').append('
'); 60 | } 61 | jQuery('#errorPopin').text(libeConfig.error_message + ' (Err. ' + xhr.status + ')'); 62 | jQuery('#bookPages, #pagesBefore, #pagesAfter, #pagesSlider').hide(); 63 | jQuery('#errorPopin').show(); 64 | }; 65 | 66 | this.settings = { 67 | 'authenticated' : authenticated, 68 | 'accessLevel' : accessLevel, 69 | 'accessLevelNeeded' : accessLevelNeeded, 70 | 'changeAccessLevelNeeded' : changeAccessLevelNeeded, 71 | 'changeAccessLevel' : changeAccessLevel, 72 | 'changeAuthStatus' : changeAuthStatus, 73 | 'canAccess': canAccess, 74 | 'canZoom': canZoom, 75 | 'canUseMap': canUseMap, 76 | 'restrictedAccess' : restrictedAccess, 77 | 'setSize' : setSize, 78 | 'evenSideElement' : evenSideElement, 79 | 'oddSideElement' : oddSideElement, 80 | 'defaultError' : defaultError, 81 | 'pageWidth': 0, 82 | 'pageHeight': 0, 83 | 'pageThumbnailWidth': 0, 84 | 'pageThumbnailHeight': 0, 85 | 'pagesFree': [], 86 | 'modelmapping': { 87 | 'default': 'iframe' 88 | } 89 | }; 90 | 91 | // The remaining settings need to be set using the arguments (use config.js 92 | // as jsonp) 93 | var d; 94 | for (d in data) { 95 | if (typeof d == 'string') { 96 | this.settings[d] = data[d]; 97 | } 98 | } 99 | return this.settings; 100 | }; 101 | -------------------------------------------------------------------------------- /digitalpaper/templates/digitalpaper/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n reader_tags %} 2 | 3 | 4 | 5 | 6 | {% block title %}Web Reader Demo - {{ publication }}{% endblock %} 7 | {% block readercss %} 8 | 9 | 10 | {% endblock %} 11 | 14 | 20 | {% block extrahead %}{% endblock %} 21 | 22 | 23 | {% block content %} 24 |
25 | 31 |
{% block extra %}{% endblock %}
32 |
33 |
{% spaceless %} 34 |
35 | 36 | 37 | 38 |
    39 |
  • 40 |
  • 41 |
42 |
    43 |
  • 44 |
  • 45 |
46 | 47 | {% endspaceless %}
48 |
49 | {% block slider %} 50 |
51 | 52 |
53 |
54 |
55 | 56 |
57 | {% endblock %} 58 | {% block switcher %} 59 |
60 | {% endblock %} 61 | {% endblock %} 62 | 63 | {% block jqueryjs %} 64 | {% include "digitalpaper/_jquery.html" %} 65 | {% endblock %} 66 | 67 | {% block dynamicjs %} 68 | 74 | {% endblock %} 75 | 76 | {% block readerjsonp %} 77 | 78 | 79 | {% endblock %} 80 | 81 | {% block readerjs %} 82 | 83 | 84 | 85 | 86 | {% endblock %} 87 |
88 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/example_app/views.py: -------------------------------------------------------------------------------- 1 | from django.core.serializers.json import DateTimeAwareJSONEncoder 2 | from django.conf import settings 3 | from django.utils import simplejson as json 4 | from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseServerError 5 | from django.core.urlresolvers import reverse 6 | from django.shortcuts import get_object_or_404 7 | from django.views.decorators.csrf import csrf_exempt 8 | 9 | from digitalpaper import constants 10 | from digitalpaper.utils import (get_model_for_paperpage, 11 | get_model_for_publication, 12 | get_manager_for_publication, 13 | get_uri_template, 14 | PaperPageThumbnail, HttpResponseXFile) 15 | 16 | def reader_settings(request, *args, **kwargs): 17 | data = { 18 | "pageThumbnailHeight": constants.PAPERPAGE_IMAGE_SMALL_HEIGHT, 19 | "pageSmallThumbnailHeight": constants.PAPERPAGE_IMAGE_EXTRASMALL_HEIGHT, 20 | "error_message": "Oops !", 21 | "pageHeight": constants.PAPERPAGE_IMAGE_PREVIEW_HEIGHT, 22 | "imagesPerRow": constants.PAPERPAGE_CROP_IMAGES_PER_ROW, 23 | "imagesPerColumn": constants.PAPERPAGE_CROP_IMAGES_PER_COLUMN, 24 | "zoomFactor": constants.PAPERPAGE_CROPPED_ZOOM_FACTOR, 25 | "pageLimitedAccessImage": "%sdigitalpaper/img/page-only-subscribers.png" % (settings.STATIC_URL), 26 | "pageUnderConstructionImage": "%sdigitalpaper/img/page-under-construction.png" % (settings.STATIC_URL), 27 | "pageUnderConstructionImageSmallest": "%sdigitalpaper/img/page-under-construction_smallest.png" % (settings.STATIC_URL), 28 | "webservices": { 29 | "token": reverse("reader_token"), 30 | "publication": get_uri_template("reader_publication"), 31 | "reader_by_date": get_uri_template("digitalpaper_reader_date"), 32 | "paper_page_resized": get_uri_template("reader_page_resized"), 33 | "paper_page_cropped": "", # FIXME 34 | "contentmodel": "", # FIXME 35 | } 36 | } 37 | result = json.dumps(data, cls=DateTimeAwareJSONEncoder, indent=1) 38 | return HttpResponse("%s(%s)" % (request.GET['jsonp'], result), mimetype='text/javascript') 39 | 40 | def reader_page_resized(request, *args, **kwargs): 41 | # FIXME handle different formats, size, cropping. 42 | paperpage_model = get_model_for_paperpage() 43 | page = get_object_or_404(paperpage_model, pk=int(kwargs['id'])) 44 | rval, detail = PaperPageThumbnail(page).generate_thumbnail(request.GET.get('size', 'x500')) 45 | 46 | if rval == PaperPageThumbnail.P_ERROR_BAD: # Something is wrong with the arguments 47 | return HttpResponseBadRequest(str(detail)) 48 | elif rval == PaperPageThumbnail.P_ERROR_OS: # Something is wrong with the command used to generate the thumbnail. Bad file maybe ? 49 | return HttpResponseServerError(str(detail)) 50 | else: 51 | filename = detail 52 | 53 | # Return the file 54 | return HttpResponseXFile(filename) 55 | 56 | 57 | @csrf_exempt # FIXME ! shouldn't be necessary 58 | def reader_token(request, *args, **kwargs): 59 | level = "PRE" if request.user.is_authenticated() else "BAS" # FIXME: those level names shouldn't exist, shouldn't be hardcoded in the app 60 | data = { 61 | "access_level": level, 62 | } 63 | result = json.dumps(data, cls=DateTimeAwareJSONEncoder, indent=1) 64 | return HttpResponse(result, mimetype='text/javascript') 65 | 66 | def reader_publication(request, *args, **kwargs): 67 | publication_model = get_model_for_publication() 68 | manager = get_manager_for_publication(publication_model) 69 | pub = get_object_or_404(manager, pk=int(kwargs['id'])) 70 | data = { 71 | 'books': [] 72 | } 73 | for book in pub.book_set.all(): 74 | bookdata = { 75 | "pagination": book.pagination, 76 | "pages": [], 77 | } 78 | for page in book.paperpage_set.all(): 79 | pagedata = { 80 | 'maps': {}, # FIXME 81 | 'page_number': page.page_number, 82 | 'id': page.pk, 83 | 'paper_channel': page.paper_channel, 84 | } 85 | bookdata['pages'].append(pagedata) 86 | data['books'].append(bookdata) 87 | result = json.dumps(data, cls=DateTimeAwareJSONEncoder, indent=1) 88 | return HttpResponse(result, mimetype='text/javascript') -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/css/jdpicker.css: -------------------------------------------------------------------------------- 1 | .jdpicker_w .date_selector * { 2 | width: auto; 3 | height: auto; 4 | border: none; 5 | background: none; 6 | margin: 0; 7 | padding: 0; 8 | text-align: left; 9 | text-decoration: none; 10 | font-size:12px; 11 | } 12 | .jdpicker_w .date_selector { 13 | background: #FFF; 14 | border: 1px solid #bbb; 15 | padding: 5px; 16 | margin: -1px 0 0 0; 17 | position: absolute; 18 | z-index: 100000; 19 | display: none; 20 | width:210px 21 | } 22 | 23 | .jdpicker_w .date_selector table{ 24 | width:210px; 25 | margin-left:3px; 26 | } 27 | 28 | .jdpicker_w .date_clearer{ 29 | color: #a00; 30 | padding-left:3px; 31 | cursor:pointer; 32 | font-weight:bold; 33 | font-family:sans-serif 34 | } 35 | 36 | .jdpicker_w .date_selector_ieframe { 37 | position: absolute; 38 | z-index: 99999; 39 | display: none; 40 | } 41 | 42 | .jdpicker_w .error_msg{ 43 | display:none; 44 | text-align:center; 45 | font-size:0.8em; 46 | color:#666 47 | } 48 | 49 | .jdpicker_w .date_selector .nav { 50 | width: 17.5em; /* 7 * 2.5em */ 51 | } 52 | .jdpicker_w .date_selector .month_nav, .jdpicker_w .date_selector .year_nav { 53 | margin: 0 0 3px 0; 54 | padding: 0; 55 | display: block; 56 | position: relative; 57 | text-align: center; 58 | } 59 | .jdpicker_w .date_selector .month_nav { 60 | float: left; 61 | width: 56%; 62 | } 63 | .jdpicker_w .date_selector .year_nav { 64 | float: right; 65 | width: 37%; 66 | position:relative; 67 | } 68 | 69 | .jdpicker_w .date_selector .month_nav select { 70 | width:75px; 71 | margin: 0 auto; 72 | border:1px solid #ccc; 73 | position:relative; 74 | top:1px 75 | } 76 | 77 | .jdpicker_w .date_selector .year_nav .year_input { 78 | text-align:center; 79 | width:36px; 80 | border:1px solid #ccc; 81 | position:relative; 82 | top:2px 83 | } 84 | 85 | .jdpicker_w .date_selector .month_name, .jdpicker_w .date_selector .year_name { 86 | font-weight: bold; 87 | line-height: 20px; 88 | } 89 | .jdpicker_w .date_selector .button { 90 | display: block; 91 | position: absolute; 92 | top: 0; 93 | width: 18px; 94 | height: 18px; 95 | line-height: 17px; 96 | font-weight: bold; 97 | color: #333; 98 | text-align: center; 99 | font-size: 120%; 100 | overflow: hidden; 101 | border: 1px solid #F2F2F2; 102 | cursor:pointer; 103 | } 104 | 105 | .jdpicker_w .date_selector .button:hover, .jdpicker_w .date_selector .button.hover { 106 | background: none; 107 | color: #333; 108 | cursor: pointer; 109 | border-color: #ccc; 110 | } 111 | 112 | .jdpicker_w .date_selector .prev { 113 | left: 0; 114 | } 115 | .jdpicker_w .date_selector .next { 116 | right: 0; 117 | } 118 | 119 | .jdpicker_w .date_selector table { 120 | margin:0 auto; 121 | border-spacing: 1px; 122 | 123 | clear: both; 124 | } 125 | .jdpicker_w .date_selector th{ 126 | padding-top:5px 127 | } 128 | 129 | .jdpicker_w .date_selector th, .jdpicker_w .date_selector td { 130 | text-align: center; 131 | color: black; 132 | } 133 | 134 | .jdpicker_w .date_selector th.week_label{ 135 | font-weight:normal; 136 | font-style:italic; 137 | font-size:80%; 138 | width:25px 139 | } 140 | 141 | .jdpicker_w .date_selector .week_num, .jdpicker_w .date_selector .hover .week_num, .jdpicker_w .date_selector .selected .week_num{ 142 | font-style:italic; 143 | color:#333 !important; 144 | vertical-align:bottom !important; 145 | text-align:right; 146 | border:none !important; 147 | font-size:70%; 148 | background:#FCFCFC !important; 149 | padding-right:4px; 150 | } 151 | 152 | .jdpicker_w .date_selector td { 153 | border: 1px solid #ccc; 154 | line-height: 2em; 155 | text-align: center; 156 | white-space: nowrap; 157 | color: #003C78; 158 | background: white; 159 | } 160 | .jdpicker_w .date_selector td.today { 161 | background: #FFFEB3; 162 | } 163 | .jdpicker_w .date_selector td.unselected_month { 164 | color: #ccc; 165 | } 166 | .jdpicker_w .date_selector td.selectable_day, .jdpicker_w .date_selector tr.selectable_week td { 167 | cursor: pointer; 168 | background:url('images/bg_selectable.png'); 169 | border:1px solid #aaa; 170 | color:#333 171 | } 172 | .jdpicker_w .date_selector td.selected, .jdpicker_w .date_selector tr.selected td{ 173 | background: url('images/bg_selected.png'); 174 | font-weight: bold; 175 | } 176 | .jdpicker_w .date_selector tr.selectable_week.hover td, .jdpicker_w .date_selector td.selectable_day.hover { 177 | background: url('images/bg_hover.png'); 178 | color: white; 179 | } 180 | -------------------------------------------------------------------------------- /example_project/example_digitalpaper_project/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for example_digitalpaper_project project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | # ('Your Name', 'your_email@example.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 15 | 'NAME': 'example_digitalpaper_project.db', # Or path to database file if using sqlite3. 16 | 'USER': '', # Not used with sqlite3. 17 | 'PASSWORD': '', # Not used with sqlite3. 18 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 19 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 20 | } 21 | } 22 | 23 | # Local time zone for this installation. Choices can be found here: 24 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 25 | # although not all choices may be available on all operating systems. 26 | # On Unix systems, a value of None will cause Django to use the same 27 | # timezone as the operating system. 28 | # If running in a Windows environment this must be set to the same as your 29 | # system time zone. 30 | TIME_ZONE = 'America/Chicago' 31 | 32 | # Language code for this installation. All choices can be found here: 33 | # http://www.i18nguy.com/unicode/language-identifiers.html 34 | LANGUAGE_CODE = 'en-us' 35 | 36 | SITE_ID = 1 37 | 38 | # If you set this to False, Django will make some optimizations so as not 39 | # to load the internationalization machinery. 40 | USE_I18N = True 41 | 42 | # If you set this to False, Django will not format dates, numbers and 43 | # calendars according to the current locale. 44 | USE_L10N = True 45 | 46 | # If you set this to False, Django will not use timezone-aware datetimes. 47 | USE_TZ = True 48 | 49 | # Absolute filesystem path to the directory that will hold user-uploaded files. 50 | # Example: "/home/media/media.lawrence.com/media/" 51 | MEDIA_ROOT = '' 52 | 53 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 54 | # trailing slash. 55 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 56 | MEDIA_URL = '' 57 | 58 | # Absolute path to the directory static files should be collected to. 59 | # Don't put anything in this directory yourself; store your static files 60 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 61 | # Example: "/home/media/media.lawrence.com/static/" 62 | STATIC_ROOT = '' 63 | 64 | # URL prefix for static files. 65 | # Example: "http://media.lawrence.com/static/" 66 | STATIC_URL = '/static/' 67 | 68 | # Additional locations of static files 69 | STATICFILES_DIRS = ( 70 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 71 | # Always use forward slashes, even on Windows. 72 | # Don't forget to use absolute paths, not relative paths. 73 | ) 74 | 75 | # List of finder classes that know how to find static files in 76 | # various locations. 77 | STATICFILES_FINDERS = ( 78 | 'django.contrib.staticfiles.finders.FileSystemFinder', 79 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 80 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 81 | ) 82 | 83 | # Make this unique, and don't share it with anybody. 84 | SECRET_KEY = '%**%z&(1uvy8ksc+jkvh00x5kvb^&(w*&*l6fx28$(#k@iof2c' 85 | 86 | # List of callables that know how to import templates from various sources. 87 | TEMPLATE_LOADERS = ( 88 | 'django.template.loaders.filesystem.Loader', 89 | 'django.template.loaders.app_directories.Loader', 90 | # 'django.template.loaders.eggs.Loader', 91 | ) 92 | 93 | MIDDLEWARE_CLASSES = ( 94 | 'django.middleware.common.CommonMiddleware', 95 | 'django.contrib.sessions.middleware.SessionMiddleware', 96 | 'django.middleware.csrf.CsrfViewMiddleware', 97 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 98 | 'django.contrib.messages.middleware.MessageMiddleware', 99 | # Uncomment the next line for simple clickjacking protection: 100 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 101 | ) 102 | 103 | ROOT_URLCONF = 'example_digitalpaper_project.urls' 104 | 105 | # Python dotted path to the WSGI application used by Django's runserver. 106 | WSGI_APPLICATION = 'example_digitalpaper_project.wsgi.application' 107 | 108 | TEMPLATE_DIRS = ( 109 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 110 | # Always use forward slashes, even on Windows. 111 | # Don't forget to use absolute paths, not relative paths. 112 | ) 113 | 114 | INSTALLED_APPS = ( 115 | 'django.contrib.auth', 116 | 'django.contrib.contenttypes', 117 | 'django.contrib.sessions', 118 | 'django.contrib.sites', 119 | 'django.contrib.messages', 120 | 'django.contrib.staticfiles', 121 | 'django.contrib.admin', 122 | 'django.contrib.admindocs', 123 | 124 | 'digitalpaper', 125 | 'example_digitalpaper_project.example_app', 126 | ) 127 | 128 | READER_PUBLICATION_APP_NAME = 'example_app' 129 | READER_PAPERPAGE_APP_NAME = 'example_app' 130 | READER_PUBLICATION_MODEL_NAME = 'publication' 131 | READER_PAPERPAGE_MODEL_NAME = 'paperpage' 132 | READER_PUBLICATION_DATE_FIELD = 'publication_date' 133 | READER_PUBLICATION_MANAGER_METHOD_BYDATE_NAME = 'implement_me' # FIXME 134 | 135 | # A sample logging configuration. The only tangible logging 136 | # performed by this configuration is to send an email to 137 | # the site admins on every HTTP 500 error when DEBUG=False. 138 | # See http://docs.djangoproject.com/en/dev/topics/logging for 139 | # more details on how to customize your logging configuration. 140 | LOGGING = { 141 | 'version': 1, 142 | 'disable_existing_loggers': False, 143 | 'filters': { 144 | }, 145 | 'handlers': { 146 | 'mail_admins': { 147 | 'level': 'ERROR', 148 | 'class': 'django.utils.log.AdminEmailHandler' 149 | } 150 | }, 151 | 'loggers': { 152 | 'django.request': { 153 | 'handlers': ['mail_admins'], 154 | 'level': 'ERROR', 155 | 'propagate': True, 156 | }, 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/css/reader.css: -------------------------------------------------------------------------------- 1 | body, html, .book *, p { 2 | padding: 0; 3 | margin: 0; 4 | border: 0; 5 | } 6 | 7 | body { 8 | position: relative; 9 | font-family: sans-serif; 10 | } 11 | 12 | img { 13 | border: 0; 14 | } 15 | 16 | html, body { 17 | background: white; 18 | } 19 | 20 | #main { 21 | width: 900px; 22 | background: white; 23 | margin-right: auto; 24 | margin-left: auto; 25 | } 26 | 27 | .book { 28 | position: relative; 29 | display: block; 30 | margin-right: auto; 31 | margin-left: auto; 32 | text-align: center; 33 | } 34 | #pagesAfter, #pagesBefore { 35 | display: block; 36 | position: absolute; 37 | right: -48px; 38 | top: 175px; /* (500-150)/2 */ 39 | width: 48px; 40 | outline: 1px solid #CCC; 41 | height: 150px; 42 | } 43 | 44 | #pagesBefore { 45 | left: -48px; 46 | } 47 | 48 | #pagesAfter li, #pagesBefore li { 49 | display: block; 50 | } 51 | 52 | #pagesAfter a, #pagesBefore a { 53 | background-repeat: no-repeat; 54 | position: absolute; 55 | display: block; 56 | } 57 | 58 | #firstButton, #lastButton { 59 | width: 48px; 60 | height: 48px; 61 | top: 5px; 62 | right: 7px; 63 | } 64 | 65 | #nextButton, #previousButton { 66 | width: 48px; 67 | height: 48px; 68 | top: 48px; 69 | right: 7px; 70 | } 71 | 72 | #nextButton, #lastButton { 73 | right: auto; 74 | left: 7px; 75 | } 76 | 77 | #firstButton { 78 | background-image: url(../img/button-first.png); 79 | } 80 | 81 | #lastButton { 82 | background-image: url(../img/button-last.png); 83 | } 84 | 85 | #nextButton, #sliderNext { 86 | background-image: url(../img/button-next.png); 87 | } 88 | 89 | #previousButton, #sliderPrev { 90 | background-image: url(../img/button-prev.png); 91 | } 92 | 93 | #bookPages { 94 | display: inline-block; 95 | margin-right: auto; 96 | margin-left: auto; 97 | position: relative; 98 | margin-top: 50px; 99 | } 100 | #evenSide, #oddSide { 101 | position: relative; 102 | display: inline-block; 103 | height: 100%; 104 | outline: 1px solid #CCC; 105 | cursor: -moz-zoom-in; 106 | cursor: -webkit-zoom-in; 107 | } 108 | .pageInfo { 109 | position: absolute; 110 | bottom: -20px; 111 | left: 0; 112 | z-index: 50; 113 | } 114 | #oddSide .pageInfo { 115 | left: auto; 116 | right: 0; 117 | text-align: right; 118 | } 119 | .page { 120 | position: relative; 121 | display: none; 122 | overflow: hidden; 123 | } 124 | .page .area { 125 | position: absolute; 126 | display: block; 127 | background-color: black; 128 | opacity: 0; 129 | filter: alpha(opacity=0); 130 | } 131 | 132 | .page img { 133 | display: block; 134 | } 135 | 136 | .leftPage, .rightPage { 137 | background-color: white; 138 | width: 50%; 139 | height: 100%; 140 | display: inline-block; 141 | } 142 | .leftPage { 143 | background-position: left top; 144 | } 145 | .rightPage { 146 | background-position: right top; 147 | } 148 | .transitionPage { 149 | position: absolute; 150 | width: 0; 151 | top: 0; 152 | z-index: 42; 153 | } 154 | .transitionContainer { 155 | position: absolute; 156 | z-index: 42; 157 | top: 0; 158 | } 159 | #previousCorner, #nextCorner { 160 | position: absolute; 161 | top: 0; 162 | opacity: 0; 163 | filter: alpha(opacity=0); 164 | width: 30px; 165 | height: 26px; 166 | cursor: pointer; 167 | z-index: 45; 168 | } 169 | #previousCorner:focus, #nextCorner:focus { 170 | outline: none; 171 | } 172 | #previousCorner { 173 | left: 0; 174 | background-image: url(../img/left-corner.png); 175 | } 176 | #nextCorner { 177 | right: 0; 178 | background-image: url(../img/right-corner.png); 179 | } 180 | #zoomWindow { 181 | position: absolute; 182 | top: 0; 183 | left: 0; 184 | height: 100%; 185 | width: 100%; 186 | background: white; 187 | overflow: hidden; 188 | z-index: 94; 189 | } 190 | .grab { 191 | cursor: move; 192 | cursor: -moz-grab; 193 | } 194 | 195 | .grabbing { 196 | cursor: move; 197 | cursor: -moz-grabbing; 198 | } 199 | #zoomedPages, #HDGrid { 200 | position: absolute; 201 | } 202 | #leftPageZoomed, #rightPageZoomed { 203 | display: inline-block; 204 | width: 50%; 205 | height: 100%; 206 | } 207 | .grid { 208 | float: left; 209 | margin: 0; padding: 0; border: 0; 210 | } 211 | 212 | /* FIXME: absolute height */ 213 | #bookSwitcher { 214 | height: 150px; 215 | } 216 | 217 | #pagesSlider { 218 | margin: 15px 0; 219 | } 220 | 221 | #pagesSlider, #bookSwitcher, #innerPagesSlider { 222 | overflow: hidden; 223 | white-space: nowrap; 224 | position: relative; 225 | } 226 | 227 | #pagesSlider, #innerPagesSlider { 228 | height: 50px; 229 | margin: 25px 0; 230 | } 231 | 232 | #bookSwitcher .pageNumber, #bookSwitcher .pageChannel { 233 | display: none; 234 | } 235 | 236 | #pagesList a { 237 | position: relative; 238 | margin: 10px 0; 239 | text-align: center; 240 | display: inline-block; 241 | text-decoration: none; 242 | } 243 | 244 | #innerPagesSlider, #pagesSlider { 245 | height: 75px; 246 | } 247 | 248 | #pagesList a.odd { 249 | margin-right: 10px; 250 | border-left: none; 251 | } 252 | 253 | #pagesList .pageNumber { 254 | font-size: 10px; 255 | color: #333; 256 | } 257 | 258 | #pagesList .even .pageNumber { 259 | right: 4px; 260 | left: auto; 261 | } 262 | 263 | #bookSwitcher a, #pagesList img, #bookSwitcher img { 264 | display: inline-block; 265 | height: 148px; 266 | } 267 | 268 | #bookSwitcher .bookName { 269 | display: none; 270 | } 271 | 272 | #pagesList img { 273 | display: block; 274 | height: 50px; 275 | border: 1px solid #CCC; 276 | } 277 | 278 | #pagesList { 279 | position: absolute; 280 | top: 0; 281 | left: 0; 282 | min-width: 812px; 283 | } 284 | 285 | #innerPagesSlider { 286 | margin: 0 50px; 287 | padding: 0; 288 | } 289 | 290 | #sliderNext, #sliderPrev { 291 | position: absolute; 292 | width: 48px; 293 | height: 48px; 294 | top: 15px; 295 | z-index: 2; 296 | } 297 | 298 | #sliderNext { 299 | right: 0; 300 | } 301 | 302 | #sliderPrev { 303 | left: 0; 304 | } 305 | 306 | p { 307 | padding: 0.5em; 308 | } 309 | 310 | #menu { 311 | height: 55px; 312 | width: 100%; 313 | border-bottom: 2px solid #CCC; 314 | overflow: hidden; 315 | } 316 | 317 | #menu .date_clearer { 318 | display: none; 319 | } 320 | 321 | .loading { 322 | background-image: url(../img/loading.gif); 323 | background-repeat: no-repeat; 324 | background-position: center center; 325 | min-width: 39px; 326 | } 327 | 328 | .warning { 329 | background-image: url(../img/warning.png); 330 | background-repeat: no-repeat; 331 | background-position: center center; 332 | width: 39px; 333 | } 334 | 335 | #errorPopin { 336 | background: black; 337 | color: #f6f6f6; 338 | position: absolute; 339 | padding: 10px; 340 | z-index: 155; 341 | width: 450px; 342 | top: 200px; 343 | left: 260px; 344 | display: none; 345 | } 346 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/js/page.js: -------------------------------------------------------------------------------- 1 | var libePage = function(pageNumber, pageId, pageChannel, pageMaps) { 2 | var _pageElement = [], _areasElement = []; 3 | var map = {}; 4 | var _pageNumber = -1, _pageId = -1; 5 | var _mapsLoaded = false; 6 | var _smallImageSource = null, _smallestImageSource = null, _imageSource = null; 7 | var _pageChannel = ""; 8 | 9 | function hoverArea() { 10 | var target = jQuery(this); 11 | var objectId = target.data('area').object_id; 12 | jQuery('.area').trigger("highlight-area-" + objectId); 13 | } 14 | 15 | function unhoverArea() { 16 | var target = jQuery(this); 17 | var objectId = target.data('area').object_id; 18 | jQuery('.area').trigger("unhighlight-area-" + objectId); 19 | } 20 | 21 | function handleMap() { 22 | if (_mapsLoaded) { 23 | return; 24 | } 25 | if (!libeConfig.pageWidth) { 26 | // too soon! better luck next time. 27 | return; 28 | } 29 | _mapsLoaded = true; 30 | if (!map || !map.areas || !map.areas.length || !map.width || !map.height) { 31 | return; 32 | } 33 | var reductionRatio = libeConfig.pageWidth / map.width; 34 | for (var i = 0, il = map.areas.length; i < il; i++) { 35 | var area = map.areas[i]; 36 | var coords = area.coords.split(","); 37 | var left = Math.ceil(coords[0] * reductionRatio); 38 | var top = Math.ceil(coords[1] * reductionRatio); 39 | var width = Math.ceil((coords[2] - coords[0]) * reductionRatio); 40 | var height = Math.ceil((coords[3] - coords[1]) * reductionRatio); 41 | 42 | var areaElement = document.createElement('a'); 43 | areaElement.className = "area"; 44 | areaElement.href = "#!/a" + area.object_id; 45 | areaElement.style.left = left + "px"; 46 | areaElement.style.top = top + "px"; 47 | areaElement.style.width = width + "px"; 48 | areaElement.style.height = height + "px"; 49 | 50 | areaElement = jQuery(areaElement); 51 | areaElement.click(openArea); 52 | _areasElement.push(areaElement); 53 | jQuery(_pageElement).append(areaElement); 54 | 55 | areaElement.data('area', area); 56 | areaElement.hover(hoverArea, unhoverArea); 57 | 58 | areaElement.bind('highlight-area-' + area.object_id, highlightArea); 59 | areaElement.bind('unhighlight-area-' + area.object_id, unhighlightArea); 60 | } 61 | } 62 | 63 | function openArea() { 64 | if (!libeConfig.canUseMap(_pageNumber, _pageId)) { 65 | libeConfig.restrictedAccess(); 66 | return false; 67 | } 68 | var data = jQuery(this).data('area'); 69 | if (data.object_class == "article") { 70 | var url = libeConfig.webservices.contentmodel; 71 | var replaces = { 72 | '{format}' : 'html', 73 | '{id}' : data.object_id, 74 | '{classname}' : 'article' 75 | }; 76 | 77 | var k; 78 | for (k in replaces) { 79 | url = url.replace(k, replaces[k]); 80 | } 81 | 82 | k = 'default'; 83 | if (typeof libeConfig.modelmapping[data.object_class] !== 'undefined') { 84 | k = data.object_class; 85 | } 86 | 87 | if (libeConfig.modelmapping[k] == 'iframe' || 88 | libeConfig.modelmapping[k] == 'ajax' || 89 | libeConfig.modelmapping[k] == 'inline') { 90 | var dw = jQuery(this).openDOMWindow({ 91 | windowSourceURL: url, 92 | windowSourceID: '#contentmodelContent', 93 | width: parseInt(document.documentElement.clientWidth * 90 / 100, 10), 94 | height: parseInt(document.documentElement.clientHeight * 90 / 100, 10), 95 | fixedWindowY: 0, 96 | windowSource: libeConfig.modelmapping[k], 97 | loader: 1, 98 | functionCallOnClose: function() { 99 | jQuery('body').css({'overflow': 'auto'}); 100 | }, 101 | functionCallOnOpen: function() { 102 | jQuery('body').css({'overflow': 'hidden'}); 103 | } 104 | }); 105 | if (libeConfig.modelmapping[k] == 'inline') { 106 | // inline is interesting to add custom content, but 107 | // it doesn't load the URL, so we have to do it manually 108 | // Note: have a .inner element inside your #contentmodelContent 109 | // when you want to use this! 110 | jQuery('#DOMWindow .inner').load(url); 111 | } 112 | } else { 113 | window.open(url); 114 | } 115 | } 116 | return false; 117 | } 118 | 119 | function highlightArea() { 120 | jQuery(this).clearQueue().animate({opacity: 0.1}, 250); 121 | } 122 | function unhighlightArea() { 123 | jQuery(this).clearQueue().animate({opacity: 0}, 250); 124 | } 125 | 126 | function show() { 127 | jQuery(_pageElement).show(); 128 | } 129 | 130 | function hide() { 131 | jQuery(_pageElement).hide(); 132 | } 133 | 134 | function getPageInfo() { 135 | return '' + _pageNumber + '' + 136 | '' + _pageChannel + ''; 137 | } 138 | 139 | function canAccess() { 140 | return libeConfig.canAccess(_pageNumber, _pageId); 141 | } 142 | 143 | function getThumbnailForList(book, size) { 144 | var src; 145 | if (typeof size == 'undefined' || size != 'smallest') { 146 | size = 'small'; 147 | src = _smallImageSource; 148 | } else { 149 | src = _smallestImageSource; 150 | } 151 | var href = '#!/' + book + '_' + _pageNumber; 152 | var a = jQuery(''); 153 | var img = jQuery(''); 154 | img.bind('load', function(e) { 155 | jQuery(this).parent().removeClass('loading'); 156 | }); 157 | img.bind('error', function(e) { 158 | jQuery(this).parent().removeClass('loading'); 159 | jQuery(this).parent().addClass('warning'); 160 | }); 161 | a.append(img); 162 | a.append('' + _pageNumber + ''); 163 | return a; 164 | } 165 | 166 | // Init Code 167 | 168 | if (typeof(pageNumber) !== 'undefined') { 169 | _pageNumber = parseInt(pageNumber, 10); 170 | } 171 | 172 | if (typeof(pageId) !== 'undefined') { 173 | _pageId = parseInt(pageId, 10); 174 | } 175 | 176 | if (typeof(pageChannel) !== 'undefined') { 177 | _pageChannel = pageChannel; 178 | } 179 | 180 | _pageElement = document.createElement("div"); 181 | _pageElement.className = "page loading"; 182 | jQuery(_pageElement).height(libeConfig.pageHeight); // page height is always fixed from config - width is dynamic 183 | if (_pageId > 0) { 184 | _pageElement.id = 'page_' + _pageId; 185 | } 186 | 187 | var baseSrc = ""; 188 | // Set thumbnails, they are always visible, unless the page is under construction 189 | if (_pageId > 0) { 190 | baseSrc = libeConfig.webservices.paper_page_resized.replace('{format}', 'jpg').replace('{id}', _pageId); 191 | _smallestImageSource = baseSrc.replace('{size}', 'x' + libeConfig.pageSmallThumbnailHeight); 192 | _smallImageSource = baseSrc.replace('{size}', 'x' + libeConfig.pageThumbnailHeight); 193 | } else { 194 | _smallestImageSource = libeConfig.pageUnderConstructionImageSmallest; 195 | _smallImageSource = libeConfig.pageUnderConstructionImage; 196 | } 197 | 198 | var img; 199 | if (_pageNumber <= 0) { 200 | // non existant page, do nothing 201 | } else if (!canAccess()) { 202 | // page that the user isn't allowed to read 203 | img = document.createElement("img"); 204 | img.src = _imageSource = libeConfig.pageLimitedAccessImage; 205 | _pageElement.appendChild(img); 206 | } else if (_pageId <= 0) { 207 | // page not yet included in the book, but that should exist: display it as "under construction" 208 | img = document.createElement("img"); 209 | img.src = _imageSource = libeConfig.pageUnderConstructionImage; 210 | _pageElement.appendChild(img); 211 | } else { 212 | // normal page 213 | map = pageMaps; 214 | img = document.createElement("img"); 215 | jQuery(img).bind('load', function(e) { 216 | jQuery(_pageElement).removeClass('loading'); 217 | // Little trick: use _pageElement and not the image to find out the dimensions of the 218 | // content, since the load event might occur at a time the image is hidden (if the user 219 | // is flipping through pages very fast). 220 | if (libeConfig.setSize(jQuery(_pageElement).width(), jQuery(_pageElement).height())) { 221 | // handleMap() would make more sense in showPage(), but we really need 222 | // to know the right width before calling it, so we call it here. 223 | handleMap(); 224 | } 225 | }); 226 | jQuery(img).bind('error', function(e) { 227 | jQuery(_pageElement).removeClass('loading'); 228 | jQuery(_pageElement).addClass('warning'); 229 | }); 230 | img.src = _imageSource = baseSrc.replace('{size}', 'x' + libeConfig.pageHeight); 231 | _pageElement.appendChild(img); 232 | } 233 | 234 | if (_pageNumber % 2 === 0) { 235 | libeConfig.evenSideElement.appendChild(_pageElement); 236 | } else { 237 | libeConfig.oddSideElement.appendChild(_pageElement); 238 | } 239 | 240 | return { 241 | show: show, 242 | hide: hide, 243 | imageSource: _imageSource, 244 | pageId: _pageId, 245 | pageNumber: _pageNumber, 246 | handleMap: handleMap, 247 | getPageInfo: getPageInfo, 248 | getThumbnailForList: getThumbnailForList, 249 | canAccess: canAccess 250 | }; 251 | }; 252 | -------------------------------------------------------------------------------- /digitalpaper/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import math 3 | import mimetypes 4 | import os 5 | import os.path 6 | import stat 7 | import subprocess 8 | import time 9 | import unicodedata 10 | from urllib import quote, quote_plus 11 | 12 | from django.http import HttpResponse 13 | from django.conf import settings 14 | from django.db.models import Model, loading 15 | from django.core.urlresolvers import resolve, Resolver404, get_resolver 16 | 17 | from digitalpaper.constants import (ACCEPTED_THUMB_SIZE, PAPERPAGE_IMAGE_HEIGHT, 18 | PAPERPAGE_CROP_IMAGES_PER_COLUMN, 19 | PAPERPAGE_CROP_IMAGES_PER_ROW) 20 | 21 | 22 | def get_publication_date_field(): 23 | return settings.READER_PUBLICATION_DATE_FIELD 24 | 25 | def get_model_for_publication(): 26 | return loading.get_model(settings.READER_PUBLICATION_APP_NAME, settings.READER_PUBLICATION_MODEL_NAME) 27 | 28 | def get_model_for_paperpage(): 29 | return loading.get_model(settings.READER_PAPERPAGE_APP_NAME, settings.READER_PAPERPAGE_MODEL_NAME) 30 | 31 | def get_manager_for_publication(inst): 32 | name = getattr(settings, 'READER_PUBLICATION_MANAGER_NAME', 'objects') 33 | manager = getattr(get_model_for_publication(), name) 34 | return manager 35 | 36 | def get_manager_method_for_publication_by_date(inst): 37 | manager = get_manager_for_publication(inst) 38 | method = getattr(manager, settings.READER_PUBLICATION_MANAGER_METHOD_BYDATE_NAME) 39 | return method 40 | 41 | def build_parameters(*args, **kwargs): 42 | ''' 43 | Utility function to build a list of parameters for an URI template 44 | args are used to build parameters without values like this: a={a}&b={b}... 45 | kwargs are used to build parameters with values like this: a=42&b=56&... 46 | ''' 47 | def _urlencode_custom(d): 48 | rval = [] 49 | for k, v in d.items(): 50 | k = quote_plus(str(k)) 51 | v = quote_plus(str(v), safe='{}') # To allow {} in uri templates without escaping 52 | rval.append('%s=%s' % (k, v)) 53 | return "&".join(rval) 54 | 55 | d = dict(zip(args, ['{%s}' % (str(a), ) for a in args])) 56 | d.update(kwargs) 57 | return _urlencode_custom(d) 58 | 59 | def get_uris_templates_for_settings(urlnames, prefix=""): 60 | ''' 61 | Utility function to return a dict mapping urlnames to URI Templates 62 | See get_uri_template() for how it works 63 | ''' 64 | rval = {} 65 | for urlname in urlnames: 66 | rval[urlname.split(':')[-1]] = get_uri_template(urlname, prefix=prefix) 67 | return rval 68 | 69 | def get_uris_templates_for_settings_with_params(urlnamesandargs, prefix=""): 70 | ''' 71 | Utility function to return a dict mapping urlnames to URI Templates, 72 | adding specific GET params to each one. 73 | See get_uri_template() for how it works 74 | ''' 75 | rval = {} 76 | for urlname, args in urlnamesandargs: 77 | rval[urlname.split(':')[-1]] = get_uri_template(urlname, prefix=prefix) + '?' + build_parameters(*args) 78 | return rval 79 | 80 | def get_uri_template(urlname, args=None, prefix=""): 81 | ''' 82 | Utility function to return an URI Template from a named URL in django 83 | Copy from piston doc.py, added namespacing support and removing args 84 | checks. 85 | 86 | Restrictions: 87 | - Only supports named urls! i.e. url(... name="toto") 88 | - Only support one namespace level 89 | - Only returns the first URL possibility. Don't re-use the same urlname multiple times! 90 | - Supports multiple pattern possibilities (i.e., patterns with non-capturing parenthesis in them) 91 | by trying to find a pattern whose optional parameters match those you specified 92 | (a parameter is considered optional if it doesn't appear in every pattern possibility) 93 | ''' 94 | def _convert(template, args=None): 95 | """URI template converter""" 96 | if not args: 97 | args = [] 98 | paths = template % dict([p, "{%s}" % p] for p in args) 99 | return u'%s/%s' % (prefix, paths) 100 | 101 | resolver = get_resolver(None) 102 | parts = urlname.split(':') 103 | if len(parts) > 1 and resolver.namespace_dict.has_key(parts[0]): 104 | namespace = parts[0] 105 | urlname = parts[1] 106 | nprefix, resolver = resolver.namespace_dict[namespace] 107 | prefix = prefix + '/' + nprefix.rstrip('/') 108 | possibilities = resolver.reverse_dict.getlist(urlname) 109 | for tmp in possibilities: 110 | possibility, pattern = tmp[:2] 111 | if not args: 112 | # If not args are specified, we only consider the first pattern 113 | # django gives us 114 | result, params = possibility[0] 115 | return _convert(result, params) 116 | else: 117 | # If there are optionnal arguments passed, use them to try to find 118 | # the correct pattern. 119 | # First, we need to build a list with all the arguments 120 | seen_params = [] 121 | for result, params in possibility: 122 | seen_params.append(params) 123 | # Then build a set to find the common ones, and use it to build the 124 | # list of all the expected params 125 | common_params = reduce(lambda x, y: set(x) & set(y), seen_params) 126 | expected_params = sorted(common_params.union(args)) 127 | # Then finally, loop again over the pattern possibilities and return 128 | # the first one that strictly match expected params 129 | for result, params in possibility: 130 | if sorted(params) == expected_params: 131 | return _convert(result, params) 132 | return None 133 | 134 | class HttpResponseXFile(HttpResponse): 135 | def __init__(self, filename, *args, **kwargs): 136 | attachment_filename = kwargs.pop('attachment_filename', None) 137 | 138 | if settings.DEBUG: 139 | # In debug mode, we read the file like mod_xsendfile would do, so that 140 | # this class can work without the mod_xsendfile module 141 | kwargs['content'] = open(filename, 'rb').read() 142 | 143 | super(HttpResponseXFile, self).__init__(*args, **kwargs) 144 | self['X-Sendfile'] = quote(filename.encode('utf8')) 145 | self['Content-Type'] = mimetypes.guess_type(filename)[0] or 'application/octet-stream' 146 | 147 | # Content-encoding is here to make sure gzip middleware is not triggered. 148 | # It should not be sent back to the user, mod_xsendfile will intercept it 149 | # and remove it. 150 | self['Content-Encoding'] = 'x-send-file' 151 | 152 | if not settings.DEBUG and attachment_filename: 153 | attachment_filename = unicodedata.normalize('NFKD', unicode(attachment_filename)).encode('ascii', 'ignore') 154 | # In production, sent the file as attachement if attachment_filename is set 155 | self['Content-Disposition'] = 'attachment; filename="%s"' % (attachment_filename, ) 156 | 157 | 158 | class FileLock(object): 159 | """Represents a file lock on disk, the object may or may not 160 | own the lock. If it doesn't own the lock it can not delete it""" 161 | 162 | LOCK_EXPIRY_DELAY = 5 * 60 163 | LOCK_TIMEOUT = 5 164 | 165 | def __init__(self, path): 166 | self.lockname = path + '.lock' 167 | 168 | def acquire_lock(self): 169 | """ 170 | Try to acquire a lock on file. If the lock is 5min old it raise an Exception. 171 | """ 172 | try: 173 | fd = os.open(self.lockname, 174 | os.O_WRONLY | os.O_EXCL | os.O_CREAT) 175 | os.close(fd) 176 | return True 177 | except OSError: 178 | stats = os.stat(self.lockname) 179 | access_time = stats[stat.ST_ATIME] 180 | if time.time() - access_time > FileLock.LOCK_EXPIRY_DELAY: # the lock is 5min old 181 | os.remove(self.lockname) 182 | raise Exception('Found an old lock %s, last popen call failed.' % self.lockname) 183 | return False 184 | 185 | def release_lock(self): 186 | """ 187 | Release a lock around this file 188 | """ 189 | try: 190 | os.unlink(self.lockname) 191 | except OSError: 192 | pass 193 | 194 | def is_locked(self): 195 | """ 196 | Check if file is locked 197 | """ 198 | return os.path.exists(self.lockname) 199 | 200 | 201 | def open_pdf(filename, password=None): 202 | """ 203 | Open a PDF with PdfFileReader and return it. 204 | If the PDF seems encrypted, try to decrypt with the given password. 205 | Some PDFs are encrypted with a default empty password, so we try with this 206 | if no password is given 207 | """ 208 | from pyPdf import PdfFileReader 209 | from pyPdf.utils import PdfReadError 210 | pdf = PdfFileReader(file(filename, "rb")) 211 | if pdf.isEncrypted: 212 | if not pdf.decrypt('' if password is None else password): 213 | raise PdfReadError('Unable to decrypt pdf %s' % filename) 214 | return pdf 215 | 216 | 217 | class PaperPageThumbnail(object): 218 | P_ERROR_BAD = 1 219 | P_ERROR_OS = 2 220 | P_SUCCESS = 3 221 | 222 | def __init__(self, paperpage, field="original_file", pdf_password=None): 223 | self._infos_loaded = False 224 | self.paperpage = paperpage 225 | self.file_field = getattr(paperpage, field) 226 | self.pdf_password = pdf_password 227 | 228 | def _load_pdf_infos(self): 229 | if self._infos_loaded: 230 | return 231 | 232 | # Read the pdf to find the correct ratio to use 233 | pdf = open_pdf(self.file_field.path, self.pdf_password) 234 | box = pdf.getPage(0).cropBox 235 | topleft = box.getUpperLeft() 236 | bottomright = box.getLowerRight() 237 | self.width = round(abs(topleft[0] - bottomright[0])) 238 | self.height = round(abs(topleft[1] - bottomright[1])) 239 | self.width = int(round(self.width * PAPERPAGE_IMAGE_HEIGHT / self.height)) 240 | self.resolution = 72.0 * PAPERPAGE_IMAGE_HEIGHT / self.height 241 | self.height = PAPERPAGE_IMAGE_HEIGHT 242 | self._infos_loaded = True 243 | 244 | def _get_paths(self, size=None, format=None): 245 | filename = self.file_field.name.replace('.pdf', '_%s.%s' % (size, format)) 246 | filename = os.path.join(settings.MEDIA_ROOT, 'cache', filename) 247 | 248 | dirname = os.path.dirname(filename) 249 | if not os.path.exists(dirname): 250 | # Create directory structure if necessary 251 | os.makedirs(dirname) 252 | 253 | return self.file_field.path, filename 254 | 255 | def _subprocess_action(self, args=None, filename=None): 256 | # TODO: analyse and rewrote this function, see why in comments below 257 | lock = FileLock(filename) 258 | if lock.acquire_lock(): 259 | try: 260 | pp = subprocess.Popen(args, stdout=subprocess.PIPE) 261 | #output = pp.stdout.read() 262 | pp.wait() 263 | except OSError: 264 | try: 265 | os.remove(filename) 266 | finally: 267 | lock.release_lock() 268 | # The message of this exception is strange, because having an 269 | # OSError on the subprocess is not linked to the lock !!!! 270 | raise Exception('Found an old lock') 271 | else: 272 | # Being here does not guarantee use that the command was 273 | # successfully done. We should test the existence of the 274 | # generated filename, as it's done in the main "else" block 275 | lock.release_lock() 276 | #print output 277 | return PaperPageThumbnail.P_SUCCESS, filename 278 | 279 | else: # another thread is already working on the file, we wait for it 280 | t = time.time() 281 | while lock.is_locked(): 282 | time.sleep(0.1) 283 | if time.time() - t > FileLock.LOCK_TIMEOUT: # don't wait more than LOCK_TIMEOUT seconds 284 | raise Exception('Waiting for thumbnail generation, but it took way too long') 285 | 286 | if os.path.exists(filename): 287 | return PaperPageThumbnail.P_SUCCESS, filename 288 | else: 289 | raise Exception('File `%s` does not exist, but it should' % filename) 290 | 291 | def _generate_big_image_from_pdf(self, force_resolution=None): 292 | pdf_filename, img_filename = self._get_paths(size=PAPERPAGE_IMAGE_HEIGHT, format='png') 293 | 294 | if self.check_file_freshness(pdf_filename, img_filename): 295 | return PaperPageThumbnail.P_SUCCESS, img_filename 296 | 297 | # pyPDF raise when opening some bad constructed PDFs, so instead of not 298 | # having any image at all, simply tell gs about the wanted resolution 299 | size_arg = None 300 | try: 301 | self._load_pdf_infos() 302 | if not force_resolution: 303 | size_arg = '-g%sx%s' % (self.width, self.height) 304 | except (TypeError, # bad xfer resulting in "TypeError 'NumberObject' object has no attribute '__getitem__'" 305 | AssertionError, # no xfer resulting in "raise False" in pyPDF if no xfer 306 | ValueError): # bad xfer can also result in "ValueError invalid literal for int() with base 10: 'f'" 307 | if not force_resolution: 308 | self.resolution = 72.0 309 | size_arg = '-dDEVICEHEIGHT=%s' % PAPERPAGE_IMAGE_HEIGHT # may not be used 310 | # without width 311 | args = ['gs', 312 | '-dBATCH', 313 | '-dNOPAUSE', 314 | '-sDEVICE=png16m', 315 | '-dTextAlphaBits=4', 316 | '-sOutputFile=%s' % img_filename, 317 | '-r%s' % (force_resolution or self.resolution), 318 | pdf_filename 319 | ] 320 | if size_arg: 321 | args.append(size_arg) 322 | return self._subprocess_action(args=args, filename=img_filename) 323 | 324 | def check_file_freshness(self, pdf_filename, image_filename): 325 | if os.path.exists(image_filename): 326 | if os.stat(image_filename)[stat.ST_MTIME] > os.stat(pdf_filename)[stat.ST_MTIME]: 327 | # File already exists and is up-to-date! 328 | return True 329 | return False 330 | 331 | @classmethod 332 | def validate_size(cls, size): 333 | if size not in ACCEPTED_THUMB_SIZE: 334 | return False 335 | return True 336 | 337 | def generate_thumbnail(self, size): 338 | if not self.validate_size(size): 339 | return PaperPageThumbnail.P_ERROR_BAD, 'Bad size specified: %s' % (str(size), ) 340 | 341 | pdf_filename, thumb_filename = self._get_paths(size=size, format='jpg') 342 | rval, detail = self._generate_big_image_from_pdf() 343 | if rval != PaperPageThumbnail.P_SUCCESS: 344 | return rval, detail 345 | else: 346 | big_filename = detail 347 | 348 | if self.check_file_freshness(pdf_filename, thumb_filename): 349 | return PaperPageThumbnail.P_SUCCESS, thumb_filename 350 | 351 | args = ('convert', 352 | '-resize', 353 | '%s' % size, 354 | '-antialias', 355 | '-quality', '95', 356 | big_filename, 357 | thumb_filename 358 | ) 359 | return self._subprocess_action(args=args, filename=thumb_filename) 360 | 361 | def crop(self, x, y): 362 | import Image 363 | x = int(x) 364 | if x >= PAPERPAGE_CROP_IMAGES_PER_ROW: 365 | # The js is sending us coordinates starting from the page on the 366 | # left, so we need to adjust them to the current page only. 367 | x = x - PAPERPAGE_CROP_IMAGES_PER_ROW 368 | y = int(y) 369 | 370 | pdf_filename, img_filename = self._get_paths(size=PAPERPAGE_IMAGE_HEIGHT, format='png') 371 | rval, detail = self._generate_big_image_from_pdf() 372 | if rval != PaperPageThumbnail.P_SUCCESS: 373 | return rval, detail 374 | else: 375 | img_filename = detail 376 | 377 | # ImageMagick will automatically generate a file named like name-0.ext, name-1.ext, etc. 378 | # We need that name. 379 | ord_img = y * PAPERPAGE_CROP_IMAGES_PER_ROW + x 380 | cropped_filename = img_filename.replace('.png', '-%d.png' % (ord_img, )) 381 | 382 | if self.check_file_freshness(pdf_filename, cropped_filename): 383 | return PaperPageThumbnail.P_SUCCESS, cropped_filename 384 | 385 | size = Image.open(open(img_filename)).size 386 | width = math.ceil(float(size[0]) / PAPERPAGE_CROP_IMAGES_PER_COLUMN) 387 | height = math.ceil(float(size[1]) / PAPERPAGE_CROP_IMAGES_PER_ROW) 388 | 389 | args = ('convert', 390 | img_filename, 391 | '-colorspace', 'rgb', 392 | '-crop', '%dx%d' % (width, height), 393 | img_filename 394 | ) 395 | return self._subprocess_action(args=args, filename=cropped_filename) 396 | 397 | 398 | def split_multipage_pdf(filename, destination=None, pdf_password=None): 399 | """ 400 | Split a PDF with many pages into many PDFs, one per page. 401 | `filename` is the absolute path of the pdf to split. 402 | New PDFs will be saved in the desination path provided, or in the same 403 | directory as the original. "-%03d" is added to new PDFs before the extension. 404 | This function returns a list of the absolute file names of the new PDFs. 405 | If no PDF was generated, the returned list is empty. 406 | This method DO NOT manage any exception raised by pyPdf 407 | """ 408 | from pyPdf import PdfFileWriter 409 | 410 | if not destination: 411 | destination = os.path.dirname(filename) 412 | 413 | basename = os.path.splitext(os.path.basename(filename))[0] 414 | 415 | input_pdf = open_pdf(filename, pdf_password) 416 | if input_pdf.numPages < 2: 417 | return [] 418 | 419 | output_names = [] 420 | for num, page in enumerate(input_pdf.pages): 421 | output_name = os.path.join(destination, '%s-%03d.pdf' % (basename, num + 1)) 422 | 423 | output_pdf = PdfFileWriter() 424 | output_pdf.addPage(page) 425 | 426 | output_stream = file(output_name, "wb") 427 | output_pdf.write(output_stream) 428 | output_stream.close() 429 | 430 | output_names.append(output_name) 431 | 432 | return output_names 433 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/js/jquery.DOMWindow.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | 3 | //closeDOMWindow 4 | $.fn.closeDOMWindow = function(settings){ 5 | 6 | if(!settings){settings={};} 7 | 8 | var run = function(passingThis){ 9 | 10 | if(settings.anchoredClassName){ 11 | var $anchorClassName = $('.'+settings.anchoredClassName); 12 | $anchorClassName.fadeOut('fast',function(){ 13 | if($.fn.draggable){ 14 | $anchorClassName.draggable('destroy').trigger("unload").remove(); 15 | }else{ 16 | $anchorClassName.trigger("unload").remove(); 17 | } 18 | }); 19 | if(settings.functionCallOnClose){settings.functionCallOnClose();} 20 | }else{ 21 | var $DOMWindowOverlay = $('#DOMWindowOverlay'); 22 | var $DOMWindow = $('#DOMWindow'); 23 | $DOMWindowOverlay.fadeOut('fast',function(){ 24 | $DOMWindowOverlay.trigger('unload').unbind().remove(); 25 | }); 26 | $DOMWindow.fadeOut('fast',function(){ 27 | if($.fn.draggable){ 28 | $DOMWindow.draggable("destroy").trigger("unload").remove(); 29 | }else{ 30 | $DOMWindow.trigger("unload").remove(); 31 | } 32 | }); 33 | 34 | $(window).unbind('scroll.DOMWindow'); 35 | $(window).unbind('resize.DOMWindow'); 36 | 37 | if($.fn.openDOMWindow.isIE6){$('#DOMWindowIE6FixIframe').remove();} 38 | if(settings.functionCallOnClose){settings.functionCallOnClose();} 39 | } 40 | }; 41 | 42 | if(settings.eventType){//if used with $(). 43 | return this.each(function(index){ 44 | $(this).bind(settings.eventType, function(){ 45 | run(this); 46 | return false; 47 | }); 48 | }); 49 | }else{//else called as $.function 50 | run(); 51 | } 52 | 53 | }; 54 | 55 | //allow for public call, pass settings 56 | $.closeDOMWindow = function(s){$.fn.closeDOMWindow(s);}; 57 | 58 | //openDOMWindow 59 | $.fn.openDOMWindow = function(instanceSettings){ 60 | 61 | var shortcut = $.fn.openDOMWindow; 62 | 63 | //default settings combined with callerSettings 64 | 65 | shortcut.defaultsSettings = { 66 | anchoredClassName:'', 67 | anchoredSelector:'', 68 | borderColor:'#ccc', 69 | borderSize:'4', 70 | draggable:0, 71 | eventType:null, //click, blur, change, dblclick, error, focus, load, mousedown, mouseout, mouseup etc... 72 | fixedWindowY:100, 73 | functionCallOnOpen:null, 74 | functionCallOnClose:null, 75 | height:500, 76 | loader:0, 77 | loaderHeight:0, 78 | loaderImagePath:'', 79 | loaderWidth:0, 80 | modal:0, 81 | overlay:1, 82 | overlayColor:'#000', 83 | overlayOpacity:'85', 84 | positionLeft:0, 85 | positionTop:0, 86 | positionType:'centered', // centered, anchored, absolute, fixed 87 | width:500, 88 | windowBGColor:'#fff', 89 | windowBGImage:null, // http path 90 | windowHTTPType:'get', 91 | windowPadding:10, 92 | windowSource:'inline', //inline, ajax, iframe 93 | windowSourceID:'', 94 | windowSourceURL:'', 95 | windowSourceAttrURL:'href' 96 | }; 97 | 98 | var settings = $.extend({}, $.fn.openDOMWindow.defaultsSettings , instanceSettings || {}); 99 | 100 | //Public functions 101 | 102 | shortcut.viewPortHeight = function(){ return self.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;}; 103 | shortcut.viewPortWidth = function(){ return self.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;}; 104 | shortcut.scrollOffsetHeight = function(){ return self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;}; 105 | shortcut.scrollOffsetWidth = function(){ return self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;}; 106 | shortcut.isIE6 = typeof document.body.style.maxHeight === "undefined"; 107 | 108 | //Private Functions 109 | 110 | var sizeOverlay = function(){ 111 | var $DOMWindowOverlay = $('#DOMWindowOverlay'); 112 | if(shortcut.isIE6){//if IE 6 113 | var overlayViewportHeight = document.documentElement.offsetHeight + document.documentElement.scrollTop - 4; 114 | var overlayViewportWidth = document.documentElement.offsetWidth - 21; 115 | $DOMWindowOverlay.css({'height':overlayViewportHeight +'px','width':overlayViewportWidth+'px'}); 116 | }else{//else Firefox, safari, opera, IE 7+ 117 | $DOMWindowOverlay.css({'height':'100%','width':'100%','position':'fixed'}); 118 | } 119 | }; 120 | 121 | var sizeIE6Iframe = function(){ 122 | var overlayViewportHeight = document.documentElement.offsetHeight + document.documentElement.scrollTop - 4; 123 | var overlayViewportWidth = document.documentElement.offsetWidth - 21; 124 | $('#DOMWindowIE6FixIframe').css({'height':overlayViewportHeight +'px','width':overlayViewportWidth+'px'}); 125 | }; 126 | 127 | var centerDOMWindow = function() { 128 | var $DOMWindow = $('#DOMWindow'); 129 | if(settings.height + 50 > shortcut.viewPortHeight()){//added 50 to be safe 130 | $DOMWindow.css('left',Math.round(shortcut.viewPortWidth()/2) + shortcut.scrollOffsetWidth() - Math.round(($DOMWindow.outerWidth())/2)); 131 | }else{ 132 | $DOMWindow.css('left',Math.round(shortcut.viewPortWidth()/2) + shortcut.scrollOffsetWidth() - Math.round(($DOMWindow.outerWidth())/2)); 133 | $DOMWindow.css('top',Math.round(shortcut.viewPortHeight()/2) + shortcut.scrollOffsetHeight() - Math.round(($DOMWindow.outerHeight())/2)); 134 | } 135 | }; 136 | 137 | var centerLoader = function() { 138 | var $DOMWindowLoader = $('#DOMWindowLoader'); 139 | if(shortcut.isIE6){//if IE 6 140 | $DOMWindowLoader.css({'left':Math.round(shortcut.viewPortWidth()/2) + shortcut.scrollOffsetWidth() - Math.round(($DOMWindowLoader.innerWidth())/2),'position':'absolute'}); 141 | $DOMWindowLoader.css({'top':Math.round(shortcut.viewPortHeight()/2) + shortcut.scrollOffsetHeight() - Math.round(($DOMWindowLoader.innerHeight())/2),'position':'absolute'}); 142 | }else{ 143 | $DOMWindowLoader.css({'left':'50%','top':'50%','position':'fixed'}); 144 | } 145 | 146 | }; 147 | 148 | var fixedDOMWindow = function(){ 149 | var $DOMWindow = $('#DOMWindow'); 150 | $DOMWindow.css('left', settings.positionLeft + shortcut.scrollOffsetWidth()); 151 | $DOMWindow.css('top', + settings.positionTop + shortcut.scrollOffsetHeight()); 152 | }; 153 | 154 | var showDOMWindow = function(instance){ 155 | if(instance){ 156 | $('.'+instance+' #DOMWindowLoader').remove(); 157 | $('.'+instance+' #DOMWindowContent').fadeIn('fast',function(){if(settings.functionCallOnOpen){settings.functionCallOnOpen();}}); 158 | $('.'+instance+ '.closeDOMWindow').click(function(){ 159 | $.closeDOMWindow(settings); 160 | return false; 161 | }); 162 | }else{ 163 | $('#DOMWindowLoader').remove(); 164 | $('#DOMWindow').fadeIn('fast',function(){if(settings.functionCallOnOpen){settings.functionCallOnOpen();}}); 165 | $('#DOMWindow .closeDOMWindow').click(function(){ 166 | $.closeDOMWindow(settings); 167 | return false; 168 | }); 169 | } 170 | }; 171 | 172 | var urlQueryToObject = function(s){ 173 | var query = {}; 174 | s.replace(/b([^&=]*)=([^&=]*)b/g, function (m, a, d) { 175 | if (typeof query[a] != 'undefined') { 176 | query[a] += ',' + d; 177 | } else { 178 | query[a] = d; 179 | } 180 | }); 181 | return query; 182 | }; 183 | 184 | //Run Routine 185 | var run = function(passingThis){ 186 | 187 | //get values from element clicked, or assume its passed as an option 188 | settings.windowSourceID = $(passingThis).attr('href') || settings.windowSourceID; 189 | settings.windowSourceURL = $(passingThis).attr(settings.windowSourceAttrURL) || settings.windowSourceURL; 190 | settings.windowBGImage = settings.windowBGImage ? 'background-image:url('+settings.windowBGImage+')' : ''; 191 | var urlOnly, urlQueryObject, anchoredPositions, anchoredPositionX, anchoredPositionY; 192 | 193 | if(settings.positionType == 'anchored'){//anchored DOM window 194 | 195 | anchoredPositions = $(settings.anchoredSelector).position(); 196 | anchoredPositionX = anchoredPositions.left + settings.positionLeft; 197 | anchoredPositionY = anchoredPositions.top + settings.positionTop; 198 | 199 | $('body').append('
'); 200 | //loader 201 | if(settings.loader && settings.loaderImagePath !== ''){ 202 | $('.'+settings.anchoredClassName).append('
'); 203 | 204 | } 205 | 206 | if($.fn.draggable){ 207 | if(settings.draggable){$('.' + settings.anchoredClassName).draggable({cursor:'move'});} 208 | } 209 | 210 | switch(settings.windowSource){ 211 | case 'inline': 212 | $('.' + settings.anchoredClassName+" #DOMWindowContent").append($(settings.windowSourceID).children()); 213 | $('.' + settings.anchoredClassName).unload(function(){// move elements back when you're finished 214 | $('.' + settings.windowSourceID).append( $('.' + settings.anchoredClassName+" #DOMWindowContent").children()); 215 | }); 216 | showDOMWindow(settings.anchoredClassName); 217 | break; 218 | case 'iframe': 219 | $('.' + settings.anchoredClassName+" #DOMWindowContent").append(''); 220 | $('.'+settings.anchoredClassName+'Iframe').load(showDOMWindow(settings.anchoredClassName)); 221 | break; 222 | case 'ajax': 223 | if(settings.windowHTTPType == 'post'){ 224 | 225 | if(settings.windowSourceURL.indexOf("?") !== -1){//has a query string 226 | urlOnly = settings.windowSourceURL.substr(0, settings.windowSourceURL.indexOf("?")); 227 | urlQueryObject = urlQueryToObject(settings.windowSourceURL); 228 | }else{ 229 | urlOnly = settings.windowSourceURL; 230 | urlQueryObject = {}; 231 | } 232 | $('.' + settings.anchoredClassName+" #DOMWindowContent").load(urlOnly,urlQueryObject,function(){ 233 | showDOMWindow(settings.anchoredClassName); 234 | }); 235 | }else{ 236 | if(settings.windowSourceURL.indexOf("?") == -1){ //no query string, so add one 237 | settings.windowSourceURL += '?'; 238 | } 239 | $('.' + settings.anchoredClassName+" #DOMWindowContent").load( 240 | settings.windowSourceURL + '&random=' + (new Date().getTime()),function(){ 241 | showDOMWindow(settings.anchoredClassName); 242 | }); 243 | } 244 | break; 245 | } 246 | 247 | }else{//centered, fixed, absolute DOM window 248 | 249 | //overlay & modal 250 | if(settings.overlay){ 251 | $('body').append(''); 252 | if(shortcut.isIE6){//if IE 6 253 | $('body').append(''); 254 | sizeIE6Iframe(); 255 | } 256 | sizeOverlay(); 257 | var $DOMWindowOverlay = $('#DOMWindowOverlay'); 258 | $DOMWindowOverlay.fadeIn('fast'); 259 | if(!settings.modal) { 260 | $DOMWindowOverlay.click(function(){$.closeDOMWindow(settings);}); 261 | $(window).bind('keydown', function(e) { if (e.keyCode == 27) { $.closeDOMWindow(settings); } }); 262 | } 263 | } 264 | 265 | //loader 266 | if(settings.loader && settings.loaderImagePath !== ''){ 267 | $('body').append('
'); 268 | centerLoader(); 269 | } 270 | 271 | //add DOMwindow 272 | $('body').append(''); 273 | 274 | var $DOMWindow = $('#DOMWindow'); 275 | //centered, absolute, or fixed 276 | switch(settings.positionType){ 277 | case 'centered': 278 | centerDOMWindow(); 279 | if(settings.height + 50 > shortcut.viewPortHeight()){//added 50 to be safe 280 | $DOMWindow.css('top', (settings.fixedWindowY + shortcut.scrollOffsetHeight()) + 'px'); 281 | } 282 | break; 283 | case 'absolute': 284 | $DOMWindow.css({'top':(settings.positionTop+shortcut.scrollOffsetHeight())+'px','left':(settings.positionLeft+shortcut.scrollOffsetWidth())+'px'}); 285 | if($.fn.draggable){ 286 | if(settings.draggable){$DOMWindow.draggable({cursor:'move'});} 287 | } 288 | break; 289 | case 'fixed': 290 | fixedDOMWindow(); 291 | break; 292 | case 'anchoredSingleWindow': 293 | anchoredPositions = $(settings.anchoredSelector).position(); 294 | anchoredPositionX = anchoredPositions.left + settings.positionLeft; 295 | anchoredPositionY = anchoredPositions.top + settings.positionTop; 296 | $DOMWindow.css({'top':anchoredPositionY + 'px','left':anchoredPositionX+'px'}); 297 | 298 | break; 299 | } 300 | 301 | $(window).bind('scroll.DOMWindow',function(){ 302 | if(settings.overlay){sizeOverlay();} 303 | if(shortcut.isIE6){sizeIE6Iframe();} 304 | if(settings.positionType == 'centered'){centerDOMWindow();} 305 | if(settings.positionType == 'fixed'){fixedDOMWindow();} 306 | }); 307 | 308 | $(window).bind('resize.DOMWindow',function(){ 309 | if(shortcut.isIE6){sizeIE6Iframe();} 310 | if(settings.overlay){sizeOverlay();} 311 | if(settings.positionType == 'centered'){centerDOMWindow();} 312 | }); 313 | 314 | switch(settings.windowSource){ 315 | case 'inline': 316 | $DOMWindow.append($(settings.windowSourceID).children()); 317 | $DOMWindow.unload(function(){// move elements back when you're finished 318 | $(settings.windowSourceID).append($DOMWindow.children()); 319 | }); 320 | showDOMWindow(); 321 | break; 322 | case 'iframe': 323 | $DOMWindow.append(''); 324 | $('#DOMWindowIframe').load(showDOMWindow()); 325 | break; 326 | case 'ajax': 327 | if(settings.windowHTTPType == 'post'){ 328 | 329 | if(settings.windowSourceURL.indexOf("?") !== -1){//has a query string 330 | urlOnly = settings.windowSourceURL.substr(0, settings.windowSourceURL.indexOf("?")); 331 | urlQueryObject = urlQueryToObject(settings.windowSourceURL); 332 | }else{ 333 | urlOnly = settings.windowSourceURL; 334 | urlQueryObject = {}; 335 | } 336 | $DOMWindow.load(urlOnly,urlQueryObject,function(){ 337 | showDOMWindow(); 338 | }); 339 | }else{ 340 | if(settings.windowSourceURL.indexOf("?") == -1){ //no query string, so add one 341 | settings.windowSourceURL += '?'; 342 | } 343 | $DOMWindow.load( 344 | settings.windowSourceURL + '&random=' + (new Date().getTime()),function(){ 345 | showDOMWindow(); 346 | }); 347 | } 348 | break; 349 | } 350 | 351 | }//end if anchored, or absolute, fixed, centered 352 | 353 | };//end run() 354 | 355 | if(settings.eventType){//if used with $(). 356 | return this.each(function(index){ 357 | $(this).bind(settings.eventType,function(){ 358 | run(this); 359 | return false; 360 | }); 361 | }); 362 | }else{//else called as $.function 363 | run(); 364 | } 365 | 366 | };//end function openDOMWindow 367 | 368 | //allow for public call, pass settings 369 | $.openDOMWindow = function(s){$.fn.openDOMWindow(s);}; 370 | 371 | })(jQuery); 372 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/js/jquery.jdpicker.js: -------------------------------------------------------------------------------- 1 | /* 2 | jdPicker 1.0 3 | Requires jQuery version: >= 1.2.6 4 | 5 | 2010 - ? -- Paul Da Silva, AMJ Groupe 6 | 7 | Copyright (c) 2007-2008 Jonathan Leighton & Torchbox Ltd 8 | 9 | Permission is hereby granted, free of charge, to any person 10 | obtaining a copy of this software and associated documentation 11 | files (the "Software"), to deal in the Software without 12 | restriction, including without limitation the rights to use, 13 | copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the 15 | Software is furnished to do so, subject to the following 16 | conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | OTHER DEALINGS IN THE SOFTWARE. 29 | */ 30 | jdPicker = (function($) { 31 | 32 | function jdPicker(el, opts) { 33 | if (typeof(opts) != "object") opts = {}; 34 | $.extend(this, jdPicker.DEFAULT_OPTS, opts); 35 | 36 | this.input = $(el); 37 | this.bindMethodsToObj("show", "hide", "hideIfClickOutside", "keydownHandler", "selectDate"); 38 | 39 | this.build(); 40 | this.selectDate(); 41 | 42 | this.hide(); 43 | }; 44 | jdPicker.DEFAULT_OPTS = { 45 | month_names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], 46 | short_month_names: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 47 | short_day_names: ["S", "M", "T", "W", "T", "F", "S"], 48 | error_out_of_range: "Selected date is out of range", 49 | selectable_days: [0, 1, 2, 3, 4, 5, 6], 50 | non_selectable: [], 51 | rec_non_selectable: [], 52 | start_of_week: 1, 53 | show_week: 0, 54 | select_week: 0, 55 | week_label: "", 56 | date_min: "", 57 | date_max: "", 58 | date_format: "YYYY/mm/dd" 59 | }; 60 | jdPicker.prototype = { 61 | build: function() { 62 | 63 | this.wrapp = this.input.wrap('
'); 64 | 65 | if(this.input.context.type!="hidden"){ 66 | var clearer = $('×'); 67 | clearer.click(this.bindToObj(function(){this.input.val(""); this.selectDate();})); 68 | this.input.after(clearer); 69 | } 70 | 71 | switch (this.date_format){ 72 | case "dd/mm/YYYY": 73 | this.reg = new RegExp(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); 74 | this.date_decode = "new Date(matches[3], parseInt(matches[2]-1), matches[1]);"; 75 | this.date_encode = 'this.strpad(date.getDate()) + "/" + this.strpad(date.getMonth()+1) + "/" + date.getFullYear();'; 76 | this.date_encode_s = 'this.strpad(date.getDate()) + "/" + this.strpad(date.getMonth()+1)'; 77 | break; 78 | case "FF dd YYYY": 79 | this.reg = new RegExp(/^([a-zA-Z]+) (\d{1,2}) (\d{4})$/); 80 | this.date_decode = "new Date(matches[3], this.indexFor(this.month_names, matches[1]), matches[2]);"; 81 | this.date_encode = 'this.month_names[date.getMonth()] + " " + this.strpad(date.getDate()) + " " + date.getFullYear();'; 82 | this.date_encode_s = 'this.month_names[date.getMonth()] + " " + this.strpad(date.getDate());'; 83 | break; 84 | case "dd MM YYYY": 85 | this.reg = new RegExp(/^(\d{1,2}) ([a-zA-Z]{3}) (\d{4})$/); 86 | this.date_decode = "new Date(matches[3], this.indexFor(this.short_month_names, matches[2]), matches[1]);"; 87 | this.date_encode = 'this.strpad(date.getDate()) + " " + this.short_month_names[date.getMonth()] + " " + date.getFullYear();'; 88 | this.date_encode_s = 'this.strpad(date.getDate()) + " " + this.short_month_names[date.getMonth()];'; 89 | break; 90 | case "MM dd YYYY": 91 | this.reg = new RegExp(/^([a-zA-Z]{3}) (\d{1,2}) (\d{4})$/); 92 | this.date_decode = "new Date(matches[3], this.indexFor(this.short_month_names, matches[1]), matches[2]);"; 93 | this.date_encode = 'this.short_month_names[date.getMonth()] + " " + this.strpad(date.getDate()) + " " + date.getFullYear();'; 94 | this.date_encode_s = 'this.short_month_names[date.getMonth()] + " " + this.strpad(date.getDate());'; 95 | break; 96 | case "dd FF YYYY": 97 | this.reg = new RegExp(/^(\d{1,2}) ([a-zA-Z]+) (\d{4})$/); 98 | this.date_decode = "new Date(matches[3], this.indexFor(this.month_names, matches[2]), matches[1]);"; 99 | this.date_encode = 'this.strpad(date.getDate()) + " " + this.month_names[date.getMonth()] + " " + date.getFullYear();'; 100 | this.date_encode_s = 'this.strpad(date.getDate()) + " " + this.month_names[date.getMonth()];'; 101 | break; 102 | case "YYYY/mm/dd": 103 | default: 104 | this.reg = new RegExp(/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/); 105 | this.date_decode = "new Date(matches[1], parseInt(matches[2]-1), matches[3]);"; 106 | this.date_encode = 'date.getFullYear() + "/" + this.strpad(date.getMonth()+1) + "/" + this.strpad(date.getDate());'; 107 | this.date_encode_s = 'this.strpad(date.getMonth()+1) + "/" + this.strpad(date.getDate());'; 108 | break; 109 | } 110 | 111 | if(this.date_max != "" && this.date_max.match(this.reg)){ 112 | var matches = this.date_max.match(this.reg); 113 | this.date_max = eval(this.date_decode); 114 | }else 115 | this.date_max = ""; 116 | 117 | if(this.date_min != "" && this.date_min.match(this.reg)){ 118 | var matches = this.date_min.match(this.reg); 119 | this.date_min = eval(this.date_decode); 120 | }else 121 | this.date_min = ""; 122 | 123 | var monthNav = $('

' + 124 | '«' + 125 | ' ' + 126 | '»' + 127 | '

'); 128 | 129 | this.monthNameSpan = $(".month_name", monthNav); 130 | $(".prev", monthNav).click(this.bindToObj(function() { this.moveMonthBy(-1); })); 131 | $(".next", monthNav).click(this.bindToObj(function() { this.moveMonthBy(1); })); 132 | 133 | this.monthNameSpan.dblclick(this.bindToObj(function(){ 134 | this.monthNameSpan.empty().append(this.getMonthSelect()); 135 | $('select', this.monthNameSpan).change(this.bindToObj(function(){ 136 | this.moveMonthBy(parseInt($('select :selected', this.monthNameSpan).val()) - this.currentMonth.getMonth()); 137 | })); 138 | })); 139 | 140 | var yearNav = $('

' + 141 | '«' + 142 | ' ' + 143 | '»' + 144 | '

'); 145 | 146 | this.yearNameSpan = $(".year_name", yearNav); 147 | $(".prev", yearNav).click(this.bindToObj(function() { this.moveMonthBy(-12); })); 148 | $(".next", yearNav).click(this.bindToObj(function() { this.moveMonthBy(12); })); 149 | 150 | this.yearNameSpan.dblclick(this.bindToObj(function(){ 151 | 152 | if($('.year_name input', this.rootLayers).length==0){ 153 | var initialDate = this.yearNameSpan.html(); 154 | 155 | var yearNameInput = $(''); 156 | this.yearNameSpan.empty().append(yearNameInput); 157 | 158 | $(".year_input", yearNav).keyup(this.bindToObj(function(){ 159 | if($('input',this.yearNameSpan).val().length == 4 && $('input',this.yearNameSpan).val() != initialDate && parseInt($('input',this.yearNameSpan).val()) == $('input',this.yearNameSpan).val()){ 160 | this.moveMonthBy(parseInt(parseInt(parseInt($('input',this.yearNameSpan).val()) - initialDate)*12)); 161 | }else if($('input',this.yearNameSpan).val().length>4) 162 | $('input',this.yearNameSpan).val($('input',this.yearNameSpan).val().substr(0, 4)); 163 | })); 164 | 165 | $('input',this.yearNameSpan).focus(); 166 | $('input',this.yearNameSpan).select(); 167 | } 168 | 169 | })); 170 | 171 | var error_msg = $('
'); 172 | 173 | var nav = $('').append(error_msg, monthNav, yearNav); 174 | 175 | var tableShell = ""; 176 | 177 | if(this.show_week == 1) tableShell +=''; 178 | 179 | $(this.adjustDays(this.short_day_names)).each(function() { 180 | tableShell += ""; 181 | }); 182 | 183 | tableShell += "
'+(this.week_label)+'" + this + "
"; 184 | 185 | var style = (this.input.context.type=="hidden")?' style="display:block; position:static; margin:0 auto"':''; 186 | 187 | this.dateSelector = this.rootLayers = $('
').append(nav, tableShell).insertAfter(this.input); 188 | 189 | if ($.browser.msie && $.browser.version < 7) { 190 | 191 | this.ieframe = $('').insertBefore(this.dateSelector); 192 | this.rootLayers = this.rootLayers.add(this.ieframe); 193 | 194 | $(".button", nav).mouseover(function() { $(this).addClass("hover"); }); 195 | $(".button", nav).mouseout(function() { $(this).removeClass("hover"); }); 196 | }; 197 | 198 | this.tbody = $("tbody", this.dateSelector); 199 | 200 | this.input.change(this.bindToObj(function() { this.selectDate(); })); 201 | this.selectDate(); 202 | 203 | }, 204 | 205 | selectMonth: function(date) { 206 | var newMonth = new Date(date.getFullYear(), date.getMonth(), date.getDate()); 207 | if(this.isNewDateAllowed(newMonth)){ 208 | if (!this.currentMonth || !(this.currentMonth.getFullYear() == newMonth.getFullYear() && 209 | this.currentMonth.getMonth() == newMonth.getMonth())) { 210 | 211 | this.currentMonth = newMonth; 212 | 213 | var rangeStart = this.rangeStart(date), rangeEnd = this.rangeEnd(date); 214 | var numDays = this.daysBetween(rangeStart, rangeEnd); 215 | var dayCells = ""; 216 | 217 | for (var i = 0; i <= numDays; i++) { 218 | var currentDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + i, 12, 00); 219 | 220 | if (this.isFirstDayOfWeek(currentDay)){ 221 | 222 | var firstDayOfWeek = currentDay; 223 | var lastDayOfWeek = new Date(currentDay.getFullYear(), currentDay.getMonth(), currentDay.getDate()+6, 12, 00); 224 | 225 | if(this.select_week && this.isNewDateAllowed(firstDayOfWeek)) 226 | dayCells += ""; 227 | else 228 | dayCells += ""; 229 | 230 | if(this.show_week==1) 231 | dayCells += ''+this.getWeekNum(currentDay)+''; 232 | } 233 | if ((this.select_week == 0 && currentDay.getMonth() == date.getMonth() && this.isNewDateAllowed(currentDay) && !this.isHoliday(currentDay)) || (this.select_week==1 && currentDay.getMonth() == date.getMonth() && this.isNewDateAllowed(firstDayOfWeek))) { 234 | dayCells += '' + currentDay.getDate() + ''; 235 | } else { 236 | dayCells += '' + currentDay.getDate() + ''; 237 | }; 238 | 239 | if (this.isLastDayOfWeek(currentDay)) dayCells += ""; 240 | }; 241 | this.tbody.empty().append(dayCells); 242 | 243 | this.monthNameSpan.empty().append(this.monthName(date)); 244 | this.yearNameSpan.empty().append(this.currentMonth.getFullYear()); 245 | 246 | if(this.select_week == 0){ 247 | $(".selectable_day", this.tbody).click(this.bindToObj(function(event) { 248 | this.changeInput($(event.target).attr("date")); 249 | })); 250 | }else{ 251 | $(".selectable_week", this.tbody).click(this.bindToObj(function(event) { 252 | this.changeInput($(event.target.parentNode).attr("date")); 253 | })); 254 | } 255 | 256 | $("td[date=\"" + this.dateToString(new Date()) + "\"]", this.tbody).addClass("today"); 257 | if(this.select_week == 1){ 258 | $("tr", this.tbody).mouseover(function() { $(this).addClass("hover"); }); 259 | $("tr", this.tbody).mouseout(function() { $(this).removeClass("hover"); }); 260 | }else{ 261 | $("td.selectable_day", this.tbody).mouseover(function() { $(this).addClass("hover"); }); 262 | $("td.selectable_day", this.tbody).mouseout(function() { $(this).removeClass("hover"); }); 263 | } 264 | }; 265 | 266 | $('.selected', this.tbody).removeClass("selected"); 267 | $('td[date="' + this.selectedDateString + '"], tr[date="' + this.selectedDateString + '"]', this.tbody).addClass("selected"); 268 | }else 269 | this.show_error(this.error_out_of_range); 270 | }, 271 | 272 | selectDate: function(date) { 273 | if (typeof(date) == "undefined") { 274 | date = this.stringToDate(this.input.val()); 275 | }; 276 | if (!date) date = new Date(); 277 | 278 | if(this.select_week == 1 && !this.isFirstDayOfWeek(date)) 279 | date = new Date(date.getFullYear(), date.getMonth(), (date.getDate() - date.getDay() + this.start_of_week), 12, 00); 280 | 281 | if(this.isNewDateAllowed(date)){ 282 | this.selectedDate = date; 283 | this.selectedDateString = this.dateToString(this.selectedDate); 284 | this.selectMonth(this.selectedDate); 285 | }else if((this.date_min) && this.daysBetween(this.date_min, date)<0){ 286 | this.selectedDate = this.date_min; 287 | this.selectMonth(this.date_min); 288 | this.input.val(" "); 289 | }else{ 290 | this.selectMonth(this.date_max); 291 | this.input.val(" "); 292 | } 293 | }, 294 | 295 | isNewDateAllowed: function(date){ 296 | return ((!this.date_min) || this.daysBetween(this.date_min, date)>=0) && ((!this.date_max) || this.daysBetween(date, this.date_max)>=0); 297 | }, 298 | 299 | isHoliday: function(date){ 300 | return ((this.indexFor(this.selectable_days, date.getDay())===false || this.indexFor(this.non_selectable, this.dateToString(date))!==false) || this.indexFor(this.rec_non_selectable, this.dateToShortString(date))!==false); 301 | }, 302 | 303 | changeInput: function(dateString) { 304 | this.input.val(dateString).change(); 305 | if(this.input.context.type!="hidden") 306 | this.hide(); 307 | }, 308 | 309 | show: function() { 310 | $('.error_msg', this.rootLayers).css('display', 'none'); 311 | this.rootLayers.fadeIn(); 312 | $([window, document.body]).click(this.hideIfClickOutside); 313 | this.input.unbind("click", this.show); 314 | this.input.attr('readonly', true); 315 | $(document.body).keydown(this.keydownHandler); 316 | this.setPosition(); 317 | return false; 318 | }, 319 | 320 | hide: function() { 321 | if(this.input.context.type!="hidden"){ 322 | this.input.removeAttr('readonly'); 323 | this.rootLayers.fadeOut(); 324 | $([window, document.body]).unbind("click", this.hideIfClickOutside); 325 | this.input.click(this.show); 326 | $(document.body).unbind("keydown", this.keydownHandler); 327 | } 328 | }, 329 | 330 | hideIfClickOutside: function(event) { 331 | if (event.target != this.input[0] && !this.insideSelector(event)) { 332 | this.hide(); 333 | }; 334 | }, 335 | 336 | insideSelector: function(event) { 337 | var offset = this.dateSelector.offset(); 338 | offset.right = offset.left + this.dateSelector.outerWidth(); 339 | offset.bottom = offset.top + this.dateSelector.outerHeight(); 340 | 341 | return event.pageY < offset.bottom && 342 | event.pageY > offset.top && 343 | event.pageX < offset.right && 344 | event.pageX > offset.left; 345 | }, 346 | 347 | keydownHandler: function(event) { 348 | switch (event.keyCode) 349 | { 350 | case 9: 351 | case 27: 352 | this.hide(); 353 | return; 354 | break; 355 | case 13: 356 | if(this.isNewDateAllowed(this.stringToDate(this.selectedDateString)) && !this.isHoliday(this.stringToDate(this.selectedDateString))) 357 | this.changeInput(this.selectedDateString); 358 | break; 359 | case 33: 360 | this.moveDateMonthBy(event.ctrlKey ? -12 : -1); 361 | break; 362 | case 34: 363 | this.moveDateMonthBy(event.ctrlKey ? 12 : 1); 364 | break; 365 | case 38: 366 | this.moveDateBy(-7); 367 | break; 368 | case 40: 369 | this.moveDateBy(7); 370 | break; 371 | case 37: 372 | if(this.select_week == 0) this.moveDateBy(-1); 373 | break; 374 | case 39: 375 | if(this.select_week == 0) this.moveDateBy(1); 376 | break; 377 | default: 378 | return; 379 | } 380 | event.preventDefault(); 381 | }, 382 | 383 | stringToDate: function(string) { 384 | var matches; 385 | 386 | if (matches = string.match(this.reg)) { 387 | if(matches[3]==0 && matches[2]==0 && matches[1]==0) 388 | return null; 389 | else 390 | return eval(this.date_decode); 391 | } else { 392 | return null; 393 | }; 394 | }, 395 | 396 | dateToString: function(date) { 397 | return eval(this.date_encode); 398 | }, 399 | 400 | dateToShortString: function(date){ 401 | return eval(this.date_encode_s); 402 | }, 403 | 404 | setPosition: function() { 405 | var position = this.input.position(); 406 | this.rootLayers.css({ 407 | top: position.top + this.input.outerHeight(), 408 | left: position.left 409 | }); 410 | 411 | if (this.ieframe) { 412 | this.ieframe.css({ 413 | width: this.dateSelector.outerWidth(), 414 | height: this.dateSelector.outerHeight() 415 | }); 416 | }; 417 | }, 418 | 419 | moveDateBy: function(amount) { 420 | var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate() + amount); 421 | this.selectDate(newDate); 422 | }, 423 | 424 | moveDateMonthBy: function(amount) { 425 | var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth() + amount, this.selectedDate.getDate()); 426 | if (newDate.getMonth() == this.selectedDate.getMonth() + amount + 1) { 427 | newDate.setDate(0); 428 | }; 429 | this.selectDate(newDate); 430 | }, 431 | 432 | moveMonthBy: function(amount) { 433 | if(amount<0) 434 | var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + amount+1, -1); 435 | else 436 | var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + amount, 1); 437 | this.selectMonth(newMonth); 438 | }, 439 | 440 | monthName: function(date) { 441 | return this.month_names[date.getMonth()]; 442 | }, 443 | 444 | getMonthSelect:function(){ 445 | var month_select = ''; 453 | 454 | return month_select; 455 | }, 456 | 457 | bindToObj: function(fn) { 458 | var self = this; 459 | return function() { return fn.apply(self, arguments) }; 460 | }, 461 | 462 | bindMethodsToObj: function() { 463 | for (var i = 0; i < arguments.length; i++) { 464 | this[arguments[i]] = this.bindToObj(this[arguments[i]]); 465 | }; 466 | }, 467 | 468 | indexFor: function(array, value) { 469 | for (var i = 0; i < array.length; i++) { 470 | if (value == array[i]) return i; 471 | }; 472 | return false; 473 | }, 474 | 475 | monthNum: function(month_name) { 476 | return this.indexFor(this.month_names, month_name); 477 | }, 478 | 479 | shortMonthNum: function(month_name) { 480 | return this.indexFor(this.short_month_names, month_name); 481 | }, 482 | 483 | shortDayNum: function(day_name) { 484 | return this.indexFor(this.short_day_names, day_name); 485 | }, 486 | 487 | daysBetween: function(start, end) { 488 | start = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate()); 489 | end = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate()); 490 | return (end - start) / 86400000; 491 | }, 492 | 493 | changeDayTo: function(dayOfWeek, date, direction) { 494 | var difference = direction * (Math.abs(date.getDay() - dayOfWeek - (direction * 7)) % 7); 495 | return new Date(date.getFullYear(), date.getMonth(), date.getDate() + difference); 496 | }, 497 | 498 | rangeStart: function(date) { 499 | return this.changeDayTo(this.start_of_week, new Date(date.getFullYear(), date.getMonth()), -1); 500 | }, 501 | 502 | rangeEnd: function(date) { 503 | return this.changeDayTo((this.start_of_week - 1) % 7, new Date(date.getFullYear(), date.getMonth() + 1, 0), 1); 504 | }, 505 | 506 | isFirstDayOfWeek: function(date) { 507 | return date.getDay() == this.start_of_week; 508 | }, 509 | 510 | getWeekNum:function(date){ 511 | date_week= new Date(date.getFullYear(), date.getMonth(), date.getDate()+6); 512 | var firstDayOfYear = new Date(date_week.getFullYear(), 0, 1, 12, 00); 513 | var n = parseInt(this.daysBetween(firstDayOfYear, date_week)) + 1; 514 | return Math.floor((date_week.getDay() + n + 5)/7) - Math.floor(date_week.getDay() / 5); 515 | }, 516 | 517 | isLastDayOfWeek: function(date) { 518 | return date.getDay() == (this.start_of_week - 1) % 7; 519 | }, 520 | 521 | show_error: function(error){ 522 | $('.error_msg', this.rootLayers).html(error); 523 | $('.error_msg', this.rootLayers).fadeIn(400, function(){ 524 | setTimeout("$('.error_msg', this.rootLayers).fadeOut(200);", 2000); 525 | }); 526 | }, 527 | 528 | adjustDays: function(days) { 529 | var newDays = []; 530 | for (var i = 0; i < days.length; i++) { 531 | newDays[i] = days[(i + this.start_of_week) % 7]; 532 | }; 533 | return newDays; 534 | }, 535 | 536 | strpad: function(num){ 537 | if(parseInt(num)<10) return "0"+parseInt(num); 538 | else return parseInt(num); 539 | } 540 | 541 | }; 542 | 543 | $.fn.jdPicker = function(opts) { 544 | return this.each(function() { new jdPicker(this, opts); }); 545 | }; 546 | $.jdPicker = { initialize: function(opts) { 547 | $("input.jdpicker").jdPicker(opts); 548 | } }; 549 | 550 | return jdPicker; 551 | })(jQuery); 552 | 553 | $($.jdPicker.initialize); 554 | -------------------------------------------------------------------------------- /digitalpaper/static/digitalpaper/js/reader.js: -------------------------------------------------------------------------------- 1 | var libeReader = function() { 2 | var _publicationId, _bookName, _publication, _selectedBook, _pages, 3 | _displayedPage, _displayedBook, _zoomWindow, _winHeight, _winWidth, 4 | _numberOfPages, _isZoomed, _zoomedPageHeight, _zoomedPageWidth, 5 | _zoomMouseInit, _zoomPosInit, _zoomedPages, _zoomMouseDown, _step, 6 | _HDgridContainer; 7 | 8 | _step = 21; 9 | 10 | function bindButtons() { 11 | jQuery('#previousButton, #previousCorner').click(showPreviousPage); 12 | jQuery('#nextButton, #nextCorner').click(showNextPage); 13 | jQuery('#firstButton').click(showFirstPage); 14 | jQuery('#lastButton').click(showLastPage); 15 | jQuery('#evenSide, #oddSide').click(zoom); 16 | } 17 | 18 | function unbindButtons() { 19 | jQuery('#previousButton, #previousCorner').unbind("click", showPreviousPage); 20 | jQuery('#nextButton, #nextCorner').unbind("click", showNextPage); 21 | jQuery('#firstButton').unbind("click", showFirstPage); 22 | jQuery('#lastButton').unbind("click", showLastPage); 23 | jQuery('#evenSide, #oddSide').unbind("click"); 24 | } 25 | 26 | function bindKeyboard() { 27 | jQuery(document).bind('keydown', keyboardCallback); 28 | } 29 | 30 | function unbindKeyboard() { 31 | jQuery(document).unbind('keydown', keyboardCallback); 32 | } 33 | 34 | function zoom(event) { 35 | var offset = jQuery(this).offset(); 36 | if (!offset) { 37 | offset = { 38 | 'left': 0, 39 | 'top': 0 40 | }; 41 | } 42 | var x = event.pageX - offset.left; 43 | var y = event.pageY - offset.top; 44 | 45 | var previousElement = jQuery(this).prev(); 46 | if (previousElement) { 47 | x = x + previousElement.width(); 48 | } 49 | zoomAtCoordinates(x, y); 50 | return false; 51 | } 52 | 53 | function zoomAtCoordinates(x, y) { 54 | if (!libeConfig.canZoom()) { 55 | libeConfig.restrictedAccess(); 56 | return false; 57 | } 58 | if (_isZoomed) { 59 | return false; 60 | } 61 | 62 | jQuery(document).trigger('page-beforezoom', [_displayedPage]); 63 | 64 | x = x * libeConfig.zoomFactor; 65 | y = y * libeConfig.zoomFactor; 66 | 67 | _zoomedPageHeight = libeConfig.pageHeight * libeConfig.zoomFactor; 68 | _zoomedPageWidth = libeConfig.pageWidth * libeConfig.zoomFactor; 69 | 70 | jQuery('#pagesSlider').hide(); 71 | jQuery('#bookSwitcher').hide(); 72 | 73 | var height = jQuery(window).height(); 74 | jQuery(document.body).css({'overflow': 'hidden', 'height': height }); 75 | document.body.scrollTop = 0; 76 | document.documentElement.scrollTop = 0; 77 | 78 | _zoomWindow = jQuery(document.createElement('div')); 79 | _zoomWindow.attr('id', 'zoomWindow'); 80 | _zoomWindow.addClass('grab'); 81 | 82 | zoomResize(); 83 | 84 | _zoomedPages = jQuery(document.createElement('div')); 85 | _zoomedPages.attr('id', 'zoomedPages'); 86 | 87 | _numberOfPages = 0; 88 | if (_pages[_displayedPage]) { 89 | var leftPage = jQuery(document.createElement('img')); 90 | leftPage.attr({'id': 'leftPageZoomed', 'src': _pages[_displayedPage].imageSource}); 91 | _zoomedPages.append(leftPage); 92 | _numberOfPages++; 93 | } 94 | if (_pages[_displayedPage + 1]) { 95 | var rightPage = jQuery(document.createElement('img')); 96 | rightPage.attr({'id': 'rightPageZoomed', 'src': _pages[_displayedPage + 1].imageSource}); 97 | _zoomedPages.append(rightPage); 98 | _numberOfPages++; 99 | } 100 | if (_numberOfPages == 1) { 101 | _zoomedPages.children().first().css('width', '100%'); 102 | } 103 | 104 | var top = y - (_winHeight / 2); 105 | top = zoomTopInArea(top); 106 | 107 | if (_numberOfPages == 1 && x > _zoomedPageWidth) { 108 | x = x - _zoomedPageWidth; 109 | } 110 | var left = x - (_winWidth / 2); 111 | left = zoomLeftInArea(left); 112 | _zoomedPages.css({'height': _zoomedPageHeight, 'width': _numberOfPages * _zoomedPageWidth, 'top': -top, 'left': -left}); 113 | 114 | _zoomWindow.append(_zoomedPages); 115 | _zoomWindow.dblclick(quitZoom); 116 | 117 | _zoomWindow.mousedown(zoomMouseDown); 118 | _zoomWindow.mouseup(zoomMouseUp); 119 | _zoomWindow.mousemove(zoomMouseMove); 120 | jQuery(document.body).mouseleave(zoomMouseUp); 121 | jQuery(document.body).bind('mousewheel', zoomMouseWheel); 122 | jQuery(document.body).addClass('zoomed'); 123 | jQuery(window).bind('resize', zoomResize); 124 | 125 | jQuery(document.body).append(_zoomWindow); 126 | 127 | // Just for Apple touch, jQuery suxx and delete e.touches 128 | var zw = document.getElementById('zoomWindow'); 129 | if (zw && zw.addEventListener) { 130 | zw.addEventListener('touchstart', zoomMouseDown, true); 131 | zw.addEventListener('touchend', zoomMouseUp, true); 132 | zw.addEventListener('touchmove', zoomMouseMove, true); 133 | } 134 | 135 | zoomInitHDGrid(top, left); 136 | _isZoomed = true; 137 | jQuery('#zoomButton').addClass('unzoom'); 138 | jQuery(document).trigger('page-afterzoom', [_displayedPage]); 139 | } 140 | 141 | function zoomInitHDGrid(top, left) { 142 | _HDgridContainer = jQuery(document.createElement('div')); 143 | _HDgridContainer.css({'height': _zoomedPageHeight, 'width': _numberOfPages * _zoomedPageWidth, 'top': -top, 'left': -left}); 144 | _HDgridContainer.attr('id', 'HDGrid'); 145 | 146 | var nbZones = _numberOfPages * libeConfig.imagesPerRow * libeConfig.imagesPerColumn; 147 | var xRowSize = Math.floor(_zoomedPageWidth / libeConfig.imagesPerRow); 148 | var yColumnSize = Math.floor(_zoomedPageHeight / libeConfig.imagesPerColumn); 149 | 150 | for (var i = 0; i < nbZones; i++) { 151 | var img = jQuery(document.createElement('img')); 152 | img.addClass('grid'); 153 | img.css({'height': yColumnSize, 'width': xRowSize}); 154 | _HDgridContainer.append(img); 155 | } 156 | _zoomWindow.append(_HDgridContainer); 157 | 158 | zoomHighDefAtCoordinates(left, top); 159 | } 160 | 161 | function quitZoom() { 162 | jQuery(document).trigger('page-leavezoom', [_displayedPage]); 163 | jQuery(_zoomWindow).detach(); 164 | jQuery(window).unbind('resize', zoomResize); 165 | jQuery(document.body).unbind('mousewheel'); 166 | jQuery(document.body).removeClass('zoomed'); 167 | jQuery('#pagesSlider').show(); 168 | jQuery('#bookSwitcher').show(); 169 | jQuery(document.body).css({'overflow': 'visible', 'height': 'auto' }); 170 | _isZoomed = false; 171 | jQuery('#zoomButton').removeClass('unzoom'); 172 | return false; 173 | } 174 | 175 | function zoomResize() { 176 | var win = jQuery(window); 177 | _winHeight = win.height(); 178 | _winWidth = win.width(); 179 | } 180 | 181 | function keyboardCallback(e) { 182 | if (jQuery('#DOMWindow').length <= 0) { 183 | if (_isZoomed) { 184 | return zoomedKeyboardCallback(e); 185 | } else { 186 | return normalKeyboardCallback(e); 187 | } 188 | } 189 | } 190 | 191 | function normalKeyboardCallback(e) { 192 | if (e.ctrlKey) { 193 | switch (e.which) { 194 | case 109: // - 195 | case 40: // bottom 196 | e.preventDefault(); 197 | break; 198 | case 61: // = 199 | case 107: // + 200 | case 38: // up 201 | zoom(e); 202 | e.preventDefault(); 203 | break; 204 | case 35: // end 205 | showLastPage(e); 206 | break; 207 | case 36: // home 208 | showFirstPage(e); 209 | break; 210 | case 37: // left 211 | showPreviousPage(e); 212 | break; 213 | case 39: // right 214 | showNextPage(e); 215 | break; 216 | default: 217 | break; 218 | } 219 | } 220 | } 221 | 222 | function zoomedKeyboardCallback(e) { 223 | _zoomLoadPosInit(); 224 | var x = 0; 225 | var y = 0; 226 | if (e.ctrlKey) { 227 | switch (e.which) { 228 | case 27: // esc 229 | case 109: // - 230 | case 40: // bottom 231 | quitZoom(); 232 | e.preventDefault(); 233 | break; 234 | default: 235 | break; 236 | } 237 | } else { 238 | switch (e.which) { 239 | case 27: // esc 240 | quitZoom(); 241 | e.preventDefault(); 242 | break; 243 | case 37: // left 244 | x = -_step; 245 | break; 246 | case 38: // up 247 | y = -_step; 248 | break; 249 | case 39: // right 250 | x = _step; 251 | break; 252 | case 40: // bottom 253 | y = _step; 254 | break; 255 | default: 256 | break; 257 | } 258 | } 259 | if (x || y) { 260 | zoomBy(x, y); 261 | e.preventDefault(); 262 | } 263 | } 264 | 265 | function _zoomLoadPosInit() { 266 | _zoomPosInit = {x: -parseInt(_zoomedPages.css('left'), 10), y: -parseInt(_zoomedPages.css('top'), 10)}; 267 | } 268 | 269 | function zoomMouseDown(e) { 270 | // iPhone/iPad 271 | if (e.touches) { 272 | e.preventDefault(); 273 | e = e.touches[0]; 274 | } else { 275 | e.preventDefault(); 276 | } 277 | 278 | _zoomMouseDown = true; 279 | _zoomLoadPosInit(); 280 | _zoomMouseInit = {x: e.clientX, y: e.clientY}; 281 | _zoomWindow.addClass('grabbing'); 282 | _zoomWindow.removeClass('grab'); 283 | } 284 | 285 | function zoomMouseUp(e) { 286 | _zoomMouseDown = false; 287 | _zoomWindow.addClass('grab'); 288 | _zoomWindow.removeClass('grabbing'); 289 | e.preventDefault(); 290 | 291 | zoomHighDefAtCoordinates(-parseInt(_zoomedPages.css('left'), 10), -parseInt(_zoomedPages.css('top'), 10)); 292 | } 293 | 294 | function zoomMouseMove(e) { 295 | if (_zoomMouseDown !== true) { 296 | return; 297 | } 298 | 299 | // iPhone/iPad 300 | if (e.touches) { 301 | e.preventDefault(); 302 | e = e.touches[0]; 303 | } else { 304 | e.preventDefault(); 305 | } 306 | 307 | zoomBy(_zoomMouseInit.x - e.clientX, _zoomMouseInit.y - e.clientY); 308 | } 309 | 310 | function zoomMouseWheel(e, deltaX, deltaY) { 311 | _zoomLoadPosInit(); 312 | zoomBy(-_step * deltaX, -_step * deltaY); 313 | e.preventDefault(); 314 | } 315 | 316 | function zoomBy(x, y) { 317 | var newLeft = _zoomPosInit.x + (x); 318 | var newTop = _zoomPosInit.y + (y); 319 | 320 | newLeft = zoomLeftInArea(newLeft); 321 | newTop = zoomTopInArea(newTop); 322 | 323 | _zoomedPages.css({'left': -newLeft, 'top': -newTop}); 324 | _HDgridContainer.css({'left': -newLeft, 'top': -newTop}); 325 | } 326 | 327 | function zoomLeftInArea(left) { 328 | if (left < 0) { 329 | left = 0; 330 | } 331 | if (left > _numberOfPages * _zoomedPageWidth - _winWidth) { 332 | left = _numberOfPages * _zoomedPageWidth - _winWidth; 333 | } 334 | 335 | return left; 336 | } 337 | function zoomTopInArea(top) { 338 | if (top < 0) { 339 | top = 0; 340 | } 341 | if (top > _zoomedPageHeight - _winHeight) 342 | { 343 | top = _zoomedPageHeight - _winHeight; 344 | } 345 | return top; 346 | } 347 | 348 | function zoomHighDefAtCoordinates(x, y) { 349 | x = x + (_winWidth / 2); 350 | y = y + (_winHeight / 2); 351 | 352 | var xRowSize = _zoomedPageWidth / libeConfig.imagesPerRow; 353 | var yColumnSize = _zoomedPageHeight / libeConfig.imagesPerColumn; 354 | 355 | var xRow = Math.floor(x / xRowSize); 356 | var yColumn = Math.floor(y / yColumnSize); 357 | getZoomImage(xRow, yColumn); 358 | 359 | for (var i = 0; i < _numberOfPages * libeConfig.imagesPerRow + libeConfig.imagesPerColumn; i++) { 360 | for (var j = 0; j < i; j++) { 361 | var plop = i - j; 362 | getZoomImage(xRow - j, yColumn - plop); 363 | getZoomImage(xRow + j, yColumn + plop); 364 | getZoomImage(xRow - plop, yColumn + j); 365 | getZoomImage(xRow + plop, yColumn - j); 366 | } 367 | } 368 | } 369 | 370 | function getZoomImage(xRow, yColumn) { 371 | if (xRow < 0 || yColumn < 0) { 372 | return; 373 | } 374 | 375 | if (yColumn >= libeConfig.imagesPerColumn) { 376 | return; 377 | } 378 | 379 | if (_displayedPage === 0) { 380 | // If _displayedPage is 0, it means we are displaying the first page, 381 | // which is alone on the *right* hand side. 382 | // Constraints need to change in that case, to allow the coordinates 383 | // on the right (where the first page is) and disallow the ones on the left 384 | // (where there isn't any page) 385 | if (xRow >= 2 * libeConfig.imagesPerRow || xRow < libeConfig.imagesPerRow) { 386 | return; 387 | } 388 | } else { 389 | if (xRow >= _numberOfPages * libeConfig.imagesPerRow) { 390 | return; 391 | } 392 | } 393 | 394 | var imgIndex = 0; 395 | if (_displayedPage === 0) { 396 | // Another hack for the first page: there are only half the number of images, 397 | // the indexing need to be changed. (Note: we don't want to change xRow and yColumn 398 | // directly, the web services expect x to be > imagesPerRow on the right page, 399 | // even if it's the first one!) 400 | imgIndex = yColumn * libeConfig.imagesPerRow + xRow - libeConfig.imagesPerRow; 401 | } else { 402 | imgIndex = yColumn * libeConfig.imagesPerRow * _numberOfPages + xRow; 403 | } 404 | 405 | var img = _HDgridContainer.children().eq(imgIndex); 406 | if (!img) { 407 | return; 408 | } 409 | 410 | if (img.attr('src')) { 411 | return; 412 | } 413 | 414 | var currentPage = _pages[_displayedPage + Math.floor(xRow / libeConfig.imagesPerRow)]; 415 | if (!currentPage) { 416 | return; 417 | } 418 | 419 | if (currentPage.pageId <= 0) { 420 | img.css({'visibility' : 'hidden'}); 421 | } else { 422 | var replaces = { 423 | '{format}': 'png', 424 | '{id}': currentPage.pageId, 425 | '{crop}': libeConfig.imagesPerRow + 'x' + libeConfig.imagesPerColumn, 426 | '{x}': xRow, 427 | '{y}': yColumn 428 | }; 429 | 430 | var src = libeConfig.webservices.paper_page_cropped; 431 | var k; 432 | for (k in replaces) { 433 | src = src.replace(k, replaces[k]); 434 | } 435 | img.attr('src', src); 436 | } 437 | } 438 | 439 | function showHoverCorner() { 440 | jQuery(this).css('opacity', 1); 441 | } 442 | function hideHoverCorner() { 443 | jQuery(this).css('opacity', 0); 444 | } 445 | 446 | function displayPagination() { 447 | var previousButtons = jQuery('#previousCorner, #pagesBefore'); 448 | if (_displayedPage - 2 >= 0) { 449 | previousButtons.show(); 450 | } else { 451 | previousButtons.hide(); 452 | } 453 | 454 | var nextButtons = jQuery('#nextCorner, #pagesAfter'); 455 | if (_displayedPage + 2 <= _selectedBook.pagination) { 456 | nextButtons.show(); 457 | } else { 458 | nextButtons.hide(); 459 | } 460 | readerSlider.moveIntoView(_displayedPage); 461 | } 462 | 463 | function showPage(number) { 464 | var newDisplayedPage = number - number % 2; 465 | 466 | // Non-existant page, nothing to do 467 | if (!_pages[newDisplayedPage] && !_pages[newDisplayedPage + 1]) { 468 | return; 469 | } 470 | 471 | jQuery('#oddSide .pageInfo, #evenSide .pageInfo').fadeOut(); 472 | 473 | unbindButtons(); 474 | unbindKeyboard(); 475 | 476 | var evenSide = jQuery('#evenSide'); 477 | var oddSide = jQuery('#oddSide'); 478 | var finalWidth = evenSide.width(); 479 | var height = evenSide.parent().height(); 480 | var position = evenSide.position(); 481 | 482 | var leftPage = jQuery(document.createElement('div')); 483 | leftPage.addClass('leftPage'); 484 | if (_pages[newDisplayedPage]) { 485 | evenSide.css({'visibility' : 'visible'}); 486 | leftPage.css('background-image', 'url(' + _pages[newDisplayedPage].imageSource + ')'); 487 | } else { 488 | evenSide.css({'visibility' : 'hidden'}); 489 | } 490 | 491 | var rightPage = jQuery(document.createElement('div')); 492 | rightPage.addClass('rightPage'); 493 | if (_pages[newDisplayedPage + 1]) { 494 | oddSide.css({'visibility' : 'visible'}); 495 | rightPage.css('background-image', 'url(' + _pages[newDisplayedPage + 1].imageSource + ')'); 496 | } else { 497 | oddSide.css({'visibility' : 'hidden'}); 498 | } 499 | 500 | var transitionElement = jQuery(document.createElement('div')); 501 | transitionElement.addClass('transitionPage'); 502 | transitionElement.css('height', height); 503 | if (_displayedPage > newDisplayedPage) { 504 | transitionElement.css('left', 0); 505 | } else { 506 | transitionElement.css('right', 0); 507 | } 508 | transitionElement.append(leftPage); 509 | transitionElement.append(rightPage); 510 | 511 | var transitionContainerElement = jQuery(document.createElement('div')); 512 | transitionContainerElement.addClass('transitionContainer'); 513 | transitionContainerElement.css({'width': 2 * finalWidth, 'height': height, 514 | 'left': position.left}); 515 | transitionContainerElement.append(transitionElement); 516 | evenSide.parent().append(transitionContainerElement); 517 | 518 | transitionElement.animate({'width': 2 * finalWidth}, function() { 519 | cleanAfterShowPage(number); 520 | jQuery(this).parent().detach(); 521 | }); 522 | } 523 | 524 | function _hideOldPages() { 525 | if (typeof _displayedPage != "undefined") { 526 | if (_pages[_displayedPage]) { 527 | _pages[_displayedPage].hide(); 528 | } 529 | if (_pages[_displayedPage + 1]) { 530 | _pages[_displayedPage + 1].hide(); 531 | } 532 | } 533 | unHighlightCurrentPages(); 534 | } 535 | 536 | function displayPage(number) { 537 | var page = _pages[number]; 538 | page.show(); 539 | highlightCurrentPages(_displayedBook, number); 540 | var elm = jQuery('#' + ((page.pageNumber % 2 === 0) ? 'even' : 'odd') + 'Side .pageInfo'); 541 | elm.html(page.getPageInfo()); 542 | elm.fadeIn(); 543 | } 544 | 545 | function bookDisplayed() { 546 | return _displayedBook; 547 | } 548 | 549 | function pageDisplayed() { 550 | return _displayedPage; 551 | } 552 | 553 | function cleanAfterShowPage(number) { 554 | _hideOldPages(); 555 | 556 | var newDisplayedPage = number - number % 2; 557 | if (_pages[newDisplayedPage] || _pages[newDisplayedPage + 1]) { 558 | _displayedPage = newDisplayedPage; 559 | window.location.hash = "#!/" + _displayedBook + '_' + _displayedPage; 560 | } 561 | 562 | var showRestrictedAccess = false; 563 | var shownPages = []; 564 | if (_pages[_displayedPage]) { 565 | shownPages.push(_displayedPage); 566 | displayPage(_displayedPage); 567 | if (!_pages[newDisplayedPage].canAccess()) { 568 | showRestrictedAccess = true; 569 | } 570 | } 571 | if (_pages[_displayedPage + 1]) { 572 | shownPages.push(_displayedPage + 1); 573 | displayPage(_displayedPage + 1); 574 | if (!_pages[newDisplayedPage + 1].canAccess()) { 575 | showRestrictedAccess = true; 576 | } 577 | } 578 | 579 | if (showRestrictedAccess) { 580 | // show "access restricted" lightbox if the displayed page 581 | // is restricted - the user will be able to close it, 582 | // it's just to remind him the page isn't free 583 | libeConfig.restrictedAccess(); 584 | } 585 | displayPagination(); 586 | bindButtons(); 587 | bindKeyboard(); 588 | jQuery(document).trigger('pages-shown', [_displayedBook, shownPages]); 589 | } 590 | 591 | function showSelectedPage(e) { 592 | e.preventDefault(); 593 | var tmp = _parseHashtoGetParams(this.href.split('#!/')[1]); 594 | var newDisplayedBook = tmp[0]; 595 | var newDisplayedPage = tmp[1] - tmp[1] % 2; 596 | 597 | if (newDisplayedBook != _displayedBook) { 598 | showBook(newDisplayedBook, newDisplayedPage); 599 | } else if (newDisplayedPage != _displayedPage) { 600 | showPage(newDisplayedPage); 601 | } 602 | } 603 | 604 | function showPreviousPage(e) { 605 | e.preventDefault(); 606 | showPage(_displayedPage - 2); 607 | } 608 | function showNextPage(e) { 609 | e.preventDefault(); 610 | showPage(_displayedPage + 2); 611 | } 612 | 613 | function showFirstPage(e) { 614 | e.preventDefault(); 615 | showPage(0); 616 | } 617 | function showLastPage(e) { 618 | e.preventDefault(); 619 | showPage(_selectedBook.pagination); 620 | } 621 | 622 | function sizeKnown(e) { 623 | var sides = jQuery('#evenSide, #oddSide'); 624 | sides.width(libeConfig.pageWidth); 625 | sides.css('max-height', libeConfig.pageHeight + 'px'); 626 | var parent = sides.parent(); 627 | if (parent) { 628 | parent.width(sides.outerWidth() * 2); 629 | } 630 | jQuery(window).unbind(e); 631 | } 632 | 633 | function unHighlightHoveredPages(e) { 634 | jQuery('#pagesList a.hovered').removeClass('hovered'); 635 | } 636 | 637 | function unHighlightCurrentPages(e) { 638 | jQuery('#pagesList a.current').removeClass('current'); 639 | } 640 | 641 | function highlightCurrentPages(book, page) { 642 | var current = jQuery('#thumb_' + book + '_' + page); 643 | current.addClass('current'); 644 | } 645 | 646 | function highlightHoveredPages(e) { 647 | // remove old highlight 648 | unHighlightHoveredPages(); 649 | 650 | var hovered = jQuery(this); 651 | var neighbour = jQuery(); 652 | 653 | // if it's an even page, find the one on the right if it exists 654 | if (hovered.hasClass('even')) { 655 | neighbour = hovered.next(); 656 | } 657 | // if it's an odd page, find the one on the left if it exists 658 | if (hovered.hasClass('odd')) { 659 | neighbour = hovered.prev(); 660 | } 661 | 662 | // highlight the relevant pages 663 | hovered.addClass('hovered'); 664 | neighbour.addClass('hovered'); 665 | } 666 | 667 | function _changeBook(newBook) { 668 | if (newBook > _publication.books.length) { 669 | newBook = 0; 670 | } 671 | if (_displayedBook != newBook) { 672 | jQuery('#pagesList').empty(); 673 | jQuery('#pagesList').css({'left' : 0 }); 674 | jQuery('#bookSwitcher a').removeClass('selected'); 675 | jQuery('#bookThumb-' + parseInt(newBook, 10)).addClass('selected'); 676 | } 677 | _selectedBook = _publication.books[newBook]; 678 | _displayedBook = newBook; 679 | 680 | if (typeof _selectedBook == 'undefined' || !_selectedBook.pagination) { 681 | libeConfig.defaultError({'status' : 418}); 682 | return false; 683 | } 684 | return true; 685 | } 686 | 687 | function showBook(bookToShow, possiblePage) { 688 | 689 | _hideOldPages(); 690 | if (!_changeBook(bookToShow)) { 691 | return false; 692 | } 693 | 694 | var pageToShow = 0; 695 | if (possiblePage >= 0 && possiblePage <= _selectedBook.pagination) { 696 | pageToShow = possiblePage; 697 | } 698 | 699 | jQuery(window).bind('size-known', sizeKnown); 700 | 701 | _pages = new Array(parseInt(_selectedBook.pagination, 10)); 702 | var i; 703 | for (i = 0, il = _selectedBook.pages.length; i < il ; i++) { 704 | var page = _selectedBook.pages[i]; 705 | _pages[page.page_number] = libePage(page.page_number, page.id, page.paper_channel, page.maps); 706 | } 707 | for (i = 1; i <= _selectedBook.pagination; i++) { 708 | if (!_pages[i]) { 709 | _pages[i] = libePage(i); 710 | } 711 | var a = _pages[i].getThumbnailForList(_displayedBook, 'smallest'); 712 | a.attr({'id' : 'thumb_' + _displayedBook + '_' + i}); 713 | jQuery('#pagesList').append(a); 714 | a.bind('click', showSelectedPage); 715 | a.bind('mouseover', highlightHoveredPages); 716 | } 717 | jQuery(document).trigger('book-load', [_selectedBook, _displayedBook]); 718 | showPage(pageToShow); 719 | } 720 | 721 | function findPageFromId(id) { 722 | var len = _publication.books.length; 723 | for (var i = 0; i < len; i++) { 724 | var book = _publication.books[i]; 725 | var plen = book.pages.length; 726 | for (var j = 0; j < plen; j++) { 727 | var page = book.pages[j]; 728 | if (page.id == id) { 729 | return [i, page.page_number]; // don't trust j! book might be incomplete 730 | } 731 | } 732 | } 733 | return null; 734 | } 735 | 736 | function _parseHashtoGetParams(hash) { 737 | if (!hash) { 738 | return [0, 0]; 739 | } 740 | if (hash[0] == 'p') { 741 | // if hash starts with "p", try to find a page with this id 742 | var result = findPageFromId(parseInt(hash.substr(1), 10)); 743 | if (result) { 744 | return result; 745 | } 746 | } 747 | var bookToShow = parseInt(hash.split('_')[0], 10); 748 | var possiblePage = parseInt(hash.split('_')[1], 10); 749 | return [bookToShow, possiblePage]; 750 | } 751 | 752 | function handlePublication(data) { 753 | _publication = data; 754 | 755 | // If the publication data contains an access level, use it as the new 756 | // access level needed. 757 | if (typeof data.access !== 'undefined') { 758 | libeConfig.changeAccessLevelNeeded(parseInt(data.access, 10)); 759 | } 760 | 761 | jQuery('#pagesList').bind('mouseout', unHighlightHoveredPages); 762 | 763 | // Trigger a first event before showing any pages 764 | jQuery(document).trigger('publication-beforeload', [_publication, _publicationId]); 765 | 766 | var tmp = [0, 0]; 767 | if (location.hash !== "") { 768 | tmp = _parseHashtoGetParams(location.hash.split('#!/')[1]); 769 | } 770 | 771 | showBookList(); // call first, so that we can play with the list in showBook() 772 | showBook((tmp[0] || 0), (tmp[1] || 0)); 773 | 774 | jQuery(document).trigger('publication-load', [data, _publicationId]); 775 | } 776 | 777 | function init(publicationId) { 778 | _publicationId = publicationId; 779 | 780 | var url = libeConfig.webservices.publication.replace('{format}', 'json').replace('{id}', publicationId); 781 | 782 | jQuery.ajax({url: url, dataType: "json", success: handlePublication, error: libeConfig.defaultError}); 783 | 784 | jQuery('#zoomButton').click(function (e) { 785 | if (_isZoomed) { 786 | quitZoom(); 787 | } else { 788 | zoomAtCoordinates(0, 0); 789 | } 790 | return false; 791 | }); 792 | jQuery('#previousCorner, #nextCorner').hover(showHoverCorner, hideHoverCorner); 793 | } 794 | 795 | function showBookList() { 796 | var len = _publication.books.length; 797 | for (var i = 0; i < len; i++) { 798 | var page = _publication.books[i].pages[0]; 799 | var obj; 800 | if (typeof page == 'undefined' || !page || page.page_number > 1) { 801 | // First page should always be numbered 1. If it's non existant 802 | // or if it's not numbered 1, then the first page is still in 803 | // construction... Fake it. 804 | obj = libePage(1); 805 | } else { 806 | obj = libePage(page.page_number, page.id, page.paper_channel, page.maps); 807 | } 808 | 809 | var a = obj.getThumbnailForList(i); 810 | a.attr('id', "bookThumb-" + i); 811 | a.append('' + _publication.books[i].name + ''); 812 | jQuery('#bookSwitcher').append(a); 813 | a.bind('click', showSelectedPage); 814 | } 815 | } 816 | 817 | return { 818 | init: init, 819 | showPage: showPage, 820 | showPreviousPage: showPreviousPage, 821 | showNextPage: showNextPage, 822 | showSelectedPage: showSelectedPage, 823 | showBook: showBook, 824 | bookDisplayed : bookDisplayed, 825 | pageDisplayed: pageDisplayed 826 | }; 827 | }(); 828 | --------------------------------------------------------------------------------