diff --git a/README b/README index 141f866..4c30cdf 100644 --- a/README +++ b/README @@ -31,9 +31,11 @@ Finally, all the input (search terms, etc.) is verified by the CLI script, so th The shell script may be copied anywhere. Its dependencies are standard, although I only tested on Debian and Arch Linux: bash, find, gawk, sed, grep, file, base64, ls. Besides, if pdfinfo and pdftoppm are installed, the script detects these and handles the pages from PDF documents in an individual manner (as JPEG images), instead of returning the whole document when a single page is requested. Once the script is installed, the path to the Paperwork data must be changed (edit the script). Optionally, the number of DPI (dots per inch) for the extraction of pages from PDF documents may be adjusted as well. -Same goes for the PHP file's installation (```index.php```). It depends on PHP 5.2 or better (so that JSON is included). -Alongside the PHP file, there must be a configuration file named ```paperweb.ini.php```, which should be created by copying and changing the provided example file: ```example-paperweb.ini.php```. -The CSS file is optional, and it can be adapted. +On the web side, the only mandatory files are ```index.php```, ```query.inc.php```, ```thumbs.inc.php```, and ```paperweb.ini.php```, which should be created by copying and changing the provided example file: ```example-paperweb.ini.php```. The code depends on PHP 5.2 or better (so that JSON is included). +Other, optional, files are provided in order to offer better cache handling, by using HTTP GET requests through Ajax calls, instead of relying on the POST requests that are necessary for Javascript-less plain HTML. These files are: +* ```ajax.js```: a very minimal Ajax helper; +* ```query.ajax.php``` and ```thumbs.ajax.php```: the receiving components of Ajax calls. +And there’s the CSS file, which is optional too, although it is much better to use it, even though it could be improved. Finally, ```visudo``` must be run so that the web server user is allowed to execute the CLI script, for example: ``` @@ -116,4 +118,4 @@ $ paperfind.sh -D 20151217_0000_01 -p 3 | json_pp = THE WEB INTERFACE = There is not much to say, appart from the fact that the web server should probably be configured to authenticate users before granting access to the interface. It depends on the value of the data managed by Paperwork. -The current web UI is minimal and very much a prototype. I may improve it, depending on my own needs, or if someone volunteers to contribute ;-) +The current web UI is minimal and barely more than a prototype. I may improve it, depending on my own needs, or if someone volunteers to contribute ;-) diff --git a/web/ajax.js b/web/ajax.js new file mode 100644 index 0000000..3890723 --- /dev/null +++ b/web/ajax.js @@ -0,0 +1,197 @@ +// PAPERWEB - GPLv3 licence +// Copyright 2006-2016 Yves Gablin +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// = Credits = +// Tutorial: http://robloche.free.fr/javascript/tuto_xhr/tuto_xhr.html + +var ajax = { + XML: 1, + TXT: 2, + + // public class Connection + // + start() + // - xmlhttp + Connection: function() { + var xh; + try { + xh = new XMLHttpRequest(); + } catch (e1) { // IE + try { + xh = new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e2) { + try { + xh = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e3) { + xh = null; + } + } + } + this.xmlhttp = xh; + }, + + // private class Link + // - connection: Connection + // - urlBuilder: user-provided function + // - resultHandler: user-provided function + // - resultType: XML|TXT + // - preventDefault : true|false = prevent default event consequence + Link: function(pSource, pEvent, pUrlBuilder, pResultHandler, pType, pPreventDefault) { + this.connection = new ajax.Connection(); + this.urlBuilder = pUrlBuilder; + this.resultHandler = pResultHandler; + this.resultType = pType; + this.preventDefault = pPreventDefault; + if (!pSource.ajaxLinks) { + pSource.ajaxLinks = new Array(); + pSource.addEventListener(pEvent, this.evhSourceTrigger); + } + pSource.ajaxLinks[pSource.ajaxLinks.length] = this; + if (pEvent != 'click') { + this.evhSourceTrigger.call(pSource, null); + } + }, + + // public static function link() + link: function(pSource, pUrlBuilder, pDestHandler, pType, pPreventDefault) { + var event; + switch (pSource.tagName) { + case 'SELECT': + case 'TEXTAREA': + event = 'change'; + break; + case 'BUTTON': + event = 'click'; + break; + case 'INPUT': + switch (pSource.type) { + case 'text': + case 'password': + case 'file': + case 'hidden': + event = 'change'; + break; + case 'checkbox': + case 'radio': + case 'submit': + case 'reset': + case 'image': + case 'button': + event = 'click'; + break; + } + break; + } + new ajax.Link(pSource, event, pUrlBuilder, pDestHandler, pType, pPreventDefault); + }, + + // public static function importNode() + importNode: function(pSourceNode, pDestDoc) { + var destNode; + switch (pSourceNode.nodeType) { + case 1: // Element + destNode = pDestDoc.createElement(pSourceNode.tagName); + break; + case 3: // TextNode + destNode = pDestDoc.createTextNode(pSourceNode.data); + break; + case 4: // CDATASection + destNode = pDestDoc.createCDATASection(pSourceNode.data); + break; + case 5: // EntityReference + destNode = pDestDoc.createEntityReference(pSourceNode.nodeName); + break; + case 7: // ProcessingInstruction + destNode = pDestDoc.createProcessingInstruction(pSourceNode.nodeName, pSourceNode.nodeValue); + break; + case 8: // Comment + destNode = pDestDoc.createComment(pSourceNode.data); + break; + case 9: // Document + return ajax.importNode(pSourceNode.documentElement, pDestDoc); + default: + return null; + } + if (pSourceNode.attributes) { + var att; + for (var numAtt = 0; numAtt < pSourceNode.attributes.length; numAtt++) { + att = pSourceNode.attributes[numAtt]; + destNode.setAttribute(att.name, att.value); + } + } + if (pSourceNode.childNodes) { + var child; + for (var numCh = 0; numCh < pSourceNode.childNodes.length; numCh++) { + child = ajax.importNode(pSourceNode.childNodes[numCh], pDestDoc); + if (child != null) destNode.appendChild(child); + } + } + return destNode; + } +} + +// public method Connection.start() +ajax.Connection.prototype.start = function(pUrl, pPostParams, pCallback, pType) { + if (!this.xmlhttp || !pCallback) return; + var xh = this.xmlhttp; + var async = true; + if (xh.ajaxConnectionConnected == true) { + xh.abort(); + } + if (pPostParams) { + xh.open("POST", pUrl, async); + } else { + xh.open("GET", pUrl, async); + } + xh.onreadystatechange = function() { + if (xh.readyState == 4) { + var status = -1; + try { status = xh.status; } catch (unreachable) { } + if (xh.ajaxConnectionNotice) { + xh.ajaxConnectionNotice.parentNode.removeChild(xh.ajaxConnectionNotice); + xh.ajaxConnectionNotice = null; + } + var result = null; + if (status == 200) { + if (pType == ajax.XML) { + result = xh.responseXML; + } else { + result = xh.responseText; + } + } + xh.ajaxConnectionConnected = false; + pCallback(result); + } + }; + var notice = document.createElement('div'); + notice.appendChild(document.createTextNode('...')); + notice.className = 'ajax-connection-wait'; + xh.ajaxConnectionNotice = document.getElementsByTagName('body').item(0).appendChild(notice); + + if (pType == ajax.XML) + xh.overrideMimeType("text/xml"); + xh.ajaxConnectionConnected = true; + xh.send(pPostParams); +}; + +// public method Link.evhSourceTrigger() +ajax.Link.prototype.evhSourceTrigger = function(pEvt) { + var link, prevent = false; + for (var i = 0; i < this.ajaxLinks.length; i++) { + link = this.ajaxLinks[i]; + link.connection.start(link.urlBuilder(this), null, link.resultHandler, link.resultType); + prevent = prevent || link.preventDefault; + } + if (prevent) { + pEvt.preventDefault(); + } +}; diff --git a/web/index.php b/web/index.php index 9e31d4a..eb6c0c0 100644 --- a/web/index.php +++ b/web/index.php @@ -31,6 +31,7 @@ if (array_key_exists('doDownload', $_REQUEST)) { $ext = explode('/', $meta['mime'])[1]; header("Content-Type: {$meta['mime']}"); header("ETag: \"{$meta['etag']}\""); + header("Cache-Control: public"); header("Content-Disposition: inline; filename=\"{$date}_{$page}.{$ext}\""); passthru("sudo -u {$CONF['USER']} {$CONF['PATH']} -R {$datearg} -p {$pagearg}"); } @@ -41,6 +42,8 @@ if (array_key_exists('doDownload', $_REQUEST)) { PaperWeb, a simple web interface for Paperwork + +
@@ -54,85 +57,10 @@ if (array_key_exists('doDownload', $_REQUEST)) {

-
-

No result.

- -

Documents found

- - - - -
-
- -

Pages

- - - $p) { - $nump = $n+1; -?> - ">Page <?php echo $nump; ?> - -
diff --git a/web/paperweb.js b/web/paperweb.js new file mode 100644 index 0000000..232cf63 --- /dev/null +++ b/web/paperweb.js @@ -0,0 +1,73 @@ +// PAPERWEB - GPLv3 licence +// Copyright 2006-2016 Yves Gablin +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +var paperweb = { + //private static function purgeSection() + purgeSection: function(section) { + for (var i = section.childNodes.length; i--;) { + section.removeChild(section.childNodes[i]); + } + }, + + // public static function prepareQuery() + prepareQuery: function() { + var button = document.querySelector('header button'); + ajax.link(button, + function(thisButton) { + var inputs = document.querySelectorAll('header input'); + return encodeURI('query.ajax.php?doQuery=1&D='+inputs[0].value + +'&L='+inputs[1].value+'&K='+inputs[2].value); + }, + paperweb.updateResult, + ajax.XML, true); + }, + + // public static function prepareThumbs() + prepareThumbs: function() { + var buttons = document.querySelectorAll('#result button'); + for (var i = buttons.length; i--;) { + buttons[i].addEventListener('click', paperweb.changeCurrentDocument); + ajax.link(buttons[i], + function(thisButton) { + return encodeURI('thumbs.ajax.php?doThumbnails='+thisButton.value); + }, + paperweb.updateThumbs, + ajax.XML, true); + } + }, + + // public static function updateResult(xml) + updateResult: function(xml) { + var newSection = ajax.importNode(xml.documentElement, document); + var section = document.getElementById('result'); + section.parentNode.replaceChild(newSection, section); + paperweb.purgeSection(document.getElementById('thumbs')); + paperweb.prepareThumbs(); + }, + + // public static function updateThumbs(xml) + updateThumbs: function(xml) { + var section = document.getElementById('thumbs'); + section.parentNode.replaceChild(ajax.importNode(xml.documentElement, document), section); + }, + + // public static function changeCurrentDocument(evt) + changeCurrentDocument: function(evt) { + var buttons = document.querySelectorAll('#result button'); + for (var i = buttons.length; i--;) { + buttons[i].disabled = (buttons[i] === this); + } + } +}; + +window.addEventListener('load', paperweb.prepareQuery); diff --git a/web/query.ajax.php b/web/query.ajax.php new file mode 100644 index 0000000..12bdb91 --- /dev/null +++ b/web/query.ajax.php @@ -0,0 +1,17 @@ + +. +# +$CONF = parse_ini_file('paperweb.ini.php'); +require('query.inc.php'); diff --git a/web/query.inc.php b/web/query.inc.php new file mode 100644 index 0000000..dd0900d --- /dev/null +++ b/web/query.inc.php @@ -0,0 +1,51 @@ +
+ +

No result.

+ +

Documents found

+ + + + +
diff --git a/web/thumbs.ajax.php b/web/thumbs.ajax.php new file mode 100644 index 0000000..2712c2f --- /dev/null +++ b/web/thumbs.ajax.php @@ -0,0 +1,30 @@ +. +# +$CONF = parse_ini_file('paperweb.ini.php'); + +$datearg = escapeshellarg($_REQUEST['doThumbnails']); +$json = exec("sudo -u {$CONF['USER']} {$CONF['PATH']} -M {$datearg}"); +$meta = json_decode($json, true); +$knownetag = trim(@$_SERVER['HTTP_IF_NONE_MATCH'], ' "'); +if ($meta['etag'] == $knownetag) { + header('HTTP/1.1 304 Not Modified'); +} else { + header("ETag: \"{$meta['etag']}\""); + header("Cache-Control: public"); +?> + + + +

Pages

+ + + $p) { + $nump = $n+1; +?> + ">Page <?php echo $nump; ?> + +