Initial commit from existing app
116
CHANGELOG.md
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
## 1.2.3 - 2018-02-24
|
||||||
|
### Fixed
|
||||||
|
- (#76) typo plus some missing code kept Reader from being used to preview shared files
|
||||||
|
- (#79) typo kept Reader from being used by default for CBx
|
||||||
|
- (#82) missing setDefault kept actual style settings from being saved
|
||||||
|
|
||||||
|
## 1.2.2 - 2018-02-02
|
||||||
|
### Fixed
|
||||||
|
- (#75) NC and OC are diverging, NC encodes everything on $settings as JSON, OC does not yet.
|
||||||
|
|
||||||
|
## 1.2.1 - 2018-01-31
|
||||||
|
### Changed
|
||||||
|
- change default settings to enabled for all supported mime types
|
||||||
|
|
||||||
|
## 1.2.0 - 2018-01-31
|
||||||
|
### Added
|
||||||
|
- PDF: (#73) new preference 'scroll to top of page on page turn'
|
||||||
|
- PDF: defaults and per-document settings are now saved and restored
|
||||||
|
- PDF: nightmode (using CSS3 filters, only works in recent browsers), toggle with 'd', by clicking nightmode button or clicking in empty area on button bar, adjust in settings
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- remove <base> from templates to avoid warning in console, <base> statement was ineffective anyway de to (overly restrictive) hardcoded policy in NC/OC.
|
||||||
|
- removed (or rather disabled) merging of PDF annotations into user bookmarks as it only served to mess up the bookmark list and slowed things down. This feature can be re-enabled once Reader gains a functional PDF annotation editor.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- PDF: (#72) $title not ['title'] in pdfreader template, hopefully the last remaining bug related to template refactoring
|
||||||
|
- PDF: browsing the thumbnail list in single-page mode did not work as intended due to datatype mismatch in page calculation routine, fixed with explicit toString()
|
||||||
|
- PDF: page 0 does not exist so don't try to go there
|
||||||
|
|
||||||
|
## 1.1.1 - 2018-01-19
|
||||||
|
### Added
|
||||||
|
- signed package for publication in Owncloud marketplace
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- updated bitjs unrar.js and rarvm.js
|
||||||
|
|
||||||
|
## 1.1.0 - 2018-01-18
|
||||||
|
### Added
|
||||||
|
- Reader now supports PDF
|
||||||
|
- PDF double page spreads are supported
|
||||||
|
- optional double-buffering for faster rendering, can be disabled for low-memory devices
|
||||||
|
- optional selectable text layer, can be disabled for low-memory devices
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- #38: moved declarations in js/ready.js one level lower to work around a bug in the Palemoon browser
|
||||||
|
- new version bitjs archive tools, fixes compatibility problems with some CBR files
|
||||||
|
- increased maximum supported version for OC and NC
|
||||||
|
|
||||||
|
## 1.0.4 - 2017-04-09
|
||||||
|
### Fixed
|
||||||
|
- #43, remove table aliases in hooks to avoid being bit by querybuilder/doctrine/MySQL incompatibility/idiosyncracy
|
||||||
|
- #39, #41 and #42, NOTE: if you're on MySQL or MariaDB you might need to enable 4-byte support if this has not been done yet, otherwise you'll get a '1071 Specified key was too long' error on install. More information on this issue - which also occurs when trying to use Emoji characters in a NC/OC installation on a MySQL or MariaDB database - can be found here: https://docs.nextcloud.com/server/11/admin_manual/maintenance/mysql_4byte_support.html
|
||||||
|
|
||||||
|
## 1.0.3 - 2017-03-29
|
||||||
|
### Fixed
|
||||||
|
- #40, detect shared file OR folder and (try to) get fileId for such when applicable
|
||||||
|
|
||||||
|
## 1.0.2 - 2017-03-25
|
||||||
|
### Fixed
|
||||||
|
- #37, use getAppManager()->isInstalled('files_opds') instead of class_exists to avoid log spam
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- new version bitjs unarchiver, increases compatibility with CBR files (at the cost of some speed)
|
||||||
|
- move function declarations in js/ready.js down one block level so browsers which do not support
|
||||||
|
ES6 (e.g. Palemoon) can find them. Unfortunately the above new version of bitjs uses another ES6
|
||||||
|
feature (classes) which Palemoon does not support so this change may be moot...
|
||||||
|
|
||||||
|
## 1.0.1 - 2017-03-19
|
||||||
|
### Fixed
|
||||||
|
- #35: Internal Server Error: fixed path resolution so app works when NC/OC hosted in subdirectory
|
||||||
|
|
||||||
|
## 1.0.0 - 2017-03-15
|
||||||
|
### Added
|
||||||
|
- Reader now supports CBR/CBZ ('comics') files
|
||||||
|
- Book position ('cursor') is saved on server and restored on next invocation
|
||||||
|
- Default settings (independent of fileid) and file-specific settings are saved and restored
|
||||||
|
- Bookmarks and annotations (notes) are saved and restored (bookmarks are a type of annotation).
|
||||||
|
- Full-text search implemented.
|
||||||
|
- Framework to support more file format renderers
|
||||||
|
- hooks added to remove defaults, settings and annotations/bookmarks for deleted files or users
|
||||||
|
- epubreader
|
||||||
|
* night mode now works more reliably
|
||||||
|
* new 'day mode', ie. user-defined colours
|
||||||
|
* new font settings: font weight
|
||||||
|
* column width user-configurable
|
||||||
|
* new mode: maximize reader area, for small-screen devices
|
||||||
|
* page turn arrows optional, hidden by default
|
||||||
|
- cbreader
|
||||||
|
* supports CBR (rar) and CBZ (zip) archives
|
||||||
|
* single and double page (spread) mode, auto-adjusts to screen geometry
|
||||||
|
* optional image enhancement filters
|
||||||
|
* seamless full screen mode (where browser allows user full control of experience, ie. not on apple)
|
||||||
|
|
||||||
|
## 0.8.3 - 2017-02-02
|
||||||
|
### Fixed
|
||||||
|
. #31: ReferenceError: cleanStartTextContent is not defined, caused by failure to declare local var in epub.js
|
||||||
|
|
||||||
|
## 0.8.3 - 2017-02-01
|
||||||
|
### Fixed
|
||||||
|
- missing $title parameter in template/reader.php caused warnings in log, fixed
|
||||||
|
|
||||||
|
## 0.8.2 - 2017-01-10
|
||||||
|
### Fixed
|
||||||
|
- Nextcloud-port broke compatibility with Owncloud due to OC not supporting CSPv3, workaround implemented
|
||||||
|
|
||||||
|
## 0.8.1 - 2017-01-09
|
||||||
|
### Added
|
||||||
|
- Modified info.xml, added screenshots
|
||||||
|
|
||||||
|
## 0.8.0 - 2017-01-09
|
||||||
|
### Added
|
||||||
|
- new version 0.2.15 of Futurepress epub.js renderer
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- New logo
|
||||||
|
- First release to be compatible with Nextcloud
|
134
README.md
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
files_reader
|
||||||
|
------------
|
||||||
|
|
||||||
|
Reader is an ebook reader based on pure javascript renderers. It works for publications formatted according to the following standards:
|
||||||
|
|
||||||
|
- [Epub]
|
||||||
|
- [PDF]
|
||||||
|
- [CBR and CBZ] ('comics')
|
||||||
|
|
||||||
|
For Epub Reader uses the futurepress [epub.js] renderer to provide near-native looks, especially when used full-screen. Turn pages by pressing the left/right hand side of the screen/window or using the cursor keys (if you have those), use the sidebar to browse through chapters or bookmarks and add annotations.
|
||||||
|
|
||||||
|
PDF is handled by Mozilla's [pdf.js] renderer in combination with a custom reader app to enable side-by-side display, batch search and more. Controls are like those used in the Epub renderer with a few exceptions, e.g. night mode has not been implemented yet.
|
||||||
|
|
||||||
|
CBR and CBZ are supported using a custom renderer inspired by [balaclark]'s work. As with Epub, books can be navigated using the keyboard as well as mouse or touch navigation. Reader generates a visual index of all pages, show in the sidebar (thumbnail generation can be disabled for low-memory and/or -speed devices). As CBx files are often of varying quality, a set of image enhancement filters are provided.
|
||||||
|
|
||||||
|
# Features
|
||||||
|
Reader remembers the last-visited page in a book and returns to that page when the book is re-opened. As all settings are stored on the server these features are device-independent, ie. you can start reading on a mobile device, continue on a PC to finish the book on a tablet.
|
||||||
|
|
||||||
|
### Text-based formats incl. PDF
|
||||||
|
|
||||||
|
- seamless full-screen mode supported on browsers which allow full user-control, ie. not on Apple)
|
||||||
|
- single- and double-page viewing mode
|
||||||
|
- user-configurable font and colour settings
|
||||||
|
- night mode, toggled by clicking the book title/author on top of the viewer or the night mode button (PDF)
|
||||||
|
- full-text search with keyword highlighting
|
||||||
|
- bookmarks (with automatic snippet generation)
|
||||||
|
- annotations (not yet implemented for PDF)
|
||||||
|
- keyboard and pointer/touch-based navigation
|
||||||
|
|
||||||
|
### CBR/CBZ ('Comics')
|
||||||
|
|
||||||
|
- seamless full-screen mode supported on browsers which allow full user-control, ie. not on Apple)
|
||||||
|
- single- and double-page viewing mode
|
||||||
|
- optional image enhancement filters
|
||||||
|
- Left-to-right and right-to-left (_manga_) modes
|
||||||
|
- visual index (thumbnail size user-configurable, can be disabled for low-memory or -cpu devices)
|
||||||
|
- keyboard and pointer/touch-based navigation
|
||||||
|
|
||||||
|
PDF support is still somewhat rough around the edges, not all features have been implemented yet. There is a known cosmetical issue in that in spread mode the (invisible but selectable) text layer for the left page is offset from the left when opening a document. As soon as a page is turned this problem disappears.
|
||||||
|
|
||||||
|
### Keyboard navigation
|
||||||
|
Reader supports both pointer/touch-based as well as keyboard-based navigation. Pointer/touch based is mostly self-explanatory,
|
||||||
|
|
||||||
|
| key | function |
|
||||||
|
| ---:| --- |
|
||||||
|
|_left_, _page-up_ | move to previous page; move to next page in RTL(_manga_) mode |
|
||||||
|
|_right_, _page-down_, _space_ | move to next page; move to previous page in RTL (_mange_) mode |
|
||||||
|
|_home_| move to first page |
|
||||||
|
|_end_| move to last page |
|
||||||
|
|s| toggle side bar |
|
||||||
|
|_esc_| close sidebar |
|
||||||
|
|f| toggle full screen|
|
||||||
|
|t| toggle toolbar |
|
||||||
|
|l| _CBR_: toggle layout |
|
||||||
|
|a| _EPUB_: annotate |
|
||||||
|
|b| _EPUB_: bookmark |
|
||||||
|
|r| _EPUB_: reflow text when sidebar is open |
|
||||||
|
|d| _EPUB_: toggle day (custom colour) mode |
|
||||||
|
|n| _EPUB_: toggle night mode |
|
||||||
|
|
||||||
|
### Defaults and Preferences
|
||||||
|
|
||||||
|
Reader stores __defaults__ - settings which are independent of _fileId_ (ie. independent of the book currently open) - and __preferences__ - _fileId_-dependent (ie. different for every book) - on the server. Defaults are not shared between renderers, ie. the CBR renderer does not share defaults with the EPUB or PDF renderer. Defaults and preferences are removed from the server when the associated book or user is deleted.
|
||||||
|
|
||||||
|
### Annotations and Bookmarks
|
||||||
|
|
||||||
|
Reader supports _annotations_ (notes linked to a given position in a book) and _bookmarks_ (position markers with automatically generated text snippets). An automatically generated bookmark (called '__ CURSOR __', not visible in the bookmarks list) is used to keep track of the current reading position. Annotations and bookmark snippets can be edited or deleted in the sidebar.
|
||||||
|
|
||||||
|
|
||||||
|
# Screenshots
|
||||||
|
### Epub
|
||||||
|
| | |
|
||||||
|
---|---
|
||||||
|
Reader showing page spread in 'night mode'|![Reader showing page spread in 'night mode'][SS01]
|
||||||
|
Epub single page, full screen on a small-screen device |![Epub single page, full screen][SS02]
|
||||||
|
Day mode color selector|![Day mode color selector][SS03]
|
||||||
|
Longing for that olde-time terminal feeling...|![Longing for that olde-time terminal feeling...][SS04]
|
||||||
|
Full-text search|![Full-text search][SS05]
|
||||||
|
Small screen device, __maximize text area__ enabled|![Small screen device, maximize text area enabled][SS06]
|
||||||
|
Search on small-screen device|![Search on small-screen device][SS07]
|
||||||
|
As close to full-screen as you can get on iOS|![As close to full-screen as you can get on iOS][SS08]
|
||||||
|
Android supports true fullscreen (as do most other systems)|![Android supports true fullscreen (as do most other systems)][SS09]
|
||||||
|
|
||||||
|
### PDF
|
||||||
|
| | |
|
||||||
|
---|---
|
||||||
|
Reader showing PDF Reference document in spread mode (pages side by side)|![Reader showing PDF Reference document in spread mode (pages side by side)][SS20]
|
||||||
|
Search through a document, showing all results in the sidebar|![Search through a document, showing all results in the sidebar][SS19]
|
||||||
|
Dropdown showing page format options - spread, single page, page width and zoom options|![Dropdown showing page format options - spread, single page, page width and zoom options][SS21]
|
||||||
|
Reader showing PDF in spread mode, thumbnails in the sidebar|![Reader showing PDF in spread mode, thumbnails in the sidebar][SS22]
|
||||||
|
|
||||||
|
### CBR/CBZ
|
||||||
|
| | |
|
||||||
|
---|---
|
||||||
|
iOS approximation of full screen, CBR|![iOS approximation of full screen, CBR][SS10]
|
||||||
|
The same book, now in landscape mode, switch to 2-page spread is automatic|![The same book, now in landscape mode, switch to 2-page spread is automatic][SS11]
|
||||||
|
Sidebar open, showing index, landscape mode|![Sidebar open, showing index, landscape mode][SS12]
|
||||||
|
Sidebar open, showing index, portrait mode|![Sidebar open, showing index, portrait mode][SS13]
|
||||||
|
Image enhancement filters, desaturate (grayscale) active|![Image enhancement filters, desaturate (grayscale) active][SS14]
|
||||||
|
full screen (apart from iOS restrictions), controls hidden|![full screen (apart from iOS restrictions), controls hidden][SS15]
|
||||||
|
Same page, zoomed in|![Same page, zoomed in][SS16]
|
||||||
|
Small-screen, low memory (Android) device showing full-page book cover|![Small-screen, low memory Android device showing full-page book cover][SS17]
|
||||||
|
The same Android device showing a zoomed-in part of a page|![The same Android device showing a zoomed-in part of a page][SS18]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[epub.js]: https://github.com/futurepress/epub.js
|
||||||
|
[Epub]: http://idpf.org/epub
|
||||||
|
[CBR and CBZ]: https://wiki.mobileread.com/wiki/CBR_and_CBZ
|
||||||
|
[balaclark]: https://github.com/balaclark/HTML5-Comic-Book-Reader
|
||||||
|
[PDF]: https://en.wikipedia.org/wiki/Portable_Document_Format
|
||||||
|
[pdf.js]: https://github.com/mozilla/pdf.js
|
||||||
|
[SS01]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader-1.png "Reader showing day/nighyt mode"
|
||||||
|
[SS02]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader-3.png "Single page full screen on a small-screen device"
|
||||||
|
[SS03]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-21-39.jpg "Day mode color selector"
|
||||||
|
[SS04]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-21-41.jpg?raw=true "Longing For that olde-time terminal feeling..."
|
||||||
|
[SS05]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-21-53.jpg "Full-text search"
|
||||||
|
[SS06]: https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_18-28-46.jpg "Small screen device, __maximize text area__ enabled"
|
||||||
|
[SS07]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_18-28-49.jpg?raw=true "Search on small-screen device"
|
||||||
|
[SS08]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-21-52.jpg?raw=true "As close to full-screen as you can get on iOS"
|
||||||
|
[SS09]: https://github.com/e-alfred/epubreader/blob/master/screenshots/Screenshot_2014-09-29-20-21-50.png?raw=true "Android supports true fullscreen (as do most other systems)"
|
||||||
|
[SS10]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-21-59.jpg?raw=true "iOS approximation of full screen, CBR"
|
||||||
|
[SS11]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-00.jpg?raw=true "The same book, now in landscape mode, switch to 2-page spread is automatic"
|
||||||
|
[SS12]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-01.jpg?raw=true "Sidebar open, showing index, landscape mode"
|
||||||
|
[SS13]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-02.jpg?raw=true "Sidebar open, showing index, portrait mode"
|
||||||
|
[SS14]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-05.jpg?raw=true "Image enhancement filters, desaturate (grayscale) active"
|
||||||
|
[SS15]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-08.jpg?raw=true "full screen (apart from iOS restrictions), controls hidden"
|
||||||
|
[SS16]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_17-22-10.jpg?raw=true "Same page, zoomed in"
|
||||||
|
[SS17]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_18-28-54.jpg?raw=true "Small-screen, low memory (Android) device showing full-page book cover"
|
||||||
|
[SS18]: https://github.com/e-alfred/epubreader/blob/master/screenshots/photo_2017-03-15_18-28-56.jpg?raw=true "The same Android device showing a zoomed-in part of a page"
|
||||||
|
[SS19]: https://github.com/e-alfred/epubreader/blob/master/screenshots/files_reader_PDF_001.png?raw=true "Search through a document, showing all results in the sidebar"
|
||||||
|
[SS20]: https://github.com/e-alfred/epubreader/blob/master/screenshots/files_reader_PDF_002.png?raw=true "Reader showing PDF Reference document in spread mode (pages side by side)"
|
||||||
|
[SS21]: https://github.com/e-alfred/epubreader/blob/master/screenshots/files_reader_PDF_005.png?raw=true "Dropdown showing page format options - spread, single page, page width and zoom options"
|
||||||
|
[SS22]: https://github.com/e-alfred/epubreader/blob/master/screenshots/files_reader_PDF_006.png?raw=true "Reader showing PDF in spread mode, thumbnails in the sidebar"
|
5
TODO
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
- annotations
|
||||||
|
- rtl and ltr
|
||||||
|
- test canvas size restriction
|
||||||
|
- add IDs to highlights so they can be marked when hovered - or when related list item is hovered
|
||||||
|
|
35
ajax/personal.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ownCloud - Files_Reader App
|
||||||
|
*
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2014,2018 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader;
|
||||||
|
|
||||||
|
\OC_JSON::callCheck();
|
||||||
|
\OC_JSON::checkLoggedIn();
|
||||||
|
|
||||||
|
$l = \OC::$server->getL10N('files_reader');
|
||||||
|
|
||||||
|
$EpubEnable = isset($_POST['EpubEnable']) ? $_POST['EpubEnable'] : 'false';
|
||||||
|
$PdfEnable = isset($_POST['PdfEnable']) ? $_POST['PdfEnable'] : 'false';
|
||||||
|
$CbxEnable = isset($_POST['CbxEnable']) ? $_POST['CbxEnable'] : 'false';
|
||||||
|
|
||||||
|
Config::set('epub_enable', $EpubEnable);
|
||||||
|
Config::set('pdf_enable', $PdfEnable);
|
||||||
|
Config::set('cbx_enable', $CbxEnable);
|
||||||
|
|
||||||
|
\OC_JSON::success(
|
||||||
|
array(
|
||||||
|
'data' => array('message'=> $l->t('Settings updated successfully.'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
exit();
|
||||||
|
|
22
appinfo/app.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ownCloud - Files_Reader App
|
||||||
|
*
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2015 - 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\AppInfo;
|
||||||
|
|
||||||
|
use OCP\AppFramework\App;
|
||||||
|
use OCP\Util;
|
||||||
|
|
||||||
|
$l = \OC::$server->getL10N('files_reader');
|
||||||
|
|
||||||
|
\OCA\Files_Reader\Hooks::register();
|
||||||
|
Util::addscript('files_reader', 'plugin');
|
||||||
|
\OCP\App::registerPersonal('files_reader', 'personal');
|
203
appinfo/database.xml
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||||
|
<database>
|
||||||
|
<name>*dbname*</name>
|
||||||
|
<create>true</create>
|
||||||
|
<overwrite>false</overwrite>
|
||||||
|
<charset>utf8</charset>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<name>*dbprefix*reader_bookmarks</name>
|
||||||
|
<declaration>
|
||||||
|
<field>
|
||||||
|
<!-- id -->
|
||||||
|
<name>id</name>
|
||||||
|
<type>integer</type>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<autoincrement>true</autoincrement>
|
||||||
|
<unsigned>true</unsigned>
|
||||||
|
<primary>true</primary>
|
||||||
|
<length>8</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- user ID, maps bookmark to NC/OC user -->
|
||||||
|
<name>user_id</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<length>64</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- file ID, maps to NC/OC file ID -->
|
||||||
|
<name>file_id</name>
|
||||||
|
<type>integer</type>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<unsigned>true</unsigned>
|
||||||
|
<length>11</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- type (bookmark, annotation, etc) -->
|
||||||
|
<name>type</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<length>32</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- name -->
|
||||||
|
<name>name</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<length>512</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- value -->
|
||||||
|
<name>value</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<length>512</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- content -->
|
||||||
|
<name>content</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>false</notnull>
|
||||||
|
<length>4096</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- last_modified -->
|
||||||
|
<name>last_modified</name>
|
||||||
|
<type>integer</type>
|
||||||
|
<default>0</default>
|
||||||
|
<length>8</length>
|
||||||
|
<notnull>false</notnull>
|
||||||
|
<unsigned>true</unsigned>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<index>
|
||||||
|
<name>reader_bookmarks_file_id_index</name>
|
||||||
|
<field>
|
||||||
|
<name>file_id</name>
|
||||||
|
</field>
|
||||||
|
</index>
|
||||||
|
|
||||||
|
<index>
|
||||||
|
<name>reader_bookmarks_user_id_index</name>
|
||||||
|
<field>
|
||||||
|
<name>user_id</name>
|
||||||
|
</field>
|
||||||
|
</index>
|
||||||
|
|
||||||
|
<index>
|
||||||
|
<name>reader_bookmarks_name_index</name>
|
||||||
|
<field>
|
||||||
|
<name>name</name>
|
||||||
|
</field>
|
||||||
|
</index>
|
||||||
|
|
||||||
|
</declaration>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<name>*dbprefix*reader_preferences</name>
|
||||||
|
<declaration>
|
||||||
|
<field>
|
||||||
|
<!-- id -->
|
||||||
|
<name>id</name>
|
||||||
|
<type>integer</type>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<autoincrement>true</autoincrement>
|
||||||
|
<unsigned>true</unsigned>
|
||||||
|
<primary>true</primary>
|
||||||
|
<length>8</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- user ID, maps preference to NC/OC user -->
|
||||||
|
<name>user_id</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<length>64</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- file ID, maps to NC/OC file ID -->
|
||||||
|
<name>file_id</name>
|
||||||
|
<type>integer</type>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<unsigned>true</unsigned>
|
||||||
|
<length>11</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- scope -->
|
||||||
|
<name>scope</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<length>32</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- name -->
|
||||||
|
<name>name</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<length>128</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- value -->
|
||||||
|
<name>value</name>
|
||||||
|
<type>text</type>
|
||||||
|
<default></default>
|
||||||
|
<notnull>true</notnull>
|
||||||
|
<length>4096</length>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<field>
|
||||||
|
<!-- last_modified -->
|
||||||
|
<name>last_modified</name>
|
||||||
|
<type>integer</type>
|
||||||
|
<default>0</default>
|
||||||
|
<length>8</length>
|
||||||
|
<notnull>false</notnull>
|
||||||
|
<unsigned>true</unsigned>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
<index>
|
||||||
|
<name>reader_preferences_file_id_index</name>
|
||||||
|
<field>
|
||||||
|
<name>file_id</name>
|
||||||
|
</field>
|
||||||
|
</index>
|
||||||
|
|
||||||
|
<index>
|
||||||
|
<name>reader_preferences_user_id_index</name>
|
||||||
|
<field>
|
||||||
|
<name>user_id</name>
|
||||||
|
</field>
|
||||||
|
</index>
|
||||||
|
|
||||||
|
<index>
|
||||||
|
<name>reader_preferences_scope_index</name>
|
||||||
|
<field>
|
||||||
|
<name>scope</name>
|
||||||
|
</field>
|
||||||
|
</index>
|
||||||
|
|
||||||
|
</declaration>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</database>
|
51
appinfo/info.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<info>
|
||||||
|
<id>epubreader</id>
|
||||||
|
<name>Reader (ebook reader)</name>
|
||||||
|
<summary>A multi-format browser-based ebook reader, supports EPUB and CBR/CBZ</summary>
|
||||||
|
<description>
|
||||||
|
<![CDATA[
|
||||||
|
# Reader
|
||||||
|
Reader is an ebook reader based on pure javascript renderers. It works for publications formatted according to the following standards:
|
||||||
|
|
||||||
|
- [Epub]
|
||||||
|
- [PDF]
|
||||||
|
- [CBR and CBZ] ('comics')
|
||||||
|
|
||||||
|
For Epub Reader uses the futurepress [epub.js] renderer to provide near-native looks, especially when used full-screen. Turn pages by pressing the left/right hand side of the screen/window or using the cursor keys (if you have those), use the sidebar to browse through chapters or bookmarks and add annotations.
|
||||||
|
|
||||||
|
PDF is handled by Mozilla's [pdf.js] renderer in combination with a custom reader app to enable side-by-side display, batch search and more. Controls are like those used in the Epub renderer with a few exceptions, e.g. night mode has not been implemented yet.
|
||||||
|
|
||||||
|
CBR and CBZ are supported using a custom renderer inspired by [balaclark]'s work. As with Epub, books can be navigated using the keyboard as well as mouse or touch navigation. Reader generates a visual index of all pages, show in the sidebar (thumbnail generation can be disabled for low-memory and/or -speed devices). As CBx files are often of varying quality, a set of image enhancement filters are provided.
|
||||||
|
|
||||||
|
See [README] for more exhaustive information on features and potential misfeatures of this app
|
||||||
|
|
||||||
|
[epub.js]: https://github.com/futurepress/epub.js
|
||||||
|
[Epub]: http://idpf.org/epub
|
||||||
|
[CBR and CBZ]: https://wiki.mobileread.com/wiki/CBR_and_CBZ
|
||||||
|
[balaclark]: https://github.com/balaclark/HTML5-Comic-Book-Reader
|
||||||
|
[PDF]: https://en.wikipedia.org/wiki/Portable_Document_Format
|
||||||
|
[pdf.js]: https://github.com/mozilla/pdf.js
|
||||||
|
[README]: https://github.com/e-alfred/epubreader/blob/master/files_reader/README.md
|
||||||
|
]]>
|
||||||
|
</description>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<licence>AGPL</licence>
|
||||||
|
<author>Frank de Lange</author>
|
||||||
|
<author>e-alfred</author>
|
||||||
|
<documentation>https://github.com/e-alfred/epubreader</documentation>
|
||||||
|
<bugs>https://github.com/e-alfred/epubreader</bugs>
|
||||||
|
<website>https://github.com/e-alfred/epubreader</website>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader-1.png</screenshot>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader-3.png</screenshot>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader_PDF_005.png</screenshot>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/files_reader_PDF_006.png</screenshot>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-22-00.jpg</screenshot>
|
||||||
|
<screenshot>https://raw.githubusercontent.com/e-alfred/epubreader/master/screenshots/photo_2017-03-15_17-22-02.jpg</screenshot>
|
||||||
|
<category>files</category>
|
||||||
|
<category>multimedia</category>
|
||||||
|
<category>office</category>
|
||||||
|
<dependencies>
|
||||||
|
<nextcloud min-version="15" max-version="19"/>
|
||||||
|
</dependencies>
|
||||||
|
</info>
|
40
appinfo/routes.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ownCloud - Files_Reader App
|
||||||
|
*
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2015 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$this->create('reader_personal_settings', 'ajax/personal.php')->actionInclude('files_reader/ajax/personal.php');
|
||||||
|
|
||||||
|
return ['routes' => [
|
||||||
|
// Page
|
||||||
|
['name' => 'page#showReader', 'url' => '/', 'verb' => 'GET'],
|
||||||
|
|
||||||
|
// Bookmarks
|
||||||
|
['name' => 'bookmark#get_cursor', 'url' => '/bookmark/cursor/{fileId}', 'verb' => 'GET'],
|
||||||
|
['name' => 'bookmark#set_cursor', 'url' => '/bookmark/cursor', 'verb' => 'POST'],
|
||||||
|
['name' => 'bookmark#delete_cursor', 'url' => '/bookmark/cursor/{fileId}', 'verb' => 'DELETE'],
|
||||||
|
['name' => 'bookmark#get', 'url' => '/bookmark/{fileId}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']],
|
||||||
|
['name' => 'bookmark#get', 'url' => '/bookmark/{fileId}/{type}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']],
|
||||||
|
['name' => 'bookmark#set', 'url' => '/bookmark', 'verb' => 'POST'],
|
||||||
|
['name' => 'bookmark#delete', 'url' => '/bookmark/{fileId}/{name}', 'verb' => 'DELETE'],
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
['name' => 'metadata#get', 'url' => '/metadata/{fileId}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']],
|
||||||
|
['name' => 'metadata#set', 'url' => '/metadata/{fileId}/{name}/{value}', 'verb' => 'POST'],
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
['name' => 'preference#get_default', 'url' => '/preference/default/{scope}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']],
|
||||||
|
['name' => 'preference#set_default', 'url' => '/preference/default', 'verb' => 'POST'],
|
||||||
|
['name' => 'preference#delete_default', 'url' => '/preference/default/{scope}/{name}', 'verb' => 'DELETE'],
|
||||||
|
['name' => 'preference#get', 'url' => '/preference/{fileId}/{scope}/{name}', 'verb' => 'GET', 'defaults' => ['name' => '']],
|
||||||
|
['name' => 'preference#set', 'url' => '/preference', 'verb' => 'POST'],
|
||||||
|
['name' => 'preference#delete', 'url' => '/preference/{fileId}/{scope}/{name}', 'verb' => 'DELETE'],
|
||||||
|
]];
|
||||||
|
|
3
css/settings.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.nav-icon-reader {
|
||||||
|
background-image: url('../img/app.svg?v=2');
|
||||||
|
}
|
50
img/app.svg
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
id="svg5261"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
width="32"
|
||||||
|
height="32">
|
||||||
|
<metadata
|
||||||
|
id="metadata5267">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs5265" />
|
||||||
|
<g
|
||||||
|
style="fill:#060000;fill-opacity:1"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(1.4457782,-1020.2752)">
|
||||||
|
<path
|
||||||
|
d="m 11.168042,1021.5246 c -2.2036689,-1.0138 -6.5467059,0.9451 -7.7942059,2.6697 -0.555819,0.7717 -0.516525,1.3278 -0.516525,1.6433 v 16.8796 l 16.2867569,8.8577 3.062736,-1.4617 v -16.4387 l -16.7222168,-8.3914 c 0.897356,-0.9871 2.9155749,-2.1908 4.4288007,-1.7074 l 14.8949581,6.9633 v 18.3129 l 3.070508,-1.4642 v -18.3122 z"
|
||||||
|
id="path2989"
|
||||||
|
style="fill:#060000;fill-opacity:1" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
id="text3335"
|
||||||
|
y="11.710122"
|
||||||
|
x="6.2117367"
|
||||||
|
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
style="font-size:40px;line-height:1.25"
|
||||||
|
y="11.710122"
|
||||||
|
x="6.2117367"
|
||||||
|
id="tspan3337"> </tspan></text>
|
||||||
|
<path
|
||||||
|
style="fill:#fcfcfc;fill-opacity:1"
|
||||||
|
id="path4015-6"
|
||||||
|
d="m 11.935898,22.551661 -3.7773648,-6.800426 4.5440888,-2.201163 1.259258,2.266597 -3.029623,1.467379 1.259148,2.266823 4.544091,-2.201162 -3.290554,-5.923647 c -0.268675,-0.483984 -0.748735,-0.749275 -1.072248,-0.592546 l -6.4023558,3.101677 c -0.323238,0.156596 -0.367469,0.675885 -0.09879,1.159868 l 5.3222018,9.580704 c 0.268927,0.484123 0.748989,0.749415 1.072249,0.592546 l 6.402425,-3.10145 c 0.32326,-0.156869 0.367492,-0.676158 0.09879,-1.159868 l -0.772645,-1.390597 z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
BIN
img/book.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
img/loading.gif
Normal file
After Width: | Height: | Size: 3.1 KiB |
197
js/lib/Blob.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/* Blob.js
|
||||||
|
* A Blob implementation.
|
||||||
|
* 2014-07-24
|
||||||
|
*
|
||||||
|
* By Eli Grey, http://eligrey.com
|
||||||
|
* By Devin Samarin, https://github.com/dsamarin
|
||||||
|
* License: X11/MIT
|
||||||
|
* See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*global self, unescape */
|
||||||
|
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
||||||
|
plusplus: true */
|
||||||
|
|
||||||
|
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
|
||||||
|
|
||||||
|
(function (view) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
view.URL = view.URL || view.webkitURL;
|
||||||
|
|
||||||
|
if (view.Blob && view.URL) {
|
||||||
|
try {
|
||||||
|
new Blob;
|
||||||
|
return;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internally we use a BlobBuilder implementation to base Blob off of
|
||||||
|
// in order to support older browsers that only have BlobBuilder
|
||||||
|
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
|
||||||
|
var
|
||||||
|
get_class = function(object) {
|
||||||
|
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
|
||||||
|
}
|
||||||
|
, FakeBlobBuilder = function BlobBuilder() {
|
||||||
|
this.data = [];
|
||||||
|
}
|
||||||
|
, FakeBlob = function Blob(data, type, encoding) {
|
||||||
|
this.data = data;
|
||||||
|
this.size = data.length;
|
||||||
|
this.type = type;
|
||||||
|
this.encoding = encoding;
|
||||||
|
}
|
||||||
|
, FBB_proto = FakeBlobBuilder.prototype
|
||||||
|
, FB_proto = FakeBlob.prototype
|
||||||
|
, FileReaderSync = view.FileReaderSync
|
||||||
|
, FileException = function(type) {
|
||||||
|
this.code = this[this.name = type];
|
||||||
|
}
|
||||||
|
, file_ex_codes = (
|
||||||
|
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
|
||||||
|
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
|
||||||
|
).split(" ")
|
||||||
|
, file_ex_code = file_ex_codes.length
|
||||||
|
, real_URL = view.URL || view.webkitURL || view
|
||||||
|
, real_create_object_URL = real_URL.createObjectURL
|
||||||
|
, real_revoke_object_URL = real_URL.revokeObjectURL
|
||||||
|
, URL = real_URL
|
||||||
|
, btoa = view.btoa
|
||||||
|
, atob = view.atob
|
||||||
|
|
||||||
|
, ArrayBuffer = view.ArrayBuffer
|
||||||
|
, Uint8Array = view.Uint8Array
|
||||||
|
|
||||||
|
, origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/
|
||||||
|
;
|
||||||
|
FakeBlob.fake = FB_proto.fake = true;
|
||||||
|
while (file_ex_code--) {
|
||||||
|
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
|
||||||
|
}
|
||||||
|
// Polyfill URL
|
||||||
|
if (!real_URL.createObjectURL) {
|
||||||
|
URL = view.URL = function(uri) {
|
||||||
|
var
|
||||||
|
uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
||||||
|
, uri_origin
|
||||||
|
;
|
||||||
|
uri_info.href = uri;
|
||||||
|
if (!("origin" in uri_info)) {
|
||||||
|
if (uri_info.protocol.toLowerCase() === "data:") {
|
||||||
|
uri_info.origin = null;
|
||||||
|
} else {
|
||||||
|
uri_origin = uri.match(origin);
|
||||||
|
uri_info.origin = uri_origin && uri_origin[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uri_info;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
URL.createObjectURL = function(blob) {
|
||||||
|
var
|
||||||
|
type = blob.type
|
||||||
|
, data_URI_header
|
||||||
|
;
|
||||||
|
if (type === null) {
|
||||||
|
type = "application/octet-stream";
|
||||||
|
}
|
||||||
|
if (blob instanceof FakeBlob) {
|
||||||
|
data_URI_header = "data:" + type;
|
||||||
|
if (blob.encoding === "base64") {
|
||||||
|
return data_URI_header + ";base64," + blob.data;
|
||||||
|
} else if (blob.encoding === "URI") {
|
||||||
|
return data_URI_header + "," + decodeURIComponent(blob.data);
|
||||||
|
} if (btoa) {
|
||||||
|
return data_URI_header + ";base64," + btoa(blob.data);
|
||||||
|
} else {
|
||||||
|
return data_URI_header + "," + encodeURIComponent(blob.data);
|
||||||
|
}
|
||||||
|
} else if (real_create_object_URL) {
|
||||||
|
return real_create_object_URL.call(real_URL, blob);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
URL.revokeObjectURL = function(object_URL) {
|
||||||
|
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
|
||||||
|
real_revoke_object_URL.call(real_URL, object_URL);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FBB_proto.append = function(data/*, endings*/) {
|
||||||
|
var bb = this.data;
|
||||||
|
// decode data to a binary string
|
||||||
|
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
|
||||||
|
var
|
||||||
|
str = ""
|
||||||
|
, buf = new Uint8Array(data)
|
||||||
|
, i = 0
|
||||||
|
, buf_len = buf.length
|
||||||
|
;
|
||||||
|
for (; i < buf_len; i++) {
|
||||||
|
str += String.fromCharCode(buf[i]);
|
||||||
|
}
|
||||||
|
bb.push(str);
|
||||||
|
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
|
||||||
|
if (FileReaderSync) {
|
||||||
|
var fr = new FileReaderSync;
|
||||||
|
bb.push(fr.readAsBinaryString(data));
|
||||||
|
} else {
|
||||||
|
// async FileReader won't work as BlobBuilder is sync
|
||||||
|
throw new FileException("NOT_READABLE_ERR");
|
||||||
|
}
|
||||||
|
} else if (data instanceof FakeBlob) {
|
||||||
|
if (data.encoding === "base64" && atob) {
|
||||||
|
bb.push(atob(data.data));
|
||||||
|
} else if (data.encoding === "URI") {
|
||||||
|
bb.push(decodeURIComponent(data.data));
|
||||||
|
} else if (data.encoding === "raw") {
|
||||||
|
bb.push(data.data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof data !== "string") {
|
||||||
|
data += ""; // convert unsupported types to strings
|
||||||
|
}
|
||||||
|
// decode UTF-16 to binary string
|
||||||
|
bb.push(unescape(encodeURIComponent(data)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FBB_proto.getBlob = function(type) {
|
||||||
|
if (!arguments.length) {
|
||||||
|
type = null;
|
||||||
|
}
|
||||||
|
return new FakeBlob(this.data.join(""), type, "raw");
|
||||||
|
};
|
||||||
|
FBB_proto.toString = function() {
|
||||||
|
return "[object BlobBuilder]";
|
||||||
|
};
|
||||||
|
FB_proto.slice = function(start, end, type) {
|
||||||
|
var args = arguments.length;
|
||||||
|
if (args < 3) {
|
||||||
|
type = null;
|
||||||
|
}
|
||||||
|
return new FakeBlob(
|
||||||
|
this.data.slice(start, args > 1 ? end : this.data.length)
|
||||||
|
, type
|
||||||
|
, this.encoding
|
||||||
|
);
|
||||||
|
};
|
||||||
|
FB_proto.toString = function() {
|
||||||
|
return "[object Blob]";
|
||||||
|
};
|
||||||
|
FB_proto.close = function() {
|
||||||
|
this.size = 0;
|
||||||
|
delete this.data;
|
||||||
|
};
|
||||||
|
return FakeBlobBuilder;
|
||||||
|
}(view));
|
||||||
|
|
||||||
|
view.Blob = function(blobParts, options) {
|
||||||
|
var type = options ? (options.type || "") : "";
|
||||||
|
var builder = new BlobBuilder();
|
||||||
|
if (blobParts) {
|
||||||
|
for (var i = 0, len = blobParts.length; i < len; i++) {
|
||||||
|
builder.append(blobParts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.getBlob(type);
|
||||||
|
};
|
||||||
|
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
|
31
js/lib/blob.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Blob = (function() {
|
||||||
|
var nativeBlob = Blob;
|
||||||
|
|
||||||
|
// Add unprefixed slice() method.
|
||||||
|
if (Blob.prototype.webkitSlice) {
|
||||||
|
Blob.prototype.slice = Blob.prototype.webkitSlice;
|
||||||
|
}
|
||||||
|
else if (Blob.prototype.mozSlice) {
|
||||||
|
Blob.prototype.slice = Blob.prototype.mozSlice;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporarily replace Blob() constructor with one that checks support.
|
||||||
|
return function(parts, properties) {
|
||||||
|
try {
|
||||||
|
// Restore native Blob() constructor, so this check is only evaluated once.
|
||||||
|
Blob = nativeBlob;
|
||||||
|
return new Blob(parts || [], properties || {});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// If construction fails provide one that uses BlobBuilder.
|
||||||
|
Blob = function (parts, properties) {
|
||||||
|
var bb = new (WebKitBlobBuilder || MozBlobBuilder), i;
|
||||||
|
for (i in parts) {
|
||||||
|
bb.append(parts[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bb.getBlob(properties && properties.type ? properties.type : undefined);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
629
js/lib/typedarray.js
Normal file
@ -0,0 +1,629 @@
|
|||||||
|
/*
|
||||||
|
$LicenseInfo:firstyear=2010&license=mit$
|
||||||
|
|
||||||
|
Copyright (c) 2010, Linden Research, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
$/LicenseInfo$
|
||||||
|
*/
|
||||||
|
/*global document*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// ES3/ES5 implementation of the Krhonos TypedArray Working Draft (work in progress):
|
||||||
|
// Ref: https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/doc/spec/TypedArray-spec.html
|
||||||
|
// Date: 2011-02-01
|
||||||
|
//
|
||||||
|
// Variations:
|
||||||
|
// * Float/Double -> Float32/Float64, per WebGL-Public mailing list conversations (post 5/17)
|
||||||
|
// * Allows typed_array.get/set() as alias for subscripts (typed_array[])
|
||||||
|
|
||||||
|
var ArrayBuffer, ArrayBufferView,
|
||||||
|
Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array,
|
||||||
|
DataView;
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
/*jslint bitwise: false, nomen: false */
|
||||||
|
|
||||||
|
// Approximations of internal ECMAScript conversion functions
|
||||||
|
var ECMAScript = {
|
||||||
|
ToInt32: function (v) { return v >> 0; },
|
||||||
|
ToUint32: function (v) { return v >>> 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Raise an INDEX_SIZE_ERR event - intentionally induces a DOM error
|
||||||
|
function raise_INDEX_SIZE_ERR() {
|
||||||
|
if (document) {
|
||||||
|
// raises DOMException(INDEX_SIZE_ERR)
|
||||||
|
document.createTextNode("").splitText(1);
|
||||||
|
}
|
||||||
|
throw new RangeError("INDEX_SIZE_ERR");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ES5: lock down object properties
|
||||||
|
function configureProperties(obj) {
|
||||||
|
if (Object.getOwnPropertyNames && Object.defineProperty) {
|
||||||
|
var props = Object.getOwnPropertyNames(obj), i;
|
||||||
|
for (i = 0; i < props.length; i += 1) {
|
||||||
|
Object.defineProperty(obj, props[i], {
|
||||||
|
value: obj[props[i]],
|
||||||
|
writable: false,
|
||||||
|
enumerable: false,
|
||||||
|
configurable: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// emulate ES5 getter/setter API using legacy APIs
|
||||||
|
// http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx
|
||||||
|
if (Object.prototype.__defineGetter__ && !Object.defineProperty) {
|
||||||
|
Object.defineProperty = function (obj, prop, desc) {
|
||||||
|
if (desc.hasOwnProperty('get')) { obj.__defineGetter__(prop, desc.get); }
|
||||||
|
if (desc.hasOwnProperty('set')) { obj.__defineSetter__(prop, desc.set); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ES5: Make obj[index] an alias for obj._getter(index)/obj._setter(index, value)
|
||||||
|
// for index in 0 ... obj.length
|
||||||
|
function makeArrayAccessors(obj) {
|
||||||
|
if (!Object.defineProperty) { return; }
|
||||||
|
|
||||||
|
function makeArrayAccessor(index) {
|
||||||
|
Object.defineProperty(obj, index, {
|
||||||
|
'get': function () { return obj._getter(index); },
|
||||||
|
'set': function (v) { obj._setter(index, v); },
|
||||||
|
enumerable: true,
|
||||||
|
configurable: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < obj.length; i += 1) {
|
||||||
|
makeArrayAccessor(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal conversion functions:
|
||||||
|
// pack<Type>() - take a number (interpreted as Type), output a byte array
|
||||||
|
// unpack<Type>() - take a byte array, output a Type-like number
|
||||||
|
|
||||||
|
function as_signed(value, bits) { var s = 32 - bits; return (value << s) >> s; }
|
||||||
|
function as_unsigned(value, bits) { var s = 32 - bits; return (value << s) >>> s; }
|
||||||
|
|
||||||
|
function packInt8(n) { return [n & 0xff]; }
|
||||||
|
function unpackInt8(bytes) { return as_signed(bytes[0], 8); }
|
||||||
|
|
||||||
|
function packUint8(n) { return [n & 0xff]; }
|
||||||
|
function unpackUint8(bytes) { return as_unsigned(bytes[0], 8); }
|
||||||
|
|
||||||
|
function packInt16(n) { return [(n >> 8) & 0xff, n & 0xff]; }
|
||||||
|
function unpackInt16(bytes) { return as_signed(bytes[0] << 8 | bytes[1], 16); }
|
||||||
|
|
||||||
|
function packUint16(n) { return [(n >> 8) & 0xff, n & 0xff]; }
|
||||||
|
function unpackUint16(bytes) { return as_unsigned(bytes[0] << 8 | bytes[1], 16); }
|
||||||
|
|
||||||
|
function packInt32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; }
|
||||||
|
function unpackInt32(bytes) { return as_signed(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); }
|
||||||
|
|
||||||
|
function packUint32(n) { return [(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; }
|
||||||
|
function unpackUint32(bytes) { return as_unsigned(bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3], 32); }
|
||||||
|
|
||||||
|
function packIEEE754(v, ebits, fbits) {
|
||||||
|
|
||||||
|
var bias = (1 << (ebits - 1)) - 1,
|
||||||
|
s, e, f, ln,
|
||||||
|
i, bits, str, bytes;
|
||||||
|
|
||||||
|
// Compute sign, exponent, fraction
|
||||||
|
if (isNaN(v)) {
|
||||||
|
// http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping
|
||||||
|
e = (1 << bias) - 1; f = Math.pow(2, fbits - 1); s = 0;
|
||||||
|
}
|
||||||
|
else if (v === Infinity || v === -Infinity) {
|
||||||
|
e = (1 << bias) - 1; f = 0; s = (v < 0) ? 1 : 0;
|
||||||
|
}
|
||||||
|
else if (v === 0) {
|
||||||
|
e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s = v < 0;
|
||||||
|
v = Math.abs(v);
|
||||||
|
|
||||||
|
if (v >= Math.pow(2, 1 - bias)) {
|
||||||
|
// Normalized
|
||||||
|
ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
|
||||||
|
e = ln + bias;
|
||||||
|
f = Math.round(v * Math.pow(2, fbits - ln) - Math.pow(2, fbits));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Denormalized
|
||||||
|
e = 0;
|
||||||
|
f = Math.round(v / Math.pow(2, 1 - bias - fbits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack sign, exponent, fraction
|
||||||
|
bits = [];
|
||||||
|
for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = Math.floor(f / 2); }
|
||||||
|
for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = Math.floor(e / 2); }
|
||||||
|
bits.push(s ? 1 : 0);
|
||||||
|
bits.reverse();
|
||||||
|
str = bits.join('');
|
||||||
|
|
||||||
|
// Bits to bytes
|
||||||
|
bytes = [];
|
||||||
|
while (str.length) {
|
||||||
|
bytes.push(parseInt(str.substring(0, 8), 2));
|
||||||
|
str = str.substring(8);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpackIEEE754(bytes, ebits, fbits) {
|
||||||
|
|
||||||
|
// Bytes to bits
|
||||||
|
var bits = [], i, j, b, str,
|
||||||
|
bias, s, e, f;
|
||||||
|
|
||||||
|
for (i = bytes.length; i; i -= 1) {
|
||||||
|
b = bytes[i - 1];
|
||||||
|
for (j = 8; j; j -= 1) {
|
||||||
|
bits.push(b % 2 ? 1 : 0); b = b >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bits.reverse();
|
||||||
|
str = bits.join('');
|
||||||
|
|
||||||
|
// Unpack sign, exponent, fraction
|
||||||
|
bias = (1 << (ebits - 1)) - 1;
|
||||||
|
s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
|
||||||
|
e = parseInt(str.substring(1, 1 + ebits), 2);
|
||||||
|
f = parseInt(str.substring(1 + ebits), 2);
|
||||||
|
|
||||||
|
// Produce number
|
||||||
|
if (e === (1 << ebits) - 1) {
|
||||||
|
return f !== 0 ? NaN : s * Infinity;
|
||||||
|
}
|
||||||
|
else if (e > 0) {
|
||||||
|
// Normalized
|
||||||
|
return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits));
|
||||||
|
}
|
||||||
|
else if (f !== 0) {
|
||||||
|
// Denormalized
|
||||||
|
return s * Math.pow(2, -(bias - 1)) * (f / Math.pow(2, fbits));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return s < 0 ? -0 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unpackFloat64(b) { return unpackIEEE754(b, 11, 52); }
|
||||||
|
function packFloat64(v) { return packIEEE754(v, 11, 52); }
|
||||||
|
function unpackFloat32(b) { return unpackIEEE754(b, 8, 23); }
|
||||||
|
function packFloat32(v) { return packIEEE754(v, 8, 23); }
|
||||||
|
|
||||||
|
|
||||||
|
if (!ArrayBuffer) {
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
//
|
||||||
|
// 3 The ArrayBuffer Type
|
||||||
|
//
|
||||||
|
|
||||||
|
ArrayBuffer = function (length) {
|
||||||
|
length = ECMAScript.ToInt32(length);
|
||||||
|
if (length < 0) { throw new RangeError('ArrayBuffer size is not a small enough positive integer.'); }
|
||||||
|
|
||||||
|
this.byteLength = length;
|
||||||
|
this._bytes = [];
|
||||||
|
this._bytes.length = length;
|
||||||
|
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < this.byteLength; i += 1) {
|
||||||
|
this._bytes[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
configureProperties(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// 4 The ArrayBufferView Type
|
||||||
|
//
|
||||||
|
|
||||||
|
// NOTE: this constructor is not exported
|
||||||
|
ArrayBufferView = function () {
|
||||||
|
//this.buffer = null;
|
||||||
|
//this.byteOffset = 0;
|
||||||
|
//this.byteLength = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// 5 The Typed Array View Types
|
||||||
|
//
|
||||||
|
|
||||||
|
function makeTypedArrayConstructor(bytesPerElement, pack, unpack) {
|
||||||
|
// Each TypedArray type requires a distinct constructor instance with
|
||||||
|
// identical logic, which this produces.
|
||||||
|
|
||||||
|
var ctor;
|
||||||
|
ctor = function (buffer, byteOffset, length) {
|
||||||
|
var array, sequence, i, s;
|
||||||
|
|
||||||
|
// Constructor(unsigned long length)
|
||||||
|
if (!arguments.length || typeof arguments[0] === 'number') {
|
||||||
|
this.length = ECMAScript.ToInt32(arguments[0]);
|
||||||
|
if (length < 0) { throw new RangeError('ArrayBufferView size is not a small enough positive integer.'); }
|
||||||
|
|
||||||
|
this.byteLength = this.length * this.BYTES_PER_ELEMENT;
|
||||||
|
this.buffer = new ArrayBuffer(this.byteLength);
|
||||||
|
this.byteOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor(TypedArray array)
|
||||||
|
else if (typeof arguments[0] === 'object' && arguments[0].constructor === ctor) {
|
||||||
|
array = arguments[0];
|
||||||
|
|
||||||
|
this.length = array.length;
|
||||||
|
this.byteLength = this.length * this.BYTES_PER_ELEMENT;
|
||||||
|
this.buffer = new ArrayBuffer(this.byteLength);
|
||||||
|
this.byteOffset = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < this.length; i += 1) {
|
||||||
|
this._setter(i, array._getter(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor(sequence<type> array)
|
||||||
|
else if (typeof arguments[0] === 'object' && !(arguments[0] instanceof ArrayBuffer)) {
|
||||||
|
sequence = arguments[0];
|
||||||
|
|
||||||
|
this.length = ECMAScript.ToUint32(sequence.length);
|
||||||
|
this.byteLength = this.length * this.BYTES_PER_ELEMENT;
|
||||||
|
this.buffer = new ArrayBuffer(this.byteLength);
|
||||||
|
this.byteOffset = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < this.length; i += 1) {
|
||||||
|
s = sequence[i];
|
||||||
|
this._setter(i, Number(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor(ArrayBuffer buffer,
|
||||||
|
// optional unsigned long byteOffset, optional unsigned long length)
|
||||||
|
else if (typeof arguments[0] === 'object' && arguments[0] instanceof ArrayBuffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
|
||||||
|
this.byteOffset = ECMAScript.ToUint32(byteOffset);
|
||||||
|
if (this.byteOffset > this.buffer.byteLength) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // byteOffset out of range
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.byteOffset % this.BYTES_PER_ELEMENT) {
|
||||||
|
// The given byteOffset must be a multiple of the element
|
||||||
|
// size of the specific type, otherwise an exception is raised.
|
||||||
|
//raise_INDEX_SIZE_ERR();
|
||||||
|
throw new RangeError("ArrayBuffer length minus the byteOffset is not a multiple of the element size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.length < 3) {
|
||||||
|
this.byteLength = this.buffer.byteLength - this.byteOffset;
|
||||||
|
|
||||||
|
if (this.byteLength % this.BYTES_PER_ELEMENT) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // length of buffer minus byteOffset not a multiple of the element size
|
||||||
|
}
|
||||||
|
this.length = this.byteLength / this.BYTES_PER_ELEMENT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.length = ECMAScript.ToUint32(length);
|
||||||
|
this.byteLength = this.length * this.BYTES_PER_ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.byteOffset + this.byteLength) > this.buffer.byteLength) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // byteOffset and length reference an area beyond the end of the buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new TypeError("Unexpected argument type(s)");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.constructor = ctor;
|
||||||
|
|
||||||
|
// ES5-only magic
|
||||||
|
configureProperties(this);
|
||||||
|
makeArrayAccessors(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
ctor.prototype = new ArrayBufferView();
|
||||||
|
ctor.prototype.BYTES_PER_ELEMENT = bytesPerElement;
|
||||||
|
ctor.prototype._pack = pack;
|
||||||
|
ctor.prototype._unpack = unpack;
|
||||||
|
ctor.BYTES_PER_ELEMENT = bytesPerElement;
|
||||||
|
|
||||||
|
// getter type (unsigned long index);
|
||||||
|
ctor.prototype._getter = function (index) {
|
||||||
|
if (arguments.length < 1) { throw new SyntaxError("Not enough arguments"); }
|
||||||
|
|
||||||
|
index = ECMAScript.ToUint32(index);
|
||||||
|
if (index >= this.length) {
|
||||||
|
//raise_INDEX_SIZE_ERR(); // Array index out of range
|
||||||
|
return; // undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = [], i, o;
|
||||||
|
for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT;
|
||||||
|
i < this.BYTES_PER_ELEMENT;
|
||||||
|
i += 1, o += 1) {
|
||||||
|
bytes.push(this.buffer._bytes[o]);
|
||||||
|
}
|
||||||
|
return this._unpack(bytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
// NONSTANDARD: convenience alias for getter: type get(unsigned long index);
|
||||||
|
ctor.prototype.get = ctor.prototype._getter;
|
||||||
|
|
||||||
|
// setter void (unsigned long index, type value);
|
||||||
|
ctor.prototype._setter = function (index, value) {
|
||||||
|
if (arguments.length < 2) { throw new SyntaxError("Not enough arguments"); }
|
||||||
|
|
||||||
|
index = ECMAScript.ToUint32(index);
|
||||||
|
if (index >= this.length) {
|
||||||
|
//raise_INDEX_SIZE_ERR(); // Array index out of range
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = this._pack(value), i, o;
|
||||||
|
for (i = 0, o = this.byteOffset + index * this.BYTES_PER_ELEMENT;
|
||||||
|
i < this.BYTES_PER_ELEMENT;
|
||||||
|
i += 1, o += 1) {
|
||||||
|
this.buffer._bytes[o] = bytes[i];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// void set(TypedArray array, optional unsigned long offset);
|
||||||
|
// void set(sequence<type> array, optional unsigned long offset);
|
||||||
|
ctor.prototype.set = function (index, value) {
|
||||||
|
if (arguments.length < 1) { throw new SyntaxError("Not enough arguments"); }
|
||||||
|
var array, sequence, offset, len,
|
||||||
|
i, s, d,
|
||||||
|
byteOffset, byteLength, tmp;
|
||||||
|
|
||||||
|
// void set(TypedArray array, optional unsigned long offset);
|
||||||
|
if (typeof arguments[0] === 'object' && arguments[0].constructor === this.constructor) {
|
||||||
|
array = arguments[0];
|
||||||
|
offset = ECMAScript.ToUint32(arguments[1]);
|
||||||
|
|
||||||
|
if (offset + array.length > this.length) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // Offset plus length of array is out of range
|
||||||
|
}
|
||||||
|
|
||||||
|
byteOffset = this.byteOffset + offset * this.BYTES_PER_ELEMENT;
|
||||||
|
byteLength = array.length * this.BYTES_PER_ELEMENT;
|
||||||
|
|
||||||
|
if (array.buffer === this.buffer) {
|
||||||
|
tmp = [];
|
||||||
|
for (i = 0, s = array.byteOffset; i < byteLength; i += 1, s += 1) {
|
||||||
|
tmp[i] = array.buffer._bytes[s];
|
||||||
|
}
|
||||||
|
for (i = 0, d = byteOffset; i < byteLength; i += 1, d += 1) {
|
||||||
|
this.buffer._bytes[d] = tmp[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (i = 0, s = array.byteOffset, d = byteOffset;
|
||||||
|
i < byteLength; i += 1, s += 1, d += 1) {
|
||||||
|
this.buffer._bytes[d] = array.buffer._bytes[s];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// void set(sequence<type> array, optional unsigned long offset);
|
||||||
|
else if (typeof arguments[0] === 'object' && typeof arguments[0].length !== 'undefined') {
|
||||||
|
sequence = arguments[0];
|
||||||
|
len = ECMAScript.ToUint32(sequence.length);
|
||||||
|
offset = ECMAScript.ToUint32(arguments[1]);
|
||||||
|
|
||||||
|
if (offset + len > this.length) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // Offset plus length of array is out of range
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < len; i += 1) {
|
||||||
|
s = sequence[i];
|
||||||
|
this._setter(offset + i, Number(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
throw new TypeError("Unexpected argument type(s)");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TypedArray subarray(long begin, optional long end);
|
||||||
|
ctor.prototype.subarray = function (start, end) {
|
||||||
|
function clamp(v, min, max) { return v < min ? min : v > max ? max : v; }
|
||||||
|
|
||||||
|
start = ECMAScript.ToInt32(start);
|
||||||
|
end = ECMAScript.ToInt32(end);
|
||||||
|
|
||||||
|
if (arguments.length < 1) { start = 0; }
|
||||||
|
if (arguments.length < 2) { end = this.length; }
|
||||||
|
|
||||||
|
if (start < 0) { start = this.length + start; }
|
||||||
|
if (end < 0) { end = this.length + end; }
|
||||||
|
|
||||||
|
start = clamp(start, 0, this.length);
|
||||||
|
end = clamp(end, 0, this.length);
|
||||||
|
|
||||||
|
var len = end - start;
|
||||||
|
if (len < 0) {
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new this.constructor(this.buffer, start * this.BYTES_PER_ELEMENT, len);
|
||||||
|
};
|
||||||
|
|
||||||
|
return ctor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Int8Array = Int8Array || makeTypedArrayConstructor(1, packInt8, unpackInt8);
|
||||||
|
Uint8Array = Uint8Array || makeTypedArrayConstructor(1, packUint8, unpackUint8);
|
||||||
|
Int16Array = Int16Array || makeTypedArrayConstructor(2, packInt16, unpackInt16);
|
||||||
|
Uint16Array = Uint16Array || makeTypedArrayConstructor(2, packUint16, unpackUint16);
|
||||||
|
Int32Array = Int32Array || makeTypedArrayConstructor(4, packInt32, unpackInt32);
|
||||||
|
Uint32Array = Uint32Array || makeTypedArrayConstructor(4, packUint32, unpackUint32);
|
||||||
|
Float32Array = Float32Array || makeTypedArrayConstructor(4, packFloat32, unpackFloat32);
|
||||||
|
Float64Array = Float64Array || makeTypedArrayConstructor(8, packFloat64, unpackFloat64);
|
||||||
|
|
||||||
|
} ());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!DataView) {
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
//
|
||||||
|
// 6 The DataView View Type
|
||||||
|
//
|
||||||
|
|
||||||
|
function r(array, index) {
|
||||||
|
if (typeof array.get === 'function') {
|
||||||
|
return array.get(index);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return array[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var IS_BIG_ENDIAN = (function () {
|
||||||
|
var u16array = new Uint16Array([0x1234]),
|
||||||
|
u8array = new Uint8Array(u16array.buffer);
|
||||||
|
return r(u8array, 0) === 0x12;
|
||||||
|
} ());
|
||||||
|
|
||||||
|
// Constructor(ArrayBuffer buffer,
|
||||||
|
// optional unsigned long byteOffset,
|
||||||
|
// optional unsigned long byteLength)
|
||||||
|
DataView = function (buffer, byteOffset, byteLength) {
|
||||||
|
if (!(typeof buffer === 'object' && buffer instanceof ArrayBuffer)) {
|
||||||
|
throw new TypeError("TypeError");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer = buffer;
|
||||||
|
|
||||||
|
this.byteOffset = ECMAScript.ToUint32(byteOffset);
|
||||||
|
if (this.byteOffset > this.buffer.byteLength) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // byteOffset out of range
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.length < 3) {
|
||||||
|
this.byteLength = this.buffer.byteLength - this.byteOffset;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.byteLength = ECMAScript.ToUint32(byteLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.byteOffset + this.byteLength) > this.buffer.byteLength) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // byteOffset and length reference an area beyond the end of the buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// ES5-only magic
|
||||||
|
configureProperties(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ArrayBufferView) {
|
||||||
|
DataView.prototype = new ArrayBufferView();
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDataView_getter(arrayType) {
|
||||||
|
return function (byteOffset, littleEndian) {
|
||||||
|
/*jslint newcap: false*/
|
||||||
|
byteOffset = ECMAScript.ToUint32(byteOffset);
|
||||||
|
|
||||||
|
if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // Array index out of range
|
||||||
|
}
|
||||||
|
byteOffset += this.byteOffset;
|
||||||
|
|
||||||
|
var uint8Array = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT),
|
||||||
|
bytes = [], i;
|
||||||
|
for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) {
|
||||||
|
bytes.push(r(uint8Array, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) {
|
||||||
|
bytes.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return r(new arrayType(new Uint8Array(bytes).buffer), 0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
DataView.prototype.getUint8 = makeDataView_getter(Uint8Array);
|
||||||
|
DataView.prototype.getInt8 = makeDataView_getter(Int8Array);
|
||||||
|
DataView.prototype.getUint16 = makeDataView_getter(Uint16Array);
|
||||||
|
DataView.prototype.getInt16 = makeDataView_getter(Int16Array);
|
||||||
|
DataView.prototype.getUint32 = makeDataView_getter(Uint32Array);
|
||||||
|
DataView.prototype.getInt32 = makeDataView_getter(Int32Array);
|
||||||
|
DataView.prototype.getFloat32 = makeDataView_getter(Float32Array);
|
||||||
|
DataView.prototype.getFloat64 = makeDataView_getter(Float64Array);
|
||||||
|
|
||||||
|
function makeDataView_setter(arrayType) {
|
||||||
|
return function (byteOffset, value, littleEndian) {
|
||||||
|
/*jslint newcap: false*/
|
||||||
|
byteOffset = ECMAScript.ToUint32(byteOffset);
|
||||||
|
if (byteOffset + arrayType.BYTES_PER_ELEMENT > this.byteLength) {
|
||||||
|
raise_INDEX_SIZE_ERR(); // Array index out of range
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get bytes
|
||||||
|
var typeArray = new arrayType([value]),
|
||||||
|
byteArray = new Uint8Array(typeArray.buffer),
|
||||||
|
bytes = [], i, byteView;
|
||||||
|
|
||||||
|
for (i = 0; i < arrayType.BYTES_PER_ELEMENT; i += 1) {
|
||||||
|
bytes.push(r(byteArray, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flip if necessary
|
||||||
|
if (Boolean(littleEndian) === Boolean(IS_BIG_ENDIAN)) {
|
||||||
|
bytes.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write them
|
||||||
|
byteView = new Uint8Array(this.buffer, byteOffset, arrayType.BYTES_PER_ELEMENT);
|
||||||
|
byteView.set(bytes);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
DataView.prototype.setUint8 = makeDataView_setter(Uint8Array);
|
||||||
|
DataView.prototype.setInt8 = makeDataView_setter(Int8Array);
|
||||||
|
DataView.prototype.setUint16 = makeDataView_setter(Uint16Array);
|
||||||
|
DataView.prototype.setInt16 = makeDataView_setter(Int16Array);
|
||||||
|
DataView.prototype.setUint32 = makeDataView_setter(Uint32Array);
|
||||||
|
DataView.prototype.setInt32 = makeDataView_setter(Int32Array);
|
||||||
|
DataView.prototype.setFloat32 = makeDataView_setter(Float32Array);
|
||||||
|
DataView.prototype.setFloat64 = makeDataView_setter(Float64Array);
|
||||||
|
|
||||||
|
} ());
|
||||||
|
}
|
||||||
|
|
||||||
|
} ());
|
||||||
|
|
1
js/lib/typedarray.min.js
vendored
Normal file
75
js/lib/wgxpath.install.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
(function(){function h(a){return function(){return this[a]}}function l(a){return function(){return a}}var m=this;
|
||||||
|
function ba(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
|
||||||
|
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function n(a){return"string"==typeof a}function ca(a,b,c){return a.call.apply(a.bind,arguments)}function da(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}
|
||||||
|
function ea(a,b,c){ea=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?ca:da;return ea.apply(null,arguments)}function fa(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}}
|
||||||
|
function q(a){var b=r;function c(){}c.prototype=b.prototype;a.G=b.prototype;a.prototype=new c;a.F=function(a,c,f){for(var g=Array(arguments.length-2),k=2;k<arguments.length;k++)g[k-2]=arguments[k];return b.prototype[c].apply(a,g)}};/*
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2007 Cybozu Labs, Inc.
|
||||||
|
Copyright (c) 2012 Google Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
function t(a,b,c){this.a=a;this.b=b||1;this.f=c||1};var ga=String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};function u(a,b){return-1!=a.indexOf(b)}function ha(a,b){return a<b?-1:a>b?1:0};var v=Array.prototype,ia=v.indexOf?function(a,b,c){return v.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(n(a))return n(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},w=v.forEach?function(a,b,c){v.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=n(a)?a.split(""):a,f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)},ja=v.filter?function(a,b,c){return v.filter.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=[],f=0,g=n(a)?
|
||||||
|
a.split(""):a,k=0;k<d;k++)if(k in g){var p=g[k];b.call(c,p,k,a)&&(e[f++]=p)}return e},x=v.reduce?function(a,b,c,d){d&&(b=ea(b,d));return v.reduce.call(a,b,c)}:function(a,b,c,d){var e=c;w(a,function(c,g){e=b.call(d,e,c,g,a)});return e},ka=v.some?function(a,b,c){return v.some.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=n(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return!0;return!1};
|
||||||
|
function la(a,b){var c;a:{c=a.length;for(var d=n(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){c=e;break a}c=-1}return 0>c?null:n(a)?a.charAt(c):a[c]}function ma(a){return v.concat.apply(v,arguments)}function na(a,b,c){return 2>=arguments.length?v.slice.call(a,b):v.slice.call(a,b,c)};var y;a:{var oa=m.navigator;if(oa){var pa=oa.userAgent;if(pa){y=pa;break a}}y=""};var qa=u(y,"Opera")||u(y,"OPR"),A=u(y,"Trident")||u(y,"MSIE"),ra=u(y,"Edge"),sa=u(y,"Gecko")&&!(u(y.toLowerCase(),"webkit")&&!u(y,"Edge"))&&!(u(y,"Trident")||u(y,"MSIE"))&&!u(y,"Edge"),ta=u(y.toLowerCase(),"webkit")&&!u(y,"Edge");function ua(){var a=y;if(sa)return/rv\:([^\);]+)(\)|;)/.exec(a);if(ra)return/Edge\/([\d\.]+)/.exec(a);if(A)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(ta)return/WebKit\/(\S+)/.exec(a)}function va(){var a=m.document;return a?a.documentMode:void 0}
|
||||||
|
var wa=function(){if(qa&&m.opera){var a=m.opera.version;return"function"==ba(a)?a():a}var a="",b=ua();b&&(a=b?b[1]:"");return A&&(b=va(),b>parseFloat(a))?String(b):a}(),xa={};
|
||||||
|
function ya(a){if(!xa[a]){for(var b=0,c=ga(String(wa)).split("."),d=ga(String(a)).split("."),e=Math.max(c.length,d.length),f=0;0==b&&f<e;f++){var g=c[f]||"",k=d[f]||"",p=RegExp("(\\d*)(\\D*)","g"),z=RegExp("(\\d*)(\\D*)","g");do{var E=p.exec(g)||["","",""],aa=z.exec(k)||["","",""];if(0==E[0].length&&0==aa[0].length)break;b=ha(0==E[1].length?0:parseInt(E[1],10),0==aa[1].length?0:parseInt(aa[1],10))||ha(0==E[2].length,0==aa[2].length)||ha(E[2],aa[2])}while(0==b)}xa[a]=0<=b}}
|
||||||
|
var za=m.document,Aa=za&&A?va()||("CSS1Compat"==za.compatMode?parseInt(wa,10):5):void 0;var B=A&&!(9<=Aa),Ba=A&&!(8<=Aa);function C(a,b,c,d){this.a=a;this.nodeName=c;this.nodeValue=d;this.nodeType=2;this.parentNode=this.ownerElement=b}function Ca(a,b){var c=Ba&&"href"==b.nodeName?a.getAttribute(b.nodeName,2):b.nodeValue;return new C(b,a,b.nodeName,c)};function Da(a){this.b=a;this.a=0}function Ea(a){a=a.match(Fa);for(var b=0;b<a.length;b++)Ga.test(a[b])&&a.splice(b,1);return new Da(a)}var Fa=RegExp("\\$?(?:(?![0-9-])[\\w-]+:)?(?![0-9-])[\\w-]+|\\/\\/|\\.\\.|::|\\d+(?:\\.\\d*)?|\\.\\d+|\"[^\"]*\"|'[^']*'|[!<>]=|\\s+|.","g"),Ga=/^\s/;function D(a,b){return a.b[a.a+(b||0)]}function F(a){return a.b[a.a++]}function Ha(a){return a.b.length<=a.a};!sa&&!A||A&&9<=Aa||sa&&ya("1.9.1");A&&ya("9");function Ia(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}
|
||||||
|
function Ja(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(A&&!(9<=Aa)){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?Ka(a,b):!c&&Ia(e,b)?-1*La(a,b):!d&&Ia(f,a)?La(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=9==a.nodeType?a:a.ownerDocument||
|
||||||
|
a.document;c=d.createRange();c.selectNode(a);c.collapse(!0);d=d.createRange();d.selectNode(b);d.collapse(!0);return c.compareBoundaryPoints(m.Range.START_TO_END,d)}function La(a,b){var c=a.parentNode;if(c==b)return-1;for(var d=b;d.parentNode!=c;)d=d.parentNode;return Ka(d,a)}function Ka(a,b){for(var c=b;c=c.previousSibling;)if(c==a)return-1;return 1};function G(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?"":b);if("string"!=typeof b)if(B&&"title"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;for(var c=0,d=[],b="";a;){do 1!=a.nodeType&&(b+=a.nodeValue),B&&"title"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return""+b}
|
||||||
|
function H(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Ba&&"class"==b&&(b="className");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function Ma(a,b,c,d,e){return(B?Na:Oa).call(null,a,b,n(c)?c:null,n(d)?d:null,e||new I)}
|
||||||
|
function Na(a,b,c,d,e){if(a instanceof J||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=Pa(a);if("*"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],k=0;b=f[k++];)H(b,c,d)&&g.push(b);f=g}for(k=0;b=f[k++];)"*"==a&&"!"==b.tagName||K(e,b);return e}Qa(a,b,c,d,e);return e}
|
||||||
|
function Oa(a,b,c,d,e){b.getElementsByName&&d&&"name"==c&&!A?(b=b.getElementsByName(d),w(b,function(b){a.a(b)&&K(e,b)})):b.getElementsByClassName&&d&&"class"==c?(b=b.getElementsByClassName(d),w(b,function(b){b.className==d&&a.a(b)&&K(e,b)})):a instanceof L?Qa(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),w(b,function(a){H(a,c,d)&&K(e,a)}));return e}
|
||||||
|
function Ra(a,b,c,d,e){var f;if((a instanceof J||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=Pa(a);if("*"!=g&&(f=ja(f,function(a){return a.tagName&&a.tagName.toLowerCase()==g}),!f))return e;c&&(f=ja(f,function(a){return H(a,c,d)}));w(f,function(a){"*"==g&&("!"==a.tagName||"*"==g&&1!=a.nodeType)||K(e,a)});return e}return Sa(a,b,c,d,e)}function Sa(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)H(b,c,d)&&a.a(b)&&K(e,b);return e}
|
||||||
|
function Qa(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)H(b,c,d)&&a.a(b)&&K(e,b),Qa(a,b,c,d,e)}function Pa(a){if(a instanceof L){if(8==a.b)return"!";if(null===a.b)return"*"}return a.f()};function I(){this.b=this.a=null;this.l=0}function Ta(a){this.node=a;this.a=this.b=null}function Ua(a,b){if(!a.a)return b;if(!b.a)return a;for(var c=a.a,d=b.a,e=null,f=null,g=0;c&&d;){var f=c.node,k=d.node;f==k||f instanceof C&&k instanceof C&&f.a==k.a?(f=c,c=c.a,d=d.a):0<Ja(c.node,d.node)?(f=d,d=d.a):(f=c,c=c.a);(f.b=e)?e.a=f:a.a=f;e=f;g++}for(f=c||d;f;)f.b=e,e=e.a=f,g++,f=f.a;a.b=e;a.l=g;return a}function Va(a,b){var c=new Ta(b);c.a=a.a;a.b?a.a.b=c:a.a=a.b=c;a.a=c;a.l++}
|
||||||
|
function K(a,b){var c=new Ta(b);c.b=a.b;a.a?a.b.a=c:a.a=a.b=c;a.b=c;a.l++}function Wa(a){return(a=a.a)?a.node:null}function Xa(a){return(a=Wa(a))?G(a):""}function M(a,b){return new Ya(a,!!b)}function Ya(a,b){this.f=a;this.b=(this.c=b)?a.b:a.a;this.a=null}function N(a){var b=a.b;if(null==b)return null;var c=a.a=b;a.b=a.c?b.b:b.a;return c.node};function Za(a){switch(a.nodeType){case 1:return fa($a,a);case 9:return Za(a.documentElement);case 11:case 10:case 6:case 12:return ab;default:return a.parentNode?Za(a.parentNode):ab}}function ab(){return null}function $a(a,b){if(a.prefix==b)return a.namespaceURI||"http://www.w3.org/1999/xhtml";var c=a.getAttributeNode("xmlns:"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?$a(a.parentNode,b):null};function r(a){this.i=a;this.b=this.g=!1;this.f=null}function O(a){return"\n "+a.toString().split("\n").join("\n ")}function bb(a,b){a.g=b}function cb(a,b){a.b=b}function P(a,b){var c=a.a(b);return c instanceof I?+Xa(c):+c}function Q(a,b){var c=a.a(b);return c instanceof I?Xa(c):""+c}function R(a,b){var c=a.a(b);return c instanceof I?!!c.l:!!c};function db(a,b,c){r.call(this,a.i);this.c=a;this.h=b;this.o=c;this.g=b.g||c.g;this.b=b.b||c.b;this.c==eb&&(c.b||c.g||4==c.i||0==c.i||!b.f?b.b||b.g||4==b.i||0==b.i||!c.f||(this.f={name:c.f.name,s:b}):this.f={name:b.f.name,s:c})}q(db);
|
||||||
|
function S(a,b,c,d,e){b=b.a(d);c=c.a(d);var f;if(b instanceof I&&c instanceof I){e=M(b);for(d=N(e);d;d=N(e))for(b=M(c),f=N(b);f;f=N(b))if(a(G(d),G(f)))return!0;return!1}if(b instanceof I||c instanceof I){b instanceof I?e=b:(e=c,c=b);e=M(e);b=typeof c;for(d=N(e);d;d=N(e)){switch(b){case "number":d=+G(d);break;case "boolean":d=!!G(d);break;case "string":d=G(d);break;default:throw Error("Illegal primitive type for comparison.");}if(a(d,c))return!0}return!1}return e?"boolean"==typeof b||"boolean"==typeof c?
|
||||||
|
a(!!b,!!c):"number"==typeof b||"number"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}db.prototype.a=function(a){return this.c.m(this.h,this.o,a)};db.prototype.toString=function(){var a="Binary Expression: "+this.c,a=a+O(this.h);return a+=O(this.o)};function fb(a,b,c,d){this.a=a;this.w=b;this.i=c;this.m=d}fb.prototype.toString=h("a");var gb={};function T(a,b,c,d){if(gb.hasOwnProperty(a))throw Error("Binary operator already created: "+a);a=new fb(a,b,c,d);return gb[a.toString()]=a}
|
||||||
|
T("div",6,1,function(a,b,c){return P(a,c)/P(b,c)});T("mod",6,1,function(a,b,c){return P(a,c)%P(b,c)});T("*",6,1,function(a,b,c){return P(a,c)*P(b,c)});T("+",5,1,function(a,b,c){return P(a,c)+P(b,c)});T("-",5,1,function(a,b,c){return P(a,c)-P(b,c)});T("<",4,2,function(a,b,c){return S(function(a,b){return a<b},a,b,c)});T(">",4,2,function(a,b,c){return S(function(a,b){return a>b},a,b,c)});T("<=",4,2,function(a,b,c){return S(function(a,b){return a<=b},a,b,c)});
|
||||||
|
T(">=",4,2,function(a,b,c){return S(function(a,b){return a>=b},a,b,c)});var eb=T("=",3,2,function(a,b,c){return S(function(a,b){return a==b},a,b,c,!0)});T("!=",3,2,function(a,b,c){return S(function(a,b){return a!=b},a,b,c,!0)});T("and",2,2,function(a,b,c){return R(a,c)&&R(b,c)});T("or",1,2,function(a,b,c){return R(a,c)||R(b,c)});function hb(a,b){if(b.a.length&&4!=a.i)throw Error("Primary expression must evaluate to nodeset if filter has predicate(s).");r.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}q(hb);hb.prototype.a=function(a){a=this.c.a(a);return ib(this.h,a)};hb.prototype.toString=function(){var a;a="Filter:"+O(this.c);return a+=O(this.h)};function jb(a,b){if(b.length<a.A)throw Error("Function "+a.j+" expects at least"+a.A+" arguments, "+b.length+" given");if(null!==a.v&&b.length>a.v)throw Error("Function "+a.j+" expects at most "+a.v+" arguments, "+b.length+" given");a.B&&w(b,function(b,d){if(4!=b.i)throw Error("Argument "+d+" to function "+a.j+" is not of type Nodeset: "+b);});r.call(this,a.i);this.h=a;this.c=b;bb(this,a.g||ka(b,function(a){return a.g}));cb(this,a.D&&!b.length||a.C&&!!b.length||ka(b,function(a){return a.b}))}q(jb);
|
||||||
|
jb.prototype.a=function(a){return this.h.m.apply(null,ma(a,this.c))};jb.prototype.toString=function(){var a="Function: "+this.h;if(this.c.length)var b=x(this.c,function(a,b){return a+O(b)},"Arguments:"),a=a+O(b);return a};function kb(a,b,c,d,e,f,g,k,p){this.j=a;this.i=b;this.g=c;this.D=d;this.C=e;this.m=f;this.A=g;this.v=void 0!==k?k:g;this.B=!!p}kb.prototype.toString=h("j");var lb={};
|
||||||
|
function U(a,b,c,d,e,f,g,k){if(lb.hasOwnProperty(a))throw Error("Function already created: "+a+".");lb[a]=new kb(a,b,c,d,!1,e,f,g,k)}U("boolean",2,!1,!1,function(a,b){return R(b,a)},1);U("ceiling",1,!1,!1,function(a,b){return Math.ceil(P(b,a))},1);U("concat",3,!1,!1,function(a,b){var c=na(arguments,1);return x(c,function(b,c){return b+Q(c,a)},"")},2,null);U("contains",2,!1,!1,function(a,b,c){return u(Q(b,a),Q(c,a))},2);U("count",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0);
|
||||||
|
U("false",2,!1,!1,l(!1),0);U("floor",1,!1,!1,function(a,b){return Math.floor(P(b,a))},1);U("id",4,!1,!1,function(a,b){function c(a){if(B){var b=e.all[a];if(b){if(b.nodeType&&a==b.id)return b;if(b.length)return la(b,function(b){return a==b.id})}return null}return e.getElementById(a)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument,d=Q(b,a).split(/\s+/),f=[];w(d,function(a){a=c(a);!a||0<=ia(f,a)||f.push(a)});f.sort(Ja);var g=new I;w(f,function(a){K(g,a)});return g},1);U("lang",2,!1,!1,l(!1),1);
|
||||||
|
U("last",1,!0,!1,function(a){if(1!=arguments.length)throw Error("Function last expects ()");return a.f},0);U("local-name",3,!1,!0,function(a,b){var c=b?Wa(b.a(a)):a.a;return c?c.localName||c.nodeName.toLowerCase():""},0,1,!0);U("name",3,!1,!0,function(a,b){var c=b?Wa(b.a(a)):a.a;return c?c.nodeName.toLowerCase():""},0,1,!0);U("namespace-uri",3,!0,!1,l(""),0,1,!0);U("normalize-space",3,!1,!0,function(a,b){return(b?Q(b,a):G(a.a)).replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")},0,1);
|
||||||
|
U("not",2,!1,!1,function(a,b){return!R(b,a)},1);U("number",1,!1,!0,function(a,b){return b?P(b,a):+G(a.a)},0,1);U("position",1,!0,!1,function(a){return a.b},0);U("round",1,!1,!1,function(a,b){return Math.round(P(b,a))},1);U("starts-with",2,!1,!1,function(a,b,c){b=Q(b,a);a=Q(c,a);return 0==b.lastIndexOf(a,0)},2);U("string",3,!1,!0,function(a,b){return b?Q(b,a):G(a.a)},0,1);U("string-length",1,!1,!0,function(a,b){return(b?Q(b,a):G(a.a)).length},0,1);
|
||||||
|
U("substring",3,!1,!1,function(a,b,c,d){c=P(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return"";d=d?P(d,a):Infinity;if(isNaN(d)||-Infinity===d)return"";c=Math.round(c)-1;var e=Math.max(c,0);a=Q(b,a);if(Infinity==d)return a.substring(e);b=Math.round(d);return a.substring(e,c+b)},2,3);U("substring-after",3,!1,!1,function(a,b,c){b=Q(b,a);a=Q(c,a);c=b.indexOf(a);return-1==c?"":b.substring(c+a.length)},2);
|
||||||
|
U("substring-before",3,!1,!1,function(a,b,c){b=Q(b,a);a=Q(c,a);a=b.indexOf(a);return-1==a?"":b.substring(0,a)},2);U("sum",1,!1,!1,function(a,b){for(var c=M(b.a(a)),d=0,e=N(c);e;e=N(c))d+=+G(e);return d},1,1,!0);U("translate",3,!1,!1,function(a,b,c,d){b=Q(b,a);c=Q(c,a);var e=Q(d,a);a=[];for(d=0;d<c.length;d++){var f=c.charAt(d);f in a||(a[f]=e.charAt(d))}c="";for(d=0;d<b.length;d++)f=b.charAt(d),c+=f in a?a[f]:f;return c},3);U("true",2,!1,!1,l(!0),0);function L(a,b){this.h=a;this.c=void 0!==b?b:null;this.b=null;switch(a){case "comment":this.b=8;break;case "text":this.b=3;break;case "processing-instruction":this.b=7;break;case "node":break;default:throw Error("Unexpected argument");}}function mb(a){return"comment"==a||"text"==a||"processing-instruction"==a||"node"==a}L.prototype.a=function(a){return null===this.b||this.b==a.nodeType};L.prototype.f=h("h");L.prototype.toString=function(){var a="Kind Test: "+this.h;null===this.c||(a+=O(this.c));return a};function nb(a){r.call(this,3);this.c=a.substring(1,a.length-1)}q(nb);nb.prototype.a=h("c");nb.prototype.toString=function(){return"Literal: "+this.c};function J(a,b){this.j=a.toLowerCase();this.c=b?b.toLowerCase():"http://www.w3.org/1999/xhtml"}J.prototype.a=function(a){var b=a.nodeType;return 1!=b&&2!=b?!1:"*"!=this.j&&this.j!=a.nodeName.toLowerCase()?!1:this.c==(a.namespaceURI?a.namespaceURI.toLowerCase():"http://www.w3.org/1999/xhtml")};J.prototype.f=h("j");J.prototype.toString=function(){return"Name Test: "+("http://www.w3.org/1999/xhtml"==this.c?"":this.c+":")+this.j};function ob(a){r.call(this,1);this.c=a}q(ob);ob.prototype.a=h("c");ob.prototype.toString=function(){return"Number: "+this.c};function pb(a,b){r.call(this,a.i);this.h=a;this.c=b;this.g=a.g;this.b=a.b;if(1==this.c.length){var c=this.c[0];c.u||c.c!=qb||(c=c.o,"*"!=c.f()&&(this.f={name:c.f(),s:null}))}}q(pb);function rb(){r.call(this,4)}q(rb);rb.prototype.a=function(a){var b=new I;a=a.a;9==a.nodeType?K(b,a):K(b,a.ownerDocument);return b};rb.prototype.toString=l("Root Helper Expression");function sb(){r.call(this,4)}q(sb);sb.prototype.a=function(a){var b=new I;K(b,a.a);return b};sb.prototype.toString=l("Context Helper Expression");
|
||||||
|
function tb(a){return"/"==a||"//"==a}pb.prototype.a=function(a){var b=this.h.a(a);if(!(b instanceof I))throw Error("Filter expression must evaluate to nodeset.");a=this.c;for(var c=0,d=a.length;c<d&&b.l;c++){var e=a[c],f=M(b,e.c.a),g;if(e.g||e.c!=ub)if(e.g||e.c!=vb)for(g=N(f),b=e.a(new t(g));null!=(g=N(f));)g=e.a(new t(g)),b=Ua(b,g);else g=N(f),b=e.a(new t(g));else{for(g=N(f);(b=N(f))&&(!g.contains||g.contains(b))&&b.compareDocumentPosition(g)&8;g=b);b=e.a(new t(g))}}return b};
|
||||||
|
pb.prototype.toString=function(){var a;a="Path Expression:"+O(this.h);if(this.c.length){var b=x(this.c,function(a,b){return a+O(b)},"Steps:");a+=O(b)}return a};function wb(a,b){this.a=a;this.b=!!b}
|
||||||
|
function ib(a,b,c){for(c=c||0;c<a.a.length;c++)for(var d=a.a[c],e=M(b),f=b.l,g,k=0;g=N(e);k++){var p=a.b?f-k:k+1;g=d.a(new t(g,p,f));if("number"==typeof g)p=p==g;else if("string"==typeof g||"boolean"==typeof g)p=!!g;else if(g instanceof I)p=0<g.l;else throw Error("Predicate.evaluate returned an unexpected type.");if(!p){p=e;g=p.f;var z=p.a;if(!z)throw Error("Next must be called at least once before remove.");var E=z.b,z=z.a;E?E.a=z:g.a=z;z?z.b=E:g.b=E;g.l--;p.a=null}}return b}
|
||||||
|
wb.prototype.toString=function(){return x(this.a,function(a,b){return a+O(b)},"Predicates:")};function V(a,b,c,d){r.call(this,4);this.c=a;this.o=b;this.h=c||new wb([]);this.u=!!d;b=this.h;b=0<b.a.length?b.a[0].f:null;a.b&&b&&(a=b.name,a=B?a.toLowerCase():a,this.f={name:a,s:b.s});a:{a=this.h;for(b=0;b<a.a.length;b++)if(c=a.a[b],c.g||1==c.i||0==c.i){a=!0;break a}a=!1}this.g=a}q(V);
|
||||||
|
V.prototype.a=function(a){var b=a.a,c=null,c=this.f,d=null,e=null,f=0;c&&(d=c.name,e=c.s?Q(c.s,a):null,f=1);if(this.u)if(this.g||this.c!=xb)if(a=M((new V(yb,new L("node"))).a(a)),b=N(a))for(c=this.m(b,d,e,f);null!=(b=N(a));)c=Ua(c,this.m(b,d,e,f));else c=new I;else c=Ma(this.o,b,d,e),c=ib(this.h,c,f);else c=this.m(a.a,d,e,f);return c};V.prototype.m=function(a,b,c,d){a=this.c.f(this.o,a,b,c);return a=ib(this.h,a,d)};
|
||||||
|
V.prototype.toString=function(){var a;a="Step:"+O("Operator: "+(this.u?"//":"/"));this.c.j&&(a+=O("Axis: "+this.c));a+=O(this.o);if(this.h.a.length){var b=x(this.h.a,function(a,b){return a+O(b)},"Predicates:");a+=O(b)}return a};function zb(a,b,c,d){this.j=a;this.f=b;this.a=c;this.b=d}zb.prototype.toString=h("j");var Ab={};function W(a,b,c,d){if(Ab.hasOwnProperty(a))throw Error("Axis already created: "+a);b=new zb(a,b,c,!!d);return Ab[a]=b}
|
||||||
|
W("ancestor",function(a,b){for(var c=new I,d=b;d=d.parentNode;)a.a(d)&&Va(c,d);return c},!0);W("ancestor-or-self",function(a,b){var c=new I,d=b;do a.a(d)&&Va(c,d);while(d=d.parentNode);return c},!0);
|
||||||
|
var qb=W("attribute",function(a,b){var c=new I,d=a.f();if("style"==d&&b.style&&B)return K(c,new C(b.style,b,"style",b.style.cssText)),c;var e=b.attributes;if(e)if(a instanceof L&&null===a.b||"*"==d)for(var d=0,f;f=e[d];d++)B?f.nodeValue&&K(c,Ca(b,f)):K(c,f);else(f=e.getNamedItem(d))&&(B?f.nodeValue&&K(c,Ca(b,f)):K(c,f));return c},!1),xb=W("child",function(a,b,c,d,e){return(B?Ra:Sa).call(null,a,b,n(c)?c:null,n(d)?d:null,e||new I)},!1,!0);W("descendant",Ma,!1,!0);
|
||||||
|
var yb=W("descendant-or-self",function(a,b,c,d){var e=new I;H(b,c,d)&&a.a(b)&&K(e,b);return Ma(a,b,c,d,e)},!1,!0),ub=W("following",function(a,b,c,d){var e=new I;do for(var f=b;f=f.nextSibling;)H(f,c,d)&&a.a(f)&&K(e,f),e=Ma(a,f,c,d,e);while(b=b.parentNode);return e},!1,!0);W("following-sibling",function(a,b){for(var c=new I,d=b;d=d.nextSibling;)a.a(d)&&K(c,d);return c},!1);W("namespace",function(){return new I},!1);
|
||||||
|
var Bb=W("parent",function(a,b){var c=new I;if(9==b.nodeType)return c;if(2==b.nodeType)return K(c,b.ownerElement),c;var d=b.parentNode;a.a(d)&&K(c,d);return c},!1),vb=W("preceding",function(a,b,c,d){var e=new I,f=[];do f.unshift(b);while(b=b.parentNode);for(var g=1,k=f.length;g<k;g++){var p=[];for(b=f[g];b=b.previousSibling;)p.unshift(b);for(var z=0,E=p.length;z<E;z++)b=p[z],H(b,c,d)&&a.a(b)&&K(e,b),e=Ma(a,b,c,d,e)}return e},!0,!0);
|
||||||
|
W("preceding-sibling",function(a,b){for(var c=new I,d=b;d=d.previousSibling;)a.a(d)&&Va(c,d);return c},!0);var Cb=W("self",function(a,b){var c=new I;a.a(b)&&K(c,b);return c},!1);function Db(a){r.call(this,1);this.c=a;this.g=a.g;this.b=a.b}q(Db);Db.prototype.a=function(a){return-P(this.c,a)};Db.prototype.toString=function(){return"Unary Expression: -"+O(this.c)};function Eb(a){r.call(this,4);this.c=a;bb(this,ka(this.c,function(a){return a.g}));cb(this,ka(this.c,function(a){return a.b}))}q(Eb);Eb.prototype.a=function(a){var b=new I;w(this.c,function(c){c=c.a(a);if(!(c instanceof I))throw Error("Path expression must evaluate to NodeSet.");b=Ua(b,c)});return b};Eb.prototype.toString=function(){return x(this.c,function(a,b){return a+O(b)},"Union Expression:")};function Fb(a,b){this.a=a;this.b=b}function Gb(a){for(var b,c=[];;){X(a,"Missing right hand side of binary expression.");b=Hb(a);var d=F(a.a);if(!d)break;var e=(d=gb[d]||null)&&d.w;if(!e){a.a.a--;break}for(;c.length&&e<=c[c.length-1].w;)b=new db(c.pop(),c.pop(),b);c.push(b,d)}for(;c.length;)b=new db(c.pop(),c.pop(),b);return b}function X(a,b){if(Ha(a.a))throw Error(b);}function Ib(a,b){var c=F(a.a);if(c!=b)throw Error("Bad token, expected: "+b+" got: "+c);}
|
||||||
|
function Jb(a){a=F(a.a);if(")"!=a)throw Error("Bad token: "+a);}function Kb(a){a=F(a.a);if(2>a.length)throw Error("Unclosed literal string");return new nb(a)}function Lb(a){var b=F(a.a),c=b.indexOf(":");if(-1==c)return new J(b);var d=b.substring(0,c);a=a.b(d);if(!a)throw Error("Namespace prefix not declared: "+d);b=b.substr(c+1);return new J(b,a)}
|
||||||
|
function Mb(a){var b,c=[],d;if(tb(D(a.a))){b=F(a.a);d=D(a.a);if("/"==b&&(Ha(a.a)||"."!=d&&".."!=d&&"@"!=d&&"*"!=d&&!/(?![0-9])[\w]/.test(d)))return new rb;d=new rb;X(a,"Missing next location step.");b=Nb(a,b);c.push(b)}else{a:{b=D(a.a);d=b.charAt(0);switch(d){case "$":throw Error("Variable reference not allowed in HTML XPath");case "(":F(a.a);b=Gb(a);X(a,'unclosed "("');Ib(a,")");break;case '"':case "'":b=Kb(a);break;default:if(isNaN(+b))if(!mb(b)&&/(?![0-9])[\w]/.test(d)&&"("==D(a.a,1)){b=F(a.a);
|
||||||
|
b=lb[b]||null;F(a.a);for(d=[];")"!=D(a.a);){X(a,"Missing function argument list.");d.push(Gb(a));if(","!=D(a.a))break;F(a.a)}X(a,"Unclosed function argument list.");Jb(a);b=new jb(b,d)}else{b=null;break a}else b=new ob(+F(a.a))}"["==D(a.a)&&(d=new wb(Ob(a)),b=new hb(b,d))}if(b)if(tb(D(a.a)))d=b;else return b;else b=Nb(a,"/"),d=new sb,c.push(b)}for(;tb(D(a.a));)b=F(a.a),X(a,"Missing next location step."),b=Nb(a,b),c.push(b);return new pb(d,c)}
|
||||||
|
function Nb(a,b){var c,d,e;if("/"!=b&&"//"!=b)throw Error('Step op should be "/" or "//"');if("."==D(a.a))return d=new V(Cb,new L("node")),F(a.a),d;if(".."==D(a.a))return d=new V(Bb,new L("node")),F(a.a),d;var f;if("@"==D(a.a))f=qb,F(a.a),X(a,"Missing attribute name");else if("::"==D(a.a,1)){if(!/(?![0-9])[\w]/.test(D(a.a).charAt(0)))throw Error("Bad token: "+F(a.a));c=F(a.a);f=Ab[c]||null;if(!f)throw Error("No axis with name: "+c);F(a.a);X(a,"Missing node name")}else f=xb;c=D(a.a);if(/(?![0-9])[\w]/.test(c.charAt(0)))if("("==
|
||||||
|
D(a.a,1)){if(!mb(c))throw Error("Invalid node type: "+c);c=F(a.a);if(!mb(c))throw Error("Invalid type name: "+c);Ib(a,"(");X(a,"Bad nodetype");e=D(a.a).charAt(0);var g=null;if('"'==e||"'"==e)g=Kb(a);X(a,"Bad nodetype");Jb(a);c=new L(c,g)}else c=Lb(a);else if("*"==c)c=Lb(a);else throw Error("Bad token: "+F(a.a));e=new wb(Ob(a),f.a);return d||new V(f,c,e,"//"==b)}
|
||||||
|
function Ob(a){for(var b=[];"["==D(a.a);){F(a.a);X(a,"Missing predicate expression.");var c=Gb(a);b.push(c);X(a,"Unclosed predicate expression.");Ib(a,"]")}return b}function Hb(a){if("-"==D(a.a))return F(a.a),new Db(Hb(a));var b=Mb(a);if("|"!=D(a.a))a=b;else{for(b=[b];"|"==F(a.a);)X(a,"Missing next union location path."),b.push(Mb(a));a.a.a--;a=new Eb(b)}return a};function Pb(a,b){if(!a.length)throw Error("Empty XPath expression.");var c=Ea(a);if(Ha(c))throw Error("Invalid XPath expression.");b?"function"==ba(b)||(b=ea(b.lookupNamespaceURI,b)):b=l(null);var d=Gb(new Fb(c,b));if(!Ha(c))throw Error("Bad token: "+F(c));this.evaluate=function(a,b){var c=d.a(new t(a));return new Y(c,b)}}
|
||||||
|
function Y(a,b){if(0==b)if(a instanceof I)b=4;else if("string"==typeof a)b=2;else if("number"==typeof a)b=1;else if("boolean"==typeof a)b=3;else throw Error("Unexpected evaluation result.");if(2!=b&&1!=b&&3!=b&&!(a instanceof I))throw Error("value could not be converted to the specified type");this.resultType=b;var c;switch(b){case 2:this.stringValue=a instanceof I?Xa(a):""+a;break;case 1:this.numberValue=a instanceof I?+Xa(a):+a;break;case 3:this.booleanValue=a instanceof I?0<a.l:!!a;break;case 4:case 5:case 6:case 7:var d=
|
||||||
|
M(a);c=[];for(var e=N(d);e;e=N(d))c.push(e instanceof C?e.a:e);this.snapshotLength=a.l;this.invalidIteratorState=!1;break;case 8:case 9:d=Wa(a);this.singleNodeValue=d instanceof C?d.a:d;break;default:throw Error("Unknown XPathResult type.");}var f=0;this.iterateNext=function(){if(4!=b&&5!=b)throw Error("iterateNext called with wrong result type");return f>=c.length?null:c[f++]};this.snapshotItem=function(a){if(6!=b&&7!=b)throw Error("snapshotItem called with wrong result type");return a>=c.length||
|
||||||
|
0>a?null:c[a]}}Y.ANY_TYPE=0;Y.NUMBER_TYPE=1;Y.STRING_TYPE=2;Y.BOOLEAN_TYPE=3;Y.UNORDERED_NODE_ITERATOR_TYPE=4;Y.ORDERED_NODE_ITERATOR_TYPE=5;Y.UNORDERED_NODE_SNAPSHOT_TYPE=6;Y.ORDERED_NODE_SNAPSHOT_TYPE=7;Y.ANY_UNORDERED_NODE_TYPE=8;Y.FIRST_ORDERED_NODE_TYPE=9;function Qb(a){this.lookupNamespaceURI=Za(a)}
|
||||||
|
function Rb(a){a=a||m;var b=a.document;b.evaluate||(a.XPathResult=Y,b.evaluate=function(a,b,e,f){return(new Pb(a,e)).evaluate(b,f)},b.createExpression=function(a,b){return new Pb(a,b)},b.createNSResolver=function(a){return new Qb(a)})}var Sb=["wgxpath","install"],Z=m;Sb[0]in Z||!Z.execScript||Z.execScript("var "+Sb[0]);for(var Tb;Sb.length&&(Tb=Sb.shift());)Sb.length||void 0===Rb?Z[Tb]?Z=Z[Tb]:Z=Z[Tb]={}:Z[Tb]=Rb;})()
|
22
js/personal.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
$(document).ready(function(){
|
||||||
|
// save settings
|
||||||
|
var readerSettings = {
|
||||||
|
save : function() {
|
||||||
|
var data = {
|
||||||
|
EpubEnable: document.getElementById('EpubEnable').checked ? 'true' : 'false',
|
||||||
|
PdfEnable: document.getElementById('PdfEnable').checked ? 'true' : 'false',
|
||||||
|
CbxEnable: document.getElementById('CbxEnable').checked ? 'true' : 'false'
|
||||||
|
};
|
||||||
|
|
||||||
|
OC.msg.startSaving('#reader-personal .msg');
|
||||||
|
$.post(OC.filePath('files_reader', 'lib', 'personal-back.php'), data, readerSettings.afterSave);
|
||||||
|
},
|
||||||
|
afterSave : function(data){
|
||||||
|
OC.msg.finishedSaving('#reader-personal .msg', data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$('#EpubEnable').on("change", readerSettings.save);
|
||||||
|
$('#PdfEnable').on("change", readerSettings.save);
|
||||||
|
$('#CbxEnable').on("change", readerSettings.save);
|
||||||
|
});
|
||||||
|
|
158
js/plugin.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015-2017 Frank de Lange
|
||||||
|
* Copyright (c) 2013-2014 Lukas Reschke <lukas@owncloud.com>
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3
|
||||||
|
* or later.
|
||||||
|
*
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
(function(OCA) {
|
||||||
|
|
||||||
|
OCA.Files_Reader = OCA.Files_Reader || {};
|
||||||
|
|
||||||
|
var isMobile = navigator.userAgent.match(/Mobi/i);
|
||||||
|
var hasTouch = 'ontouchstart' in document.documentElement;
|
||||||
|
|
||||||
|
function actionHandler(fileName, mime, context) {
|
||||||
|
var downloadUrl = '';
|
||||||
|
if($('#isPublic').val()) {
|
||||||
|
var sharingToken = $('#sharingToken').val();
|
||||||
|
downloadUrl = OC.generateUrl('/s/{token}/download?files={files}&path={path}', {
|
||||||
|
token: sharingToken,
|
||||||
|
files: fileName,
|
||||||
|
path: context.dir
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
downloadUrl = Files.getDownloadUrl(fileName, context.dir);
|
||||||
|
}
|
||||||
|
OCA.Files_Reader.Plugin.show(downloadUrl, mime, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace OCA.Files_Reader.Plugin
|
||||||
|
*/
|
||||||
|
OCA.Files_Reader.Plugin = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fileList
|
||||||
|
*/
|
||||||
|
attach: function(fileList) {
|
||||||
|
this._extendFileActions(fileList.fileActions);
|
||||||
|
},
|
||||||
|
|
||||||
|
hideControls: function() {
|
||||||
|
$('#app-content #controls').hide();
|
||||||
|
// and, for NC12...
|
||||||
|
$('#app-navigation').css("display", "none");
|
||||||
|
},
|
||||||
|
|
||||||
|
hide: function() {
|
||||||
|
if ($('#fileList').length) {
|
||||||
|
FileList.setViewerMode(false);
|
||||||
|
}
|
||||||
|
$("#controls").show();
|
||||||
|
$('#app-content #controls').removeClass('hidden');
|
||||||
|
// NC12...
|
||||||
|
$('#app-navigation').css("display", "");
|
||||||
|
if ($('#isPublic').val()) {
|
||||||
|
$('#imgframe').show();
|
||||||
|
$('footer').show();
|
||||||
|
$('.directLink').show();
|
||||||
|
$('.directDownload').show();
|
||||||
|
}
|
||||||
|
$('iframe').remove();
|
||||||
|
$('body').off('focus.filesreader');
|
||||||
|
$(window).off('popstate.filesreader');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param downloadUrl
|
||||||
|
* @param isFileList
|
||||||
|
*/
|
||||||
|
show: function(downloadUrl, mimeType, isFileList) {
|
||||||
|
var self = this;
|
||||||
|
var viewer = OC.generateUrl('/apps/files_reader/?file={file}&type={type}', {file: downloadUrl, type: mimeType});
|
||||||
|
// launch in new window on all devices
|
||||||
|
window.open(viewer, downloadUrl);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fileActions
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_extendFileActions: function(fileActions) {
|
||||||
|
var self = this;
|
||||||
|
var cbxMime = [
|
||||||
|
'application/x-cbr',
|
||||||
|
'application/comicbook+7z',
|
||||||
|
'application/comicbook+ace',
|
||||||
|
'application/comicbook+rar',
|
||||||
|
'application/comicbook+tar',
|
||||||
|
'application/comicbook+truecrypt',
|
||||||
|
'application/comicbook+zip'
|
||||||
|
];
|
||||||
|
|
||||||
|
fileActions.registerAction({
|
||||||
|
name: 'view-epub',
|
||||||
|
displayName: 'View',
|
||||||
|
mime: 'application/epub+zip',
|
||||||
|
permissions: OC.PERMISSION_READ,
|
||||||
|
actionHandler: function(fileName, context){
|
||||||
|
return actionHandler(fileName, 'application/epub+zip', context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cbxMime.forEach(function(mime, i){
|
||||||
|
fileActions.registerAction({
|
||||||
|
name: 'view-cbr-' + i,
|
||||||
|
displayName: 'View',
|
||||||
|
mime: mime,
|
||||||
|
permissions: OC.PERMISSION_READ,
|
||||||
|
actionHandler: function (fileName, context) {
|
||||||
|
return actionHandler(fileName, 'application/x-cbr', context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oc_appconfig.filesReader.enableCbx === 'true')
|
||||||
|
fileActions.setDefault(mime, 'view-cbr-' + i);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileActions.registerAction({
|
||||||
|
name: 'view-pdf',
|
||||||
|
displayName: 'View',
|
||||||
|
mime: 'application/pdf',
|
||||||
|
permissions: OC.PERMISSION_READ,
|
||||||
|
actionHandler: function(fileName, context) {
|
||||||
|
return actionHandler(fileName, 'application/pdf', context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oc_appconfig.filesReader.enableEpub === 'true')
|
||||||
|
fileActions.setDefault('application/epub+zip', 'view-epub');
|
||||||
|
if (oc_appconfig.filesReader.enablePdf === 'true')
|
||||||
|
fileActions.setDefault('application/pdf', 'view-pdf');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})(OCA);
|
||||||
|
|
||||||
|
OC.Plugins.register('OCA.Files.FileList', OCA.Files_Reader.Plugin);
|
||||||
|
|
||||||
|
// FIXME: Hack for single public file view since it is not attached to the fileslist
|
||||||
|
$(document).ready(function(){
|
||||||
|
if ($('#isPublic').val()
|
||||||
|
&& ($('#mimetype').val() === 'application/epub+zip'
|
||||||
|
|| $('#mimetype').val() === 'application/pdf'
|
||||||
|
|| $('#mimetype').val() === 'application/x-cbr')
|
||||||
|
) {
|
||||||
|
var sharingToken = $('#sharingToken').val();
|
||||||
|
var downloadUrl = OC.generateUrl('/s/{token}/download', {token: sharingToken});
|
||||||
|
var viewer = OCA.Files_Reader.Plugin;
|
||||||
|
var mime = $('#mimetype').val();
|
||||||
|
viewer.show(downloadUrl, mime, false);
|
||||||
|
}
|
||||||
|
});
|
154
js/ready.js
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
document.onreadystatechange = function () {
|
||||||
|
|
||||||
|
if (document.readyState == "complete") {
|
||||||
|
|
||||||
|
var type=decodeURIComponent(getUrlParameter('type')),
|
||||||
|
file=decodeURIComponent(getUrlParameter('file')),
|
||||||
|
options = {},
|
||||||
|
$session = $('.session');
|
||||||
|
|
||||||
|
options.session = {};
|
||||||
|
options.session.filename = decodeURI($session.data('filename'));
|
||||||
|
options.session.format = $session.data('filetype');
|
||||||
|
options.session.fileId = $session.data('fileid');
|
||||||
|
options.session.title = options.session.filename;
|
||||||
|
options.session.nonce = $session.data('nonce') || "";
|
||||||
|
options.session.version = $session.data('version') || "";
|
||||||
|
options.session.metadata = $session.data('metadata') || {};
|
||||||
|
options.session.annotations = $session.data('annotations') || {};
|
||||||
|
options.session.fileId = $session.data('fileid') || "";
|
||||||
|
options.session.scope = $session.data('scope') || "";
|
||||||
|
options.session.cursor = $session.data('cursor') || {};
|
||||||
|
options.session.defaults = $session.data('defaults') || {};
|
||||||
|
options.session.preferences = $session.data('preferences') || {};
|
||||||
|
options.session.defaults = $session.data('defaults') || {};
|
||||||
|
options.session.basePath = $session.data('basepath');
|
||||||
|
options.session.downloadLink = $session.data('downloadlink');
|
||||||
|
|
||||||
|
console.log(options.session.basePath);
|
||||||
|
|
||||||
|
|
||||||
|
/* functions return jquery promises */
|
||||||
|
options.session.getPreference = function(name) {
|
||||||
|
return $.get(options.session.basePath + "preference/" + options.session.fileId + "/" + options.session.scope + "/" + name);
|
||||||
|
};
|
||||||
|
options.session.setPreference = function(name, value) {
|
||||||
|
return $.post(options.session.basePath + "preference",
|
||||||
|
{
|
||||||
|
"fileId": options.session.fileId,
|
||||||
|
"scope": options.session.scope,
|
||||||
|
"name": name,
|
||||||
|
"value": JSON.stringify(value)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
options.session.deletePreference = function(name) {
|
||||||
|
return $.delete(options.session.basePath + "preference/" + options.session.fileId + "/" + options.session.scope + "/" + name);
|
||||||
|
};
|
||||||
|
options.session.getDefault = function(name) {
|
||||||
|
return $.get(options.session.basePath + "preference/default/" + options.session.scope + "/" + name);
|
||||||
|
};
|
||||||
|
options.session.setDefault = function(name, value) {
|
||||||
|
return $.post(options.session.basePath + "preference/default",
|
||||||
|
{
|
||||||
|
"scope": options.session.scope,
|
||||||
|
"name": name,
|
||||||
|
"value": JSON.stringify(value)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
options.session.deleteDefault = function(name) {
|
||||||
|
return $.delete(options.session.basePath + "preference/default/" + options.session.scope + "/" + name);
|
||||||
|
};
|
||||||
|
options.session.getBookmark = function(name, type) {
|
||||||
|
return $.get(options.session.basePath + "bookmark/" + options.session.fileId + "/" + type + "/" + name);
|
||||||
|
};
|
||||||
|
options.session.setBookmark = function(name, value, type, content) {
|
||||||
|
return $.post(options.session.basePath + "bookmark",
|
||||||
|
{
|
||||||
|
"fileId": options.session.fileId,
|
||||||
|
"name": name,
|
||||||
|
"value": JSON.stringify(value),
|
||||||
|
"type": type,
|
||||||
|
"content": JSON.stringify(content)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
options.session.deleteBookmark = function(name) {
|
||||||
|
return $.delete(options.session.basePath + "bookmark/" + options.session.fileId + "/" + name);
|
||||||
|
};
|
||||||
|
options.session.getCursor = function() {
|
||||||
|
return $.get(options.session.basePath + "bookmark/cursor/" + options.session.fileId);
|
||||||
|
};
|
||||||
|
options.session.setCursor = function(value) {
|
||||||
|
return $.post(options.session.basePath + "bookmark/cursor",
|
||||||
|
{
|
||||||
|
"fileId": options.session.fileId,
|
||||||
|
"value": JSON.stringify(value)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
options.session.deleteCursor = function() {
|
||||||
|
return $.delete(options.session.basePath + "bookmark/cursor/" + options.session.fileId);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'application/epub+zip':
|
||||||
|
options['contained'] = true;
|
||||||
|
renderEpub(file, options);
|
||||||
|
break;
|
||||||
|
case 'application/x-cbr':
|
||||||
|
renderCbr(file, options);
|
||||||
|
break;
|
||||||
|
case 'application/pdf':
|
||||||
|
renderPdf(file, options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(type + ' is not supported by Reader');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// why is there no standard library function for this?
|
||||||
|
function getUrlParameter (param) {
|
||||||
|
var pattern = new RegExp('[?&]'+param+'((=([^&]*))|(?=(&|$)))','i');
|
||||||
|
var m = window.location.search.match(pattern);
|
||||||
|
return m && ( typeof(m[3])==='undefined' ? '' : m[3] );
|
||||||
|
}
|
||||||
|
|
||||||
|
// start epub.js renderer
|
||||||
|
function renderEpub(file, options) {
|
||||||
|
|
||||||
|
// some parameters...
|
||||||
|
EPUBJS.filePath = "vendor/epubjs/";
|
||||||
|
EPUBJS.cssPath = "vendor/epubjs/css/";
|
||||||
|
EPUBJS.basePath = $('.session').data('basepath');
|
||||||
|
|
||||||
|
/* device-specific boilerplate */
|
||||||
|
|
||||||
|
// IE < 11
|
||||||
|
if (navigator.userAgent.indexOf("MSIE") != -1) {
|
||||||
|
EPUBJS.Hooks.register("beforeChapterDisplay").wgxpath = function(callback, renderer){
|
||||||
|
wgxpath.install(renderer.render.window);
|
||||||
|
if(callback)
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
wgxpath.install(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
var reader = ePubReader(file, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start cbr.js renderer
|
||||||
|
function renderCbr(file, options) {
|
||||||
|
CBRJS.filePath = "vendor/cbrjs/";
|
||||||
|
|
||||||
|
var reader = cbReader(file, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
// start pdf.js renderer
|
||||||
|
function renderPdf(file, options) {
|
||||||
|
PDFJS.filePath = "vendor/pdfjs/";
|
||||||
|
PDFJS.imageResourcesPath = "vendor/pdfjs/css/images/";
|
||||||
|
PDFJS.workerSrc = options.session.basePath + 'vendor/pdfjs/lib/pdf.worker.js';
|
||||||
|
|
||||||
|
var reader = pdfReader(file, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
22
js/settings.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
$(document).ready(function(){
|
||||||
|
// save settings
|
||||||
|
var readerSettings = {
|
||||||
|
save : function() {
|
||||||
|
var data = {
|
||||||
|
EpubEnable: document.getElementById('EpubEnable').checked ? 'true' : 'false',
|
||||||
|
PdfEnable: document.getElementById('PdfEnable').checked ? 'true' : 'false',
|
||||||
|
CbxEnable: document.getElementById('CbxEnable').checked ? 'true' : 'false'
|
||||||
|
};
|
||||||
|
|
||||||
|
OC.msg.startSaving('#reader-personal .msg');
|
||||||
|
$.post(OC.filePath('files_reader', 'ajax', 'personal.php'), data, readerSettings.afterSave);
|
||||||
|
},
|
||||||
|
afterSave : function(data){
|
||||||
|
OC.msg.finishedSaving('#reader-personal .msg', data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$('#EpubEnable').on("change", readerSettings.save);
|
||||||
|
$('#PdfEnable').on("change", readerSettings.save);
|
||||||
|
$('#CbxEnable').on("change", readerSettings.save);
|
||||||
|
});
|
||||||
|
|
124
lib/Controller/BookmarkController.php
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Service\BookmarkService;
|
||||||
|
|
||||||
|
class BookmarkController extends Controller {
|
||||||
|
|
||||||
|
private $bookmarkService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $AppName
|
||||||
|
* @param IRequest $request
|
||||||
|
* @param BookmarkService $bookmarkService
|
||||||
|
*/
|
||||||
|
public function __construct($AppName,
|
||||||
|
IRequest $request,
|
||||||
|
BookmarkService $bookmarkService ) {
|
||||||
|
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
$this->bookmarkService = $bookmarkService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief return bookmark
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function get($fileId, $name, $type=null) {
|
||||||
|
return $this->bookmarkService->get($fileId, $name, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write bookmark
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function set($fileId, $name, $value, $type=null, $content=null) {
|
||||||
|
return $this->bookmarkService->set($fileId, $name, $value, $type, $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief return cursor for $fileId
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function getCursor($fileId) {
|
||||||
|
return $this->bookmarkService->getCursor($fileId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write cursor for $fileId
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function setCursor($fileId, $value) {
|
||||||
|
return $this->bookmarkService->setCursor($fileId, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete bookmark
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string name
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function delete($fileId, $name) {
|
||||||
|
return $this->bookmarkService->delete($fileId, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete cursor
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function deleteCursor($fileId) {
|
||||||
|
return $this->bookmarkService->deleteCursor($fileId);
|
||||||
|
}
|
||||||
|
}
|
80
lib/Controller/MetadataController.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Service\MetadataService;
|
||||||
|
|
||||||
|
class MetadataController extends Controller {
|
||||||
|
|
||||||
|
private $metadataService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $AppName
|
||||||
|
* @param IRequest $request
|
||||||
|
* @param MetadataService $metadataService
|
||||||
|
*/
|
||||||
|
public function __construct($AppName,
|
||||||
|
IRequest $request,
|
||||||
|
MetadataService $metadataService ) {
|
||||||
|
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
$this->metadataService = $metadataService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write metadata
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function setAll($fileId, $value) {
|
||||||
|
return $this->metadataService->setAll($fileId, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief return metadata item
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function get($fileId, $name) {
|
||||||
|
return $this->metadataService->get($fileId, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write metadata item
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function set($fileId, $name, $value) {
|
||||||
|
return $this->metadataService->set($fileId, $name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
177
lib/Controller/PageController.php
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2015 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Controller;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
use OCP\Files\IRootFolder;
|
||||||
|
use OCP\Share\IManager;
|
||||||
|
use OCP\Files\FileInfo;
|
||||||
|
use OCP\Files\NotFoundException;
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Service\BookmarkService;
|
||||||
|
use OCA\Files_Reader\Service\MetadataService;
|
||||||
|
use OCA\Files_Reader\Service\PreferenceService;
|
||||||
|
|
||||||
|
class PageController extends Controller {
|
||||||
|
|
||||||
|
/** @var IURLGenerator */
|
||||||
|
private $urlGenerator;
|
||||||
|
/** @var IRootFolder */
|
||||||
|
private $rootFolder;
|
||||||
|
private $shareManager;
|
||||||
|
private $userId;
|
||||||
|
private $bookmarkService;
|
||||||
|
private $metadataService;
|
||||||
|
private $preferenceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $AppName
|
||||||
|
* @param IRequest $request
|
||||||
|
* @param IURLGenerator $urlGenerator
|
||||||
|
* @param IRootFolder $rootFolder
|
||||||
|
* @param IManager $shareManager
|
||||||
|
* @param string $UserId
|
||||||
|
* @param BookmarkService $bookmarkService
|
||||||
|
* @param PreferenceService $preferenceService
|
||||||
|
* @param MetadataService $metadataService
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
$AppName,
|
||||||
|
IRequest $request,
|
||||||
|
IURLGenerator $urlGenerator,
|
||||||
|
IRootFolder $rootFolder,
|
||||||
|
IManager $shareManager,
|
||||||
|
$UserId,
|
||||||
|
BookmarkService $bookmarkService,
|
||||||
|
PreferenceService $preferenceService,
|
||||||
|
MetadataService $metadataService) {
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->rootFolder = $rootFolder;
|
||||||
|
$this->shareManager = $shareManager;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
$this->bookmarkService = $bookmarkService;
|
||||||
|
$this->metadataService = $metadataService;
|
||||||
|
$this->preferenceService = $preferenceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @PublicPage
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @return TemplateResponse
|
||||||
|
*/
|
||||||
|
public function showReader() {
|
||||||
|
$templates= [
|
||||||
|
'application/epub+zip' => 'epubreader',
|
||||||
|
'application/x-cbr' => 'cbreader',
|
||||||
|
'application/pdf' => 'pdfreader'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $fileInfo = [
|
||||||
|
* fileId => null,
|
||||||
|
* fileName => null,
|
||||||
|
* fileType => null
|
||||||
|
* ];
|
||||||
|
*/
|
||||||
|
$fileInfo = $this->getFileInfo($this->request->get['file']);
|
||||||
|
$fileId = $fileInfo['fileId'];
|
||||||
|
$type = $this->request->get["type"];
|
||||||
|
$scope = $template = $templates[$type];
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'urlGenerator' => $this->urlGenerator,
|
||||||
|
'downloadLink' => $this->request->get['file'],
|
||||||
|
'scope' => $scope,
|
||||||
|
'fileId' => $fileInfo['fileId'],
|
||||||
|
'fileName' => $fileInfo['fileName'],
|
||||||
|
'fileType' => $fileInfo['fileType'],
|
||||||
|
'cursor' => $this->toJson($this->bookmarkService->getCursor($fileId)),
|
||||||
|
'defaults' => $this->toJson($this->preferenceService->getDefault($scope)),
|
||||||
|
'preferences' => $this->toJson($this->preferenceService->get($scope, $fileId)),
|
||||||
|
'defaults' => $this->toJson($this->preferenceService->getDefault($scope)),
|
||||||
|
'metadata' => $this->toJson($this->metadataService->get($fileId)),
|
||||||
|
'annotations' => $this->toJson($this->bookmarkService->get($fileId))
|
||||||
|
];
|
||||||
|
|
||||||
|
$policy = new ContentSecurityPolicy();
|
||||||
|
$policy->addAllowedStyleDomain('\'self\'');
|
||||||
|
$policy->addAllowedStyleDomain('blob:');
|
||||||
|
$policy->addAllowedScriptDomain('\'self\'');
|
||||||
|
$policy->addAllowedFrameDomain('\'self\'');
|
||||||
|
$policy->addAllowedChildSrcDomain('\'self\'');
|
||||||
|
$policy->addAllowedFontDomain('\'self\'');
|
||||||
|
$policy->addAllowedFontDomain('data:');
|
||||||
|
$policy->addAllowedFontDomain('blob:');
|
||||||
|
$policy->addAllowedImageDomain('blob:');
|
||||||
|
|
||||||
|
$response = new TemplateResponse($this->appName, $template, $params, 'blank');
|
||||||
|
$response->setContentSecurityPolicy($policy);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief sharing-aware file info retriever
|
||||||
|
*
|
||||||
|
* Work around the differences between normal and shared file access
|
||||||
|
* (this should be abstracted away in OC/NC IMnsHO)
|
||||||
|
*
|
||||||
|
* @param string $path path-fragment from url
|
||||||
|
* @return array
|
||||||
|
* @throws NotFoundException
|
||||||
|
*/
|
||||||
|
private function getFileInfo($path) {
|
||||||
|
$count = 0;
|
||||||
|
$shareToken = preg_replace("/(?:\/index\.php)?\/s\/([A-Za-z0-9]{15,32})\/download.*/", "$1", $path, 1,$count);
|
||||||
|
|
||||||
|
if ($count === 1) {
|
||||||
|
|
||||||
|
/* shared file or directory */
|
||||||
|
$node = $this->shareManager->getShareByToken($shareToken)->getNode();
|
||||||
|
$type = $node->getType();
|
||||||
|
|
||||||
|
/* shared directory, need file path to continue, */
|
||||||
|
if ($type == \OCP\Files\FileInfo::TYPE_FOLDER) {
|
||||||
|
$query = [];
|
||||||
|
parse_str(parse_url($path, PHP_URL_QUERY), $query);
|
||||||
|
if (isset($query['path']) && isset($query['files'])) {
|
||||||
|
$node = $node->get($query['path'])->get($query['files']);
|
||||||
|
} else {
|
||||||
|
throw new NotFoundException('Shared file path or name not set');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$filePath = $node->getPath();
|
||||||
|
$fileId = $node->getId();
|
||||||
|
} else {
|
||||||
|
$filePath = $path;
|
||||||
|
$fileId = $this->rootFolder->getUserFolder($this->userId)
|
||||||
|
->get(preg_replace("/.*\/remote.php\/webdav(.*)/", "$1", rawurldecode($this->request->get['file'])))
|
||||||
|
->getFileInfo()
|
||||||
|
->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'fileName' => pathInfo($filePath, PATHINFO_FILENAME),
|
||||||
|
'fileType' => strtolower(pathInfo($filePath, PATHINFO_EXTENSION)),
|
||||||
|
'fileId' => $fileId
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toJson($value) {
|
||||||
|
return htmlspecialchars(json_encode($value), ENT_QUOTES, 'UTF-8');
|
||||||
|
}
|
||||||
|
}
|
128
lib/Controller/PreferenceController.php
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Controller;
|
||||||
|
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Service\PreferenceService;
|
||||||
|
|
||||||
|
class PreferenceController extends Controller {
|
||||||
|
|
||||||
|
private $urlGenerator;
|
||||||
|
private $preferenceService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $AppName
|
||||||
|
* @param IRequest $request
|
||||||
|
* @param IURLGenerator $urlGenerator
|
||||||
|
* @param PreferenceService $preferenceService
|
||||||
|
*/
|
||||||
|
public function __construct($AppName,
|
||||||
|
IRequest $request,
|
||||||
|
IURLGenerator $urlGenerator,
|
||||||
|
PreferenceService $preferenceService ) {
|
||||||
|
|
||||||
|
parent::__construct($AppName, $request);
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->preferenceService = $preferenceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief return preference for $fileId
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name if null, return all preferences for $scope + $fileId
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function get($scope, $fileId, $name) {
|
||||||
|
return $this->preferenceService->get($scope, $fileId, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write preference for $fileId
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function set($scope, $fileId, $name, $value) {
|
||||||
|
return $this->preferenceService->set($scope, $fileId, $name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief return default preference
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param string $name if null, return all default preferences for scope
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function getDefault($scope, $name) {
|
||||||
|
return $this->preferenceService->getDefault($scope, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write default preference
|
||||||
|
*
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array|\OCP\AppFramework\Http\JSONResponse
|
||||||
|
*/
|
||||||
|
public function setDefault($scope, $name, $value) {
|
||||||
|
return $this->preferenceService->setDefault($scope, $name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete preference
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function delete($scope, $fileId, $name) {
|
||||||
|
return $this->preferenceService->delete($scope, $fileId, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete default preference
|
||||||
|
*
|
||||||
|
* @param $scope
|
||||||
|
* @param $name
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function deleteDefault($scope, $name) {
|
||||||
|
return $this->preferenceService->deleteDefault($scope, $name);
|
||||||
|
}
|
||||||
|
}
|
48
lib/Db/Bookmark.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2015 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Db;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
class Bookmark extends ReaderEntity implements \JsonSerializable {
|
||||||
|
|
||||||
|
protected $userId; // user
|
||||||
|
protected $fileId; // book (identified by fileId) for which this mark is valid
|
||||||
|
protected $type; // type, defaults to "bookmark"
|
||||||
|
protected $name; // name, defaults to $location
|
||||||
|
protected $value; // bookmark value (format-specific, eg. page number for PDF, CFI for epub, etc)
|
||||||
|
protected $content; // bookmark content (annotations etc), can be empty
|
||||||
|
protected $lastModified; // modification timestamp
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
return [
|
||||||
|
'id' => $this->getId(),
|
||||||
|
'userId' => $this->getUserId(),
|
||||||
|
'fileId' => $this->getFileId(),
|
||||||
|
'type' => $this->getType(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'value' => static::conditional_json_decode($this->getValue()),
|
||||||
|
'content' => static::conditional_json_decode($this->getContent()),
|
||||||
|
'lastModified' => $this->getLastModified()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toService() {
|
||||||
|
return [
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'type' => $this->getType(),
|
||||||
|
'value' => $this->conditional_json_decode($this->getValue()),
|
||||||
|
'content' => $this->conditional_json_decode($this->getContent()),
|
||||||
|
'lastModified' => $this->getLastModified(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
121
lib/Db/BookmarkMapper.php
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Db;
|
||||||
|
|
||||||
|
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Utility\Time;
|
||||||
|
|
||||||
|
class BookmarkMapper extends ReaderMapper {
|
||||||
|
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IDbConnection $db
|
||||||
|
* @param $UserId
|
||||||
|
* @param Time $time
|
||||||
|
*/
|
||||||
|
public function __construct(IDBConnection $db, $UserId, Time $time) {
|
||||||
|
parent::__construct($db, 'reader_bookmarks', Bookmark::class, $time);
|
||||||
|
/** @var int $UserId */
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get bookmarks for $fileId+$userId(+$name)
|
||||||
|
* @param $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get($fileId, $name, $type=null) {
|
||||||
|
$sql = "SELECT * FROM `*PREFIX*reader_bookmarks` WHERE file_id=? AND `user_id`=?";
|
||||||
|
$args = [ $fileId, $this->userId ];
|
||||||
|
if (!(null === $type)) {
|
||||||
|
$sql .= " AND `type`=?";
|
||||||
|
$args[] = $type;
|
||||||
|
}
|
||||||
|
if (!(null === $name)) {
|
||||||
|
$sql .= " AND `name`=?";
|
||||||
|
$args[] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->findEntities($sql, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write bookmark to database
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Bookmark the newly created or updated bookmark
|
||||||
|
*/
|
||||||
|
public function set($fileId, $name, $value, $type, $content=null) {
|
||||||
|
|
||||||
|
$result = $this->get($fileId, $name);
|
||||||
|
|
||||||
|
if(empty($result)) {
|
||||||
|
|
||||||
|
// anonymous bookmarks are named after their contents
|
||||||
|
if (null === $name) {
|
||||||
|
$name = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default type is "bookmark"
|
||||||
|
if (null === $type) {
|
||||||
|
$type = "bookmark";
|
||||||
|
}
|
||||||
|
|
||||||
|
$bookmark = new Bookmark();
|
||||||
|
$bookmark->setFileId($fileId);
|
||||||
|
$bookmark->setUserId($this->userId);
|
||||||
|
$bookmark->setType($type);
|
||||||
|
$bookmark->setName($name);
|
||||||
|
$bookmark->setValue($value);
|
||||||
|
$bookmark->setContent($content);
|
||||||
|
|
||||||
|
$this->insert($bookmark);
|
||||||
|
} else {
|
||||||
|
$bookmark = $result[0];
|
||||||
|
$bookmark->setValue($value);
|
||||||
|
$bookmark->setContent($content);
|
||||||
|
|
||||||
|
$this->update($bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bookmark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* currently not used */
|
||||||
|
public function deleteForFileId($fileId) {
|
||||||
|
$sql = "SELECT * FROM `*PREFIX*reader_bookmarks` WHERE file_id=?";
|
||||||
|
$args = [ $fileId ];
|
||||||
|
array_map(
|
||||||
|
function($entity) {
|
||||||
|
$this->delete($entity);
|
||||||
|
}, $this->findEntities($sql, $args)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* currently not used */
|
||||||
|
public function deleteForUserId($userId) {
|
||||||
|
$sql = "SELECT * FROM `*PREFIX*reader_bookmarks` WHERE user_id=?";
|
||||||
|
$args = [ $userId ];
|
||||||
|
array_map(
|
||||||
|
function($entity) {
|
||||||
|
$this->delete($entity);
|
||||||
|
}, $this->findEntities($sql, $args)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
42
lib/Db/Preference.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2015 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Db;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
class Preference extends ReaderEntity implements \JsonSerializable {
|
||||||
|
|
||||||
|
protected $userId; // user for whom this preference is valid
|
||||||
|
protected $scope; // scope (default or specific renderer)
|
||||||
|
protected $fileId; // file for which this preference is set
|
||||||
|
protected $name; // preference name
|
||||||
|
protected $value; // preference value
|
||||||
|
protected $lastModified; // modification timestamp
|
||||||
|
|
||||||
|
public function jsonSerialize() {
|
||||||
|
return [
|
||||||
|
'id' => $this->getId(),
|
||||||
|
'scope' => $this->getScope(),
|
||||||
|
'fileId' => $this->getFileId(),
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'value' => $this->conditional_json_decode($this->getValue()),
|
||||||
|
'lastModified' => $this->getLastModified(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toService() {
|
||||||
|
return [
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'value' => $this->conditional_json_decode($this->getValue()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
105
lib/Db/PreferenceMapper.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Db;
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Utility\Time;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
class PreferenceMapper extends ReaderMapper {
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $db, $UserId, Time $time) {
|
||||||
|
parent::__construct($db, 'reader_preferences', Preference::class, $time);
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get preferences for $scope+$fileId+$userId(+$name)
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get($scope, $fileId, $name=null) {
|
||||||
|
if(!empty($name)) {
|
||||||
|
$sql = "SELECT * FROM `*PREFIX*reader_preferences` WHERE `scope`=? AND `file_id`=? AND `user_id`=? AND `name`=?";
|
||||||
|
$args = array(
|
||||||
|
$scope,
|
||||||
|
$fileId,
|
||||||
|
$this->userId,
|
||||||
|
$name);
|
||||||
|
} else {
|
||||||
|
$sql = "SELECT * FROM `*PREFIX*reader_preferences` WHERE `scope`=? AND `file_id`=? AND `user_id`=?";
|
||||||
|
$args = array(
|
||||||
|
$scope,
|
||||||
|
$fileId,
|
||||||
|
$this->userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->findEntities($sql, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write preference to database
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Preference the newly created or updated preference
|
||||||
|
*/
|
||||||
|
public function set($scope, $fileId, $name, $value) {
|
||||||
|
|
||||||
|
$result = $this->get($scope, $fileId, $name);
|
||||||
|
|
||||||
|
if(empty($result)) {
|
||||||
|
|
||||||
|
$preference = new Preference();
|
||||||
|
$preference->setScope($scope);
|
||||||
|
$preference->setFileId($fileId);
|
||||||
|
$preference->setUserId($this->userId);
|
||||||
|
$preference->setName($name);
|
||||||
|
$preference->setValue($value);
|
||||||
|
|
||||||
|
$this->insert($preference);
|
||||||
|
} else {
|
||||||
|
$preference = $result[0];
|
||||||
|
$preference->setValue($value);
|
||||||
|
|
||||||
|
$this->update($preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $preference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* currently not used*/
|
||||||
|
public function deleteForFileId($fileId) {
|
||||||
|
$sql = "SELECT * FROM `*PREFIX*reader_preferences` WHERE file_id=?";
|
||||||
|
$args = [ $fileId ];
|
||||||
|
array_map(
|
||||||
|
function($entity) {
|
||||||
|
$this->delete($entity);
|
||||||
|
}, $this->findEntities($sql, $args)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* currently not used*/
|
||||||
|
public function deleteForUserId($userId) {
|
||||||
|
$sql = "SELECT * FROM `*PREFIX*reader_preferences` WHERE user_id=?";
|
||||||
|
$args = [ $userId ];
|
||||||
|
array_map(
|
||||||
|
function($entity) {
|
||||||
|
$this->delete($entity);
|
||||||
|
}, $this->findEntities($sql, $args)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
39
lib/Db/ReaderEntity.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2015 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Db;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
class ReaderEntity extends Entity {
|
||||||
|
|
||||||
|
/* returns decoded json if input is json, otherwise returns input */
|
||||||
|
public static function conditional_json_decode($el) {
|
||||||
|
$result = json_decode($el);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE) {
|
||||||
|
return $result;
|
||||||
|
} else {
|
||||||
|
return $el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public function toService() {
|
||||||
|
return [
|
||||||
|
'name' => $this->getName(),
|
||||||
|
'type' => $this->getType(),
|
||||||
|
'value' => $this->conditional_json_decode($this->getValue()),
|
||||||
|
'content' => $this->conditional_json_decode($this->getContent()),
|
||||||
|
'lastModified' => $this->getLastModified(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
40
lib/Db/ReaderMapper.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Db;
|
||||||
|
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\AppFramework\Db\Mapper;
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Utility\Time;
|
||||||
|
|
||||||
|
abstract class ReaderMapper extends Mapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Time
|
||||||
|
*/
|
||||||
|
private $time;
|
||||||
|
|
||||||
|
public function __construct(IDBConnection $db, $table, $entity, Time $time) {
|
||||||
|
parent::__construct($db, $table, $entity);
|
||||||
|
$this->time = $time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Entity $entity) {
|
||||||
|
$entity->setLastModified($this->time->getMicroTime());
|
||||||
|
return parent::update($entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insert(Entity $entity) {
|
||||||
|
$entity->setLastModified($this->time->getMicroTime());
|
||||||
|
return parent::insert($entity);
|
||||||
|
}
|
||||||
|
}
|
70
lib/Hooks.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader;
|
||||||
|
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\Files\Node;
|
||||||
|
use OCP\IUser;
|
||||||
|
use OCP\Util;
|
||||||
|
use \OC\User\User as User;
|
||||||
|
|
||||||
|
class Hooks {
|
||||||
|
|
||||||
|
public static function register() {
|
||||||
|
Util::connectHook('\OCP\Config', 'js', 'OCA\Files_Reader\Hooks', 'announce_settings');
|
||||||
|
|
||||||
|
\OC::$server->getRootFolder()->listen('\OC\Files', 'preDelete', function (Node $node) {
|
||||||
|
$fileId = $node->getId();
|
||||||
|
$connection = \OC::$server->getDatabaseConnection();
|
||||||
|
self::deleteFile($connection, $fileId);
|
||||||
|
});
|
||||||
|
\OC::$server->getUserManager()->listen('\OC\User', 'preDelete', function (User $user) {
|
||||||
|
$userId = $user->getUID();
|
||||||
|
$connection = \OC::$server->getDatabaseConnection();
|
||||||
|
self::deleteUser($connection, $userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function announce_settings(array $settings) {
|
||||||
|
// Nextcloud encodes this as JSON, Owncloud does not (yet) (#75)
|
||||||
|
// TODO: rmeove this when Owncloud starts encoding oc_appconfig as JSON just like it already encodes most other properties
|
||||||
|
$isJson = self::isJson($settings['array']['oc_appconfig']);
|
||||||
|
$array = ($isJson) ? json_decode($settings['array']['oc_appconfig'], true) : $settings['array']['oc_appconfig'];
|
||||||
|
$array['filesReader']['enableEpub'] = Config::get('epub_enable', 'true');
|
||||||
|
$array['filesReader']['enablePdf'] = Config::get('pdf_enable', 'true');
|
||||||
|
$array['filesReader']['enableCbx'] = Config::get('cbx_enable', 'true');
|
||||||
|
$settings['array']['oc_appconfig'] = ($isJson) ? json_encode($array) : $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function deleteFile(IDBConnection $connection, $fileId) {
|
||||||
|
$queryBuilder = $connection->getQueryBuilder();
|
||||||
|
$queryBuilder->delete('reader_bookmarks')->where('file_id = :file_id')->setParameter(':file_id', $fileId);
|
||||||
|
$queryBuilder->execute();
|
||||||
|
|
||||||
|
$queryBuilder = $connection->getQueryBuilder();
|
||||||
|
$queryBuilder->delete('reader_preferences')->where('file_id = :file_id')->setParameter(':file_id', $fileId);
|
||||||
|
$queryBuilder->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function deleteUser(IDBConnection $connection, $userId) {
|
||||||
|
$queryBuilder = $connection->getQueryBuilder();
|
||||||
|
$queryBuilder->delete('reader_bookmarks')->where('user_id = :user_id')->setParameter(':user_id', $userId);
|
||||||
|
$queryBuilder->execute();
|
||||||
|
|
||||||
|
$queryBuilder = $connection->getQueryBuilder();
|
||||||
|
$queryBuilder->delete('reader_preferences')->where('user_id = :user_id')->setParameter(':user_id', $userId);
|
||||||
|
$queryBuilder->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isJson($string) {
|
||||||
|
return is_string($string) && is_array(json_decode($string, true)) && (json_last_error() == JSON_ERROR_NONE) ? true : false;
|
||||||
|
}
|
||||||
|
}
|
113
lib/Service/BookmarkService.php
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Service;
|
||||||
|
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Db\BookmarkMapper;
|
||||||
|
|
||||||
|
class BookmarkService extends Service {
|
||||||
|
|
||||||
|
// "bookmark" name to use for the cursor (current reading position)
|
||||||
|
const CURSOR = '__CURSOR__';
|
||||||
|
const bookmark_type = 'bookmark';
|
||||||
|
|
||||||
|
private $bookmarkMapper;
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct(BookmarkMapper $bookmarkMapper, $UserId) {
|
||||||
|
parent::__construct($bookmarkMapper);
|
||||||
|
$this->bookmarkMapper = $bookmarkMapper;
|
||||||
|
$this->userId = $UserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get bookmark
|
||||||
|
*
|
||||||
|
* bookmark type is format-dependent, eg CFI for epub, page number for CBR/CBZ, etc
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get($fileId, $name=null, $type=null) {
|
||||||
|
$result = $this->bookmarkMapper->get($fileId, $name, $type);
|
||||||
|
return array_map(
|
||||||
|
function($entity) {
|
||||||
|
return $entity->toService();
|
||||||
|
}, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write bookmark
|
||||||
|
*
|
||||||
|
* position type is format-dependent, eg CFI for epub, page number for CBR/CBZ, etc
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function set($fileId, $name, $value, $type=null, $content=null) {
|
||||||
|
return $this->bookmarkMapper->set($fileId, $name, $value, $type, $content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get cursor (current position in book)
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getCursor($fileId) {
|
||||||
|
$result = $this->get($fileId, static::CURSOR);
|
||||||
|
if (count($result) === 1) {
|
||||||
|
return $result[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief set cursor (current position in book)
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function setCursor($fileId, $value) {
|
||||||
|
return $this->bookmarkMapper->set($fileId, static::CURSOR, $value, static::bookmark_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete bookmark
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function delete($fileId, $name, $type=null) {
|
||||||
|
foreach ($this->bookmarkMapper->get($fileId, $name, $type) as $bookmark) {
|
||||||
|
$this->bookmarkMapper->delete($bookmark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete cursor
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function deleteCursor($fileId) {
|
||||||
|
$this->delete($fileId, static::CURSOR, static::bookmark_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
75
lib/Service/MetadataService.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Service;
|
||||||
|
|
||||||
|
use OCP\App\IAppManager;
|
||||||
|
|
||||||
|
class MetadataService {
|
||||||
|
|
||||||
|
private $appManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IAppManager $appManager
|
||||||
|
*/
|
||||||
|
public function __construct(IAppManager $appManager) {
|
||||||
|
$this->appManager = $appManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get metadata item(s)
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get($fileId, $name=null) {
|
||||||
|
if ($this->appManager->isInstalled('files_opds')) {
|
||||||
|
if ($meta = \OCA\Files_Opds\Meta::get($fileId)) {
|
||||||
|
if (!empty($name) && array_key_exists($name, $meta)) {
|
||||||
|
return [$item => $meta[$name]];
|
||||||
|
} else {
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write metadata to database
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param array $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function setAll($fileId, $value) {
|
||||||
|
// no-op for now
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write metadata item to database
|
||||||
|
*
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @param array $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function set($fileId, $name, $value) {
|
||||||
|
// no-op for now
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
117
lib/Service/PreferenceService.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Service;
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Db\PreferenceMapper;
|
||||||
|
|
||||||
|
class PreferenceService extends Service {
|
||||||
|
|
||||||
|
// (ab)use the fact that $fileId never goes below 1 by using the
|
||||||
|
// value 0 to indicate a default preference
|
||||||
|
const DEFAULTS = 0;
|
||||||
|
|
||||||
|
private $preferenceMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PreferenceMapper $preferenceMapper
|
||||||
|
*/
|
||||||
|
public function __construct(PreferenceMapper $preferenceMapper) {
|
||||||
|
parent::__construct($preferenceMapper);
|
||||||
|
$this->preferenceMapper = $preferenceMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get preference
|
||||||
|
*
|
||||||
|
* scope identifies preference source, i.e. which renderer the preference applies to
|
||||||
|
* preference type is format-dependent, eg CFI for epub, page number for CBR/CBZ, etc
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get($scope, $fileId, $name=null) {
|
||||||
|
$result = $this->preferenceMapper->get($scope, $fileId, $name);
|
||||||
|
return array_map(
|
||||||
|
function($entity) {
|
||||||
|
return $entity->toService();
|
||||||
|
}, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief write preference
|
||||||
|
*
|
||||||
|
* scope identifies preference source, i.e. which renderer the preference applies to
|
||||||
|
* position type is format-dependent, eg CFI for epub, page number for CBR/CBZ, etc
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function set($scope, $fileId, $name, $value) {
|
||||||
|
return $this->preferenceMapper->set($scope, $fileId, $name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get default preference
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getDefault($scope, $name=null) {
|
||||||
|
return $this->get($scope, static::DEFAULTS, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief set default preference
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function setDefault($scope, $name, $value) {
|
||||||
|
return $this->preferenceMapper->set($scope, static::DEFAULTS, $name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete preference
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param int $fileId
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function delete($scope, $fileId, $name) {
|
||||||
|
foreach($this->preferenceMapper->get($scope, $fileId, $name) as $preference) {
|
||||||
|
$this->preferenceMapper->delete($preference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief delete default
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function deleteDefault($scope, $name) {
|
||||||
|
$this->delete($scope, static::DEFAULTS, $name);
|
||||||
|
}
|
||||||
|
}
|
25
lib/Service/Service.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Service;
|
||||||
|
|
||||||
|
use OCA\Files_Reader\Db\ReaderMapper;
|
||||||
|
|
||||||
|
abstract class Service {
|
||||||
|
|
||||||
|
protected $mapper;
|
||||||
|
|
||||||
|
public function __construct(ReaderMapper $mapper){
|
||||||
|
$this->mapper = $mapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
27
lib/Utility/Time.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2017 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader\Utility;
|
||||||
|
|
||||||
|
class Time {
|
||||||
|
public function getTime() {
|
||||||
|
return time();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return int the current unix time in miliseconds
|
||||||
|
*/
|
||||||
|
public function getMicroTime() {
|
||||||
|
list($millisecs, $secs) = explode(" ", microtime());
|
||||||
|
return $secs . substr($millisecs, 2, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
63
lib/config.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ownCloud - Files_Reader App
|
||||||
|
*
|
||||||
|
* @author Frank de Lange
|
||||||
|
* @copyright 2014,2018 Frank de Lange
|
||||||
|
*
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config class for Reader
|
||||||
|
*/
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @brief get user config value
|
||||||
|
*
|
||||||
|
* @param string $key value to retrieve
|
||||||
|
* @param string $default default value to use
|
||||||
|
* @return string retrieved value or default
|
||||||
|
*/
|
||||||
|
public static function get($key, $default) {
|
||||||
|
return \OC::$server->getConfig()->getUserValue(\OCP\User::getUser(), 'files_reader', $key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief set user config value
|
||||||
|
*
|
||||||
|
* @param string $key key for value to change
|
||||||
|
* @param string $value value to use
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
public static function set($key, $value) {
|
||||||
|
return \OC::$server->getConfig()->setUserValue(\OCP\User::getUser(), 'files_reader', $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get app config value
|
||||||
|
*
|
||||||
|
* @param string $key value to retrieve
|
||||||
|
* @param string $default default value to use
|
||||||
|
* @return string retrieved value or default
|
||||||
|
*/
|
||||||
|
public static function getApp($key, $default) {
|
||||||
|
return \OC::$server->getConfig()->getAppValue('files_reader', $key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief set app config value
|
||||||
|
*
|
||||||
|
* @param string $key key for value to change
|
||||||
|
* @param string $value value to use
|
||||||
|
* @return bool success
|
||||||
|
*/
|
||||||
|
public static function setApp($key, $value) {
|
||||||
|
return \OC::$server->getConfig()->setAppValue('files_reader', $key, $value);
|
||||||
|
}
|
||||||
|
}
|
25
personal.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ownCloud - Files_Reader app
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014,2018 Frank de Lange
|
||||||
|
* This file is licensed under the Affero General Public License version 3 or
|
||||||
|
* later.
|
||||||
|
* See the COPYING-README file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Files_Reader;
|
||||||
|
|
||||||
|
use OCP\Util;
|
||||||
|
|
||||||
|
#$l = \OC::$server->getL10N('files_reader');
|
||||||
|
|
||||||
|
$tmpl = new \OCP\Template('files_reader', 'settings-personal');
|
||||||
|
$EpubEnable = Config::get('epub_enable', 'true');
|
||||||
|
$PdfEnable = Config::get('pdf_enable', 'true');
|
||||||
|
$CbxEnable = Config::get('cbx_enable', 'true');
|
||||||
|
$tmpl->assign('EpubEnable', $EpubEnable);
|
||||||
|
$tmpl->assign('PdfEnable', $PdfEnable);
|
||||||
|
$tmpl->assign('CbxEnable', $CbxEnable);
|
||||||
|
|
||||||
|
return $tmpl->fetchPage();
|
BIN
screenshots/Screenshot_2014-09-29-20-21-50.png
Normal file
After Width: | Height: | Size: 303 KiB |
BIN
screenshots/Screenshot_2014-10-02-19-16-18.png
Normal file
After Width: | Height: | Size: 155 KiB |
BIN
screenshots/Screenshot_2017-03-15_195840_593917640.png
Normal file
After Width: | Height: | Size: 374 KiB |
BIN
screenshots/Screenshot_2017-03-15_200124_360946567.png
Normal file
After Width: | Height: | Size: 372 KiB |
BIN
screenshots/files_reader-1.png
Normal file
After Width: | Height: | Size: 155 KiB |
BIN
screenshots/files_reader-2.png
Normal file
After Width: | Height: | Size: 255 KiB |
BIN
screenshots/files_reader-3.png
Normal file
After Width: | Height: | Size: 202 KiB |
BIN
screenshots/files_reader_PDF_001.png
Normal file
After Width: | Height: | Size: 812 KiB |
BIN
screenshots/files_reader_PDF_002.png
Normal file
After Width: | Height: | Size: 632 KiB |
BIN
screenshots/files_reader_PDF_003.png
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
screenshots/files_reader_PDF_004.png
Normal file
After Width: | Height: | Size: 236 KiB |
BIN
screenshots/files_reader_PDF_005.png
Normal file
After Width: | Height: | Size: 235 KiB |
BIN
screenshots/files_reader_PDF_006.png
Normal file
After Width: | Height: | Size: 770 KiB |
BIN
screenshots/photo_2017-03-15_17-21-30.jpg
Normal file
After Width: | Height: | Size: 202 KiB |
BIN
screenshots/photo_2017-03-15_17-21-37.jpg
Normal file
After Width: | Height: | Size: 206 KiB |
BIN
screenshots/photo_2017-03-15_17-21-39.jpg
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
screenshots/photo_2017-03-15_17-21-40.jpg
Normal file
After Width: | Height: | Size: 173 KiB |
BIN
screenshots/photo_2017-03-15_17-21-41.jpg
Normal file
After Width: | Height: | Size: 141 KiB |
BIN
screenshots/photo_2017-03-15_17-21-43.jpg
Normal file
After Width: | Height: | Size: 141 KiB |
BIN
screenshots/photo_2017-03-15_17-21-49.jpg
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
screenshots/photo_2017-03-15_17-21-51.jpg
Normal file
After Width: | Height: | Size: 125 KiB |
BIN
screenshots/photo_2017-03-15_17-21-52.jpg
Normal file
After Width: | Height: | Size: 217 KiB |
BIN
screenshots/photo_2017-03-15_17-21-53.jpg
Normal file
After Width: | Height: | Size: 202 KiB |
BIN
screenshots/photo_2017-03-15_17-21-54.jpg
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
screenshots/photo_2017-03-15_17-21-56.jpg
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
screenshots/photo_2017-03-15_17-21-57.jpg
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
screenshots/photo_2017-03-15_17-21-58.jpg
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
screenshots/photo_2017-03-15_17-21-59.jpg
Normal file
After Width: | Height: | Size: 196 KiB |
BIN
screenshots/photo_2017-03-15_17-22-00.jpg
Normal file
After Width: | Height: | Size: 239 KiB |
BIN
screenshots/photo_2017-03-15_17-22-01.jpg
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
screenshots/photo_2017-03-15_17-22-02.jpg
Normal file
After Width: | Height: | Size: 165 KiB |
BIN
screenshots/photo_2017-03-15_17-22-03.jpg
Normal file
After Width: | Height: | Size: 175 KiB |
BIN
screenshots/photo_2017-03-15_17-22-04.jpg
Normal file
After Width: | Height: | Size: 233 KiB |
BIN
screenshots/photo_2017-03-15_17-22-05.jpg
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
screenshots/photo_2017-03-15_17-22-06.jpg
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
screenshots/photo_2017-03-15_17-22-08.jpg
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
screenshots/photo_2017-03-15_17-22-09.jpg
Normal file
After Width: | Height: | Size: 235 KiB |
BIN
screenshots/photo_2017-03-15_17-22-10.jpg
Normal file
After Width: | Height: | Size: 194 KiB |
BIN
screenshots/photo_2017-03-15_17-22-11.jpg
Normal file
After Width: | Height: | Size: 226 KiB |
BIN
screenshots/photo_2017-03-15_17-22-12.jpg
Normal file
After Width: | Height: | Size: 227 KiB |
BIN
screenshots/photo_2017-03-15_17-22-13.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
screenshots/photo_2017-03-15_17-22-14.jpg
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
screenshots/photo_2017-03-15_17-22-15.jpg
Normal file
After Width: | Height: | Size: 137 KiB |
BIN
screenshots/photo_2017-03-15_17-22-16.jpg
Normal file
After Width: | Height: | Size: 149 KiB |
BIN
screenshots/photo_2017-03-15_17-22-17.jpg
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
screenshots/photo_2017-03-15_17-22-18.jpg
Normal file
After Width: | Height: | Size: 159 KiB |
BIN
screenshots/photo_2017-03-15_17-22-19.jpg
Normal file
After Width: | Height: | Size: 184 KiB |
BIN
screenshots/photo_2017-03-15_17-22-20.jpg
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
screenshots/photo_2017-03-15_17-22-21.jpg
Normal file
After Width: | Height: | Size: 183 KiB |
BIN
screenshots/photo_2017-03-15_17-22-22.jpg
Normal file
After Width: | Height: | Size: 191 KiB |
BIN
screenshots/photo_2017-03-15_17-22-23.jpg
Normal file
After Width: | Height: | Size: 174 KiB |
BIN
screenshots/photo_2017-03-15_17-22-24.jpg
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
screenshots/photo_2017-03-15_17-22-26.jpg
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
screenshots/photo_2017-03-15_17-22-27.jpg
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
screenshots/photo_2017-03-15_17-22-28.jpg
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
screenshots/photo_2017-03-15_18-28-36.jpg
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
screenshots/photo_2017-03-15_18-28-37.jpg
Normal file
After Width: | Height: | Size: 141 KiB |
BIN
screenshots/photo_2017-03-15_18-28-43.jpg
Normal file
After Width: | Height: | Size: 134 KiB |
BIN
screenshots/photo_2017-03-15_18-28-46.jpg
Normal file
After Width: | Height: | Size: 137 KiB |
BIN
screenshots/photo_2017-03-15_18-28-49.jpg
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
screenshots/photo_2017-03-15_18-28-52.jpg
Normal file
After Width: | Height: | Size: 100 KiB |