├── .gitignore ├── COPYING.jQuery ├── COPYING.requireJS ├── LICENSE ├── README.md ├── build-embed.def ├── build.def ├── compile ├── css ├── lib │ ├── dragdealer.css │ ├── icomoon.css │ ├── jquery-ui-autocomplete.css │ └── qunit-1.11.0.css └── sass │ ├── main.scss │ └── pscp.scss ├── fonts ├── icomoon.dev.svg ├── icomoon.eot ├── icomoon.svg ├── icomoon.ttf └── icomoon.woff ├── img ├── app │ ├── arxiv.png │ ├── cross.png │ ├── doi.png │ ├── favicon.ico │ ├── favicon.png │ ├── inspire.png │ ├── mypscp.png │ ├── paperscapeBanner.png │ ├── paperscapeCol.png │ ├── paperscapeTransparent.png │ └── pdf.png └── svg │ ├── paperscapeBanner.svg │ ├── paperscapeCol.svg │ ├── paperscapeFB.svg │ ├── paperscapeFBIcon.svg │ ├── paperscapeIcon.svg │ └── paperscapeTransparent.svg ├── index-embed.html ├── index-multimap.html ├── index.html ├── js ├── app-build-embed.js ├── app-build.js ├── coffee │ ├── Vec2D.coffee │ ├── Vec2D_test.coffee │ ├── ajax.coffee │ ├── ajax_test.coffee │ ├── config-embed.coffee │ ├── config.coffee │ ├── config_unit_test.coffee │ ├── detailview.coffee │ ├── fadeview.coffee │ ├── infoview.coffee │ ├── input.coffee │ ├── input_test.coffee │ ├── main-embed.coffee │ ├── main.coffee │ ├── main_test.coffee │ ├── mapview.coffee │ ├── mapview_test.coffee │ ├── newpapersview.coffee │ ├── search.coffee │ ├── selected.coffee │ ├── world.coffee │ └── world_test.coffee └── lib │ ├── jquery-3.1.1.js │ ├── jquery-3.1.1.min.js │ ├── jquery-ui-autocomplete.html.js │ ├── jquery-ui-autocomplete.min.js │ ├── jquery.mousewheel.js │ ├── mousetrap.js │ ├── qunit-1.11.0.js │ └── require.js ├── minhtml ├── .gitignore ├── lexer.py ├── minhtml.py └── parser.py ├── r.js └── unittests.html /.gitignore: -------------------------------------------------------------------------------- 1 | css/sass/.sass-cache 2 | css/app/*.css 3 | css/app/*.css.map 4 | local_serve 5 | local_serve/ 6 | js/tests/*.js 7 | js/tests/*.map 8 | js/app-build/*.js 9 | js/app/*.js 10 | js/app/*.map 11 | build 12 | build-embed 13 | deploy* 14 | -------------------------------------------------------------------------------- /COPYING.jQuery: -------------------------------------------------------------------------------- 1 | Copyright 2014 jQuery Foundation and other contributors 2 | http://jquery.com/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /COPYING.requireJS: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2014, The Dojo Foundation 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of the Dojo Foundation nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (C) 2011-2017 Damien P. George and Robert Knegjens 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Paperscape Map Client 2 | ===================== 3 | 4 | This is the source code of the browser-based client of the [Paperscape map](http://paperscape.org) project. 5 | The source code of the [map generation](https://github.com/paperscape/paperscape-mapgen) and [backend web server](https://github.com/paperscape/paperscape-backend), as well as the [Paperscape data](https://github.com/paperscape/paperscape-data), are also available on Github. 6 | For more details and progress on Paperscape please visit the [development blog](http://blog.paperscape.org). 7 | 8 | Compiling 9 | --------- 10 | 11 | The Paperscape map client is written in [CoffeeScript](http://coffeescript.org), which must first be (trans)compiled into JavaScript code for it to run in a browser. 12 | Similarly, the style sheets are written in [Sass](http://sass-lang.com), which must be compiled into CSS. 13 | With CoffeeScript and Sass installed, the _compile_ script can be used (on unix-like systems) to automatically perform the compilations. 14 | Once started this script will continue to monitor for changes using the `inotifywait` command (from the inotify toolset) unless the runtime command `--single` is specified. 15 | 16 | The JavaScript/CoffeeScript code is split into modules, which are managed and loaded using [RequireJS](http://requirejs.org). 17 | 18 | Building (deploying) 19 | -------------------- 20 | 21 | The Paperscape map client can be deployed using the included build script. 22 | Copy the default build script file _build.def_ to a file named _build_ (i.e. without the extension), and edit the build paths in the new file as appropriate. 23 | Then give the new build script executable permissions with `chmod +x`, and run it with the command `./build`. 24 | The JavaScript minification is handled by [RequireJS](http://requirejs.org). 25 | The HTML index file is minified by _minhtml/minthml.py_. 26 | 27 | About the Paperscape map 28 | ------------------------ 29 | 30 | Paperscape is an interactive map that visualises the [arXiv](http://arxiv.org/), an open, online repository for scientific research papers. 31 | The map, which can be explored by panning and zooming, currently includes all of the papers from the arXiv and is updated daily. 32 | 33 | Each scientific paper is represented in the map by a circle whose size is determined by the number of times that paper has been cited by others. 34 | A paper's position in the map is determined by both its citation links (papers that cite it) and its reference links (papers it refers to). 35 | These links pull related papers together, whereas papers with no or few links in common push each other away. 36 | 37 | In the default colour scheme, where papers are coloured according to their scientific category, coloured "continents" emerge, such as theoretical high energy physics (blue) or astrophysics (pink). 38 | At their interface one finds cross-disciplinary fields, such as dark matter and cosmological inflation. 39 | Zooming in on a continent reveals substructures representing more specific fields of research. 40 | The automatically extracted keywords that appear on top of papers help to identify interesting papers and fields. 41 | 42 | Clicking on a paper reveals its meta data, such as title, authors, journal and abstract, as well as a link to the full text. 43 | It is also possible to view the references or citations for a paper as a star-like background on the map. 44 | 45 | Copyright 46 | --------- 47 | 48 | The MIT License (MIT) 49 | 50 | Copyright (C) 2011-2017 Damien P. George and Robert Knegjens 51 | 52 | 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: 53 | 54 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 55 | 56 | 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. 57 | -------------------------------------------------------------------------------- /build-embed.def: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SRC_DIR=. 4 | 5 | BUILD_DEST=embed-deploy 6 | 7 | mkdir -p $BUILD_DEST/img/app 8 | mkdir -p $BUILD_DEST/js/app 9 | mkdir -p $BUILD_DEST/css/lib 10 | mkdir -p $BUILD_DEST/css/app 11 | mkdir -p $BUILD_DEST/fonts 12 | 13 | $SRC_DIR/compile --single 14 | 15 | python2 $SRC_DIR/minhtml/minhtml.py $SRC_DIR/index-embed.html > $BUILD_DEST/index.html 16 | 17 | cp $SRC_DIR/img/app/* $BUILD_DEST/img/app/ 18 | 19 | # optimize and copy javascript 20 | node $SRC_DIR/r.js -o $SRC_DIR/js/app-build-embed.js 21 | cp $SRC_DIR/js/app-build/pscp-embed.js $BUILD_DEST/js/app/ 22 | gzip -kf $BUILD_DEST/js/app/*.js 23 | 24 | cp $SRC_DIR/css/lib/* $BUILD_DEST/css/lib/ 25 | cp $SRC_DIR/css/app/* $BUILD_DEST/css/app/ 26 | cp $SRC_DIR/fonts/* $BUILD_DEST/fonts/ 27 | -------------------------------------------------------------------------------- /build.def: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SRC_DIR=. 4 | 5 | BUILD_DEST=deploy 6 | 7 | mkdir -p $BUILD_DEST/img/app 8 | mkdir -p $BUILD_DEST/js/app 9 | mkdir -p $BUILD_DEST/css/lib 10 | mkdir -p $BUILD_DEST/css/app 11 | mkdir -p $BUILD_DEST/fonts 12 | 13 | $SRC_DIR/compile --single 14 | 15 | python2 $SRC_DIR/minhtml/minhtml.py $SRC_DIR/index.html > $BUILD_DEST/index.html 16 | #python2 $SRC_DIR/minhtml/minhtml.py $SRC_DIR/index-multimap.html > $BUILD_DEST/index.html 17 | 18 | cp $SRC_DIR/img/app/* $BUILD_DEST/img/app/ 19 | 20 | # optimize and copy javascript 21 | node $SRC_DIR/r.js -o $SRC_DIR/js/app-build.js 22 | cp $SRC_DIR/js/app-build/pscp.js $BUILD_DEST/js/app/ 23 | gzip -kf $BUILD_DEST/js/app/*.js 24 | 25 | cp $SRC_DIR/css/lib/* $BUILD_DEST/css/lib/ 26 | cp $SRC_DIR/css/app/* $BUILD_DEST/css/app/ 27 | cp $SRC_DIR/fonts/* $BUILD_DEST/fonts/ 28 | -------------------------------------------------------------------------------- /compile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | single=false 5 | 6 | while [ $# -gt 0 ] ; do 7 | if [ $1 == "-s" -o $1 == "--single" ] ; then 8 | single=true 9 | fi 10 | shift 1 11 | done 12 | 13 | JS_BASE=js 14 | CSS_BASE=css 15 | 16 | JS_APP_DIR=${JS_BASE}/app 17 | JS_TESTS_DIR=${JS_BASE}/tests 18 | 19 | 20 | function compile { 21 | coffee_files="" 22 | coffee_test_files="" 23 | for f in ${JS_BASE}/coffee/*.coffee; do 24 | if [ ${f: -12} == "_test.coffee" ]; then 25 | coffee_test_files=$coffee_test_files" "$f 26 | else 27 | coffee_files=$coffee_files" "$f 28 | fi 29 | done 30 | 31 | if [ ${#coffee_files} -gt 0 ]; then 32 | coffee -o $JS_APP_DIR -c --map ${coffee_files} 33 | fi 34 | if [ ${#coffee_test_files} -gt 0 ]; then 35 | coffee -o $JS_TESTS_DIR -c --map ${coffee_test_files} 36 | fi 37 | sass --cache-location ${CSS_BASE}/sass/.sass-cache --update ${CSS_BASE}/sass:${CSS_BASE}/app 38 | } 39 | 40 | compile 41 | 42 | if [ $single = false ]; then 43 | echo "watching for changes and running coffee and sass" 44 | while true; do 45 | inotifywait -q -e modify ${JS_BASE}/coffee/*.coffee ${CSS_BASE}/sass/*.scss; 46 | compile; 47 | done 48 | fi 49 | -------------------------------------------------------------------------------- /css/lib/dragdealer.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Dragdealer JS v0.9.5 3 | * http://code.ovidiu.ch/dragdealer-js 4 | * 5 | * Copyright (c) 2010, Ovidiu Chereches 6 | * MIT License 7 | * http://legal.ovidiu.ch/licenses/MIT 8 | */ 9 | 10 | .dragdealer { 11 | position: relative; 12 | height: 30px; 13 | background: #EEE; 14 | } 15 | .dragdealer .handle { 16 | position: absolute; 17 | cursor: pointer; 18 | } 19 | .dragdealer .red-bar { 20 | width: 100px; 21 | height: 30px; 22 | background: #CC0000; 23 | color: #FFF; 24 | line-height: 30px; 25 | text-align: center; 26 | } 27 | .dragdealer .disabled { 28 | background: #898989; 29 | } 30 | -------------------------------------------------------------------------------- /css/lib/icomoon.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('../../fonts/icomoon.eot'); 4 | src:url('../../fonts/icomoon.eot?#iefix') format('embedded-opentype'), 5 | url('../../fonts/icomoon.woff') format('woff'), 6 | url('../../fonts/icomoon.ttf') format('truetype'), 7 | url('../../fonts/icomoon.svg#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | /* Use the following CSS code if you want to use data attributes for inserting your icons */ 13 | [data-icon]:before { 14 | font-family: 'icomoon'; 15 | content: attr(data-icon); 16 | speak: none; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | -webkit-font-smoothing: antialiased; 22 | } 23 | 24 | /* Use the following CSS code if you want to have a class per icon */ 25 | /* 26 | Instead of a list of all class selectors, 27 | you can use the generic selector below, but it's slower: 28 | [class*="icon-"] { 29 | */ 30 | .icon-checkmark, .icon-close, .icon-notebook, .icon-checkbox-checked, .icon-link, .icon-file, .icon-file-2, .icon-share, .icon-cloud, .icon-cloud-2, .icon-stats, .icon-pencil, .icon-minus, .icon-plus, .icon-trashcan, .icon-remove, .icon-remove-2, .icon-arrow-down, .icon-arrow-up, .icon-arrow-right, .icon-arrow-left, .icon-arrow-down-2, .icon-arrow-up-2, .icon-arrow-right-2, .icon-arrow-left-2, .icon-arrow-down-3, .icon-arrow-up-3, .icon-arrow-right-3, .icon-arrow-left-3, .icon-move-horizontal, .icon-transfer, .icon-loop, .icon-loop-2, .icon-loop-3, .icon-zoom-out, .icon-zoom-in, .icon-search, .icon-info, .icon-list, .icon-star, .icon-tags, .icon-cog, .icon-coguser, .icon-groupselect2, .icon-sun, .icon-sun-2, .icon-radio-checked, .icon-radio-unchecked { 31 | font-family: 'icomoon'; 32 | speak: none; 33 | font-style: normal; 34 | font-weight: normal; 35 | font-variant: normal; 36 | text-transform: none; 37 | line-height: 1; 38 | -webkit-font-smoothing: antialiased; 39 | } 40 | .icon-checkmark:before { 41 | content: "\e02a"; 42 | } 43 | .icon-close:before { 44 | content: "\e029"; 45 | } 46 | .icon-notebook:before { 47 | content: "\e027"; 48 | } 49 | .icon-checkbox-checked:before { 50 | content: "\e026"; 51 | } 52 | .icon-link:before { 53 | content: "\e023"; 54 | } 55 | .icon-file:before { 56 | content: "\e025"; 57 | } 58 | .icon-file-2:before { 59 | content: "\e024"; 60 | } 61 | .icon-share:before { 62 | content: "\e022"; 63 | } 64 | .icon-cloud:before { 65 | content: "\e021"; 66 | } 67 | .icon-cloud-2:before { 68 | content: "\e020"; 69 | } 70 | .icon-stats:before { 71 | content: "\e01f"; 72 | } 73 | .icon-pencil:before { 74 | content: "\e01e"; 75 | } 76 | .icon-minus:before { 77 | content: "\e01d"; 78 | } 79 | .icon-plus:before { 80 | content: "\e01c"; 81 | } 82 | .icon-trashcan:before { 83 | content: "\e01b"; 84 | } 85 | .icon-remove:before { 86 | content: "\e01a"; 87 | } 88 | .icon-remove-2:before { 89 | content: "\e019"; 90 | } 91 | .icon-arrow-down:before { 92 | content: "\e018"; 93 | } 94 | .icon-arrow-up:before { 95 | content: "\e017"; 96 | } 97 | .icon-arrow-right:before { 98 | content: "\e016"; 99 | } 100 | .icon-arrow-left:before { 101 | content: "\e015"; 102 | } 103 | .icon-arrow-down-2:before { 104 | content: "\e014"; 105 | } 106 | .icon-arrow-up-2:before { 107 | content: "\e013"; 108 | } 109 | .icon-arrow-right-2:before { 110 | content: "\e012"; 111 | } 112 | .icon-arrow-left-2:before { 113 | content: "\e011"; 114 | } 115 | .icon-arrow-down-3:before { 116 | content: "\e010"; 117 | } 118 | .icon-arrow-up-3:before { 119 | content: "\e00f"; 120 | } 121 | .icon-arrow-right-3:before { 122 | content: "\e00e"; 123 | } 124 | .icon-arrow-left-3:before { 125 | content: "\e00d"; 126 | } 127 | .icon-move-horizontal:before { 128 | content: "\e00c"; 129 | } 130 | .icon-transfer:before { 131 | content: "\e00b"; 132 | } 133 | .icon-loop:before { 134 | content: "\e00a"; 135 | } 136 | .icon-loop-2:before { 137 | content: "\e009"; 138 | } 139 | .icon-loop-3:before { 140 | content: "\e008"; 141 | } 142 | .icon-zoom-out:before { 143 | content: "\e004"; 144 | } 145 | .icon-zoom-in:before { 146 | content: "\e003"; 147 | } 148 | .icon-search:before { 149 | content: "\e007"; 150 | } 151 | .icon-info:before { 152 | content: "\e006"; 153 | } 154 | .icon-list:before { 155 | content: "\e005"; 156 | } 157 | .icon-star:before { 158 | content: "\e002"; 159 | } 160 | .icon-tags:before { 161 | content: "\e001"; 162 | } 163 | .icon-cog:before { 164 | content: "\e000"; 165 | } 166 | .icon-coguser:before { 167 | content: "\e028"; 168 | } 169 | .icon-groupselect2:before { 170 | content: "\e02b"; 171 | } 172 | .icon-sun:before { 173 | content: "\e02c"; 174 | } 175 | .icon-sun-2:before { 176 | content: "\e02d"; 177 | } 178 | .icon-radio-checked:before { 179 | content: "\e02e"; 180 | } 181 | .icon-radio-unchecked:before { 182 | content: "\e02f"; 183 | } 184 | -------------------------------------------------------------------------------- /css/lib/qunit-1.11.0.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.11.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests li .runtime { 115 | float: right; 116 | font-size: smaller; 117 | } 118 | 119 | .qunit-assert-list { 120 | margin-top: 0.5em; 121 | padding: 0.5em; 122 | 123 | background-color: #fff; 124 | 125 | border-radius: 5px; 126 | -moz-border-radius: 5px; 127 | -webkit-border-radius: 5px; 128 | } 129 | 130 | .qunit-collapsed { 131 | display: none; 132 | } 133 | 134 | #qunit-tests table { 135 | border-collapse: collapse; 136 | margin-top: .2em; 137 | } 138 | 139 | #qunit-tests th { 140 | text-align: right; 141 | vertical-align: top; 142 | padding: 0 .5em 0 0; 143 | } 144 | 145 | #qunit-tests td { 146 | vertical-align: top; 147 | } 148 | 149 | #qunit-tests pre { 150 | margin: 0; 151 | white-space: pre-wrap; 152 | word-wrap: break-word; 153 | } 154 | 155 | #qunit-tests del { 156 | background-color: #e0f2be; 157 | color: #374e0c; 158 | text-decoration: none; 159 | } 160 | 161 | #qunit-tests ins { 162 | background-color: #ffcaca; 163 | color: #500; 164 | text-decoration: none; 165 | } 166 | 167 | /*** Test Counts */ 168 | 169 | #qunit-tests b.counts { color: black; } 170 | #qunit-tests b.passed { color: #5E740B; } 171 | #qunit-tests b.failed { color: #710909; } 172 | 173 | #qunit-tests li li { 174 | padding: 5px; 175 | background-color: #fff; 176 | border-bottom: none; 177 | list-style-position: inside; 178 | } 179 | 180 | /*** Passing Styles */ 181 | 182 | #qunit-tests li li.pass { 183 | color: #3c510c; 184 | background-color: #fff; 185 | border-left: 10px solid #C6E746; 186 | } 187 | 188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 189 | #qunit-tests .pass .test-name { color: #366097; } 190 | 191 | #qunit-tests .pass .test-actual, 192 | #qunit-tests .pass .test-expected { color: #999999; } 193 | 194 | #qunit-banner.qunit-pass { background-color: #C6E746; } 195 | 196 | /*** Failing Styles */ 197 | 198 | #qunit-tests li li.fail { 199 | color: #710909; 200 | background-color: #fff; 201 | border-left: 10px solid #EE5757; 202 | white-space: pre; 203 | } 204 | 205 | #qunit-tests > li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | -------------------------------------------------------------------------------- /css/sass/main.scss: -------------------------------------------------------------------------------- 1 | /* definitions */ 2 | 3 | $col-bg: #456; 4 | $col-dark-blue: #346; 5 | $col-gold: #fb0; 6 | 7 | // within some CSS, use "@include no-select;" to prevent that element from being able to be text-selected 8 | @mixin no-select { 9 | -webkit-touch-callout: none; 10 | -webkit-user-select: none; 11 | -khtml-user-select: none; 12 | -moz-user-select: none; 13 | -ms-user-select: none; 14 | user-select: none; 15 | } 16 | 17 | /*****************/ 18 | 19 | h1, h2 { 20 | color: white; 21 | } 22 | 23 | body { 24 | background-color: $col-bg; 25 | //background-image: url("bgtile.png"); 26 | color: white; 27 | font-family: Verdana, Arial, sans-serif; 28 | } 29 | 30 | a:link { 31 | text-decoration: none; 32 | } 33 | 34 | h1.maintitle { 35 | font-size: 48pt; 36 | } 37 | 38 | h2.maintitle { 39 | font-size: 40pt; 40 | } 41 | 42 | .absolute { 43 | position: absolute; 44 | } 45 | 46 | /* common colours */ 47 | 48 | #topHeader { 49 | background-image: url("../../img/app/paperscapeBanner.png"); 50 | background-repeat: no-repeat; 51 | background-position: 25px 3px; 52 | width: 100%; 53 | height: 66px; 54 | } 55 | 56 | #searchHeader { 57 | position: absolute; 58 | top: 10px; 59 | 60 | } 61 | 62 | #searchMessage { 63 | font-size: 60%; 64 | margin-left: 3px; 65 | 66 | .clear { 67 | text-decoration: underline; 68 | cursor: pointer; 69 | } 70 | } 71 | 72 | #searchBox { 73 | -webkit-appearance: none; 74 | -webkit-border-radius:0; 75 | border-radius: 0; 76 | width: 200px; 77 | height: 24px; 78 | line-height: 24px; 79 | vertical-align: top; 80 | padding-right: 0px; 81 | padding-left: 6px; 82 | margin: 0px 0px 0px 0px; 83 | border: 1px solid $col-gold; 84 | text-shadow:none; 85 | box-shadow:none; 86 | } 87 | 88 | #searchButton { 89 | -webkit-appearance: none; 90 | -webkit-border-radius:0; 91 | border-radius: 0; 92 | width: 30px; 93 | height: 28px; 94 | line-height: 28px; 95 | vertical-align: top; 96 | cursor: pointer; 97 | position: relative; 98 | left: -6px; 99 | background-color: $col-gold; 100 | color: white; 101 | padding: 0px; 102 | padding-left: 0px; 103 | margin: 0px 0px 0px 0px; 104 | border:1px solid $col-gold; 105 | text-shadow:none; 106 | box-shadow:none; 107 | } 108 | 109 | #searchNewPapers { 110 | font-size: 80%; 111 | padding-left: 6px; 112 | cursor: pointer; 113 | text-decoration: underline; 114 | color: $col-gold; 115 | } 116 | 117 | #topRightMenu { 118 | position: absolute; 119 | top: 13px; 120 | right: 6px; 121 | cursor: default; 122 | 123 | @include no-select; 124 | 125 | a:visited, a:active, a:hover, a:link { 126 | text-decoration: none; 127 | color: white; 128 | } 129 | 130 | div { 131 | display: inline-block; 132 | color: white; 133 | font-size: 20px; 134 | padding: 5px; 135 | //border: 1px solid black; 136 | margin: 3px; 137 | /*background-color: #40557f;*/ 138 | /*background-color: #234; TODO*/ 139 | //background-color: $col-dark-blue; 140 | font-weight: bold; 141 | cursor: pointer; 142 | } 143 | 144 | .active { 145 | color: $col-gold; 146 | } 147 | 148 | .inactive { 149 | color: #999; 150 | cursor: default; 151 | } 152 | } 153 | 154 | // Obsolete? 155 | #newPapersButton { 156 | top: 18px; 157 | right: 96px; 158 | } 159 | 160 | #newpapersPopup { 161 | position: absolute; 162 | border: 1px solid black; 163 | z-index: 100; 164 | background-color: white; 165 | color: black; 166 | font-size: 75%; 167 | text-align: center; 168 | overflow-y: auto; 169 | 170 | .close { 171 | color: $col-dark-blue; 172 | float: right; 173 | cursor: pointer; 174 | margin: 4px; 175 | } 176 | 177 | .heading { 178 | font-size: 120%; 179 | font-weight: bold; 180 | margin-top: 10px; 181 | margin-bottom: 10px; 182 | } 183 | 184 | .searchButton { 185 | margin-bottom: 10px; 186 | } 187 | 188 | .slider-range { 189 | margin-top: 10px; 190 | margin-bottom: 10px; 191 | left: 50px; 192 | } 193 | 194 | .catTable { 195 | margin-left: 50px; 196 | } 197 | 198 | .catCheckbox { 199 | display: inline-block; 200 | font-size: 90%; 201 | } 202 | 203 | .catName { 204 | width: 90px; 205 | } 206 | 207 | z-index: 200; 208 | } 209 | 210 | -------------------------------------------------------------------------------- /css/sass/pscp.scss: -------------------------------------------------------------------------------- 1 | /* contains style features for elements with xiwiArea */ 2 | 3 | $col-bg: #456; 4 | $col-dark-blue: #346; 5 | $col-gold: #fb0; 6 | 7 | /* this gets dynamically sized */ 8 | #xiwiArea { 9 | position: absolute; 10 | top: 0; 11 | left: 0; 12 | height: 100%; 13 | margin: 0px; 14 | } 15 | 16 | #welcomePopup { 17 | position: absolute; 18 | border: 1px solid #666; 19 | top: 65px; 20 | left: 10px; 21 | //margin-top: 10px; //0 auto; 22 | padding: 4px 5px 0px 5px; 23 | font-size: 13px; 24 | height: 24px; 25 | //font-size: 10pt; 26 | color: white; 27 | z-index: 100; 28 | background-color: #000; 29 | //line-height: 0; 30 | 31 | .valign { 32 | vertical-align: middle; 33 | } 34 | 35 | a { 36 | color: $col-gold; 37 | text-decoration: none; 38 | } 39 | 40 | .refresh { 41 | color: $col-gold; 42 | cursor: pointer; 43 | } 44 | 45 | } 46 | 47 | #detailView { 48 | position: absolute; 49 | left: 10px; 50 | 51 | .header { 52 | text-decoration: underline; 53 | } 54 | 55 | .value { 56 | font-weight: bold; 57 | } 58 | 59 | font-size: 10px; 60 | color: $col-gold; 61 | z-index: 100; 62 | } 63 | 64 | #colourSchemeSelect, #mapSelect { 65 | position: absolute; 66 | left: 10px; 67 | color: white; 68 | //background-color: $col-dark-blue; 69 | background-color: black; 70 | border: 1px solid #666; 71 | font-size: 13px; 72 | height: 24px; 73 | //font-size: 10pt; 74 | //font-family: sans-serif; 75 | z-index: 100; 76 | padding: 2px 5px 2px 5px; 77 | 78 | .select { 79 | color: $col-gold; 80 | //background-color: $col-dark-blue; 81 | background-color: black; 82 | font-size: 13px; 83 | //font-family: sans-serif; 84 | //font-weight: bold; 85 | //text-align: center; 86 | border: none; 87 | padding: 2px; 88 | cursor: pointer; 89 | -moz-appearance: none; 90 | -webkit-appearance: none; 91 | } 92 | 93 | .select option { 94 | cursor: pointer; 95 | color: $col-gold; 96 | //background-color: $col-dark-blue; 97 | background-color: black; 98 | } 99 | 100 | .legend { 101 | width: 15px; 102 | display: inline-block; 103 | color: $col-gold; 104 | cursor: pointer; 105 | margin-left: 3px; 106 | font-size: 16px; 107 | } 108 | } 109 | 110 | 111 | #keyPopup { 112 | position: absolute; 113 | bottom: 10px; 114 | background-color: black; 115 | border: 1px solid #666; 116 | margin-top: 60px; //0 auto; 117 | margin-right: 20px; 118 | padding: 5px; 119 | font-size: 10pt; 120 | color: white; 121 | z-index: 200; 122 | 123 | .close { 124 | color: white; 125 | float: right; 126 | cursor: pointer; 127 | margin: 0 0 0 6px; 128 | font-size: 8pt; 129 | } 130 | 131 | .header { 132 | display: inline-block; 133 | text-decoration: underline; 134 | } 135 | 136 | .cat, .age { 137 | display: inline-block; 138 | font-size: 10pt; 139 | font-weight: bold; 140 | } 141 | // TODO send this information in world_index from tiles 142 | // categories 143 | .hep-th { color: #88f; } 144 | .hep-ph { color: #8f8; } 145 | .hep-ex { color: #ff8; } 146 | .hep-lat { color: rgb(70%,36%,20%); } 147 | .gr-qc { color: #8ff; } 148 | .astro-ph { color: rgb(89%,53%,60%); } 149 | .cond-mat { color: rgb(70%,50%,40%); } 150 | .quant-ph { color: rgb(40%,70%,70%); } 151 | .physics { color: rgb(100%,50%,50%); } 152 | .math { color: rgb(62%,86%,24%); } 153 | .cs { color: rgb(70%,30%,60%); } 154 | .other { color: rgb(70%,100%,30%); } 155 | 156 | // heatmap 157 | .new { color: #f00 } 158 | .old { color: #77a } 159 | } 160 | 161 | /*****************/ 162 | 163 | #canvasUnderlay { 164 | position: absolute; 165 | z-index: 0; 166 | border-top:1px solid black; 167 | margin: 0px; 168 | } 169 | 170 | 171 | #canvasTiles { 172 | position: absolute; 173 | z-index: 1; 174 | border-top:1px solid black; 175 | background-color: transparent; 176 | margin: 0px; 177 | } 178 | 179 | #canvasOverlay { 180 | position: absolute; 181 | z-index: 2; 182 | border-top:1px solid black; 183 | background-color: transparent; 184 | margin: 0px; 185 | } 186 | 187 | #zoomedPaper { 188 | border: 2px solid black; 189 | position: absolute; 190 | background-color: white; 191 | /*background-color: #ffe;*/ 192 | /*background-color: #fe8;*/ 193 | z-index: 100; 194 | } 195 | 196 | 197 | // for the zoom in/out icons 198 | #canvasZoomButtons { 199 | position: absolute; 200 | left: 10px; 201 | z-index: 100; 202 | 203 | div { 204 | display: inline-block; 205 | cursor: pointer; 206 | color: $col-gold; 207 | width: 26px; 208 | height: 20px; 209 | font-size: 20px; 210 | text-align: center; 211 | vertical-align: top; 212 | 213 | &:hover { 214 | font-size: 24px; 215 | margin-top: -6px; 216 | } 217 | } 218 | } 219 | 220 | #infoPopup { 221 | position: absolute; 222 | border: 1px solid black; 223 | z-index: 100; 224 | background-color: white; 225 | color: black; 226 | font-size: 75%; 227 | text-align: center; 228 | overflow-y: auto; 229 | 230 | .close { 231 | color: $col-dark-blue; 232 | float: right; 233 | cursor: pointer; 234 | margin: 4px; 235 | } 236 | 237 | .title { 238 | color: black; 239 | font-weight: bold; 240 | margin: 0.7em; 241 | } 242 | 243 | .authors { 244 | color: blue; 245 | font-weight: normal; 246 | margin: 0.7em; 247 | } 248 | 249 | .arxiv, .journal { 250 | color: black; 251 | margin: 0; 252 | padding: 0 0 0 2px; 253 | } 254 | 255 | .showAbstract { 256 | margin: 4px; 257 | margin-top: 10px; 258 | float: right; 259 | font-size: 90%; 260 | text-decoration: underline; 261 | cursor: pointer; 262 | } 263 | 264 | // Temporary until think up nicer style 265 | .showReferences { 266 | margin: 4px; 267 | margin-top: 10px; 268 | float: left; 269 | font-size: 90%; 270 | text-decoration: underline; 271 | cursor: pointer; 272 | } 273 | .showCitations { 274 | margin: 4px; 275 | margin-top: 10px; 276 | float: left; 277 | font-size: 90%; 278 | text-decoration: underline; 279 | cursor: pointer; 280 | } 281 | 282 | .abstract { 283 | color: black; 284 | //top: 0%; 285 | padding: 1em 1.5em; 286 | clear: left; // doesn't try wrap around previous floats 287 | //overflow-y: auto; handled by infoPopup itself 288 | overflow-x: hidden; 289 | text-align: justify; 290 | font-size: 100%; 291 | line-height: 130%; 292 | } 293 | } 294 | 295 | 296 | #aboutPopup { 297 | position: absolute; 298 | border: 1px solid black; 299 | z-index: 100; 300 | background-color: white; 301 | color: black; 302 | font-size: 75%; 303 | text-align: left; 304 | overflow-y: auto; 305 | 306 | .title { 307 | color: black; 308 | font-weight: bold; 309 | margin: 0.7em; 310 | } 311 | 312 | p { 313 | color: black; 314 | margin: 0.7em; 315 | } 316 | 317 | .close { 318 | color: $col-dark-blue; 319 | float: right; 320 | cursor: pointer; 321 | margin: 4px; 322 | } 323 | } 324 | 325 | .paperButton { 326 | min-width: 18px; 327 | margin: 0 0 0 4px; 328 | cursor: pointer; 329 | } 330 | 331 | /****************************************************************/ 332 | /* for latex math mode */ 333 | 334 | .Lmm { 335 | font-family: Times; 336 | font-style: italic; 337 | font-weight: normal; 338 | } 339 | 340 | .Lsup { 341 | position: relative; 342 | bottom: 0.5em; 343 | font-size: 0.8em; 344 | } 345 | 346 | .Lsub { 347 | position: relative; 348 | top: 0.3em; 349 | font-size: 0.8em; 350 | } 351 | 352 | .Lbar { 353 | text-decoration: overline; 354 | } 355 | -------------------------------------------------------------------------------- /fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/fonts/icomoon.eot -------------------------------------------------------------------------------- /fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/fonts/icomoon.ttf -------------------------------------------------------------------------------- /fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/fonts/icomoon.woff -------------------------------------------------------------------------------- /img/app/arxiv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/arxiv.png -------------------------------------------------------------------------------- /img/app/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/cross.png -------------------------------------------------------------------------------- /img/app/doi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/doi.png -------------------------------------------------------------------------------- /img/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/favicon.ico -------------------------------------------------------------------------------- /img/app/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/favicon.png -------------------------------------------------------------------------------- /img/app/inspire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/inspire.png -------------------------------------------------------------------------------- /img/app/mypscp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/mypscp.png -------------------------------------------------------------------------------- /img/app/paperscapeBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/paperscapeBanner.png -------------------------------------------------------------------------------- /img/app/paperscapeCol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/paperscapeCol.png -------------------------------------------------------------------------------- /img/app/paperscapeTransparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/paperscapeTransparent.png -------------------------------------------------------------------------------- /img/app/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/pdf.png -------------------------------------------------------------------------------- /img/svg/paperscapeFBIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 49 | 58 | 59 | 61 | 62 | 64 | image/svg+xml 65 | 67 | 68 | 69 | 70 | 71 | 76 | 84 | 87 | 89 | 90 | 94 | 106 | 109 | 111 | 112 | 115 | 117 | 118 | 123 | 133 | 143 | 153 | 163 | 173 | 183 | 193 | 203 | 213 | 223 | 233 | 243 | 253 | 263 | 273 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /img/svg/paperscapeIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 49 | 58 | 59 | 61 | 62 | 64 | image/svg+xml 65 | 67 | 68 | 69 | 70 | 71 | 76 | 84 | 87 | 89 | 90 | 94 | 106 | 109 | 111 | 112 | 115 | 117 | 118 | 123 | 133 | 143 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /index-embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Paperscape 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /index-multimap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Paperscape 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Paperscape 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /js/app-build-embed.js: -------------------------------------------------------------------------------- 1 | ({ 2 | baseUrl: ".", 3 | paths: { 4 | "jquery": "lib/jquery-3.1.1.min", 5 | "jquery.mousewheel": "lib/jquery.mousewheel", 6 | "jquery.autocomplete": "lib/jquery-ui-autocomplete.min", 7 | "jquery.autocomplete.html": "lib/jquery-ui-autocomplete.html", 8 | "requireLib": "lib/require" 9 | }, 10 | shim: { 11 | "jquery.mousewheel": { 12 | deps: ["jquery"] 13 | }, 14 | "jquery.autocomplete": { 15 | deps: ["jquery"] 16 | }, 17 | "jquery.autocomplete.html": { 18 | deps: ["jquery", "jquery.autocomplete"] 19 | } 20 | }, 21 | wrapShim: true, 22 | name: "app/config-embed", 23 | include: "requireLib", 24 | out: "app-build/pscp-embed.js" 25 | }) 26 | -------------------------------------------------------------------------------- /js/app-build.js: -------------------------------------------------------------------------------- 1 | ({ 2 | baseUrl: ".", 3 | paths: { 4 | "jquery": "lib/jquery-3.1.1.min", 5 | "jquery.mousewheel": "lib/jquery.mousewheel", 6 | "jquery.autocomplete": "lib/jquery-ui-autocomplete.min", 7 | "jquery.autocomplete.html": "lib/jquery-ui-autocomplete.html", 8 | "requireLib": "lib/require" 9 | }, 10 | shim: { 11 | "jquery.mousewheel": { 12 | deps: ["jquery"] 13 | }, 14 | "jquery.autocomplete": { 15 | deps: ["jquery"] 16 | }, 17 | "jquery.autocomplete.html": { 18 | deps: ["jquery", "jquery.autocomplete"] 19 | } 20 | }, 21 | wrapShim: true, 22 | name: "app/config", 23 | include: "requireLib", 24 | out: "app-build/pscp.js" 25 | }) 26 | -------------------------------------------------------------------------------- /js/coffee/Vec2D.coffee: -------------------------------------------------------------------------------- 1 | # VEC2D module 2 | # 3 | # A simple two-dimensional vector for convenience 4 | 5 | define () -> 6 | 7 | # Simple 2D vector class (extend as needed) 8 | class Vec2D 9 | constructor: (x, y) -> 10 | @x = x 11 | @y = y 12 | 13 | copy: -> new Vec2D(@x, @y) 14 | 15 | add: (v) -> new Vec2D(@x + v.x, @y + v.y) 16 | 17 | sub: (v) -> new Vec2D(@x - v.x, @y - v.y) 18 | 19 | mul: (c) -> new Vec2D(@x * c , @y * c ) 20 | 21 | scale: (cx,cy) -> new Vec2D(@x * cx , @y * cy ) 22 | 23 | round: -> new Vec2D(Math.round(@x) , Math.round(@y)) 24 | 25 | floor: -> new Vec2D(Math.floor(@x) , Math.floor(@y)) 26 | 27 | ceil : -> new Vec2D(Math.ceil(@x) , Math.ceil(@y)) 28 | 29 | len: -> Math.sqrt(@x*@x + @y*@y) 30 | 31 | return Vec2D 32 | -------------------------------------------------------------------------------- /js/coffee/Vec2D_test.coffee: -------------------------------------------------------------------------------- 1 | define ['app/Vec2D'], (Vec2D) -> 2 | run : -> 3 | module "Vec2D" 4 | 5 | v1 = new Vec2D(3,4.5) 6 | v2 = new Vec2D(9,-6) 7 | v3 = new Vec2D(-3.456,+9.9) 8 | v4 = new Vec2D(3,4) 9 | 10 | test "Arithmetic", -> 11 | vRes = v1.add(v2) 12 | equal vRes.x, 12, 13 | "add: v1.x + v2.x = v3.x" 14 | equal vRes.y, -1.5, 15 | "add: v1.y + v2.y = v3.y" 16 | deepEqual vRes,new Vec2D(12,-1.5), 17 | "add: v1 + v2 = v3" 18 | vRes = v1.sub(v2) 19 | equal vRes.x, -6, 20 | "sub: v1.x - v2.x = v3.x" 21 | equal vRes.y, 10.5, 22 | "sub: v1.y - v2.y = v3.y" 23 | deepEqual vRes,new Vec2D(-6,10.5), 24 | "sub: v1 - v2 = v3" 25 | vRes = v1.mul(10) 26 | equal vRes.x, 30, 27 | "mul: v1.x * k = v2.x" 28 | equal vRes.y, 45, 29 | "mul: v1.y * k = v2.y" 30 | deepEqual vRes, new Vec2D(30,45), 31 | "mul: v1 *k = v2" 32 | vScale = v1.scale(4,-2) 33 | equal vScale.x, 12, 34 | "scale: v1.x * kx = v2.x" 35 | equal vScale.y, -9, 36 | "scale: v1.y * ky = v2.y" 37 | deepEqual vScale, new Vec2D(12,-9), 38 | "scale: v1 * (kx,ky) = v2" 39 | vRound = v3.round() 40 | equal vRound.x, -3, 41 | "round x component" 42 | equal vRound.y, 10, 43 | "round y component" 44 | deepEqual vRound, new Vec2D(-3,10), 45 | "round" 46 | vLen = v4.len() 47 | equal vLen,5, 48 | "length" 49 | -------------------------------------------------------------------------------- /js/coffee/ajax.coffee: -------------------------------------------------------------------------------- 1 | # AJAX module 2 | # 3 | # Performs ajax requests. Holds no state except for server address. 4 | 5 | define ['jquery'], ($) -> 6 | 7 | exports = {} 8 | 9 | DEFAULT_TIMEOUT = 10000 10 | DEFAULT_URL_EXTENSION = "/wombat" 11 | DEFAULT_SERVER = "http://paperscape.org" 12 | 13 | server = $("#pscpConfig").data("server") ? DEFAULT_SERVER 14 | timeout = $("#pscpConfig").data("ajaxTimeout") ? DEFAULT_TIMEOUT 15 | 16 | ajaxSuccess = (data, status, xhr, callback) -> 17 | if callback? 18 | callback(data.r) 19 | 20 | ajaxStaticSuccess = (data, status, xhr, callback) -> 21 | if callback? 22 | callback(data) 23 | 24 | ajaxError = (status, xhr, callback) -> 25 | console.log(status,xhr) 26 | if callback? 27 | callback(status) 28 | 29 | exports.getServer = -> 30 | server 31 | 32 | exports.setTimeout = (newTimeout) -> 33 | timeout = newTimeout 34 | 35 | exports.setServer = (newServer) -> 36 | server = newServer 37 | 38 | exports.setDefaults = () -> 39 | server = DEFAULT_SERVER 40 | timeout = DEFAULT_TIMEOUT 41 | 42 | exports.doRequest = (inputData, successCallback, errorCallback, config) -> 43 | #console.log("ajax requ", inputData); 44 | urlExt = DEFAULT_URL_EXTENSION 45 | reqServer = server 46 | reqTimeout = timeout 47 | reqJsonpCallback = null 48 | reqJsonpStatic = false 49 | doPOST = false 50 | if config? 51 | if config.doPOST? 52 | doPOST = config.doPOST 53 | if config.urlExt? 54 | urlExt = config.urlExt 55 | if config.timeout? 56 | reqTimeout = config.timeout 57 | if config.server? 58 | reqServer = config.server 59 | if config.jsonpCallback? 60 | reqJsonpCallback = config.jsonpCallback 61 | if config.jsonpStatic? 62 | reqJsonpStatic = config.jsonpStatic 63 | url = reqServer + urlExt 64 | # jQuery 1.5 supports jsonp ajax fails provided a timeout is specified! 65 | # see http://www.haykranen.nl/2011/02/25/jquery-1-5-and-jsonp-requests/ 66 | ajaxParams = 67 | url: url 68 | data: inputData 69 | success: (data, textStatus, xhr) -> 70 | ajaxSuccess(data, textStatus, xhr, successCallback) 71 | error: (xhr, textStatus, errorThrown) -> 72 | ajaxError(textStatus, xhr, errorCallback) 73 | dataType: "jsonp" 74 | timeout: reqTimeout 75 | if doPOST 76 | ajaxParams.type = "POST" 77 | ajaxParams.crossDomain = true 78 | ajaxParams.dataType = "json" 79 | if reqJsonpCallback? 80 | ajaxParams.jsonpCallback = reqJsonpCallback 81 | if reqJsonpStatic 82 | ajaxParams.success = (data, textStatus, xhr) -> 83 | ajaxStaticSuccess(data, textStatus, xhr, successCallback) 84 | $.ajax(ajaxParams) 85 | 86 | return exports 87 | -------------------------------------------------------------------------------- /js/coffee/ajax_test.coffee: -------------------------------------------------------------------------------- 1 | define ['app/ajax'], (AJAX) -> 2 | run : -> 3 | module "AJAX" 4 | 5 | testServer = "http://paperscape.org" 6 | failServer = "http://paperscape.org:6666" 7 | 8 | test "GET callback: success", -> 9 | stop() 10 | AJAX.setServer(testServer) 11 | inputData = 12 | test: true 13 | testCallback = (data) -> 14 | equal data.test, "success", 15 | "Test property set to success" 16 | equal data.POST, false, 17 | "POST data set to false" 18 | start() 19 | errorCallback = (status) -> 20 | ok false, 21 | "Callback error: " + status 22 | start() 23 | AJAX.doRequest(inputData, testCallback, errorCallback) 24 | 25 | test "POST callback: success", -> 26 | stop() 27 | AJAX.setServer(testServer) 28 | inputData = 29 | test: true 30 | config = 31 | doPOST: true 32 | testCallback = (data) -> 33 | equal data.test, "success", 34 | "Test property set to success" 35 | equal data.POST, true, 36 | "POST data set to true" 37 | start() 38 | errorCallback = (status) -> 39 | ok false, 40 | "Callback error: " + status 41 | start() 42 | AJAX.doRequest(inputData, testCallback, errorCallback, config) 43 | 44 | asyncTest "Callback: error", -> 45 | AJAX.setServer(failServer) 46 | expect(1) 47 | inputData = 48 | test: true 49 | config = 50 | timeout: 1 51 | testCallback = (status) -> 52 | #equal status, "timeout", 53 | equal status, "error", 54 | "Status set to error" 55 | start() 56 | AJAX.doRequest(inputData, null, testCallback, config) 57 | -------------------------------------------------------------------------------- /js/coffee/config-embed.coffee: -------------------------------------------------------------------------------- 1 | requirejs.config 2 | baseUrl: 'js' 3 | paths: 4 | 'jquery': "lib/jquery-3.1.1" 5 | 'jquery.mousewheel': "lib/jquery.mousewheel" 6 | 'jquery.autocomplete': "lib/jquery-ui-autocomplete.min" 7 | 'jquery.autocomplete.html': "lib/jquery-ui-autocomplete.html" 8 | shim: 9 | "jquery.mousewheel": 10 | deps: ["jquery"] 11 | "jquery.autocomplete": 12 | deps: ["jquery"] 13 | "jquery.autocomplete.html": 14 | deps: ["jquery","jquery.autocomplete"] 15 | 16 | requirejs ['app/main-embed'] 17 | -------------------------------------------------------------------------------- /js/coffee/config.coffee: -------------------------------------------------------------------------------- 1 | requirejs.config 2 | baseUrl: 'js' 3 | paths: 4 | 'jquery': "lib/jquery-3.1.1" 5 | 'jquery.mousewheel': "lib/jquery.mousewheel" 6 | 'jquery.autocomplete': "lib/jquery-ui-autocomplete.min" 7 | 'jquery.autocomplete.html': "lib/jquery-ui-autocomplete.html" 8 | shim: 9 | "jquery.mousewheel": 10 | deps: ["jquery"] 11 | "jquery.autocomplete": 12 | deps: ["jquery"] 13 | "jquery.autocomplete.html": 14 | deps: ["jquery","jquery.autocomplete"] 15 | 16 | requirejs ['app/main'] 17 | -------------------------------------------------------------------------------- /js/coffee/config_unit_test.coffee: -------------------------------------------------------------------------------- 1 | requirejs.config 2 | baseUrl: 'js' 3 | paths: 4 | 'jquery': "lib/jquery-3.1.1.min" 5 | 'jquery.mousewheel': "lib/jquery.mousewheel" 6 | 'QUnit': 'lib/qunit-1.11.0' 7 | shim: 8 | "jquery.mousewheel": 9 | deps: ["jquery"] 10 | exports: "jQuery.mousewheel" 11 | 'QUnit': 12 | exports: 'QUnit' 13 | init: -> 14 | QUnit.config.autoload = false 15 | QUnit.config.autostart = false 16 | 17 | 18 | # require the unit tests. 19 | requirejs [ 20 | 'QUnit' 21 | 'tests/Vec2D_test' 22 | 'tests/ajax_test' 23 | 'tests/world_test' 24 | 'tests/mapview_test' 25 | ], (QUNIT, VEC2D_TEST, AJAX_TEST, WORLD_TEST, MAPVIEW_TEST) -> 26 | 27 | # run the tests: 28 | VEC2D_TEST.run() 29 | AJAX_TEST.run() 30 | WORLD_TEST.run() 31 | MAPVIEW_TEST.run() 32 | 33 | # start QUnit: 34 | QUNIT.load() 35 | QUNIT.start() 36 | -------------------------------------------------------------------------------- /js/coffee/detailview.coffee: -------------------------------------------------------------------------------- 1 | # DETAILVIEW module 2 | # 3 | # 4 | 5 | define ['app/mapview','jquery'], (MAPVIEW,$) -> 6 | 7 | exports = {} 8 | 9 | detailViewVisible = false 10 | 11 | ########################################################################### 12 | # Public (exports) 13 | ########################################################################### 14 | 15 | exports.reload = -> 16 | if detailViewVisible 17 | zoomx = MAPVIEW.getXZoom() 18 | $("#detailView .zoomx").html(zoomx.toFixed(2)) 19 | dims = MAPVIEW.getWorldDimensions() 20 | $("#detailView .dims").html("#{dims.x} x #{dims.y}") 21 | $("#detailView .viewx").html((dims.x/zoomx).toFixed(0)) 22 | 23 | exports.show = -> 24 | if not detailViewVisible 25 | detailViewVisible = true 26 | exports.reload() 27 | $("#detailView").show() 28 | 29 | exports.hide = -> 30 | detailViewVisible = false 31 | $("#detailView").hide() 32 | 33 | exports.toggle = -> 34 | if detailViewVisible 35 | exports.hide() 36 | else 37 | exports.show() 38 | 39 | return exports 40 | -------------------------------------------------------------------------------- /js/coffee/fadeview.coffee: -------------------------------------------------------------------------------- 1 | # FADEVIEW module 2 | # 3 | # Popup that displays information about the current WORLD. 4 | # Fades out view when user zooms in. 5 | 6 | define ['app/world','jquery'], (WORLD,$) -> 7 | 8 | exports = {} 9 | 10 | # for key-popup state; could be moved to its own module 11 | welcomePopupVisible = false 12 | 13 | ########################################################################### 14 | # Public (exports) 15 | ########################################################################### 16 | 17 | exports.show = -> 18 | if not welcomePopupVisible 19 | numPapers = "" + WORLD.getNumberArxivPapers() 20 | if numPapers.length > 6 21 | numPapers = numPapers[0...-6] + "," + numPapers[-6..] 22 | if numPapers.length > 3 23 | numPapers = numPapers[0...-3] + "," + numPapers[-3..] 24 | lastDownloadDate = WORLD.getLastDownloadDate() 25 | $("#welcomePopup .total").html(numPapers) 26 | $("#welcomePopup .update").html(lastDownloadDate) 27 | $("#welcomePopup").stop().fadeIn() 28 | welcomePopupVisible = true 29 | 30 | exports.hide = -> 31 | if welcomePopupVisible 32 | $("#welcomePopup").stop().fadeOut() 33 | welcomePopupVisible = false 34 | 35 | return exports 36 | -------------------------------------------------------------------------------- /js/coffee/infoview.coffee: -------------------------------------------------------------------------------- 1 | # INFOVIEW module 2 | # 3 | # Popup that displays information about a selected paper. 4 | 5 | define ['app/selected','app/world','app/search','jquery','jquery.mousewheel'], (SELECTED,WORLD,SEARCH,$) -> 6 | 7 | MAX_AUTHORS = 12 8 | 9 | exports = {} 10 | 11 | metas = [] 12 | 13 | class Meta 14 | constructor: (id) -> 15 | @id = id 16 | @numRefs = 0 17 | @numCites = 0 18 | @title = "Loading..." 19 | @authors = "Loading..." 20 | @journal = "" 21 | @arxivId = "" 22 | @categories = "" 23 | @inspire = "" 24 | @abstract = null 25 | @loaded = false 26 | 27 | load: => 28 | if @loaded 29 | infoPopup(this) 30 | else 31 | callback = (data) => 32 | @id = data.id 33 | @numRefs = data.numRefs 34 | @numCites = data.numCites 35 | @title = data.title 36 | @authors = data.authors 37 | @journal = data.journal 38 | @arxivId = data.arxivId 39 | @categories = data.categories 40 | @inspire = data.inspire 41 | @loaded = true 42 | infoPopup(this) 43 | WORLD.fetchMetaForPaperId(@id, callback) 44 | 45 | loadAbstract: => 46 | if @abstract? 47 | abstractPopup(this) 48 | else 49 | callback = (data) => 50 | @abstract = data.abstract 51 | abstractPopup(this) 52 | WORLD.fetchAbstractForPaperId(@id, callback) 53 | 54 | inspireURL: => 55 | if @inspire != "" 56 | return "http://inspirehep.net/record/" + @inspire 57 | else if @arxivId != "" 58 | if @arxivId.length in [9,10] and @arxivId[4] == "." 59 | return "http://inspirehep.net/search?p=find+eprint+arxiv%3A" + @arxivId 60 | else 61 | return "http://inspirehep.net/search?p=find+eprint+" + @arxivId 62 | else 63 | return null 64 | 65 | findMetaById = (id) => 66 | # TODO binary search 67 | for meta in metas when meta.id == id 68 | return meta 69 | null 70 | 71 | getMeta = (id) => 72 | meta = findMetaById(id) 73 | if not meta? 74 | meta = new Meta(id) 75 | metas.push(meta) 76 | meta 77 | 78 | infoPopup = (meta) -> 79 | if meta? 80 | #console.log meta 81 | $("#infoPopup .title").html(makePrettyTitle(meta.title)) 82 | makePrettyAuthors(meta.authors) 83 | publInfo = makePrettyJournal(meta.journal) 84 | if publInfo[0].length > 0 85 | $("#infoPopup .journal").show() 86 | $("#infoPopup .journalName").html(publInfo[0]) 87 | if publInfo[1].length == 0 88 | $("#infoPopup .icoDoi").hide() 89 | else 90 | $("#infoPopup .icoDoi").show().attr("href","http://dx.doi.org/" + publInfo[1]) 91 | else 92 | $("#infoPopup .journal").hide() 93 | if meta.arxivId? 94 | $("#infoPopup .arxiv").show() 95 | html = meta.arxivId 96 | if meta.categories != "" 97 | html += " [" + meta.categories + "]" 98 | $("#infoPopup .arxivId").html(html) 99 | $("#infoPopup .icoPDF").show().attr("href", "http://arxiv.org/pdf/" + meta.arxivId) 100 | $("#infoPopup .icoArxiv").show().attr("href", "http://arxiv.org/abs/" + meta.arxivId) 101 | else 102 | $("#infoPopup .arxiv").hide() 103 | mypscpURL = "http://my.paperscape.org/?s=#{meta.arxivId}" 104 | $("#infoPopup .icoMypscp").attr("href", mypscpURL) 105 | 106 | inspireURL = meta.inspireURL() 107 | if inspireURL? 108 | $("#infoPopup .inspire").show() 109 | $("#infoPopup .icoInspire").attr("href", inspireURL) 110 | else 111 | $("#infoPopup .inspire").hide() 112 | $("#infoPopup .showAbstract").show() 113 | $("#infoPopup .abstract").hide() 114 | $("#infoPopup").show() 115 | 116 | $("#infoPopup .showReferences").html("references (" + meta.numRefs + ")") 117 | $("#infoPopup .showCitations").html("citations (" + meta.numCites + ")") 118 | 119 | abstractPopup = (meta) -> 120 | if meta?.abstract? 121 | $("#infoPopup .abstract").html(meta.abstract).show() 122 | $("#infoPopup .showAbstract").hide() 123 | 124 | # used to parse latex in titles 125 | islower = (c) -> 'a' <= c and c <= 'z' 126 | 127 | makePrettyAuthors = (authors) -> 128 | # initially hide all author links 129 | for i in [1..13] 130 | $("#infoPopup .auth" + i).css('cursor','default').hide() 131 | 132 | if authors == undefined or authors == null or authors == "(unknown authors)" 133 | $("#infoPopup .auth1").html("(unknown authors)").show() 134 | return 135 | 136 | authList = authors.split(',') 137 | if authList.length != 0 138 | # truncate author list at a maximum of MAX_AUTHORS authors 139 | extraAuth = 0 140 | if authList.length > MAX_AUTHORS 141 | extraAuth = authList.length - (MAX_AUTHORS - 2) 142 | authList = authList.slice(0, (MAX_AUTHORS - 2)) 143 | 144 | for au, i in authList 145 | htmlAuth = "" 146 | # and add author name to the lists 147 | dot = au.lastIndexOf('.') 148 | if dot >= 0 149 | # put a (non-breaking) space between initials and last name 150 | auPre = au.slice(0, dot + 1) 151 | auPost = au.slice(dot + 1) 152 | authList[i] = auPre + " " + auPost 153 | htmlAuth += auPre + " " + auPost 154 | else 155 | #authorListLastname.push(au) 156 | htmlAuth += au 157 | # add comma separator between authors 158 | htmlAuth += ", " if i < (authList.length - 1) 159 | $("#infoPopup .auth" + (i+1)).html(htmlAuth).css('cursor','pointer').show() 160 | 161 | # if extra authors, say so 162 | if extraAuth > 0 163 | htmlAuth = " and " + extraAuth + " more authors" 164 | $("#infoPopup .auth" + (authList.length+1)).html(htmlAuth).show() 165 | 166 | makePrettyTitle = (title) -> 167 | if title == undefined or title == null 168 | return "(unknown title)" 169 | 170 | # split the title into words, so we can draw it on multiple lines in the canvas 171 | title = title 172 | titleWords = title.split(' ') 173 | 174 | # remove $'s from title words (we don't interpret them when rendering the title on the canvas) 175 | for ti, i in titleWords 176 | titleWords[i] = titleWords[i].replace(/\$/g, "") 177 | 178 | # parse some latex math elements in the title for nicer printing 179 | mathMode = false 180 | parsedTitle = "" 181 | i = 0 182 | while i < title.length 183 | if title[i] == '{' or title[i] == '}' 184 | # ignore open/close braces 185 | i += 1 186 | else if title[i] == '$' 187 | if mathMode 188 | parsedTitle += "" 189 | mathMode = false 190 | else 191 | parsedTitle += "" 192 | mathMode = true 193 | i += 1 194 | else if mathMode and i + 1 < title.length 195 | needArg = false 196 | if title[i] == '_' 197 | parsedTitle += "" 198 | needArg = true 199 | else if title[i] == '^' 200 | parsedTitle += "" 201 | needArg = true 202 | else if title[i] == '\\' and (title[i + 1] == '{' or title[i + 1] == '}') 203 | parsedTitle += title[i + 1] 204 | i += 1 205 | else if title[i] == '\\' and islower(title[i + 1]) 206 | i2 = i + 2 207 | while i2 < title.length and islower(title[i2]) 208 | i2 += 1 209 | if i2 >= title.length 210 | # need at least one char for the argument... bit of a hack 211 | i2 = title.length - 1 212 | ctrl = title.slice(i + 1, i2) 213 | needArg = true 214 | if ctrl == "bar" 215 | parsedTitle += "" 216 | i = i2 - 1 217 | else 218 | # unknown control command, just add the slash and keep going 219 | parsedTitle += title[i] 220 | needArg = false 221 | # skip space after control word 222 | if needArg and i + 2 < title.length and title[i + 1] == ' ' 223 | i += 1 224 | else 225 | parsedTitle += title[i] 226 | i += 1 227 | if needArg 228 | if title[i] == '{' 229 | j = i + 1 230 | while j < title.length and title[j] != '}' 231 | j++ 232 | if j < title.length 233 | # found an argument in braces 234 | parsedTitle += title.slice(i + 1, j) 235 | i = j + 1 236 | else 237 | # unmatched open brace; just emit the open brace as the argument 238 | parsedTitle += '{' 239 | i += 1 240 | else 241 | # single character argument 242 | parsedTitle += title[i] 243 | i += 1 244 | parsedTitle += "" 245 | else if title[i] == '\\' 246 | parsedTitle += title.slice(i, i + 2) 247 | i += 2 248 | else 249 | parsedTitle += title[i] 250 | i += 1 251 | if mathMode 252 | parsedTitle += "" 253 | title = parsedTitle 254 | 255 | return title 256 | 257 | makePrettyJournal = (publInfo) -> 258 | if publInfo == undefined or publInfo == null or publInfo.length == 0 259 | return ["", ""] 260 | 261 | journal = "" 262 | doi = "" 263 | 264 | publInfo = publInfo.split('#') 265 | if publInfo.length >= 2 266 | journal = publInfo[0].split(',') 267 | if journal[0].length > 0 and journal[1].length > 1 268 | jname = journal[0][0] 269 | lastUpper = false 270 | for i in [1 ... journal[0].length] 271 | c = journal[0].charCodeAt(i) 272 | if 65 <= c and c <= 90 273 | # upper case 274 | jname += "." 275 | lastUpper = true 276 | else 277 | lastUpper = false 278 | jname += journal[0][i] 279 | if not lastUpper or jname[jname.length - 1] == 'J' 280 | jname += ". " 281 | else 282 | jname += " " 283 | if journal.length == 3 284 | # no jpage 285 | journal = jname + journal[2] + " (" + journal[1] + ")" 286 | else 287 | # has jpage 288 | journal = jname + journal[2] + " (" + journal[1] + ") " + journal[3] 289 | if publInfo[1].length > 0 290 | doi = publInfo[1] 291 | 292 | return [journal, doi] 293 | 294 | ########################################################################### 295 | # Public (exports) 296 | ########################################################################### 297 | 298 | exports.close = -> 299 | SELECTED.clearSelection() 300 | exports.update() 301 | 302 | exports.update = -> 303 | if SELECTED.isSelected() 304 | # hide the about popup as they share screen realestate 305 | $("#aboutPopup").hide() 306 | meta = getMeta(SELECTED.getSelectedId()) 307 | meta.load() 308 | else 309 | $("#infoPopup").hide() 310 | 311 | exports.showAbstract = -> 312 | if SELECTED.isSelected() 313 | meta = getMeta(SELECTED.getSelectedId()) 314 | meta.loadAbstract() 315 | 316 | exports.showReferences = (callback) -> 317 | if SELECTED.isSelected() 318 | arXivStr = findMetaById(SELECTED.getSelectedId())?.arxivId 319 | if arXivStr? 320 | SEARCH.setSearch("?refs #{arXivStr}") 321 | SEARCH.doSearch(callback) 322 | 323 | exports.showCitations = (callback) -> 324 | if SELECTED.isSelected() 325 | arXivStr = findMetaById(SELECTED.getSelectedId())?.arxivId 326 | if arXivStr? 327 | SEARCH.setSearch("?cites #{arXivStr}") 328 | SEARCH.doSearch(callback) 329 | 330 | exports.searchAuthor = (index,callbackPass) -> 331 | if SELECTED.isSelected() 332 | meta = getMeta(SELECTED.getSelectedId()) 333 | authList = meta.authors.split(',') 334 | if authList.length <= MAX_AUTHORS or (index <= (MAX_AUTHORS - 2)) 335 | authStr = authList[index-1] 336 | if authStr? 337 | SEARCH.setSearch("?author #{authStr}") 338 | SEARCH.doSearch(callbackPass) 339 | 340 | 341 | return exports 342 | -------------------------------------------------------------------------------- /js/coffee/input.coffee: -------------------------------------------------------------------------------- 1 | # INPUT module 2 | # 3 | # Contains functions relating to user input e.g. mouse clicks, interface buttons etc. 4 | 5 | define ['app/Vec2D','app/selected','app/world','app/search','app/mapview','app/infoview','jquery','jquery.mousewheel'], (Vec2D,SELECTED,WORLD,SEARCH,MAPVIEW,INFOVIEW,$) -> 6 | 7 | exports = {} 8 | 9 | prevMouseEvent = null 10 | mouseDragged = false 11 | touchPrevNumTouches = 0 12 | touchPrevPos = null 13 | touchPrevDoubleDistance = null 14 | touchMoveCallback = null 15 | 16 | ############################################################# 17 | # public methods involving top form and related buttons 18 | ############################################################# 19 | 20 | # Called when the user submits a search query 21 | exports.formSearchSubmit = (event) -> 22 | event.preventDefault() 23 | callbackPass = -> 24 | MAPVIEW.draw() 25 | SEARCH.doSearch(callbackPass) 26 | 27 | exports.doExampleSearch = (searchTerm) -> 28 | callbackPass = -> 29 | MAPVIEW.draw() 30 | $("#formSearch input:text")[0].value = searchTerm 31 | SEARCH.doSearch(callbackPass) 32 | 33 | ############################################################# 34 | # public methods involving canvas buttons 35 | ############################################################# 36 | 37 | exports.canvasZoomInButton = (event) -> 38 | MAPVIEW.doZoomBy(null, 1 + 0.1 ) 39 | MAPVIEW.draw() 40 | 41 | exports.canvasZoomOutButton = (event) -> 42 | MAPVIEW.doZoomBy(null, 1 - 0.1 ) 43 | MAPVIEW.draw() 44 | 45 | exports.canvasColourSchemeSelect = (event) -> 46 | value = $("#colourSchemeSelect .select").val() 47 | if value == "paper_age" 48 | MAPVIEW.setHeatmap(true) 49 | $("#keyPopup .category").hide() 50 | $("#keyPopup .age").show() 51 | else 52 | MAPVIEW.setHeatmap(false) 53 | $("#keyPopup .age").hide() 54 | $("#keyPopup .category").show() 55 | MAPVIEW.draw() 56 | 57 | ############################################################# 58 | # public methods to involving mouse 59 | ############################################################# 60 | 61 | exports.iDevice = false 62 | 63 | exports.touchStart = (event) -> 64 | event.preventDefault() 65 | if event.touches.length == 1 66 | t0 = event.touches[0] 67 | touchPrevPos = {pageX:t0.pageX, pageY:t0.pageY, shiftKey:false, preventDefault:(->)} 68 | exports.mouseDown(touchPrevPos) 69 | 70 | exports.touchEnd = (event) -> 71 | event.preventDefault() 72 | if event.touches.length != 0 or touchPrevPos == null 73 | # only process a touchEnd request when all touches have finished 74 | return 75 | exports.mouseUp(touchPrevPos) 76 | touchPrevDoubleDistance = null 77 | touchPrevPos = null 78 | 79 | exports.touchMove = (event) -> 80 | event.preventDefault() 81 | 82 | # turn the touch into a centre pos and an optional distance 83 | centre 84 | dist 85 | if event.touches.length == 1 86 | t0 = event.touches[0] 87 | centre = {pageX:t0.pageX, pageY:t0.pageY, shiftKey:false} 88 | dist = null 89 | else if event.touches.length == 2 90 | t0 = event.touches[0] 91 | t1 = event.touches[1] 92 | centre = {pageX:(t0.pageX + t1.pageX) / 2, pageY:(t0.pageY + t1.pageY) / 2, shiftKey:false, preventDefault:(->)} 93 | dist = Math.sqrt(Math.pow(t0.pageX - t1.pageX, 2) + Math.pow(t0.pageY - t1.pageY, 2)) 94 | else 95 | centre = null 96 | dist = null 97 | 98 | if event.touches.length != touchPrevNumTouches 99 | # if the user change the number of fingers in the touch, we reset the motion variables 100 | touchPrevNumTouches = event.touches.length 101 | touchPrevDoubleDistance = null 102 | else 103 | if touchMoveCallback != null 104 | touchMoveCallback(centre) 105 | if dist != null and touchPrevDoubleDistance != null 106 | distDiff = dist - touchPrevDoubleDistance 107 | # TODO this code should be same as mouseWheel!! (and it's not) 108 | MAPVIEW.doZoomBy(centre, 1 + distDiff / 100) 109 | MAPVIEW.draw() 110 | touchPrevPos = centre 111 | touchPrevDoubleDistance = dist 112 | prevMouseEvent = centre 113 | 114 | exports.bindMouseMove = (callback) -> 115 | if exports.iDevice 116 | touchMoveCallback = callback 117 | else 118 | MAPVIEW.jQueryAttach().mousemove(callback) 119 | 120 | exports.cancelMouseMove = () -> 121 | if exports.iDevice 122 | touchMoveCallback = null 123 | else 124 | MAPVIEW.jQueryAttach().off("mousemove") 125 | if MAPVIEW.highVerbosity() 126 | MAPVIEW.draw() 127 | 128 | exports.mouseDown = (event) -> 129 | event.preventDefault() 130 | mouseDragged = false 131 | # Uncomment to get position of clicks: 132 | #console.log(MAPVIEW.getEventWorldPosition(event).round()) 133 | if MAPVIEW.highVerbosity() 134 | MAPVIEW.draw() 135 | # call the pan function when the mouse moves 136 | exports.bindMouseMove(exports.mouseMovePan) 137 | prevMouseEvent = {pageX:event.pageX, pageY:event.pageY} 138 | 139 | exports.mouseUp = (event) -> 140 | if not exports.iDevice 141 | event.preventDefault() 142 | 143 | exports.cancelMouseMove() # disable the mouse move callback 144 | 145 | if not mouseDragged 146 | 147 | pos = MAPVIEW.getEventWorldPosition(event) 148 | 149 | # If user clicks within white search halo, find closest search 150 | # result, which will be used if available 151 | searchData = null 152 | if SEARCH.areSearchResults() and !SEARCH.isParentLinkResult() 153 | searchData = SEARCH.closestResultWithinRadius(pos,MAPVIEW.getSearchHaloRad()) 154 | 155 | if searchData? 156 | SELECTED.setSelection(searchData) 157 | MAPVIEW.draw() 158 | INFOVIEW.update() 159 | else 160 | isPaperCallback = (data) -> 161 | if data.id != 0 162 | SELECTED.setSelection(data) 163 | #else if searchData?.id != 0 164 | # elseSELECTED.setSelection(searchData) 165 | else 166 | SELECTED.clearSelection() 167 | MAPVIEW.draw() 168 | INFOVIEW.update() 169 | 170 | WORLD.fetchPaperIdAtLocation(pos.x,pos.y,isPaperCallback) 171 | 172 | exports.mouseLeave = (event) -> 173 | # disable the mouse move callback 174 | exports.cancelMouseMove() 175 | 176 | exports.mouseMovePan = (event) -> 177 | threshold = 1 178 | if Math.abs(event.pageX - prevMouseEvent.pageX) > threshold or Math.abs(event.pageY - prevMouseEvent.pageY) > threshold 179 | mouseDragged = true 180 | MAPVIEW.lowVerbosity() 181 | 182 | MAPVIEW.doMousePan(event, prevMouseEvent) 183 | MAPVIEW.draw() 184 | 185 | prevMouseEvent = {pageX:event.pageX, pageY:event.pageY} 186 | 187 | # Called when canvas is double clicked 188 | exports.mouseDoubleClick = (event) -> 189 | event.preventDefault() 190 | # zoom in using animation 191 | MAPVIEW.animateZoomIn(event, 1.10, 10, 25) 192 | 193 | # Called when mouse is scrolled over canvas 194 | exports.mouseWheel = (event, delta, deltaX, deltaY) -> 195 | event.preventDefault() 196 | if delta 197 | # hack to fix fast Mac scrolling; TODO fix it properly! 198 | if Math.abs(delta) > 100 199 | delta /= 40 200 | if delta < -3 201 | delta = -3 202 | if delta > 3 203 | delta = 3 204 | 205 | MAPVIEW.doZoomBy(event, 1 + delta / 10) 206 | MAPVIEW.draw() 207 | 208 | ############################################################# 209 | # public methods for key presses 210 | ############################################################# 211 | 212 | exports.keypressArrow = (key) -> 213 | dx = 0 214 | dy = 0 215 | if key == "left" 216 | dx = -1 217 | else if key == "right" 218 | dx = 1 219 | else if key == "up" 220 | dy = -1 221 | else if key == "down" 222 | dy = 1 223 | # pan the graph 224 | MAPVIEW.doKeyPan(new Vec2D(dx, dy)) 225 | MAPVIEW.draw() 226 | 227 | exports.keypressZoom = (key) -> 228 | dz = 0 229 | if key == "=" or key == "+" 230 | dz = 1 231 | else if key == "-" 232 | dz = -1 233 | 234 | # zoom normally 235 | MAPVIEW.doZoomBy(null, 1 + 0.1 * dz) 236 | MAPVIEW.draw() 237 | 238 | #exports.keypressHeatmap = (key) -> 239 | # if MAPVIEW.isHeatmap() 240 | # MAPVIEW.setHeatmap(false) 241 | # else 242 | # MAPVIEW.setHeatmap(true) 243 | 244 | # MAPVIEW.draw() 245 | 246 | return exports 247 | -------------------------------------------------------------------------------- /js/coffee/input_test.coffee: -------------------------------------------------------------------------------- 1 | define ['app/input'], (INPUT) -> 2 | run : -> 3 | module "INPUT" 4 | 5 | #test "dummy test of world", -> 6 | # equal WORLD.testFunction(1,2), 3, "return result should be 3" 7 | -------------------------------------------------------------------------------- /js/coffee/main-embed.coffee: -------------------------------------------------------------------------------- 1 | # MAIN 2 | # 3 | # Module concept: 4 | # Handles non-canvas events, page refresh/resize etc. 5 | 6 | define ['app/ajax','app/mapview','app/infoview','app/input','lib/mousetrap','jquery','jquery.autocomplete','jquery.autocomplete.html'], (AJAX,MAPVIEW,INFOVIEW,INPUT,MOUSETRAP,$) -> 7 | 8 | # Version should match version stored in wombat 9 | # If not, hard reload of page is performed 10 | 11 | # NOTE this is dangerous is javascript not loaded from our server!! 12 | VERSION = "0.1" 13 | 14 | # for keeping track of day change 15 | currentPaperId = null 16 | 17 | resizeDOM = -> 18 | #newWidth = window.innerWidth 19 | #newHeight = window.innerHeight 20 | 21 | # Let xiwiArea be set manually 22 | # set xiwiArea dimensions 23 | xiwiArea = $("#xiwiArea") 24 | newWidth = xiwiArea.width() 25 | newHeight = xiwiArea.height() 26 | #xiwiArea.width(newWidth) 27 | #xiwiArea.height(newHeight) 28 | 29 | # welcome message 30 | #welcomePopup = $("#welcomePopup") 31 | #welcomePopup.css("top", 5 + 'px') 32 | 33 | # colour code 34 | #keyPopup = $("#keyPopup") 35 | #keyPopup.css("left", 30 + 'px') 36 | 37 | # colour scheme select 38 | schemeSelect = $("#colourSchemeSelect") 39 | schemeSelect.css("top", 10 + 'px') 40 | 41 | # zoom buttons 42 | zoomButtons = $("#canvasZoomButtons") 43 | zoomButtons.css("top", newHeight - 32 + 'px') 44 | 45 | # set canvas dimensions 46 | mapviewWidth = Math.round(newWidth) 47 | mapviewHeight = Math.round(newHeight) 48 | 49 | MAPVIEW.resize(mapviewWidth,mapviewHeight,0,0) 50 | MAPVIEW.draw() 51 | 52 | infoPopup = $("#infoPopup") 53 | infoPopupWidth = 400 54 | infoPopup.width(Math.min(infoPopupWidth,mapviewWidth-20)) 55 | infoPopup.css("top", 10 + "px") 56 | #infoPopup.css("left", Math.floor(0.5 * (canvasWidth - infoPopupWidth)) + "px") 57 | infoPopup.css("right", "10px") 58 | $("#infoPopup").css("max-height", newHeight - 20) 59 | 60 | 61 | checkDateAndVersion = -> 62 | request = 63 | gdmv: 1 64 | handleSuccess = (ajaxData) -> 65 | # TODO may no be loaded from our server, so do not do hard reset... 66 | #if ajaxData?.v? and ajaxData?.v != VERSION 67 | # # hard reload of page 68 | # location.reload(true) 69 | if ajaxData?.d0? 70 | if currentPaperId? and currentPaperId != ajaxData.d0 71 | MAPVIEW.reload() 72 | # for now do hard reload here too 73 | # else some tiles will still be cached 74 | # in future could consider stamping tiles with date 75 | #location.reload(true) 76 | else 77 | currentPaperId = ajaxData.d0 78 | AJAX.doRequest(request, handleSuccess) 79 | 80 | main = -> 81 | MAPVIEW.initialise() 82 | MAPVIEW.reload() 83 | 84 | # work out if we are running on a portable device 85 | agent = navigator.userAgent.toLowerCase() 86 | INPUT.iDevice = agent.indexOf("iphone") >= 0 or agent.indexOf("ipad") >= 0 or agent.indexOf("android") >= 0 87 | 88 | # resize and orientation change events 89 | window.addEventListener('resize', resizeDOM, false) 90 | window.addEventListener('orientationchange', resizeDOM, false) # for smart phones etc. 91 | 92 | # bind events for the mouse/touch 93 | if INPUT.iDevice 94 | # an iDevice; bind the events for touch 95 | MAPVIEW.getTopCanvas().addEventListener("touchstart", INPUT.touchStart) 96 | MAPVIEW.getTopCanvas().addEventListener("touchend", INPUT.touchEnd) 97 | # since touchmove event only fires when the user is actually touching, we can afford to always have it bound 98 | MAPVIEW.getTopCanvas().addEventListener("touchmove", INPUT.touchMove) 99 | else 100 | # we have a real mouse; use jQuery to bind these events 101 | MAPVIEW.jQueryAttach().mousedown(INPUT.mouseDown) 102 | MAPVIEW.jQueryAttach().mouseup(INPUT.mouseUp) 103 | MAPVIEW.jQueryAttach().mouseleave(INPUT.mouseLeave) 104 | MAPVIEW.jQueryAttach().dblclick(INPUT.mouseDoubleClick) 105 | MAPVIEW.jQueryAttach().mousewheel(INPUT.mouseWheel) 106 | 107 | # mousetrap library, used to capture keys 108 | # see: http://craig.is/killing/mice 109 | if !INPUT.iDevice 110 | #MOUSETRAP.bind("esc", (e, key) -> INPUT.keypressEscape()) 111 | #MOUSETRAP.bind("enter", (e, key) -> INPUT.keypressEnter()) 112 | MOUSETRAP.bind(["left", "right", "up", "down"], (e, key) -> INPUT.keypressArrow(key)) 113 | MOUSETRAP.bind(["=", "+", "-"], (e, key) -> INPUT.keypressZoom(key)) 114 | 115 | # show everything 116 | $("#xiwiArea").show() 117 | 118 | # refresh world 119 | #$("#welcomePopup .refresh").click -> 120 | # MAPVIEW.reload() 121 | 122 | ## keypopup 123 | #$("#keyPopup .close").click -> 124 | # $("#keyPopup").hide() 125 | #$("#topRightMenu .info").click -> 126 | # $("#keyPopup").show() 127 | #$("#keyPopup").hide() 128 | 129 | # colour scheme select 130 | $("#colourSchemeSelect .select").val('categories').bind('change', (event) -> 131 | INPUT.canvasColourSchemeSelect(event) 132 | ) 133 | 134 | # info popup 135 | $("#infoPopup .close").click -> 136 | INFOVIEW.close() 137 | MAPVIEW.draw() 138 | $("#infoPopup .showAbstract").click -> 139 | INFOVIEW.showAbstract() 140 | INFOVIEW.close() 141 | 142 | # zoom buttons 143 | $("#canvasZoomIn").click -> 144 | INPUT.canvasZoomInButton() 145 | 146 | $("#canvasZoomOut").click -> 147 | INPUT.canvasZoomOutButton() 148 | 149 | # now that the correct items are shown, resize everything to fit correctly 150 | resizeDOM() 151 | 152 | MAPVIEW.draw() 153 | 154 | checkDateAndVersion() 155 | 156 | # Check for version and date when window gets focus 157 | $(window).focus -> 158 | checkDateAndVersion() 159 | 160 | # request animation frame 161 | window.requestAnimationFrame = do -> 162 | workaround = (callback) -> 163 | window.setTimeout(callback, 1000 / 60 ) 164 | window.requestAnimationFrame or window.mozRequestAnimationFrame or window.webkitRequestAnimationFrame or window.msRequestAnimationFrame or workaround 165 | 166 | 167 | # use jQuery to run the app 168 | $(document).ready(-> main()) 169 | 170 | return {} 171 | 172 | -------------------------------------------------------------------------------- /js/coffee/main.coffee: -------------------------------------------------------------------------------- 1 | # MAIN 2 | # 3 | # Handles non-canvas events, page refresh/resize etc. 4 | 5 | define ['app/ajax','app/search','app/mapview','app/infoview','app/fadeview','app/detailview','app/newpapersview','app/input','lib/mousetrap','jquery','jquery.autocomplete','jquery.autocomplete.html'], (AJAX,SEARCH,MAPVIEW,INFOVIEW,FADEVIEW,DETAILVIEW,NEWPAPERSVIEW,INPUT,MOUSETRAP,$) -> 6 | 7 | # Version should match version stored in wombat 8 | # If not, hard reload of page is performed 9 | VERSION = "0.3" 10 | 11 | SEARCH_OFFSET_LEFT = 230 12 | SEARCH_BOX_WIDTH_MIN = 200 13 | SEARCH_BOX_WIDTH_MAX = 400 14 | TOP_RIGHT_MENU_WIDTH = 120 15 | 16 | # for keeping track of day change 17 | currentPaperId = null 18 | 19 | # callback to call when MAPVIEW reloaded 20 | mapviewReloadCallback = -> 21 | DETAILVIEW.reload() 22 | if MAPVIEW.showOverlay() 23 | FADEVIEW.show() 24 | 25 | resizeDOM = -> 26 | newWidth = window.innerWidth 27 | newHeight = window.innerHeight 28 | 29 | # set xiwiArea dimensions 30 | xiwiArea = $("#xiwiArea") 31 | xiwiArea.width(newWidth) 32 | xiwiArea.height(newHeight) 33 | 34 | 35 | # set dimensions search and top right menu 36 | topRightMenuWidth = parseInt($("#topRightMenu").width()) 37 | if topRightMenuWidth < TOP_RIGHT_MENU_WIDTH 38 | # when the page is first loaded, sometimes the font is not loaded before we compute this 39 | # so hack it to be the right size 40 | topRightMenuWidth = TOP_RIGHT_MENU_WIDTH 41 | #searchBoxWidth = Math.round(newWidth-70-parseInt(searchButton.width())-SEARCH_OFFSET_LEFT-topRightMenuWidth) 42 | searchBoxWidth = Math.round(newWidth-70-parseInt($("#searchButton").width()) - parseInt($("#searchNewPapers").width())-SEARCH_OFFSET_LEFT-TOP_RIGHT_MENU_WIDTH) 43 | if searchBoxWidth < SEARCH_BOX_WIDTH_MIN 44 | searchBoxWidth = SEARCH_BOX_WIDTH_MIN 45 | else if searchBoxWidth > SEARCH_BOX_WIDTH_MAX 46 | searchBoxWidth = SEARCH_BOX_WIDTH_MAX 47 | 48 | # set default dimensions for total header bar, based on logo image 49 | totalHeaderHeight = 67 50 | searchHeaderTop = 16 51 | # adjust defaults if necessary e.g. if an oversized input form: 52 | diff = $("#searchHeader").height() + 35 - totalHeaderHeight 53 | if diff > 0 54 | searchHeaderTop -= Math.round(diff/2) 55 | totalHeaderHeight += Math.round(diff/2) 56 | 57 | $("#searchBox").width(searchBoxWidth) 58 | $("#searchHeader").css("top", searchHeaderTop + "px") 59 | $("#searchHeader").css("left", SEARCH_OFFSET_LEFT + 'px') 60 | 61 | $("#topRightMenu").css("top", "13px") 62 | $("#topRightMenu").css("right", "6px") 63 | 64 | newpapersPopup = $("#newpapersPopup") 65 | # TODO smart width/height 66 | newpapersPopupWidth = 600 67 | #newpapersPopupHeight = 400 68 | newpapersPopup.width(newpapersPopupWidth) 69 | #newpapersPopup.height(newpapersPopupHeight) 70 | newpapersPopup.css("top",Math.round((newHeight - newpapersPopup.height())/2) + 'px') 71 | newpapersPopup.css("left",Math.round((newWidth - newpapersPopup.width())/2) + 'px') 72 | $("#newpapersPopup .slider-range").width(500) # TODO 73 | 74 | # colour scheme select 75 | schemeSelect = $("#colourSchemeSelect") 76 | schemeSelect.css("top", totalHeaderHeight + 10 + 'px') 77 | 78 | # colour scheme select 79 | mapSelect = $("#mapSelect") 80 | mapSelect.css("top", totalHeaderHeight + 10 + 'px') 81 | moveRight = 0 82 | if schemeSelect.width()? 83 | moveRight += schemeSelect.width() + 20 84 | mapSelect.css("left", 10 + moveRight + 'px') 85 | 86 | # welcome message 87 | welcomePopup = $("#welcomePopup") 88 | #welcomePopup.css("top", totalHeaderHeight + 20 + parseInt(schemeSelect.height()) + 'px') 89 | welcomePopup.css("top", totalHeaderHeight + 10 + 'px') 90 | if mapSelect.width()? 91 | moveRight += mapSelect.width() + 20 92 | welcomePopup.css("left", 10 + moveRight + 'px') 93 | 94 | # colour code 95 | keyPopup = $("#keyPopup") 96 | keyPopup.css("left", 90 + 'px') 97 | $("#keyPopup .age").hide() 98 | $("#keyPopup .category").show() 99 | 100 | # zoom buttons 101 | zoomButtons = $("#canvasZoomButtons") 102 | zoomButtons.css("top", newHeight - 32 + 'px') 103 | 104 | # detail view information 105 | detailView = $("#detailView") 106 | detailView.css("top", newHeight - 32 - 60 + 'px') 107 | if detailView.height()? 108 | detailView.css("top", newHeight - 32 - detailView.height() - 20 + 'px') 109 | 110 | 111 | # set canvas dimensions 112 | mapviewWidth = Math.round(newWidth) 113 | mapviewHeight = Math.round(newHeight-totalHeaderHeight-1); # -1 is for the 1px border 114 | 115 | MAPVIEW.resize(mapviewWidth,mapviewHeight,0,totalHeaderHeight) 116 | MAPVIEW.draw() 117 | 118 | #PANEL.resizePanel() 119 | #$("#infoPanel").hide() 120 | 121 | infoPopup = $("#infoPopup") 122 | infoPopupWidth = 400 123 | infoPopup.width(infoPopupWidth) 124 | infoPopup.css("top", totalHeaderHeight + 10 + "px") 125 | #infoPopup.css("left", Math.floor(0.5 * (canvasWidth - infoPopupWidth)) + "px") 126 | infoPopup.css("right", "10px") 127 | $("#infoPopup").css("max-height", newHeight - totalHeaderHeight - 20) 128 | 129 | aboutPopup = $("#aboutPopup") 130 | aboutPopupWidth = infoPopupWidth 131 | aboutPopup.width(aboutPopupWidth) 132 | aboutPopup.css("top", totalHeaderHeight + 10 + "px") 133 | aboutPopup.css("right", "10px") 134 | $("#aboutPopup").css("max-height", newHeight - totalHeaderHeight - 20) 135 | 136 | checkDateAndVersion = -> 137 | request = 138 | gdmv: 1 139 | handleSuccess = (ajaxData) -> 140 | if ajaxData?.v? and ajaxData?.v != VERSION 141 | # hard reload of page 142 | location.reload(true) 143 | if ajaxData?.d0? 144 | if currentPaperId? and currentPaperId != ajaxData.d0 145 | MAPVIEW.reload(mapviewReloadCallback) 146 | # for now do hard reload here too 147 | # else some tiles will still be cached 148 | # in future could consider stamping tiles with date 149 | #location.reload(true) 150 | currentPaperId = ajaxData.d0 151 | AJAX.doRequest(request, handleSuccess) 152 | 153 | checkForUrlVariables = -> 154 | vars = {} 155 | hash = null 156 | hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&') 157 | for i in [0...hashes.length] 158 | hash = hashes[i].split('=') 159 | vars[hash[0]] = decodeURIComponent((hash[1]+'').replace(/\+/g, '%20')) 160 | if vars.s? and vars.s.length < 64 161 | SEARCH.setSearch(vars.s) 162 | callbackPass = -> 163 | MAPVIEW.draw() 164 | SEARCH.doSearch(callbackPass) 165 | 166 | main = -> 167 | # bind first, so that init can safely set it after callback 168 | MAPVIEW.bindZoomFunction -> 169 | # TODO check if detailView showing 170 | DETAILVIEW.reload() 171 | if MAPVIEW.showOverlay() 172 | FADEVIEW.show() 173 | else 174 | FADEVIEW.hide() 175 | 176 | if $("#detailView").css('display') is "none" 177 | DETAILVIEW.hide() 178 | else 179 | DETAILVIEW.show() 180 | 181 | MAPVIEW.initialise() 182 | MAPVIEW.reload(mapviewReloadCallback) 183 | 184 | # work out if we are running on a portable device 185 | agent = navigator.userAgent.toLowerCase() 186 | INPUT.iDevice = agent.indexOf("iphone") >= 0 or agent.indexOf("ipad") >= 0 or agent.indexOf("android") >= 0 187 | 188 | # resize and orientation change events 189 | window.addEventListener('resize', resizeDOM, false) 190 | window.addEventListener('orientationchange', resizeDOM, false) # for smart phones etc. 191 | 192 | # bind events for the mouse/touch 193 | if INPUT.iDevice 194 | # an iDevice; bind the events for touch 195 | MAPVIEW.getTopCanvas().addEventListener("touchstart", INPUT.touchStart) 196 | MAPVIEW.getTopCanvas().addEventListener("touchend", INPUT.touchEnd) 197 | # since touchmove event only fires when the user is actually touching, we can afford to always have it bound 198 | MAPVIEW.getTopCanvas().addEventListener("touchmove", INPUT.touchMove) 199 | else 200 | # we have a real mouse; use jQuery to bind these events 201 | MAPVIEW.jQueryAttach().mousedown(INPUT.mouseDown) 202 | MAPVIEW.jQueryAttach().mouseup(INPUT.mouseUp) 203 | MAPVIEW.jQueryAttach().mouseleave(INPUT.mouseLeave) 204 | MAPVIEW.jQueryAttach().dblclick(INPUT.mouseDoubleClick) 205 | MAPVIEW.jQueryAttach().mousewheel(INPUT.mouseWheel) 206 | 207 | # mousetrap library, used to capture keys 208 | # see: http://craig.is/killing/mice 209 | if !INPUT.iDevice 210 | #MOUSETRAP.bind("esc", (e, key) -> INPUT.keypressEscape()) 211 | #MOUSETRAP.bind("enter", (e, key) -> INPUT.keypressEnter()) 212 | MOUSETRAP.bind(["left", "right", "up", "down"], (e, key) -> INPUT.keypressArrow(key)) 213 | MOUSETRAP.bind(["=", "+", "-"], (e, key) -> INPUT.keypressZoom(key)) 214 | MOUSETRAP.bind(["d"], (e, key) -> DETAILVIEW.toggle()) 215 | 216 | $("#formSearch").submit(INPUT.formSearchSubmit) 217 | $("#formSearch input:button").submit(INPUT.formSearchSubmit) 218 | $("#searchMessage .clear").click -> 219 | SEARCH.clearSearchResults() 220 | MAPVIEW.draw() 221 | 222 | # search box autocomplete 223 | searchTerms = [ 224 | {label: "?smart (examples: hep-th/9908142, e.witten, seiberg string theory)", value: "?smart "} 225 | {label: "?author (example: ?a witten; ?a z.bern)", value: "?author "} 226 | {label: "?keyword (example: ?k majorana neutrino)", value: "?keyword "} 227 | {label: "?title (example: ?ti 125 Higgs)", value: "?title "} 228 | {label: "?new-papers (example: ?n hep-ph, crosslists)", value: "?new-papers "} 229 | ] 230 | 231 | $("#searchBox").autocomplete( 232 | source: ( request, response ) -> 233 | matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( request.term ), "i" ) 234 | response( $.grep( searchTerms, ( item ) -> 235 | return matcher.test( item.value ) 236 | ) 237 | ) 238 | html: true 239 | delay : 0 240 | autoFocus : true 241 | minLength: 0 242 | ).focus -> 243 | $("#searchBox").autocomplete("search",$("#searchBox").value) 244 | 245 | $("#searchMessage").hide() 246 | 247 | # New papers view 248 | $("#searchNewPapers").click -> 249 | NEWPAPERSVIEW.popup() 250 | NEWPAPERSVIEW.close() 251 | $("#newpapersPopup .close").click -> 252 | NEWPAPERSVIEW.close() 253 | $("#newpapersPopup .form").submit(NEWPAPERSVIEW.doSearch) 254 | 255 | $("#newpapersPopup .slider-range").slider( 256 | range: true 257 | min: 0 258 | max: 32 259 | step: 1 260 | values: [ 30, 32 ] 261 | slide: (event, ui) -> 262 | if ui.values[0] == ui.values[1] or ui.values[0] == ui.values[1]-1 263 | event.preventDefault() 264 | else 265 | $("#newpapersPopup .slider-range").slider("values",0,ui.values[0]) 266 | $("#newpapersPopup .slider-range").slider("values",1,ui.values[1]) 267 | NEWPAPERSVIEW.update() 268 | change: (event, ui) -> 269 | NEWPAPERSVIEW.update() 270 | ) 271 | NEWPAPERSVIEW.update() 272 | 273 | # show everything 274 | $("#xiwiArea").show() 275 | 276 | # refresh world - should be done automatically now 277 | #$("#welcomePopup .refresh").click -> 278 | # MAPVIEW.reload() 279 | 280 | # keypopup 281 | $("#keyPopup .close").click -> 282 | $("#keyPopup").hide() 283 | $("#colourSchemeSelect .legend").click -> 284 | $("#keyPopup").toggle() 285 | $("#keyPopup").hide() 286 | 287 | # colour scheme select 288 | $("#colourSchemeSelect .select").val('categories').bind('change', (event) -> 289 | INPUT.canvasColourSchemeSelect(event) 290 | ) 291 | 292 | # map select 293 | $("#mapSelect .select").bind('change', (event) -> 294 | FADEVIEW.hide() 295 | INFOVIEW.close() 296 | MAPVIEW.reload(mapviewReloadCallback) 297 | ) 298 | 299 | # info popup 300 | $("#infoPopup .close").click -> 301 | INFOVIEW.close() 302 | MAPVIEW.draw() 303 | $("#infoPopup .showAbstract").click -> 304 | INFOVIEW.showAbstract() 305 | $("#infoPopup .showReferences").click -> 306 | INFOVIEW.showReferences(MAPVIEW.draw) 307 | $("#infoPopup .showCitations").click -> 308 | INFOVIEW.showCitations(MAPVIEW.draw) 309 | for index in [1..13] 310 | $("#infoPopup .auth" + index).click -> 311 | callbackPass = -> 312 | MAPVIEW.draw() 313 | INFOVIEW.searchAuthor($(this).data("id"),callbackPass) 314 | INFOVIEW.close() 315 | 316 | # about popup 317 | $("#topRightMenu .info").click -> 318 | INFOVIEW.close() 319 | $("#aboutPopup").toggle() 320 | MAPVIEW.draw() 321 | $("#aboutPopup .close").click -> 322 | $("#aboutPopup").hide() 323 | $("#aboutPopup").hide() 324 | 325 | # zoom buttons 326 | $("#canvasZoomIn").click -> 327 | INPUT.canvasZoomInButton() 328 | 329 | $("#canvasZoomOut").click -> 330 | INPUT.canvasZoomOutButton() 331 | 332 | # now that the correct items are shown, resize everything to fit correctly 333 | resizeDOM() 334 | 335 | MAPVIEW.draw() 336 | 337 | checkDateAndVersion() 338 | 339 | checkForUrlVariables() 340 | 341 | # Check for version and date when window gets focus 342 | $(window).focus -> 343 | checkDateAndVersion() 344 | 345 | # request animation frame 346 | window.requestAnimationFrame = do -> 347 | workaround = (callback) -> 348 | window.setTimeout(callback, 1000 / 60 ) 349 | window.requestAnimationFrame or window.mozRequestAnimationFrame or window.webkitRequestAnimationFrame or window.msRequestAnimationFrame or workaround 350 | 351 | 352 | # use jQuery to run the app 353 | $(document).ready(-> main()) 354 | 355 | return {} 356 | 357 | -------------------------------------------------------------------------------- /js/coffee/main_test.coffee: -------------------------------------------------------------------------------- 1 | define ['app/main'], (MAIN) -> 2 | run : -> 3 | module "MAIN" 4 | 5 | #test "dummy test of world", -> 6 | # equal WORLD.testFunction(1,2), 3, "return result should be 3" 7 | -------------------------------------------------------------------------------- /js/coffee/mapview_test.coffee: -------------------------------------------------------------------------------- 1 | define ['app/mapview'], (MAPVIEW) -> 2 | run : -> 3 | module "MAPVIEW" 4 | 5 | -------------------------------------------------------------------------------- /js/coffee/newpapersview.coffee: -------------------------------------------------------------------------------- 1 | # NEWPAPERSVIEW module 2 | # 3 | # Popup that lets users search for new papers. 4 | 5 | define ['app/world','app/mapview','app/search','jquery','jquery.mousewheel'], (WORLD,MAPVIEW,SEARCH,$) -> 6 | 7 | exports = {} 8 | 9 | performSearch = -> 10 | checkboxes = $("#newpapersPopup .catCheckbox") 11 | 12 | numCategories = 0 13 | categoriesString = "" 14 | for checkbox in checkboxes 15 | if checkbox.children[0].checked 16 | if categoriesString.length > 0 17 | categoriesString += "," 18 | categoriesString += checkbox.children[0].value 19 | if checkbox.children[0].value != "crosslists" 20 | numCategories += 1 21 | 22 | #if checkbox.textContent == "Include crosslists" 23 | # categoriesString += "crosslists" 24 | #else 25 | # categoriesString += checkbox.textContent 26 | 27 | if numCategories == 0 28 | # don't search 29 | return 30 | 31 | lowerBound = 31 - $("#newpapersPopup .slider-range").slider("values",0) 32 | upperBound = 32 - $("#newpapersPopup .slider-range").slider("values",1) 33 | 34 | callbackPass = -> 35 | MAPVIEW.draw() 36 | SEARCH.setSearch("?n #{lowerBound}-#{upperBound}:#{categoriesString}") 37 | SEARCH.doSearch(callbackPass) 38 | exports.close() 39 | 40 | ########################################################################### 41 | # Public (exports) 42 | ########################################################################### 43 | 44 | exports.doSearch = (event) -> 45 | event.preventDefault() 46 | performSearch() 47 | 48 | exports.close = -> 49 | $("#newpapersPopup").hide() 50 | 51 | exports.popup = -> 52 | $("#newpapersPopup").show() 53 | exports.update() 54 | 55 | exports.update = -> 56 | lowerBound = 31 - $("#newpapersPopup .slider-range").slider("values",0) 57 | upperBound = 32 - $("#newpapersPopup .slider-range").slider("values",1) 58 | val = "New papers" 59 | latestDate = WORLD.getLastDownloadDate() 60 | if upperBound == 0 61 | if lowerBound == 1 62 | val += " for last submission day (#{latestDate})" 63 | else 64 | val += " for last #{lowerBound} submission days" 65 | else 66 | val += " from #{upperBound} to #{lowerBound} submission days ago" 67 | $("#newpapersPopup .searchButton").val(val) 68 | 69 | return exports 70 | -------------------------------------------------------------------------------- /js/coffee/search.coffee: -------------------------------------------------------------------------------- 1 | # SEARCH module 2 | # 3 | # Handles user search requests. 4 | 5 | define ['app/world','jquery'], (WORLD,$) -> 6 | exports = {} 7 | 8 | SEARCH_TYPE_UNKNOWN = 0 9 | SEARCH_TYPE_AUTO = 1 10 | SEARCH_TYPE_ARXIV = 2 11 | SEARCH_TYPE_NEW = 3 12 | SEARCH_TYPE_AUTHOR = 4 13 | SEARCH_TYPE_TITLE = 5 14 | SEARCH_TYPE_KEYWORD = 6 15 | SEARCH_TYPE_REFS = 7 16 | SEARCH_TYPE_CITES = 8 17 | 18 | # Returns the type of search, and the rest of the input 19 | regexArxivOld = /^\s*[A-Za-z\-]{2,8}\/\d{7}\s*$/ 20 | regexArxivNew = /^\s*\d{4}\.\d{4,5}\s*$/ # 2015 update 21 | regexCategory = /^\s*[A-Za-z\-]{2,8}\s*$/ 22 | regexAuthor = /^\s*[A-Za-z][a-z]?\.-?[A-Za-z]/ 23 | 24 | searchResults = [] 25 | 26 | # flag for link parent node and its unique SearchResult 27 | searchParentResult = null 28 | 29 | zoomOnSearch = false 30 | 31 | class SearchResult 32 | constructor: (id,x,y,r) -> 33 | @id = id 34 | @x = x 35 | @y = y 36 | @r = r 37 | @freq = 1 # used by link search results 38 | 39 | detectSearchType = (input) -> 40 | # TODO this needs some work 41 | # A search term should end with ' ', ':', '=' 42 | # and anything up to that should qualify 43 | # e.g "?a ", "?auth ", "?author:" etc. 44 | if input.length >= 1 and input[0] == '?' 45 | # find end of command 46 | endIndex = input.indexOf(" ",1) 47 | searchTerm = "" 48 | if endIndex == -1 49 | endIndex = input.length 50 | else 51 | searchTerm = input.slice(endIndex) 52 | command = input.slice(1,endIndex) 53 | 54 | if command == "" 55 | return [SEARCH_TYPE_UNKNOWN, ""] 56 | else if command == "smart".slice(0,endIndex-1) 57 | # proceed below 58 | input = searchTerm 59 | else if command == "author".slice(0,endIndex-1) 60 | return [SEARCH_TYPE_AUTHOR, searchTerm] 61 | else if command.length >= 2 and command == "title".slice(0,endIndex-1) 62 | return [SEARCH_TYPE_TITLE, searchTerm] 63 | else if command == "keyword".slice(0,endIndex-1) 64 | return [SEARCH_TYPE_KEYWORD, searchTerm] 65 | else if command == "new-papers".slice(0,endIndex-1) 66 | return [SEARCH_TYPE_NEW, searchTerm] 67 | else if command == "refs".slice(0,endIndex-1) 68 | return [SEARCH_TYPE_REFS, searchTerm] 69 | else if command == "cites".slice(0,endIndex-1) 70 | return [SEARCH_TYPE_CITES, searchTerm] 71 | else 72 | return [SEARCH_TYPE_UNKNOWN, searchTerm] 73 | 74 | if regexArxivNew.test(input) or regexArxivOld.test(input) 75 | return [SEARCH_TYPE_ARXIV, input] 76 | else if regexAuthor.test(input) 77 | return [SEARCH_TYPE_AUTHOR, input] 78 | else 79 | return [SEARCH_TYPE_AUTO, input] 80 | 81 | printInfoMessageCheckZoom = -> 82 | if searchResults.length > 0 83 | # Search success, so zoom in on it when we have the chance 84 | $("#searchMessage .results").html("Showing #{searchResults.length} results.") 85 | $("#searchMessage .clear").show() 86 | $("#searchMessage").show() 87 | zoomOnSearch = true 88 | else 89 | $("#searchMessage .results").html("Found no results.") 90 | $("#searchMessage .clear").hide() 91 | $("#searchMessage").show() 92 | zoomOnSearch = false 93 | 94 | 95 | handleSearchWrapper = (callbackPass) -> 96 | return (ajaxData) -> 97 | if ajaxData? 98 | ids = [] 99 | for paper in ajaxData 100 | ids.push(paper.id) 101 | firstCallbackPass = (data) -> 102 | searchParentResult = null 103 | searchResults = [] 104 | if data? 105 | for paper in data 106 | searchResults.push(new SearchResult(paper.id,paper.x,paper.y,paper.r)) 107 | printInfoMessageCheckZoom() 108 | if callbackPass? 109 | callbackPass() 110 | WORLD.fetchLocationsForPaperIds(ids,firstCallbackPass) 111 | 112 | handleLinkSearchWrapper = (callbackPass) -> 113 | return (ajaxData) -> 114 | if ajaxData? 115 | searchParentResult = new SearchResult(ajaxData.id,ajaxData.x,ajaxData.y,ajaxData.r) 116 | searchResults = [] 117 | # only include links with valid positions 118 | for link in ajaxData.links when link.x? and link.y? and link.r? 119 | sRes = new SearchResult(link.id,link.x,link.y,link.r) 120 | sRes.freq = link.freq 121 | searchResults.push(sRes) 122 | printInfoMessageCheckZoom() 123 | if callbackPass? 124 | callbackPass() 125 | 126 | handleSearchError = -> 127 | $("#searchMessage .results").html("Search failed.") 128 | $("#searchMessage .clear").show() 129 | $("#searchMessage").show() 130 | 131 | ########################################################################### 132 | # Public (exports) 133 | ########################################################################### 134 | 135 | exports.getSearchResults = -> 136 | return searchResults 137 | 138 | exports.getParentLinkResult = -> 139 | return searchParentResult 140 | 141 | exports.clearSearchResults = -> 142 | searchResults = [] 143 | searchParentResult = null 144 | $("#searchMessage").hide() 145 | $("#formSearch input:text")[0].value = "" 146 | 147 | exports.areSearchResults = -> 148 | return searchResults.length != 0 149 | 150 | exports.isParentLinkResult = -> 151 | return searchParentResult != null 152 | 153 | exports.zoomOnceOnSearch = -> 154 | if zoomOnSearch 155 | zoomOnSearch = false 156 | return true 157 | else 158 | return false 159 | 160 | exports.closestResultWithinRadius = (pos,radius) -> 161 | searchData = null 162 | shortestDist2 = -1 163 | radius2 = radius*radius 164 | for res in searchResults 165 | dist2 = Math.pow(pos.x - res.x,2) + Math.pow(pos.y - res.y,2) 166 | if dist2 < radius2 and (shortestDist2 < 0 or dist2 < shortestDist2) 167 | shortestDist2 = dist2 168 | searchData = 169 | id: res.id 170 | x: res.x 171 | y: res.y 172 | r: res.r 173 | return searchData 174 | 175 | exports.setSearch = (searchString) -> 176 | $("#formSearch input:text")[0].value = searchString 177 | 178 | exports.doSearch = (callbackPass) -> 179 | # TODO should lock search while searching! 180 | # detect search type 181 | keyword = $("#formSearch input:text")[0].value 182 | searchTypeAndValue = detectSearchType(keyword) 183 | 184 | # remove white space from head and tail of search value 185 | searchValue = searchTypeAndValue[1].replace(/^\s*|\s*$/g,'') 186 | 187 | $("#searchMessage .results").html("Searching...") 188 | $("#searchMessage .clear").hide() 189 | $("#searchMessage").show() 190 | 191 | success = handleSearchWrapper(callbackPass) 192 | successLink = handleLinkSearchWrapper(callbackPass) 193 | error = handleSearchError 194 | switch searchTypeAndValue[0] 195 | when SEARCH_TYPE_AUTO 196 | WORLD.fetchSearchResults({sge: searchValue}, success, error) 197 | when SEARCH_TYPE_KEYWORD 198 | WORLD.fetchSearchResults({skw: searchValue}, success, error) 199 | when SEARCH_TYPE_ARXIV 200 | WORLD.fetchSearchResults({saxm: searchValue}, success, error) 201 | when SEARCH_TYPE_AUTHOR 202 | WORLD.fetchSearchResults({sau: searchValue}, success, error) 203 | when SEARCH_TYPE_TITLE 204 | WORLD.fetchSearchResults({sti: searchValue}, success, error) 205 | when SEARCH_TYPE_NEW 206 | #if (newPaperBoundaryId = WORLD.getNewPaperBoundaryId()) != 0 207 | crosslists = "false" 208 | daysAgoFrom = 1 209 | daysAgoTo = 0 210 | 211 | parts = searchValue.replace(/^\s*|\s*$/g,'').split(':') 212 | if parts.length >= 2 213 | days = parts[0].split('-') 214 | cats = parts[1].split(',') 215 | else 216 | cats = parts[0].split(',') 217 | 218 | if days? 219 | if days[0]? and days[0] > 0 220 | daysAgoFrom = days[0] 221 | if days[1]? and days[1] > 0 222 | daysAgoTo = days[1] 223 | 224 | newCats = [] 225 | for cat in cats 226 | # remove white space 227 | cat = cat.replace(/\s/g,'') 228 | if cat == "crosslists" 229 | crosslists = "true" 230 | else 231 | newCats.push(cat) 232 | request = 233 | sca: newCats.join(",") 234 | #f:newPaperBoundaryId 235 | #t:0 236 | fd : daysAgoFrom 237 | td : daysAgoTo 238 | x:crosslists 239 | WORLD.fetchSearchResults(request, success, error) 240 | when SEARCH_TYPE_REFS 241 | WORLD.fetchReferencesForArXivId(searchValue,successLink,error) 242 | when SEARCH_TYPE_CITES 243 | WORLD.fetchCitationsForArXivId(searchValue,successLink,error) 244 | else 245 | $("#searchMessage .results").html("Invalid search type") 246 | $("#searchMessage .clear").show() 247 | $("#searchMessage").show() 248 | searchResults = [] 249 | callbackPass() 250 | 251 | return exports 252 | -------------------------------------------------------------------------------- /js/coffee/selected.coffee: -------------------------------------------------------------------------------- 1 | # SELECTED module 2 | # 3 | # Stores state of selected paper. 4 | 5 | define ['app/Vec2D','app/world'], (Vec2D,WORLD) -> 6 | 7 | exports = {} 8 | 9 | selectedId = null 10 | selectedPos = null 11 | selectedRad = null 12 | 13 | #referenceLinks = [] 14 | #referencesLoading = false # a lock to prevent multiple requests 15 | #referencesReady = false 16 | 17 | #citationLinks = [] 18 | #citationsLoading = false # a lock to prevent multiple requests 19 | #citationsReady = false 20 | 21 | #showReferences = false 22 | #showCitations = false 23 | 24 | class Link 25 | constructor: (id,freq,order,numCites) -> 26 | @id = id 27 | @freq = freq 28 | @order = order 29 | @nc = numCites 30 | @pos = null 31 | @rad = null 32 | 33 | ########################################################################### 34 | # Public (exports) 35 | ########################################################################### 36 | 37 | exports.getSelectedId = -> 38 | return selectedId 39 | 40 | exports.getSelectedPos = -> 41 | return selectedPos 42 | 43 | exports.getSelectedRad = -> 44 | return selectedRad 45 | 46 | exports.setSelection = (data) -> 47 | if data.id? and data.id > 0 and data.id != selectedId 48 | exports.clearSelection() 49 | selectedId = data.id 50 | if data.x? and data.y? and data.r? 51 | selectedPos = new Vec2D(data.x,data.y) 52 | selectedRad = data.r 53 | 54 | exports.clearSelection = () -> 55 | selectedId = null 56 | selectedPos = null 57 | selectedRad = null 58 | #showReferences = false 59 | #showCitations = false 60 | #referencesLoading = false 61 | #referencesReady = false 62 | #citationsLoading = false 63 | #citationsReady = false 64 | 65 | exports.isSelected = -> 66 | return selectedId? 67 | 68 | ### 69 | exports.loadReferences = (callback) -> 70 | if selectedId? 71 | showReferences = true 72 | showCitations = false 73 | if !referencesLoading and !referencesReady 74 | referencesLoading = true 75 | referencesReady = false 76 | referenceLinks = [] 77 | 78 | callbackFail = () -> 79 | referencesLoading = false 80 | referenceLinks = [] 81 | 82 | # this callback is called after the next one 83 | # TODO make single call in Wombat for all this info! 84 | callbackFetchLocations = (data) -> 85 | for location in data 86 | for link in referenceLinks when link.id == location.id 87 | link.pos = new Vec2D(location.x,location.y) 88 | link.rad = location.r 89 | 90 | referencesLoading = false 91 | referencesReady = true 92 | callback() 93 | 94 | callbackFetchRefs = (data) -> 95 | refIds = [] 96 | for ref in data.references 97 | refIds.push(ref.id) 98 | referenceLinks.push(new Link(ref.id,ref.freq,ref.order,ref.numCites)) 99 | WORLD.fetchLocationsForPaperIds(refIds,callbackFetchLocations,callbackFail) 100 | WORLD.fetchReferencesForPaperId(selectedId,callbackFetchRefs,callbackFail) 101 | else 102 | callback() 103 | ### 104 | 105 | 106 | ### 107 | exports.loadCitations = (callback) -> 108 | if selectedId? 109 | showReferences = false 110 | showCitations = true 111 | if !citationsLoading and !citationsReady 112 | citationsLoading = true 113 | citationsReady = false 114 | citationLinks = [] 115 | 116 | callbackFail = () -> 117 | citationsLoading = false 118 | citationLinks = [] 119 | 120 | callbackFetchLocations = (data) -> 121 | for location in data 122 | for link in citationLinks when link.id == location.id 123 | link.pos = new Vec2D(location.x,location.y) 124 | link.rad = location.r 125 | 126 | citationsLoading = false 127 | citationsReady = true 128 | callback() 129 | 130 | callbackFetchRefs = (data) -> 131 | citeIds = [] 132 | for cite in data.citations 133 | citeIds.push(cite.id) 134 | citationLinks.push(new Link(cite.id,cite.freq,0,cite.numCites)) 135 | WORLD.fetchLocationsForPaperIds(citeIds,callbackFetchLocations,callbackFail) 136 | WORLD.fetchCitationsForPaperId(selectedId,callbackFetchRefs,callbackFail) 137 | else 138 | callback() 139 | ### 140 | 141 | ### 142 | exports.drawLinks = -> 143 | return showReferences or showCitations 144 | ### 145 | 146 | ### 147 | exports.getLinks = -> 148 | if showReferences and referencesReady 149 | return referenceLinks 150 | else if showCitations and citationsReady 151 | return citationLinks 152 | else 153 | return [] 154 | ### 155 | 156 | return exports 157 | -------------------------------------------------------------------------------- /js/coffee/world.coffee: -------------------------------------------------------------------------------- 1 | # WORLD module 2 | # 3 | # Intended as an API. 4 | # Talks to server and gives back results. 5 | # Should hold no state beyond the details of todays "world". 6 | # Should shield world from server json return formats. 7 | 8 | define ['app/Vec2D','app/ajax'], (Vec2D, AJAX) -> 9 | exports = {} 10 | 11 | # Multiple tile servers to get around max http requests 12 | # per url 13 | DEFAULT_TILE_SERVERS = [ 14 | "http://tile1.paperscape.org" 15 | "http://tile2.paperscape.org" 16 | "http://tile3.paperscape.org" 17 | "http://tile4.paperscape.org" 18 | ] 19 | 20 | tile_servers = $("#pscpConfig").data("tiles") ? DEFAULT_TILE_SERVERS 21 | 22 | tileServerIndex = 0 23 | 24 | worldReady = false 25 | 26 | subWorldPath = "/world" # if multple worlds on tile server 27 | 28 | dbSuffix = "" 29 | latestPaperId = 0 30 | newPaperBoundaryId = 0 31 | numberArxivPapers = 0 32 | lastDownloadDate = "" 33 | 34 | class TileDepth 35 | constructor: (depth, numx, numy, worldWidth, worldHeight) -> 36 | @depth = depth 37 | @numx = numx ? 0 38 | @numy = numy ? 0 39 | @width = worldWidth ? 0 40 | @height = worldHeight ? 0 41 | 42 | # Atm carbon copy of above, but may change in future 43 | class LabelDepth 44 | constructor: (depth, scale, numx, numy, worldWidth, worldHeight) -> 45 | @depth = depth 46 | @scale = scale 47 | @numx = numx ? 0 48 | @numy = numy ? 0 49 | @width = worldWidth ? 0 50 | @height = worldHeight ? 0 51 | 52 | tile_depths = null 53 | 54 | label_depths = null 55 | 56 | findTileDepth = (depth) -> 57 | for t in tile_depths when t.depth is depth 58 | return t 59 | return null 60 | 61 | findLabelDepth = (depth) -> 62 | for l in label_depths when l.depth is depth 63 | return l 64 | return null 65 | 66 | ########################################################################### 67 | # Public (exports) 68 | ########################################################################### 69 | 70 | exports.loadWorldData = (callbackPass,callbackFail) -> 71 | worldReady = false 72 | config = 73 | server: exports.getTileServer() 74 | urlExt: "/world_index.json" 75 | jsonpStatic: true 76 | jsonpCallback: "world_index" 77 | request = {} 78 | handleSuccess = (ajaxData) -> 79 | #if ajaxData? and ajaxData?.tile? 80 | if ajaxData? and ajaxData?.tilings? 81 | dbSuffix = ajaxData?.dbsuffix ? "" 82 | latestPaperId = ajaxData?.latestid ? 0 83 | 84 | newPaperBoundaryId = ajaxData?.newid ? latestPaperId 85 | 86 | numberArxivPapers = ajaxData?.numpapers ? 0 87 | lastDownloadDate = ajaxData?.lastdl ? "" 88 | 89 | # get tiles info 90 | tile_depths = [] 91 | tile_depths = (new TileDepth(tiling?.z,tiling?.nx,tiling?.ny,tiling?.tw,tiling?.th) for tiling in ajaxData.tilings when tiling?.z?) 92 | 93 | # get label zones info 94 | label_depths = [] 95 | label_depths = (new LabelDepth(labeling?.z,labeling?.s,labeling?.nx,labeling?.ny,labeling?.w,labeling?.h) for labeling in ajaxData.zones when labeling?.z?) 96 | 97 | worldReady = true 98 | 99 | returnData = 100 | x_min: ajaxData?.xmin ? 0 101 | y_min: ajaxData?.ymin ? 0 102 | x_max: ajaxData?.xmax ? 0 103 | y_max: ajaxData?.ymax ? 0 104 | tile_px_w: ajaxData?.pixelw ? 0 105 | tile_px_h: ajaxData?.pixelh ? 0 106 | 107 | if callbackPass? 108 | callbackPass(returnData) 109 | else if callbackFail? 110 | callbackFail() 111 | handleError = -> 112 | if callbackFail? 113 | callbackFail() 114 | AJAX.doRequest(request, handleSuccess, handleError,config) 115 | return 116 | 117 | exports.isWorldReady = -> 118 | worldReady 119 | 120 | exports.getLatestPaperId = -> 121 | latestPaperId 122 | 123 | exports.getNewPaperBoundaryId = -> 124 | newPaperBoundaryId 125 | 126 | exports.getNumberArxivPapers = -> 127 | numberArxivPapers 128 | 129 | exports.getLastDownloadDate = -> 130 | lastDownloadDate 131 | 132 | #exports.getClosestTiling = (totalNumTilesX) -> 133 | # if tile_depths? and tile_depths.length > 0 134 | # #closest = tile_depths[0] 135 | # closest = null 136 | # for tiling in tile_depths 137 | # #if tiling.numx >= totalNumTilesX and Math.abs(tiling.numx-totalNumTilesX) < Math.abs(closest.numx-totalNumTilesX) 138 | # if tiling.numx >= totalNumTilesX and (not closest? or tiling.numx < closest.numx) 139 | # closest = tiling 140 | # if not closest? 141 | # closest = tile_depths[0] 142 | 143 | # use maximum width calculation to make sure images are always sharp 144 | exports.getClosestTiling = (maximumTileWidth) -> 145 | if tile_depths? and tile_depths.length > 0 146 | closest = tile_depths[tile_depths.length - 1] 147 | for tiling in tile_depths 148 | if tiling.width <= maximumTileWidth and tiling.width > closest.width 149 | closest = tiling 150 | 151 | else 152 | # Make sure we return something 153 | closest = new TileDepth(0) 154 | return { 155 | depth: closest.depth 156 | numx: closest.numx 157 | numy: closest.numy 158 | width: closest.width 159 | height: closest.height 160 | } 161 | 162 | exports.getClosestLabelZone = (scale) -> 163 | if label_depths? and label_depths.length > 0 164 | closest = label_depths[0] 165 | for labeling in label_depths 166 | if Math.abs(labeling.scale-scale) < Math.abs(closest.scale-scale) 167 | closest = labeling 168 | else 169 | # Make sure we return something 170 | closest = new LabelDepth(0) 171 | return { 172 | depth: closest.depth 173 | scale: closest.scale 174 | numx: closest.numx 175 | numy: closest.numy 176 | width: closest.width 177 | height: closest.height 178 | } 179 | 180 | exports.setSubWorldPath = (path) -> 181 | if path? and path.length > 0 182 | if path[0] != "/" 183 | path = "/#{path}" 184 | subWorldPath = path 185 | 186 | exports.getTileServer = () -> 187 | tileServerIndex += 1 188 | if tileServerIndex >= tile_servers.length 189 | tileServerIndex = 0 190 | return "#{tile_servers[tileServerIndex]}#{subWorldPath}" 191 | 192 | exports.getTileInfoAtPosition = (depth,dx,dy,specialTiles) -> 193 | # dx and dy are offsets from top-left corner 194 | suffix = "" 195 | if specialTiles? 196 | if specialTiles == "heatmap" 197 | suffix = "-hm" 198 | if specialTiles == "grayscale" 199 | suffix = "-bw" 200 | # for now tiling is square 201 | tiling = findTileDepth(depth) 202 | nx = tiling.numx 203 | ny = tiling.numy 204 | ix = Math.ceil(dx / tiling.width) 205 | iy = Math.ceil(dy / tiling.height) 206 | #px = x_min + tiling.width*(ix-1) 207 | #py = y_min + tiling.height*(iy-1) 208 | if ix >= 1 and ix <= nx and iy >= 1 and iy <= ny 209 | path = "tiles#{suffix}/#{depth}/#{ix}/#{iy}.png" 210 | else 211 | path = null 212 | return { 213 | path: path 214 | #posx: px 215 | #posy: py 216 | } 217 | 218 | exports.fetchLabelZone = (depth,ix,iy,callbackPass,callbackFail) -> 219 | labeling = findLabelDepth(depth) 220 | labelZonePath = "/zones/#{depth}/#{ix}/#{iy}.json" 221 | config = 222 | server: exports.getTileServer() 223 | urlExt: labelZonePath 224 | jsonpStatic: true 225 | jsonpCallback: "lz_#{depth}_#{ix}_#{iy}" 226 | request = {} 227 | handleSuccess = (ajaxData) -> 228 | if ajaxData? 229 | returnData = 230 | lbls: ajaxData.lbls ? [] 231 | if callbackPass? 232 | callbackPass(returnData) 233 | else if callbackFail? 234 | callbackFail() 235 | handleError = -> 236 | if callbackFail? 237 | callbackFail() 238 | AJAX.doRequest(request, handleSuccess, handleError,config) 239 | return 240 | 241 | 242 | exports.fetchMetaForPaperId = (id, callbackPass, callbackFail) -> 243 | request = 244 | gdata: [id] 245 | flags: [0x01] 246 | handleSuccess = (ajaxData) -> 247 | returnData = 248 | id : ajaxData.papr[0]?.id ? 0 249 | numRefs: ajaxData.papr[0]?.nr ? 0 250 | numCites: ajaxData.papr[0]?.nc ? 0 251 | title: ajaxData.papr[0]?.titl ? "" 252 | authors: ajaxData.papr[0]?.auth ? "" 253 | journal: ajaxData.papr[0]?.publ ? "" 254 | arxivId: ajaxData.papr[0]?.arxv ? "" 255 | categories: ajaxData.papr[0]?.cats ? "" 256 | inspire: ajaxData.papr[0]?.insp ? "" 257 | callbackPass(returnData) 258 | handleError = -> 259 | if callbackFail? 260 | callbackFail() 261 | AJAX.doRequest(request, handleSuccess, handleError) 262 | 263 | exports.fetchAbstractForPaperId = (id, callbackPass, callbackFail) -> 264 | request = 265 | gdata: [id] 266 | flags: [0x20] 267 | handleSuccess = (ajaxData) -> 268 | returnData = 269 | id : ajaxData.papr[0]?.id ? 0 270 | abstract: ajaxData.papr[0]?.abst ? "" 271 | callbackPass(returnData) 272 | handleError = -> 273 | if callbackFail? 274 | callbackFail() 275 | AJAX.doRequest(request, handleSuccess, handleError) 276 | 277 | exports.fetchReferencesForArXivId = (arXivId, callbackPass, callbackFail) -> 278 | request = 279 | mr2l: arXivId 280 | tbl: dbSuffix 281 | handleSuccess = (ajaxData) -> 282 | refs = [] 283 | if ajaxData?.papr[0]?.ref? 284 | for ref in ajaxData.papr[0].ref 285 | refs.push 286 | id : ref.id ? 0 287 | freq: ref.f ? 1 288 | x : ref.x ? null 289 | y : ref.y ? null 290 | r : ref.r ? null 291 | returnData = 292 | id : ajaxData?.papr[0]?.id ? 0 293 | x : ajaxData?.papr[0]?.x ? null 294 | y : ajaxData?.papr[0]?.y ? null 295 | r : ajaxData?.papr[0]?.r ? null 296 | links : refs 297 | callbackPass(returnData) 298 | handleError = -> 299 | if callbackFail? 300 | callbackFail() 301 | AJAX.doRequest(request, handleSuccess, handleError) 302 | 303 | exports.fetchCitationsForArXivId = (arXivId, callbackPass, callbackFail) -> 304 | request = 305 | mc2l: arXivId 306 | tbl: dbSuffix 307 | handleSuccess = (ajaxData) -> 308 | cites = [] 309 | if ajaxData?.papr[0]?.cite? 310 | for cite in ajaxData?.papr[0]?.cite 311 | cites.push 312 | id : cite.id ? 0 313 | freq: cite.f ? 1 314 | x : cite.x ? null 315 | y : cite.y ? null 316 | r : cite.r ? null 317 | returnData = 318 | id : ajaxData?.papr[0]?.id ? 0 319 | x : ajaxData?.papr[0]?.x ? null 320 | y : ajaxData?.papr[0]?.y ? null 321 | r : ajaxData?.papr[0]?.r ? null 322 | links: cites 323 | callbackPass(returnData) 324 | handleError = -> 325 | if callbackFail? 326 | callbackFail() 327 | AJAX.doRequest(request, handleSuccess, handleError) 328 | 329 | exports.fetchLocationsForPaperIds = (ids, callbackPass, callbackFail) -> 330 | # TODO consider integrating this with gdata 331 | config = null 332 | request = 333 | mp2l: ids 334 | tbl: dbSuffix 335 | if ids.length > 25 336 | config = { doPOST: true} 337 | handleSuccess = (ajaxData) -> 338 | returnData = [] 339 | if ajaxData? 340 | for paper in ajaxData 341 | returnData.push 342 | id : paper?.id ? 0 343 | x : paper?.x ? null 344 | y : paper?.y ? null 345 | r : paper?.r ? null 346 | callbackPass(returnData) 347 | handleError = -> 348 | if callbackFail? 349 | callbackFail() 350 | AJAX.doRequest(request, handleSuccess, handleError, config) 351 | 352 | exports.fetchPaperIdAtLocation = (x, y, callbackPass, callbackFail) -> 353 | request = 354 | ml2p: [x,y] 355 | tbl: dbSuffix 356 | handleSuccess = (ajaxData) -> 357 | returnData = 358 | id : ajaxData?.id ? 0 359 | x : ajaxData?.x ? null 360 | y : ajaxData?.y ? null 361 | r : ajaxData?.r ? null 362 | callbackPass(returnData) 363 | handleError = -> 364 | if callbackFail? 365 | callbackFail() 366 | AJAX.doRequest(request, handleSuccess, handleError) 367 | 368 | 369 | exports.fetchSearchResults = (request,callbackPass,callbackFail) -> 370 | AJAX.doRequest(request,callbackPass,callbackFail) 371 | 372 | 373 | ### 374 | exports.fetchKeywordInWindow = (x, y, w, h, callbackPass, callbackFail) -> 375 | request = 376 | mkws: [x,y,w,h] 377 | handleSuccess = (ajaxData) -> 378 | returnData = ajaxData ? [] 379 | callbackPass(returnData) 380 | handleError = -> 381 | if callbackFail? 382 | callbackFail() 383 | AJAX.doRequest(request, handleSuccess, handleError) 384 | ### 385 | 386 | return exports 387 | -------------------------------------------------------------------------------- /js/coffee/world_test.coffee: -------------------------------------------------------------------------------- 1 | define ['app/Vec2D','app/ajax','app/world'], (Vec2D, AJAX, WORLD) -> 2 | run : -> 3 | 4 | module "WORLD" 5 | 6 | test "Load world tile data", -> 7 | AJAX.setDefaults() 8 | testCallback = (dims) -> 9 | #dims = WORLD.getDimensions() 10 | #ok not isNaN(dims.padding) and dims.padding >= 0, 11 | # "World padding should be non-negative number: #{dims.padding}" 12 | ok not isNaN(dims.tile_px_w) and dims.tile_px_w > 0 and 13 | not isNaN(dims.tile_px_h) and dims.tile_px_h > 0, 14 | "Tile pixel width and height should be positive numbers: #{dims.tile_px_w}x#{dims.tile_px_h}" 15 | 16 | ok not isNaN(dims.x_min) and not isNaN(dims.y_min) and 17 | not isNaN(dims.x_max) and not isNaN(dims.y_max) and 18 | dims.x_min < dims.x_max and dims.y_min < dims.y_max, 19 | "World dimensions should not be zero or negative: x = [#{dims.x_min},#{dims.x_max}] , y = [#{dims.y_min},#{dims.y_max}]" 20 | 21 | latestPaperId = WORLD.getLatestPaperId() 22 | ok not isNaN(latestPaperId) and latestPaperId.toString().length == 10, 23 | "Latest paper id should be 10 digit number: #{latestPaperId}" 24 | 25 | newPaperBoundaryId = WORLD.getNewPaperBoundaryId() 26 | ok not isNaN(newPaperBoundaryId) and newPaperBoundaryId.toString().length == 10 and newPaperBoundaryId < latestPaperId, 27 | "Boundary paper id should be 10 digit number: #{latestPaperId}, and less than latest paper id" 28 | 29 | ## Test tilings (tune as necessary with tile data) 30 | 31 | ok WORLD.isWorldReady(), 32 | "World ready" 33 | 34 | # TODO 35 | #equal WORLD.getClosestTiling(5).depth, 0 36 | #equal WORLD.getClosestTiling(5).numx, 4 37 | #equal WORLD.getClosestTiling(120).depth, 3 38 | #equal WORLD.getClosestTiling(120).numx, 72 39 | #equal WORLD.getClosestTiling(200).depth, 4 40 | #equal WORLD.getClosestTiling(200).numx, 216 41 | 42 | 43 | # Test tile urls 44 | #dims = WORLD.getDimensions() 45 | maxTileWidth = 1e8 46 | depth = WORLD.getClosestTiling(maxTileWidth).depth 47 | # Currently x direction sets the y-tiling 48 | farEdge = dims.x_max - 50 -dims.x_min 49 | # Add 10 as rounding errors adding up! 50 | middle = Math.ceil((dims.x_max - dims.x_min)/2)+50 51 | tileInfo = WORLD.getTileInfoAtPosition(depth,farEdge,middle) 52 | equal tileInfo.path, "tiles/0/4/3.png" 53 | 54 | equal WORLD.getClosestLabelZone(5).depth, 8 55 | 56 | start() 57 | errorCallback = -> 58 | ok false, 59 | "Callback error" 60 | start() 61 | stop() 62 | WORLD.loadWorldData(testCallback,errorCallback) 63 | 64 | test "Meta for paper id", -> 65 | testId = 2115344314 66 | AJAX.setDefaults() 67 | testCallback = (wrapData) -> 68 | equal wrapData.id, testId, 69 | "Get back correct id" 70 | equal wrapData.authors, "R.Fleischer,R.Knegjens", 71 | "Get back correct author" 72 | equal wrapData.title[..18], "Effective Lifetimes", 73 | "Get back correct title" 74 | equal wrapData.arxivId, "1109.5115", 75 | "Get back correct arxiv id" 76 | equal wrapData.categories, "hep-ph,hep-ex", 77 | "Get back correct categories" 78 | equal wrapData.inspire, "928287", 79 | "Get back correct inspire record" 80 | equal wrapData.journal[..9], "EuroPhysJC", 81 | "Get back correct journal" 82 | ok not isNaN(wrapData.numCites), 83 | "Number cites given as number" 84 | start() 85 | errorCallback = -> 86 | ok false, 87 | "Callback error" 88 | start() 89 | stop() 90 | WORLD.fetchMetaForPaperId(testId,testCallback,errorCallback) 91 | 92 | 93 | test "Abstract for paper id", -> 94 | testId = 2115344314 95 | stop() 96 | AJAX.setDefaults() 97 | testCallback = (wrapData) -> 98 | equal(wrapData.id, testId, "Get back correct id") 99 | abstStr = wrapData.abstract[..10] 100 | ok abstStr == "Measurement" or abstStr == "(no abstrac", 101 | "Get back first 10 characters of abstract" 102 | start() 103 | errorCallback = -> 104 | ok false, 105 | "Callback error" 106 | start() 107 | WORLD.fetchAbstractForPaperId(testId,testCallback,errorCallback) 108 | 109 | test "Fetch location of paper id", -> 110 | testId = 2115344314 111 | AJAX.setDefaults() 112 | testCallback = (wrapData) -> 113 | equal wrapData[0].id, testId, 114 | "Get back correct id" 115 | ok wrapData[0].x? and wrapData[0].y? and wrapData[0].r?, 116 | "x,y,r defined" 117 | start() 118 | errorCallback = -> 119 | ok false, 120 | "Callback error" 121 | start() 122 | stop() 123 | WORLD.fetchLocationsForPaperIds([testId],testCallback,errorCallback) 124 | 125 | test "Fetch paper id at location (calls inverse first)", -> 126 | testId = 2115344314 127 | AJAX.setDefaults() 128 | testCallback = (wrapData) -> 129 | equal wrapData.id, 2115344314, 130 | "Get back correct id" 131 | start() 132 | errorCallback = -> 133 | ok false, 134 | "Callback error" 135 | start() 136 | prepCallback = (wrapData) -> 137 | WORLD.fetchPaperIdAtLocation(wrapData[0].x,wrapData[0].y,testCallback,errorCallback) 138 | stop() 139 | WORLD.fetchLocationsForPaperIds([testId],prepCallback,errorCallback) 140 | 141 | ### 142 | test "Fetch keywords in window", -> 143 | AJAX.setDefaults() 144 | stop() 145 | testCallback = (wrapData) -> 146 | equal wrapData[0].kw, "test keyword", 147 | "Get back correct keyword" 148 | start() 149 | errorCallback = -> 150 | ok false, 151 | "Callback error" 152 | start() 153 | WORLD.fetchKeywordInWindow(0,0,100,100,testCallback,errorCallback) 154 | ### 155 | -------------------------------------------------------------------------------- /js/lib/jquery-ui-autocomplete.html.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Autocomplete HTML Extension 3 | * 4 | * Copyright 2010, Scott González (http://scottgonzalez.com) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * 7 | * http://github.com/scottgonzalez/jquery-ui-extensions 8 | */ 9 | (function( $ ) { 10 | 11 | var proto = $.ui.autocomplete.prototype, 12 | initSource = proto._initSource; 13 | 14 | function filter( array, term ) { 15 | var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); 16 | return $.grep( array, function(value) { 17 | return matcher.test( $( "
" ).html( value.label || value.value || value ).text() ); 18 | }); 19 | } 20 | 21 | $.extend( proto, { 22 | _initSource: function() { 23 | if ( this.options.html && $.isArray(this.options.source) ) { 24 | this.source = function( request, response ) { 25 | response( filter( this.options.source, request.term ) ); 26 | }; 27 | } else { 28 | initSource.call( this ); 29 | } 30 | }, 31 | 32 | _renderItem: function( ul, item) { 33 | return $( "
  • " ) 34 | .data( "item.autocomplete", item ) 35 | .append( $( "" )[ this.options.html ? "html" : "text" ]( item.label ) ) 36 | .appendTo( ul ); 37 | } 38 | }); 39 | 40 | })( jQuery ); 41 | -------------------------------------------------------------------------------- /js/lib/jquery.mousewheel.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net) 2 | * Licensed under the MIT License (LICENSE.txt). 3 | * 4 | * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. 5 | * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. 6 | * Thanks to: Seamus Leahy for adding deltaX and deltaY 7 | * 8 | * Version: 3.1.3 9 | * 10 | * Requires: 1.2.2+ 11 | */ 12 | 13 | (function (factory) { 14 | if ( typeof define === 'function' && define.amd ) { 15 | // AMD. Register as an anonymous module. 16 | define(['jquery'], factory); 17 | } else if (typeof exports === 'object') { 18 | // Node/CommonJS style for Browserify 19 | module.exports = factory; 20 | } else { 21 | // Browser globals 22 | factory(jQuery); 23 | } 24 | }(function ($) { 25 | 26 | var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll']; 27 | var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll']; 28 | var lowestDelta, lowestDeltaXY; 29 | 30 | if ( $.event.fixHooks ) { 31 | for ( var i = toFix.length; i; ) { 32 | $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks; 33 | } 34 | } 35 | 36 | $.event.special.mousewheel = { 37 | setup: function() { 38 | if ( this.addEventListener ) { 39 | for ( var i = toBind.length; i; ) { 40 | this.addEventListener( toBind[--i], handler, false ); 41 | } 42 | } else { 43 | this.onmousewheel = handler; 44 | } 45 | }, 46 | 47 | teardown: function() { 48 | if ( this.removeEventListener ) { 49 | for ( var i = toBind.length; i; ) { 50 | this.removeEventListener( toBind[--i], handler, false ); 51 | } 52 | } else { 53 | this.onmousewheel = null; 54 | } 55 | } 56 | }; 57 | 58 | $.fn.extend({ 59 | mousewheel: function(fn) { 60 | return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); 61 | }, 62 | 63 | unmousewheel: function(fn) { 64 | return this.unbind("mousewheel", fn); 65 | } 66 | }); 67 | 68 | 69 | function handler(event) { 70 | var orgEvent = event || window.event, 71 | args = [].slice.call(arguments, 1), 72 | delta = 0, 73 | deltaX = 0, 74 | deltaY = 0, 75 | absDelta = 0, 76 | absDeltaXY = 0, 77 | fn; 78 | event = $.event.fix(orgEvent); 79 | event.type = "mousewheel"; 80 | 81 | // Old school scrollwheel delta 82 | if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; } 83 | if ( orgEvent.detail ) { delta = orgEvent.detail * -1; } 84 | 85 | // New school wheel delta (wheel event) 86 | if ( orgEvent.deltaY ) { 87 | deltaY = orgEvent.deltaY * -1; 88 | delta = deltaY; 89 | } 90 | if ( orgEvent.deltaX ) { 91 | deltaX = orgEvent.deltaX; 92 | delta = deltaX * -1; 93 | } 94 | 95 | // Webkit 96 | if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; } 97 | if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; } 98 | 99 | // Look for lowest delta to normalize the delta values 100 | absDelta = Math.abs(delta); 101 | if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; } 102 | absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX)); 103 | if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; } 104 | 105 | // Get a whole value for the deltas 106 | fn = delta > 0 ? 'floor' : 'ceil'; 107 | delta = Math[fn](delta / lowestDelta); 108 | deltaX = Math[fn](deltaX / lowestDeltaXY); 109 | deltaY = Math[fn](deltaY / lowestDeltaXY); 110 | 111 | // Add event and delta to the front of the arguments 112 | args.unshift(event, delta, deltaX, deltaY); 113 | 114 | return ($.event.dispatch || $.event.handle).apply(this, args); 115 | } 116 | 117 | })); 118 | -------------------------------------------------------------------------------- /js/lib/mousetrap.js: -------------------------------------------------------------------------------- 1 | /* mousetrap v1.3.2 craig.is/killing/mice */ 2 | (function(){function s(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)}function y(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return h[a.which]?h[a.which]:z[a.which]?z[a.which]:String.fromCharCode(a.which).toLowerCase()}function t(a,b){a=a||{};var c=!1,d;for(d in m)a[d]&&m[d]>b?c=!0:m[d]=0;c||(p=!1)}function A(a,b,c,d,g){var f,e,h=[],j=c.type;if(!l[a])return[];"keyup"==j&&u(a)&&(b=[a]);for(f=0;fd||h.hasOwnProperty(d)&&(q[h[d]]=d)}c=q[a]?"keydown":"keypress"}"keypress"== 5 | c&&b.length&&(c="keydown");return c}function C(a,b,c,d,g){r[a+":"+c]=b;a=a.replace(/\s+/g," ");var f=a.split(" "),e,h,j=[];if(1":".","?":"/","|":"\\"},E={option:"alt",command:"meta","return":"enter",escape:"esc"},q,l={},r={},m={},D,x=!1,p=!1,g=1;20>g;++g)h[111+g]="f"+g;for(g=0;9>=g;++g)h[g+96]=g;s(document,"keypress",w);s(document,"keydown",w);s(document,"keyup",w);var k={bind:function(a,b,c){a=a instanceof Array?a:[a];for(var d=0;d': 144 | tok.kind = Token.doctype 145 | self.__inTag = False 146 | else: 147 | tok.kind = Token.invalid 148 | 149 | elif tok.str == ' 7 | stuff... 8 | 9 | 10 | Comment form (for turning stuff on): 11 | 12 | """ 13 | 14 | import sys 15 | import re 16 | 17 | from lexer import Token, Lexer 18 | 19 | class DOMDoctype(object): 20 | def __init__(self, doctype): 21 | self.doctype = doctype 22 | 23 | def toStr(self, params): 24 | return self.doctype 25 | 26 | class DOMComment(object): 27 | def __init__(self, comment): 28 | self.comment = comment 29 | self.cond = None 30 | self.condId = None 31 | self.condStr = None 32 | if comment == '': 33 | self.cond = 'endif' 34 | else: 35 | match = re.match(r'', comment, re.DOTALL) 36 | if match: 37 | groups = match.groups() 38 | if groups[0] is not None: 39 | self.cond = 'ifnot' 40 | else: 41 | self.cond = 'if' 42 | self.condId = groups[1] 43 | if groups[2] is None: 44 | self.condStr = groups[3] 45 | 46 | def toStr(self, params): 47 | if self.condStr is not None: 48 | cond = self.condId in params and params[self.condId] 49 | if self.cond == 'ifnot': 50 | cond = not cond 51 | if cond: 52 | return self.condStr 53 | else: 54 | return '' 55 | elif 'discardComments' in params and params['discardComments']: 56 | return '' 57 | else: 58 | return self.comment 59 | 60 | class DOMText(object): 61 | def __init__(self, text): 62 | self.text = text 63 | 64 | def toStr(self, params): 65 | return re.sub(r'\n+', r'\n', self.text) 66 | #return self.text 67 | 68 | class DOMElement(object): 69 | def __init__(self, tagName, attrs, body): 70 | self.tagName = tagName 71 | self.attrs = attrs 72 | self.body = body 73 | 74 | def toStr(self, params): 75 | strs = ['<{}'.format(self.tagName)] 76 | for attr in self.attrs: 77 | strs.append(' {}={}'.format(attr[0], attr[1])) 78 | if self.body is None: 79 | strs.append(' />') 80 | else: 81 | strs.append('>') 82 | strs.append(self.body.toStr(params)) 83 | strs.append(''.format(self.tagName)) 84 | return ''.join(strs) 85 | 86 | class DOMBody(object): 87 | def __init__(self, items): 88 | self.items = items 89 | 90 | def toStr(self, params={}): 91 | strs = [] 92 | keep = True 93 | for i in xrange(len(self.items)): 94 | item = self.items[i] 95 | if isinstance(item, DOMComment) and item.condStr is None: 96 | if (item.cond == 'if' or item.cond == 'ifnot'): 97 | cond = item.condId in params and params[item.condId] 98 | if item.cond == 'ifnot': 99 | cond = not cond 100 | keep = cond 101 | elif item.cond == 'endif': 102 | keep = True 103 | elif not keep: 104 | pass 105 | else: 106 | strs.append(item.toStr(params)) 107 | return ''.join(strs) 108 | 109 | class ParseException(Exception): 110 | def __init__(self, tok, msg): 111 | if tok is None: 112 | self.str = msg 113 | else: 114 | self.str = tok.locnStr() + " " + msg 115 | 116 | def parseSingleElement(lexer): 117 | if lexer.isKind(Token.doctype): 118 | tok = lexer.curToken() 119 | lexer.nextToken() 120 | return DOMDoctype(tok.str) 121 | 122 | elif lexer.isKind(Token.comment): 123 | tok = lexer.curToken() 124 | lexer.nextToken() 125 | return DOMComment(tok.str) 126 | 127 | elif lexer.optKind(Token.openStartTag): 128 | tagName = lexer.getId() 129 | attrs = [] 130 | body = None 131 | 132 | # parse attributes 133 | while lexer.isKind(Token.identifier): 134 | attrName = lexer.getId() 135 | lexer.getKind(Token.equals) 136 | attrVal = lexer.getKind(Token.string) 137 | attrs.append((attrName, attrVal)) 138 | 139 | # parse end of tag 140 | emptyElement = lexer.optKind(Token.forwardSlash) 141 | lexer.getKind(Token.closeTag) 142 | 143 | if not emptyElement: 144 | # parse body 145 | items = [] 146 | while not lexer.isKind(Token.openEndTag): 147 | item = parseSingleElement(lexer) 148 | items.append(item) 149 | body = DOMBody(items) 150 | 151 | # parse closing tag 152 | tagTok = lexer.curToken() 153 | lexer.getKind(Token.openEndTag) 154 | endTagName = lexer.getId() 155 | lexer.getKind(Token.closeTag) 156 | 157 | if endTagName != tagName: 158 | raise ParseException(tagTok, "closing tag mismatch, expecting got ".format(tagName, endTagName)) 159 | 160 | return DOMElement(tagName, attrs, body) 161 | 162 | elif lexer.isKind(Token.text): 163 | # parse text 164 | tok = lexer.curToken() 165 | text = [] 166 | while tok.kind == Token.text: 167 | text.append(tok.str) 168 | tok = lexer.nextToken() 169 | return DOMText(''.join(text)) 170 | 171 | else: 172 | raise ParseException(lexer.curToken(), "unexpected") 173 | 174 | def parseHTML(fileName): 175 | fileObj = open(fileName) 176 | lexer = Lexer(fileName, fileObj) 177 | items = [] 178 | while not lexer.isEnd(): 179 | item = parseSingleElement(lexer) 180 | items.append(item) 181 | fileObj.close() 182 | return DOMBody(items) 183 | 184 | if __name__ == "__main__": 185 | try: 186 | body = parseHTML(sys.argv[1]) 187 | sys.stdout.write(body.toStr()) 188 | except ParseException as er: 189 | sys.stderr.write(str(er) + '\n') 190 | -------------------------------------------------------------------------------- /unittests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Xiwi:boa unit tests 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------