├── .bowerrc ├── .gitignore ├── CHANGELOG ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE ├── README.md ├── WILLCHANGELOG.md ├── bower.json ├── config └── assets.yml ├── dist ├── enhance.js ├── page_embed.css └── page_embed.js ├── examples ├── document.json ├── embed.html ├── embed_code.html ├── examples.js ├── index.html └── single.html ├── index.html ├── package.json └── src ├── css ├── page_embed.scss └── vendor │ └── fontello │ ├── LICENSE.txt │ ├── animation.css │ ├── font │ ├── fontello.eot │ ├── fontello.svg │ ├── fontello.ttf │ └── fontello.woff │ ├── fontello-codes.css │ ├── fontello-embedded.css │ ├── fontello-ie7-codes.css │ ├── fontello-ie7.css │ └── fontello.css ├── images ├── documentcloud-logo.png ├── documentcloud-logo.svg └── documentcloud-logotype.png ├── js ├── config │ └── config.js.erb ├── enhance.js ├── legacy │ ├── modernizr.custom.js │ └── modernizr.js ├── loaders │ └── page.js ├── models │ ├── document.js │ └── note.js ├── util │ ├── penny.js │ ├── setup.js │ └── toolbelt.js └── views │ ├── note.js │ └── page.js └── templates ├── credit.jst ├── note.jst └── page.jst /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/js/vendor" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | src/js/vendor/backbone 3 | src/js/vendor/jquery 4 | src/js/vendor/underscore 5 | src/js/vendor/jQuery-ajaxTransport-XDomainRequest 6 | .DS_Store 7 | .tm_properties 8 | .ruby-version 9 | node_modules -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.3.0 2 | ----- 3 | * Refactor markup so that embed container is minimally changed and isn't the Backbone view site 4 | * Add Pixel Ping for counting document hits 5 | * Allow loader to receive existing DOM element, not just element ID string, as its container (#39) 6 | * Increase CSS selector specificity to minimize picking up parent styles (#40) 7 | * Allow viewing text of more than one change (#21) 8 | * Maintain page mode while changing pages (#9) 9 | * Fix undefined host names for some Pixel Ping referrers (#34) 10 | * Disable Pixel Ping on embed wizard (#33) 11 | 12 | 0.2.0 13 | ----- 14 | 15 | * Refactor for environment configurability and resource type independence (#27) 16 | * Restructure directories 17 | * Stop pointing to custom Jammit and Guard Jammit gems 18 | * Stop using Jammit for datauri compilation (#28) 19 | * Add support for high-res images (#25) 20 | 21 | 0.1.0 22 | ----- 23 | 24 | * Beta release to DocumentCloud 25 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development, :test do 4 | gem 'jammit', '~> 0.7.0' 5 | gem 'guard-jammit', '~> 1.1' 6 | gem 'sass', '~> 3.4' 7 | # evidently necessary for guard? 8 | gem 'rb-readline' 9 | end 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | coderay (1.1.0) 5 | cssmin (1.0.3) 6 | ffi (1.9.10) 7 | formatador (0.2.5) 8 | guard (2.13.0) 9 | formatador (>= 0.2.4) 10 | listen (>= 2.7, <= 4.0) 11 | lumberjack (~> 1.0) 12 | nenv (~> 0.1) 13 | notiffany (~> 0.0) 14 | pry (>= 0.9.12) 15 | shellany (~> 0.0) 16 | thor (>= 0.18.1) 17 | guard-compat (1.2.1) 18 | guard-jammit (1.1.0) 19 | guard (~> 2.12) 20 | guard-compat (~> 1.2) 21 | jammit 22 | jammit (0.7.0) 23 | cssmin (~> 1.0) 24 | jsmin (~> 1.0) 25 | jsmin (1.0.1) 26 | listen (3.0.5) 27 | rb-fsevent (>= 0.9.3) 28 | rb-inotify (>= 0.9) 29 | lumberjack (1.0.10) 30 | method_source (0.8.2) 31 | nenv (0.3.0) 32 | notiffany (0.0.8) 33 | nenv (~> 0.1) 34 | shellany (~> 0.0) 35 | pry (0.10.3) 36 | coderay (~> 1.1.0) 37 | method_source (~> 0.8.1) 38 | slop (~> 3.4) 39 | rb-fsevent (0.9.7) 40 | rb-inotify (0.9.7) 41 | ffi (>= 0.5.0) 42 | rb-readline (0.5.3) 43 | sass (3.4.21) 44 | shellany (0.0.1) 45 | slop (3.6.0) 46 | thor (0.19.1) 47 | 48 | PLATFORMS 49 | ruby 50 | 51 | DEPENDENCIES 52 | guard-jammit (~> 1.1) 53 | jammit (~> 0.7.0) 54 | rb-readline 55 | sass (~> 3.4) 56 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :jammit, :output_folder => 'dist' do 2 | watch(%r{^src/js/(.*)\.js$}) 3 | watch(%r{^src/templates/(.*)\.jst$}) 4 | watch(%r{^src/css/(.*)\.s?css$}) 5 | end 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 DocumentCloud, Investigative Reporters & Editors 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DocumentCloud Page Embed 2 | 3 | A responsive, embeddable JavaScript library which displays pages from documents on DocumentCloud (or any other API-compatible location). Follow the development on our [example page](http://documentcloud.github.io/documentcloud-pages/). 4 | 5 | ## Why? 6 | 7 | 1. DocumentCloud's existing [document-viewer](https://github.com/documentcloud/document-viewer) doesn't play terribly well with mobile devices. It was originally written in 2006, and while it's done a terrific job, it's not well-suited for a mobile-first world. 8 | 2. There are a number of use cases where it's useful to display only a single page from a document and where the rich metadata of the full document (such as [DocumentCloud notes](https://www.documentcloud.org/help/notes) or document title/contributor information) would complement the single pages of supporting material. 9 | 3. DocumentCloud plans to incrementally build a responsive viewer from smaller constituent components, and a full page with notes is the next step up from [our smaller individual note embeds](https://www.documentcloud.org/help/notes). 10 | 11 | ## Browser Compatibility 12 | 13 | * Unenhanced markup (effectively, noscript) is compatible with everything unless being served on an HTTPS page, in which case it requires an SNI-compatible browser (aka anything besides [IE on XP](https://github.com/documentcloud/documentcloud/issues/278)). 14 | * Enhanced markup is compatible with IE8+ on Vista+ and all the other usual suspects. 15 | 16 | ## Participating 17 | 18 | You should [open an issue](https://github.com/documentcloud/documentcloud-pages/issues) for suggestions or ideas you'd like to tell us about! 19 | 20 | ### Installation/setup 21 | 22 | * Install [Bower](http://bower.io) and [Bundler](http://bundler.io/) 23 | * Install JavaScript dependencies with `bower install` 24 | * Install gem dependencies with `bundle install` 25 | 26 | ### Development 27 | 28 | We use [Guard](https://github.com/guard/guard) to watch and build the project, so on the daily you'll want to `cd` to the repo directory and run `bundle exec guard`. Outputs will be built to `/dist/`. 29 | 30 | ## License 31 | 32 | Copyright (c) 2015 DocumentCloud, Investigative Reporters & Editors 33 | 34 | 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: 35 | 36 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 37 | 38 | 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. 39 | -------------------------------------------------------------------------------- /WILLCHANGELOG.md: -------------------------------------------------------------------------------- 1 | # The Will Change Log 2 | 3 | This is a document listing priorities for the page-embed. Think of this as the harbinger for the CHANGELOG. Current efforts underway are above the horizontal rule and future ideas below it. 4 | 5 | ### Current Effort: Post Launch Cleanup 6 | 7 | - [x] [Add oEmbed endpoint to platform](https://github.com/documentcloud/documentcloud/issues/306) 8 | - [x] [Add page embed support to WordPress plugin](https://github.com/documentcloud/wordpress-documentcloud/issues/28) 9 | - [ ] [Add afterLoad callback](https://github.com/documentcloud/documentcloud-pages/issues/37) 10 | - [ ] [Provide a stable, documented API for the page embedder](https://github.com/documentcloud/documentcloud-pages/issues/35) 11 | - [ ] [Add configuration options to platform embed wizard](https://github.com/documentcloud/documentcloud/issues/305) 12 | 13 | ----------------------------------- 14 | 15 | - [ ] [Support iframeable embeds](https://github.com/documentcloud/documentcloud-pages/issues/18) 16 | - [ ] [Store page aspect ratio on documents](https://github.com/documentcloud/documentcloud/issues/284) 17 | - [ ] [Perform a thorough UX review of notes](https://github.com/documentcloud/documentcloud-pages/issues/36) 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentcloud-pages", 3 | "description": "Responsively embed DocumentCloud pages", 4 | "version": "0.3.0", 5 | "authors": [ 6 | "Ted Han ", 7 | "Justin Reese " 8 | ], 9 | "keywords": [ 10 | "documentcloud", 11 | "embedded", 12 | "3rdparty" 13 | ], 14 | "license": "MIT", 15 | "main": [ 16 | "dist/page_embed.js", 17 | "dist/page_embed.css" 18 | ], 19 | "moduleType": [ 20 | "globals" 21 | ], 22 | "ignore": [ 23 | "**/.*", 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "tests" 28 | ], 29 | "dependencies": { 30 | "backbone": "~1.3.3", 31 | "jQuery-ajaxTransport-XDomainRequest": "~1.0.2", 32 | "jquery": "~1.12.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /config/assets.yml: -------------------------------------------------------------------------------- 1 | javascript_compressor: closure 2 | css_compressor: sass 3 | template_function: DocumentCloud._.template 4 | gzip_assets: off 5 | embed_assets: off 6 | rewrite_relative_paths: off 7 | 8 | javascripts: 9 | page_embed: 10 | # Modernizr sniffs svg/touch and provides our html5shim 11 | - src/js/legacy/modernizr.custom.js 12 | - src/js/vendor/jquery/dist/jquery.js 13 | # jQuery.XDomainRequest is for IE8/9 14 | - src/js/vendor/jQuery-ajaxTransport-XDomainRequest/jQuery.XDomainRequest.js 15 | - src/js/vendor/underscore/underscore.js 16 | - src/js/vendor/backbone/backbone.js 17 | - src/js/util/setup.js 18 | - src/js/util/toolbelt.js 19 | - src/js/models/*.js 20 | - src/js/views/*.js 21 | - src/js/loaders/page.js 22 | - src/templates/*.jst 23 | 24 | enhance: 25 | - src/js/util/penny.js 26 | - src/js/util/toolbelt.js 27 | - src/js/enhance.js 28 | 29 | stylesheets: 30 | page_embed: 31 | - src/css/vendor/fontello/fontello-embedded.css 32 | - src/css/vendor/fontello/animation.css 33 | - src/css/*.css 34 | - src/css/*.scss 35 | -------------------------------------------------------------------------------- /dist/enhance.js: -------------------------------------------------------------------------------- 1 | 2 | (function(){var Penny=window.Penny=window.Penny||{VERSION:'0.0.0',on:function(el,eventName,handler){if(el.addEventListener){el.addEventListener(eventName,handler);}else{el.attachEvent('on'+eventName,function(){handler.call(el);});}},ready:function(fn){if(document.readyState!='loading'){fn();}else if(document.addEventListener){document.addEventListener('DOMContentLoaded',fn);}else{document.attachEvent('onreadystatechange',function(){if(document.readyState!='loading'){fn();}});}},each:function(collection,fn){if(collection!=null&&typeof collection==='object'&&!Penny.isArrayLike(collection)){for(var key in collection){if(Penny.has(collection,key)){fn(collection[key],key);}}}else{for(var i=0,len=collection.length;i=0&&length<=MAX_ARRAY_INDEX;},isEmpty:function(obj){if(obj==null){return true;} 8 | if(obj.length>0){return false;} 9 | if(obj.length===0){return true;} 10 | for(var key in obj){if(Penny.has(obj,key)){return false;}} 11 | return true;},};}());(function(){if(!window.console){window.console={log:function(){},info:function(){},warn:function(){},error:function(){},};} 12 | var DocumentCloud=window.DocumentCloud||{};var Penny=window.Penny;if(DocumentCloud._){var _=DocumentCloud._;}else if(Penny){var _=Penny;}else{console.error("DocumentCloud embed can't load because of missing components.");return false;} 13 | var DCEmbedToolbelt=window.DCEmbedToolbelt=window.DCEmbedToolbelt||{unicodeLetter:'A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC',unicodeNumber:'0-9\xB2\xB3\xB9\xBC-\xBE\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F\u0C78-\u0C7E\u0CE6-\u0CEF\u0D66-\u0D75\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19',isResource:function(thing){return!!(_.has(thing,'resourceType'));},recognizeResource:function(originalResource){if(this.isResource(originalResource)){return originalResource;} 14 | var domainEnvPatterns={production:'www\.documentcloud\.org',staging:'staging\.documentcloud\.org',development:'dev\.dcloud\.org'};var domains=_.values(domainEnvPatterns).join('|');var unicodeSlug=this.unicodeLetter+this.unicodeNumber+'%-';var docBase='('+domains+')\/documents\/([0-9]+)-(['+unicodeSlug+']+)';var resourceTypePatterns={'document':[docBase+'\.(?:html|js|json)$'],page:[docBase+'\.html#document\/p([0-9]+)$',docBase+'\/pages\/([0-9]+)\.(?:html|js|json)$'],note:[docBase+'\/annotations\/([0-9]+)\.(?:html|js|json)$',docBase+'\.html#document\/p[0-9]+\/a([0-9]+)$',docBase+'\.html#annotation\/a([0-9]+)$']};var makeDataUrl=function(resource){var urlComponents;switch(resource.resourceType){case'document':urlComponents=[resource.domain,'documents',resource.documentSlug];break;case'page':urlComponents=[resource.domain,'documents',resource.documentSlug];break;case'note':urlComponents=[resource.domain,'documents',resource.documentSlug,'annotations',resource.noteId];break;} 15 | return'https://'+urlComponents.join('/')+'.json';};var resource={};_.each(resourceTypePatterns,function(patterns,resourceType){if(!_.isEmpty(resource)){return;} 16 | _.each(patterns,function(pattern){if(!_.isEmpty(resource)){return;} 17 | var match=originalResource.match(pattern);if(match){resource={resourceUrl:originalResource,resourceType:resourceType,environment:_.findKey(domainEnvPatterns,function(domain,env){return originalResource.match(domain);}),domain:match[1],documentId:match[2],documentSlug:match[2]+'-'+match[3],};switch(resourceType){case'document':resource.trackingId=resource.documentId;break;case'page':resource.pageNumber=match[4];resource.trackingId=resource.documentId+'p'+resource.pageNumber;resource.embedOptions={page:resource.pageNumber};break;case'note':resource.trackingId=resource.noteId=match[4];break;} 18 | resource.dataUrl=makeDataUrl(resource);}});});return resource;},toDomElement:function(thing){if(_.isElement(thing)){return thing;}else if(_.isString(thing)){return document.querySelector(thing);}else if(thing instanceof jQuery&&_.isElement(thing[0])){return thing[0];} 19 | return null;},generateUniqueElementId:function(resource){var i=1;var id='DC-'+resource.documentSlug;switch(resource.resourceType){case'document':id+='-i'+i;break;case'page':id+='-p'+resource.pageNumber+'-i'+i;break;case'note':id+='-a'+resource.noteId+'-i'+i;break;} 20 | while(document.getElementById(id)){id=id.replace(/-i[0-9]+$/,'-i'+i++);} 21 | return id;},isIframed:function(){try{return window.self!==window.top;}catch(e){return true;}},getSourceUrl:function(){var source,sourceUrl;if(this.isIframed()){source=document.createElement('A');source.href=document.referrer;}else{source=window.location;} 22 | sourceUrl=source.protocol+'//'+source.host;if(source.pathname.indexOf('/')!==0){sourceUrl+='/';};sourceUrl+=source.pathname;sourceUrl=sourceUrl.replace(/[\/]+$/,'');return sourceUrl;},pixelPing:function(resource,container){resource=this.recognizeResource(resource);container=this.toDomElement(container);var pingUrl='//'+resource.domain+'/pixel.gif';var sourceUrl=this.getSourceUrl();var key=encodeURIComponent(resource.resourceType+':'+resource.trackingId+':'+sourceUrl);var image='Anonymous hit counter for DocumentCloud';container.insertAdjacentHTML('afterend',image);}};})();(function(){Penny.ready(function(){if(!window.DCEmbedToolbelt){console.error("DocumentCloud embed can't load because of missing components.");return;} 23 | var insertStylesheet=function(href){if(!document.querySelector('link[href$="'+href+'"]')){var stylesheet=document.createElement('link');stylesheet.rel='stylesheet';stylesheet.type='text/css';stylesheet.media='screen';stylesheet.href=href;document.querySelector('head').appendChild(stylesheet);}};var insertJavaScript=function(src,onLoadCallback){if(!document.querySelector('script[src$="'+src+'"]')){var script=document.createElement('script');script.src=src;script.async=true;Penny.on(script,'load',onLoadCallback);document.querySelector('body').appendChild(script);}};var extractOptionsFromStub=function(stub){var options=stub.getAttribute('data-options');if(options){try{options=JSON.parse(options);} 24 | catch(err){console.error("Inline DocumentCloud embed options must be valid JSON. See https://www.documentcloud.org/help/publishing.");options={};}}else{options={};} 25 | return options;};var enhanceStubs=function(){var DocumentCloud=window.DocumentCloud;var stubs=document.querySelectorAll('.DC-embed[data-version^="1."]');Penny.each(stubs,function(stub,i){if(stub.className.indexOf('DC-embed-enhanced')!=-1){return;} 26 | var resourceElement=stub.querySelector('.DC-embed-resource');var resourceUrl=resourceElement.getAttribute('href');var resource=DCEmbedToolbelt.recognizeResource(resourceUrl);if(!Penny.isEmpty(resource)){stub.className+=' DC-embed-enhanced';stub.setAttribute('data-resource-type',resource.resourceType);var embedOptions=Penny.extend({},extractOptionsFromStub(stub),resource.embedOptions,{container:stub});DocumentCloud.embed.load(resource,embedOptions);}else{console.error("The DocumentCloud URL you're trying to embed doesn't look right. Please generate a new embed code.");}});};var loadConfig=function(){var defaultConfig={page:{assetPaths:{app:"../dist/page_embed.js",style:"../dist/page_embed.css"}}};try{var envConfig=window.ENV.config.embed;} 27 | catch(e){var envConfig={};} 28 | return Penny.extend({},defaultConfig,envConfig);};var config=loadConfig();insertStylesheet(config.page.assetPaths.style);if(window.DocumentCloud){enhanceStubs();}else{insertJavaScript(config.page.assetPaths.app,enhanceStubs);}});})(); -------------------------------------------------------------------------------- /examples/document.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": [ 3 | { 4 | "access": "public", 5 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/53674.html", 6 | "content": "", 7 | "id": 53674, 8 | "page": 1, 9 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/53674.js", 10 | "title": "Posted with the gracious permission of the Author" 11 | }, 12 | { 13 | "access": "public", 14 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/80993.html", 15 | "content": "", 16 | "id": 80993, 17 | "location": { 18 | "image": "292,646,616,78" 19 | }, 20 | "page": 13, 21 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/80993.js", 22 | "title": "Test Note" 23 | }, 24 | { 25 | "access": "public", 26 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42225.html", 27 | "content": "Eye dialects are instances where a written author deviates from standard spellings for the purpose of emphasizing the particular pronunciation of a word or sentence. Eye dialects are a frequently used literary device, and have a long history, notably in the works of authors like Mark Twain and Charles Dickens.", 28 | "id": 42225, 29 | "location": { 30 | "image": "136,327,148,246" 31 | }, 32 | "page": 14, 33 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42225.js", 34 | "title": "Eye Dialects" 35 | }, 36 | { 37 | "access": "public", 38 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42227.html", 39 | "content": "An observation that anyone familiar with stage action would equally make.", 40 | "id": 42227, 41 | "location": { 42 | "image": "325,630,460,78" 43 | }, 44 | "page": 15, 45 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42227.js", 46 | "title": "Stage Directions" 47 | }, 48 | { 49 | "access": "public", 50 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42228.html", 51 | "content": "", 52 | "id": 42228, 53 | "location": { 54 | "image": "111,649,376,68" 55 | }, 56 | "page": 17, 57 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42228.js", 58 | "title": "Macro Selection" 59 | }, 60 | { 61 | "access": "public", 62 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42229.html", 63 | "content": "", 64 | "id": 42229, 65 | "location": { 66 | "image": "131,628,280,74" 67 | }, 68 | "page": 18, 69 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42229.js", 70 | "title": "Category Definition" 71 | }, 72 | { 73 | "access": "public", 74 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/93323.html", 75 | "content": "An early image macro.", 76 | "id": 93323, 77 | "location": { 78 | "image": "285,353,485,215" 79 | }, 80 | "page": 18, 81 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/93323.js", 82 | "title": "Happy Cat" 83 | }, 84 | { 85 | "access": "public", 86 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/234432.html", 87 | "content": "", 88 | "id": 234432, 89 | "location": { 90 | "image": "57,669,373,39" 91 | }, 92 | "page": 23, 93 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/234432.js", 94 | "title": "Untitled Note" 95 | }, 96 | { 97 | "access": "public", 98 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/50526.html", 99 | "content": "", 100 | "id": 50526, 101 | "location": { 102 | "image": "418,575,624,143" 103 | }, 104 | "page": 27, 105 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/50526.js", 106 | "title": "Draft Note" 107 | }, 108 | { 109 | "access": "public", 110 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42273.html", 111 | "content": "", 112 | "id": 42273, 113 | "location": { 114 | "image": "271,515,297,194" 115 | }, 116 | "page": 46, 117 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42273.js", 118 | "title": "{subject} + (can) + (has) + [noun phrase] + (?)" 119 | }, 120 | { 121 | "access": "public", 122 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42301.html", 123 | "content": "", 124 | "id": 42301, 125 | "location": { 126 | "image": "382,513,403,203" 127 | }, 128 | "page": 47, 129 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42301.js", 130 | "title": "[noun phrase] + {subject} + (has) + {pronoun}" 131 | }, 132 | { 133 | "access": "public", 134 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42300.html", 135 | "content": "", 136 | "id": 42300, 137 | "location": { 138 | "image": "77,498,105,221" 139 | }, 140 | "page": 47, 141 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42300.js", 142 | "title": "{subject} + (has) + (a) + [noun phrase]" 143 | }, 144 | { 145 | "access": "public", 146 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42302.html", 147 | "content": "", 148 | "id": 42302, 149 | "location": { 150 | "image": "78,603,102,111" 151 | }, 152 | "page": 48, 153 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42302.js", 154 | "title": "(I'm) + (in) + [determiner phrase] + {verb present progressive} + [determiner phrase]" 155 | }, 156 | { 157 | "access": "public", 158 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42250.html", 159 | "content": "", 160 | "id": 42250, 161 | "location": { 162 | "image": "68,647,530,65" 163 | }, 164 | "page": 52, 165 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42250.js", 166 | "title": "The Apostle's Creed in LOLSPEAK" 167 | }, 168 | { 169 | "access": "public", 170 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42282.html", 171 | "content": "Probably should avoid Betteridge's Law. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 172 | "id": 42282, 173 | "location": { 174 | "image": "291,624,376,74" 175 | }, 176 | "page": 57, 177 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42282.js", 178 | "title": "Is LOLSPEAK a fad?" 179 | }, 180 | { 181 | "access": "public", 182 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42283.html", 183 | "content": "", 184 | "id": 42283, 185 | "location": { 186 | "image": "410,618,464,74" 187 | }, 188 | "page": 57, 189 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42283.js", 190 | "title": "Citation of \"Om Nom Nom 2011\"" 191 | }, 192 | { 193 | "access": "public", 194 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/224616.html", 195 | "content": "", 196 | "id": 224616, 197 | "location": { 198 | "image": "532,90,585,5" 199 | }, 200 | "page": 57, 201 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/224616.js", 202 | "title": "Left" 203 | }, 204 | { 205 | "access": "public", 206 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/227455.html", 207 | "content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 208 | "id": 227455, 209 | "location": { 210 | "image": "555,404,643,294" 211 | }, 212 | "page": 57, 213 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/227455.js", 214 | "title": "Untitled Annotation" 215 | }, 216 | { 217 | "access": "public", 218 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/224793.html", 219 | "content": "", 220 | "id": 224793, 221 | "location": { 222 | "image": "605,652,693,533" 223 | }, 224 | "page": 57, 225 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/224793.js", 226 | "title": "Untitled Annotation" 227 | }, 228 | { 229 | "access": "public", 230 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/227472.html", 231 | "content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 232 | "id": 227472, 233 | "location": { 234 | "image": "739,676,796,99" 235 | }, 236 | "page": 57, 237 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/227472.js", 238 | "title": "Test" 239 | }, 240 | { 241 | "access": "public", 242 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/227207.html", 243 | "content": "", 244 | "id": 227207, 245 | "location": { 246 | "image": "5,705,901,6" 247 | }, 248 | "page": 58, 249 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/227207.js", 250 | "title": "Full page note!" 251 | }, 252 | { 253 | "access": "public", 254 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/227209.html", 255 | "content": "", 256 | "id": 227209, 257 | "location": { 258 | "image": "10,139,821,82" 259 | }, 260 | "page": 59, 261 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/227209.js", 262 | "title": "Super skinny note" 263 | }, 264 | { 265 | "access": "public", 266 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42281.html", 267 | "content": "", 268 | "id": 42281, 269 | "location": { 270 | "image": "311,562,509,149" 271 | }, 272 | "page": 62, 273 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/42281.js", 274 | "title": "Macros escaping into the wild" 275 | }, 276 | { 277 | "access": "public", 278 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/53672.html", 279 | "content": "Below are the image macros from which Lefler derived her analysis. It is worth noting that her data set is quite a limited one, as it is a subset of macros posted to ICANHAZCHEEZBURGER.com, which itself was not the originator of the cat macro craze.", 280 | "id": 53672, 281 | "page": 76, 282 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/53672.js", 283 | "title": "Data Macros" 284 | }, 285 | { 286 | "access": "public", 287 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/99206.html", 288 | "content": "", 289 | "id": 99206, 290 | "location": { 291 | "image": "481,453,661,275" 292 | }, 293 | "page": 88, 294 | "resource_url": "https://www.documentcloud.org/documents/282753-lefler-thesis/annotations/99206.js", 295 | "title": "Fridge Magnets" 296 | } 297 | ], 298 | "canonical_url": "https://www.documentcloud.org/documents/282753-lefler-thesis.html", 299 | "contributor": "Ted Han", 300 | "contributor_organization": "DocumentCloud", 301 | "created_at": "Tue, 10 Jan 2012 20:20:36 +0000", 302 | "description": "A Master's Thesis on the phenomenon of \"LOLSPEAK\" and its origin in image macros.", 303 | "display_language": "eng", 304 | "file_hash": null, 305 | "id": "282753-lefler-thesis", 306 | "language": "eng", 307 | "pages": 129, 308 | "resources": { 309 | "page": { 310 | "image": "https://assets.documentcloud.org/documents/282753/pages/lefler-thesis-p{page}-{size}.gif", 311 | "text": "https://www.documentcloud.org/documents/282753/pages/lefler-thesis-p{page}.txt" 312 | }, 313 | "pdf": "https://assets.documentcloud.org/documents/282753/lefler-thesis.pdf", 314 | "print_annotations": "https://www.documentcloud.org/notes/print?docs[]=282753", 315 | "search": "https://www.documentcloud.org/documents/282753/search.json?q={query}", 316 | "text": "https://assets.documentcloud.org/documents/282753/lefler-thesis.txt", 317 | "thumbnail": "https://assets.documentcloud.org/documents/282753/pages/lefler-thesis-p1-thumbnail.gif", 318 | "translations_url": "https://www.documentcloud.org/translations/{realm}/{language}" 319 | }, 320 | "sections": [ 321 | { 322 | "page": 4, 323 | "title": "Table of Contents" 324 | }, 325 | { 326 | "page": 7, 327 | "title": "Abstract" 328 | }, 329 | { 330 | "page": 8, 331 | "title": "1. Introduction" 332 | }, 333 | { 334 | "page": 12, 335 | "title": "2. Review of Literature" 336 | }, 337 | { 338 | "page": 17, 339 | "title": "3. Methodology" 340 | }, 341 | { 342 | "page": 22, 343 | "title": "4. Language of LOLSPEAK" 344 | }, 345 | { 346 | "page": 46, 347 | "title": "5. Recurring Syntax Structures" 348 | }, 349 | { 350 | "page": 50, 351 | "title": "6. Popularity and the Future" 352 | }, 353 | { 354 | "page": 65, 355 | "title": "7. Conclusion" 356 | }, 357 | { 358 | "page": 68, 359 | "title": "References Cited" 360 | }, 361 | { 362 | "page": 76, 363 | "title": "Appendix A: Pictures of Cats" 364 | }, 365 | { 366 | "page": 87, 367 | "title": "Appendix B: Merch" 368 | }, 369 | { 370 | "page": 91, 371 | "title": "Appendix C: Data Codes" 372 | }, 373 | { 374 | "page": 129, 375 | "title": "End of document" 376 | } 377 | ], 378 | "source": null, 379 | "title": "Lefler Thesis", 380 | "updated_at": "Tue, 06 Oct 2015 17:13:30 +0000" 381 | } 382 | -------------------------------------------------------------------------------- /examples/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DocumentCloud 8 | 9 | 10 | 11 | 12 |
13 | Page 57 of Lefler Thesis 14 |
15 | 16 | 17 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/embed_code.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Page 57 of Lefler Thesis 4 |
5 |
6 | 7 |
8 |
9 | Contributed to 10 | DocumentCloud by 11 | Ted Han of 12 | DocumentCloud • 13 | View document or 14 | read text 15 |
16 |
17 | -------------------------------------------------------------------------------- /examples/examples.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | if (window.DocumentCloud) { 3 | var $ = DocumentCloud.$; 4 | var _ = DocumentCloud._; 5 | 6 | // Cache width indicators and their parents so we don't have to do 7 | // expensive DOM lookups during `resize`; 8 | var $widths = $('.width'); 9 | $widths.each(function(){ 10 | $(this).data('target', $(this).closest('.width-target')); 11 | }); 12 | var updateColWidths = _.debounce(function(){ 13 | $widths.each(function(){ 14 | $(this).text( $(this).data('target').css('width') ); 15 | }); 16 | }, 100); 17 | $(window).on('resize', updateColWidths); 18 | updateColWidths(); 19 | 20 | // Live option changing 21 | $('.option').on('change', function(){ 22 | var optionName = $(this).data('option'); 23 | var optionVal = $(this).prop('checked'); 24 | _.each(DocumentCloud.embed.views.pages, function(containers, documentId) { 25 | _.each(containers, function(view, elementId) { 26 | if (view.openNote) { 27 | view.openNote.close(); 28 | } 29 | view.options[optionName] = optionVal; 30 | view.$el.html(view.render()); 31 | }); 32 | }); 33 | }); 34 | 35 | // Ajax-inserted embed code test 36 | $.get('embed_code.html', function(data) { 37 | $('.embed_via_ajax').html(data); 38 | }); 39 | 40 | } 41 | })(); 42 | -------------------------------------------------------------------------------- /examples/single.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DocumentCloud Page Embed Test 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 | 17 | 22 |
23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentcloud-pages", 3 | "version": "0.3.0", 4 | "description": "Responsively embed DocumentCloud pages", 5 | "main": "index.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/documentcloud/documentcloud-pages.git" 12 | }, 13 | "keywords": [ 14 | "documentcloud", 15 | "embedded", 16 | "3rdparty" 17 | ], 18 | "author": "", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/documentcloud/documentcloud-pages/issues" 22 | }, 23 | "homepage": "https://github.com/documentcloud/documentcloud-pages#readme", 24 | "dependencies": { 25 | "backbone": "1.3.3", 26 | "jquery-ajax-transport-xdomainrequest": "1.0.4", 27 | "jquery": "1.12.0", 28 | "underscore": "1.9.1" 29 | }, 30 | "engines": { 31 | "yarn": ">= 1.0.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/css/page_embed.scss: -------------------------------------------------------------------------------- 1 | //------------------------------ NOTES ----------------------------------------- 2 | // 3 | // - Modernizr classes are prefixed with `DC-m-` 4 | // - For styling breakpoints, we use classes like `.DC-embed-size-0` and 5 | // `.DC-embed-size-1` instead of media queries, since the latter would 6 | // only work on iframed pages. These are applied to the Backbone view element. 7 | // 8 | 9 | //------------------------------ MIXINS/VARIABLES ------------------------------ 10 | 11 | // Colors 12 | 13 | $error: rgb(255, 50, 50); 14 | $dcBrandBlue: rgb(90, 118, 160); 15 | $dcBrandBlueLight: rgb(119, 147, 189); 16 | $highlight: rgba(255, 247, 153, 0.5); 17 | $highlightLight: rgba(255, 247, 153, 0.2); 18 | $highlightDark: rgba(255, 247, 153, 0.8); 19 | $gray: rgb(204, 204, 204); 20 | $grayLight: rgb(235, 235, 235); 21 | $grayDark: rgb(115, 115, 115); 22 | $grayDim: rgba(0, 0, 0, 0.55); 23 | $grayDimmer: rgba(0, 0, 0, 0.25); 24 | $textColor: #000000; 25 | 26 | // Font weights 27 | 28 | $fontWeightLight: 200; 29 | $fontWeightNormal: 400; 30 | $fontWeightSemiBold: 500; 31 | $fontWeightBold: 700; 32 | 33 | // Font sizes 34 | 35 | $fontSizeXSmall: 8pt; 36 | $fontSizeSmall: 9pt; 37 | $fontSizeNormal: 10pt; 38 | $fontSizeLarge: 11pt; 39 | $fontSizeXLarge: 14pt; 40 | $fontSizeXXLarge: 20pt; 41 | $lineHeightXSmall: 11pt; 42 | $lineHeightSmall: 12pt; 43 | $lineHeightNormal: 14pt; 44 | $lineHeightLarge: 16pt; 45 | $lineHeightXLarge: 21pt; 46 | $lineHeightXXLarge: 28pt; 47 | 48 | // Speeds 49 | 50 | $speedFast: 100ms; 51 | $speedNormal: 200ms; 52 | 53 | // Minimum sizes for touch/click targets 54 | 55 | $touchHeightMin: 44px; 56 | $touchWidthMin: 44px; 57 | $clickHeightMin: 38px; 58 | $clickWidthMin: 38px; 59 | $buttonPaddingMin: 6px; 60 | $buttonPaddingMax: 12px; 61 | 62 | // Mixins 63 | 64 | @mixin full-size() { 65 | position: absolute; 66 | width: 100%; 67 | height: 100%; 68 | top: 0; 69 | left: 0; 70 | right: 0; 71 | bottom: 0; 72 | } 73 | 74 | @mixin full-width() { 75 | position: absolute; 76 | width: 100%; 77 | top: 0; 78 | left: 0; 79 | right: 0; 80 | } 81 | 82 | @mixin menubar() { 83 | position: relative; 84 | height: $touchHeightMin; 85 | width: 100%; 86 | overflow: hidden; 87 | 88 | text-transform: uppercase; 89 | font-weight: $fontWeightBold; 90 | font-size: $fontSizeXSmall; 91 | background-color: $dcBrandBlue; 92 | color: white; 93 | @include antialiased; 94 | 95 | .DC-m-no-touch & { 96 | height: $clickHeightMin; 97 | } 98 | 99 | > ul { 100 | position: absolute; 101 | top: 0; 102 | margin: 0; 103 | padding: 0; 104 | list-style-type: none; 105 | height: $touchHeightMin; 106 | white-space: nowrap; 107 | 108 | .DC-m-no-touch & { 109 | height: $clickHeightMin; 110 | } 111 | 112 | .DC-embed-size-1 & { 113 | position: relative; 114 | float: left; 115 | } 116 | 117 | > li { 118 | float: left; 119 | } 120 | } 121 | 122 | > ul > li > a, 123 | .DC-action-linklike { 124 | color: white; 125 | text-decoration: none; 126 | display: block; 127 | min-width: $touchWidthMin; 128 | height: $touchHeightMin; 129 | line-height: $touchHeightMin; 130 | vertical-align: middle; 131 | padding: 0 $buttonPaddingMax; 132 | text-align: center; 133 | 134 | &:visited { 135 | color: white; 136 | } 137 | 138 | .DC-m-no-touch & { 139 | min-width: $clickWidthMin; 140 | height: $clickHeightMin; 141 | line-height: $clickHeightMin; 142 | 143 | &:hover { 144 | background-color: $dcBrandBlueLight; 145 | } 146 | } 147 | 148 | .DC-embed-size-1 & { 149 | padding: 0; 150 | } 151 | } 152 | 153 | // Manual adjustment of icon to align properly with text 154 | .DC-icon { 155 | position: relative; 156 | font-size: $fontSizeXLarge; 157 | line-height: 0; 158 | top: 2px; 159 | } 160 | } 161 | 162 | @mixin actions-left() { 163 | left: 0; 164 | 165 | > li { 166 | margin-right: 1px; 167 | } 168 | } 169 | 170 | @mixin actions-center() { 171 | left: 40%; 172 | 173 | .DC-embed-size-1 & { 174 | left: 0; 175 | } 176 | 177 | > li { 178 | margin-right: 1px; 179 | } 180 | } 181 | 182 | @mixin actions-right() { 183 | right: 0; 184 | 185 | .DC-embed-size-1 & { 186 | float: right; 187 | } 188 | 189 | > li { 190 | margin-left: 1px; 191 | } 192 | } 193 | 194 | // I can imagine this being better than just a background color 195 | @mixin indicate-scrollbreaker() { 196 | background-color: $grayLight; 197 | } 198 | 199 | @mixin antialiased() { 200 | -moz-osx-font-smoothing: grayscale; 201 | -webkit-font-smoothing: antialiased; 202 | } 203 | 204 | @mixin appearance($appearance: none) { 205 | -webkit-appearance: $appearance; 206 | -moz-appearance: $appearance; 207 | appearance: $appearance; 208 | } 209 | 210 | 211 | //---------------------------------- STYLES ------------------------------------ 212 | 213 | .DC-embed-pixel-ping { 214 | position: absolute; 215 | z-index: 0; 216 | bottom: 0; 217 | right: 0; 218 | width: 1; 219 | height: 1; 220 | } 221 | 222 | .DC-embed-unloadable { 223 | background-color: $grayLight; 224 | color: $grayDark; 225 | padding: 10%; 226 | text-align: center; 227 | font-weight: $fontWeightSemiBold; 228 | font-size: $fontSizeXLarge; 229 | overflow: hidden; 230 | 231 | .DC-icon { 232 | display: block; 233 | font-size: $fontSizeXXLarge; 234 | line-height: inherit; 235 | margin-bottom: 0.25em; 236 | } 237 | } 238 | 239 | .DC-page-embed { 240 | position: relative; 241 | font: $fontWeightNormal #{$fontSizeNormal}/#{$lineHeightNormal} -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; 242 | 243 | -webkit-box-sizing: border-box; 244 | box-sizing: border-box; 245 | 246 | *, *:before, *:after { 247 | -webkit-box-sizing: border-box; 248 | box-sizing: border-box; 249 | } 250 | 251 | a { 252 | color: $dcBrandBlue; 253 | } 254 | 255 | a:visited { 256 | color: $dcBrandBlueLight; 257 | } 258 | 259 | .DC-logotype-link, 260 | .DC-logotype-link:visited { 261 | font-family: Gotham, inherit, sans-serif; 262 | font-weight: 700; 263 | color: inherit; 264 | text-decoration: none; 265 | } 266 | 267 | img { 268 | max-width: none; /* WHACKAMOLE: Override WordPress's `img` rules. */ 269 | } 270 | 271 | // Elements that default to invisible. 272 | &.DC-mode-image .DC-page-text, 273 | &.DC-mode-text .DC-note-overlay { 274 | display: none; 275 | } 276 | &.DC-mode-text .DC-page-image { 277 | visibility: hidden; 278 | } 279 | 280 | $logoHeight: 27px; 281 | 282 | .DC-meta { 283 | position: relative; 284 | min-height: $logoHeight; 285 | padding-right: 120px; 286 | margin-bottom: 0.5em; 287 | } 288 | 289 | .DC-title { 290 | display: inline; 291 | font-weight: $fontWeightNormal; 292 | font-size: $fontSizeLarge; 293 | line-height: $logoHeight; 294 | } 295 | 296 | .DC-resource-logomark-link { 297 | display: block; 298 | position: absolute; 299 | right: 0; 300 | text-indent: -10000px; 301 | overflow: hidden; 302 | background: transparent url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxOS4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCA1ODEuNSAxMDYuNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTgxLjUgMTA2LjQ7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoJLnN0MHtmaWxsOiNDRERGRjI7fQ0KPC9zdHlsZT4NCjxnPg0KCTxwYXRoIGQ9Ik0wLDU5LjhoMTYuNGMxMy4yLDAsMjIuMyw5LjEsMjIuMywyMC45djAuMWMwLDExLjgtOS4xLDIxLTIyLjMsMjFIMFY1OS44eiBNMTYuNCw5My41QzIzLjksOTMuNSwyOSw4OC40LDI5LDgxdi0wLjENCgkJYzAtNy40LTUuMS0xMi43LTEyLjctMTIuN0g5LjJ2MjUuM0gxNi40eiIvPg0KCTxwYXRoIGQ9Ik00NC40LDg1Ljl2LTAuMWMwLTkuMiw3LjQtMTYuNywxNy41LTE2LjdjMTAsMCwxNy4zLDcuNCwxNy4zLDE2LjZ2MC4xYzAsOS4yLTcuNCwxNi43LTE3LjUsMTYuNw0KCQlDNTEuOCwxMDIuNiw0NC40LDk1LjIsNDQuNCw4NS45eiBNNzAuMiw4NS45di0wLjFjMC00LjctMy40LTguOS04LjUtOC45Yy01LjIsMC04LjMsNC04LjMsOC44djAuMWMwLDQuNywzLjQsOC45LDguNSw4LjkNCgkJQzY3LjEsOTQuNyw3MC4yLDkwLjcsNzAuMiw4NS45eiIvPg0KCTxwYXRoIGQ9Ik04NC4yLDg1Ljl2LTAuMWMwLTkuMiw3LTE2LjcsMTYuOS0xNi43YzYuMSwwLDkuOCwyLDEyLjgsNS40bC01LjYsNmMtMi0yLjItNC4xLTMuNS03LjMtMy41Yy00LjYsMC03LjgsNC03LjgsOC44djAuMQ0KCQljMCw0LjksMy4yLDguOSw4LjIsOC45YzMuMSwwLDUuMi0xLjMsNy40LTMuNGw1LjMsNS40Yy0zLjEsMy40LTYuNyw1LjktMTMuMSw1LjlDOTEuMywxMDIuNiw4NC4yLDk1LjEsODQuMiw4NS45eiIvPg0KCTxwYXRoIGQ9Ik0xMTkuOCw5MC41VjY5LjdoOS4xdjE3LjljMCw0LjMsMiw2LjUsNS41LDYuNWMzLjUsMCw1LjctMi4yLDUuNy02LjVWNjkuN2g5LjF2MzIuMmgtOS4xdi00LjZjLTIuMSwyLjctNC44LDUuMi05LjQsNS4yDQoJCUMxMjMuOCwxMDIuNCwxMTkuOCw5Ny45LDExOS44LDkwLjV6Ii8+DQoJPHBhdGggZD0iTTE1Ny40LDY5LjdoOS4xdjQuNmMyLjEtMi43LDQuOS01LjIsOS41LTUuMmM0LjIsMCw3LjQsMS45LDkuMSw1LjFjMi44LTMuMyw2LjItNS4xLDEwLjYtNS4xYzYuOCwwLDEwLjksNC4xLDEwLjksMTEuOA0KCQl2MjAuOWgtOS4xVjgzLjljMC00LjMtMS45LTYuNS01LjMtNi41cy01LjUsMi4yLTUuNSw2LjV2MTcuOWgtOS4xVjgzLjljMC00LjMtMS45LTYuNS01LjMtNi41cy01LjUsMi4yLTUuNSw2LjV2MTcuOWgtOS4xVjY5Ljd6Ig0KCQkvPg0KCTxwYXRoIGQ9Ik0yMTIuOSw4NS45di0wLjFjMC05LjIsNi41LTE2LjcsMTUuOS0xNi43YzEwLjcsMCwxNS43LDguMywxNS43LDE3LjVjMCwwLjctMC4xLDEuNi0wLjEsMi40aC0yMi40DQoJCWMwLjksNC4xLDMuOCw2LjMsNy45LDYuM2MzLjEsMCw1LjMtMSw3LjgtMy4zbDUuMiw0LjZjLTMsMy43LTcuMyw2LTEzLjEsNkMyMjAsMTAyLjYsMjEyLjksOTUuOCwyMTIuOSw4NS45eiBNMjM1LjYsODMuMg0KCQljLTAuNS00LjEtMi45LTYuOC02LjgtNi44Yy0zLjgsMC02LjIsMi43LTcsNi44SDIzNS42eiIvPg0KCTxwYXRoIGQ9Ik0yNTEuMiw2OS43aDkuMXY0LjZjMi4xLTIuNyw0LjgtNS4yLDkuNC01LjJjNi45LDAsMTAuOSw0LjYsMTAuOSwxMS45djIwLjhoLTkuMVY4My45YzAtNC4zLTItNi41LTUuNS02LjUNCgkJYy0zLjUsMC01LjcsMi4yLTUuNyw2LjV2MTcuOWgtOS4xVjY5Ljd6Ii8+DQoJPHBhdGggZD0iTTI5MC4xLDkyLjdWNzcuNWgtMy44di03LjhoMy44di04LjJoOS4xdjguMmg3LjZ2Ny44aC03LjZ2MTMuN2MwLDIuMSwwLjksMy4xLDIuOSwzLjFjMS43LDAsMy4yLTAuNCw0LjUtMS4xdjcuMw0KCQljLTEuOSwxLjEtNC4xLDEuOS03LjIsMS45QzI5My45LDEwMi40LDI5MC4xLDEwMC4yLDI5MC4xLDkyLjd6Ii8+DQoJPHBhdGggZD0iTTMxMi42LDgxdi0wLjFjMC0xMS45LDktMjEuNywyMS45LTIxLjdjNy45LDAsMTIuNywyLjYsMTYuNiw2LjVsLTUuOSw2LjhjLTMuMi0yLjktNi41LTQuNy0xMC43LTQuNw0KCQljLTcuMSwwLTEyLjIsNS45LTEyLjIsMTMuMXYwLjFjMCw3LjIsNSwxMy4yLDEyLjIsMTMuMmM0LjgsMCw3LjctMS45LDExLTQuOWw1LjksNS45Yy00LjMsNC42LTkuMSw3LjUtMTcuMiw3LjUNCgkJQzMyMS44LDEwMi42LDMxMi42LDkzLDMxMi42LDgxeiIvPg0KCTxwYXRoIGQ9Ik0zNTguMiw1OGg5LjF2NDMuOGgtOS4xVjU4eiIvPg0KCTxwYXRoIGQ9Ik0zNzQuMyw4NS45di0wLjFjMC05LjIsNy40LTE2LjcsMTcuNS0xNi43YzEwLDAsMTcuMyw3LjQsMTcuMywxNi42djAuMWMwLDkuMi03LjQsMTYuNy0xNy41LDE2LjcNCgkJQzM4MS43LDEwMi42LDM3NC4zLDk1LjIsMzc0LjMsODUuOXogTTQwMC4xLDg1Ljl2LTAuMWMwLTQuNy0zLjQtOC45LTguNS04LjljLTUuMiwwLTguMyw0LTguMyw4Ljh2MC4xYzAsNC43LDMuNCw4LjksOC41LDguOQ0KCQlDMzk3LDk0LjcsNDAwLjEsOTAuNyw0MDAuMSw4NS45eiIvPg0KCTxwYXRoIGQ9Ik00MTUuNSw5MC41VjY5LjdoOS4xdjE3LjljMCw0LjMsMiw2LjUsNS41LDYuNWMzLjUsMCw1LjctMi4yLDUuNy02LjVWNjkuN2g5LjF2MzIuMmgtOS4xdi00LjZjLTIuMSwyLjctNC44LDUuMi05LjQsNS4yDQoJCUM0MTkuNSwxMDIuNCw0MTUuNSw5Ny45LDQxNS41LDkwLjV6Ii8+DQoJPHBhdGggZD0iTTQ1MS43LDg1Ljh2LTAuMWMwLTEwLjcsNy0xNi42LDE0LjYtMTYuNmM0LjksMCw3LjksMi4yLDEwLDQuOFY1OGg5LjF2NDMuOGgtOS4xdi00LjZjLTIuMiwzLTUuMyw1LjItMTAsNS4yDQoJCUM0NTguOCwxMDIuNCw0NTEuNyw5Ni42LDQ1MS43LDg1Ljh6IE00NzYuNSw4NS44di0wLjFjMC01LjMtMy41LTguOS03LjgtOC45cy03LjksMy41LTcuOSw4Ljl2MC4xYzAsNS4zLDMuNiw4LjksNy45LDguOQ0KCQlTNDc2LjUsOTEuMiw0NzYuNSw4NS44eiIvPg0KPC9nPg0KPGc+DQoJPGc+DQoJCTxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik00NDEuOSwzMC4zYzAsMCwwLDAtMC4xLTAuMWwwLDBjLTAuMS0wLjEtMC4yLTAuMy0wLjQtMC41bDAsMGMtMC40LTAuNC0xLTEtMS44LTEuOGwwLDANCgkJCWMtMS42LTEuNS0zLjktMy42LTYuOC01LjdsMCwwYy01LjgtNC4zLTE0LTguOC0yMy4xLTEwLjFsMCwwYy0yLjMtMC4zLTQuNS0wLjUtNi42LTAuNWwwLDBjLTE2LDAtMjQuMyw5LjItMjUsOS45bDAsMGwwLDANCgkJCWwtMy4yLDMuOWwtMy43LTMuNGMwLDAsMCwwLTAuMS0wLjFsMCwwYy0wLjEtMC4xLTAuMi0wLjItMC40LTAuM2wwLDBjLTAuNC0wLjMtMC45LTAuOC0xLjYtMS4zbDAsMGMtMS41LTEuMS0zLjctMi42LTYuNC00LjFsMCwwDQoJCQljLTUuNS0zLTEzLjQtNi0yMi41LTZsMCwwYy0wLjUsMC0xLDAtMS41LDBsMCwwYy01LjEsMC4yLTguOSwxLjItMTEuOSwyLjZsMCwwbC0zLjktOC41YzQuMi0xLjksOS4zLTMuMiwxNS40LTMuNGwwLDANCgkJCWMwLjYsMCwxLjMsMCwxLjksMGwwLDBjMTYuMiwwLjEsMjguMyw3LjQsMzMuOSwxMS42bDAsMGM0LjctNCwxNC4zLTEwLjEsMjguOS0xMC4xbDAsMGMyLjUsMCw1LjIsMC4yLDcuOSwwLjZsMCwwDQoJCQljMTUuNCwyLjMsMjcuMywxMS4zLDMzLjQsMTYuOGwwLDBjMS41LTIuMSwzLjYtNC42LDYuMy03LjFsMCwwYzctNi4zLDE4LjYtMTIuNSwzNS44LTEyLjRsMCwwYzEuNiwwLDMuMiwwLDQuOCwwLjJsMCwwDQoJCQljNS4xLDAuMywxMC41LDEsMTUuNiwyLjJsMCwwbC0yLjMsOS4xYy00LjItMS4xLTkuMS0xLjctMTMuOS0ybDAsMGMtMS41LTAuMS0yLjktMC4xLTQuMi0wLjFsMCwwYy0xNC42LDAtMjMuNSw0LjgtMjksOS42bDAsMA0KCQkJYy01LjUsNC44LTcuNyw5LjctNy44LDEwbDAsMGwwLDBMNDQ3LDM2TDQ0MS45LDMwLjNMNDQxLjksMzAuM3ogTTMyNi44LDEyLjdMMzI2LjgsMTIuN0wzMjYuOCwxMi43TDMyNi44LDEyLjdMMzI2LjgsMTIuNw0KCQkJTDMyNi44LDEyLjd6Ii8+DQoJPC9nPg0KCTxnPg0KCQk8cGF0aCBjbGFzcz0ic3QwIiBkPSJNNTcyLjIsMTA2LjRjLTAuMS0zLjMtMS03LjItMy4xLTExLjhsMCwwYy03LjQtMTYuMi0yMy41LTIzLjEtMjUuMS0yMy43bDAsMGMtMC4xLDAtMC4xLDAtMC4xLDBsMCwwDQoJCQlsLTQuNy0xLjdsMi00LjZsMCwwbDAsMGwwLDBjMCwwLDAsMCwwLTAuMWwwLDBjMC0wLjEsMC4xLTAuMiwwLjItMC41bDAsMGMwLjItMC40LDAuNC0xLjEsMC42LTJsMCwwYzAuNC0xLjgsMC45LTQuNCwwLjktNy43bDAsMA0KCQkJYzAtNS45LTEuNS0xMy43LTcuNS0yMi4zbDAsMGMtOS4xLTEzLjItMjYuMS0xOS4zLTMwLTIwLjRsMCwwYy0wLjYtMC4yLTAuOC0wLjItMC44LTAuMmwwLDBsMi4zLTkuMWMwLjQsMC4yLDIyLjksNS43LDM2LjEsMjQuMw0KCQkJbDAsMGM3LjIsMTAuMiw5LjIsMjAuMiw5LjIsMjcuN2wwLDBjMCwzLjktMC41LDcuMi0xLjEsOS41bDAsMGM2LjUsMy4zLDE5LjIsMTEuMywyNi40LDI2LjhsMCwwYzIuNiw1LjYsMy44LDExLjEsMy45LDE1LjciLz4NCgk8L2c+DQo8L2c+DQo8L3N2Zz4NCg==') right bottom no-repeat; 303 | background-size: contain; 304 | 305 | // Native dimensions: 582x107. Aspect ratio: 5.4392523364:1 306 | bottom: 5px; 307 | width: 109px; 308 | height: 20px; 309 | 310 | // Support: IE8- 311 | .DC-m-no-svg & { 312 | background-image: url('data:image/png;base64,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'); 313 | } 314 | 315 | } 316 | 317 | .DC-credit { 318 | font-size: $fontSizeSmall; 319 | text-align: center; 320 | margin-top: 1em; 321 | @include antialiased; 322 | } 323 | 324 | .DC-page { 325 | position: relative; 326 | border: 1px solid $gray; 327 | } 328 | &.DC-mode-image.open .DC-page { 329 | border-color: $grayDark; 330 | } 331 | 332 | .DC-m-touch &.DC-mode-text .DC-page { 333 | // Narrow scrollable areas to create scrollbreaker margins 334 | @include indicate-scrollbreaker; 335 | 336 | &:after { 337 | left: 12%; 338 | right: 12%; 339 | } 340 | } 341 | 342 | .DC-page-image { 343 | display: block; 344 | width: 100%; 345 | height: auto; 346 | z-index: 0; 347 | } 348 | 349 | .DC-page-text { 350 | @include full-size(); 351 | padding: 5%; 352 | z-index: 1; 353 | font: 400 #{$fontSizeNormal}/#{$lineHeightNormal} serif; 354 | background-color: white; 355 | color: $textColor; 356 | overflow-x: scroll; 357 | overflow-y: scroll; 358 | 359 | &.error, 360 | &.fetching { 361 | font-family: sans-serif; 362 | text-align: center; 363 | font-size: $fontSizeXLarge; 364 | line-height: $lineHeightXLarge; 365 | } 366 | 367 | &.error { 368 | color: $error; 369 | } 370 | 371 | &.fetching { 372 | color: $dcBrandBlue; 373 | } 374 | 375 | .DC-m-touch & { 376 | left: 10%; 377 | right: 10%; 378 | width: 80%; 379 | -webkit-overflow-scrolling: touch; 380 | } 381 | 382 | } 383 | 384 | &.DC-mode-image .DC-action-mode-image, 385 | &.DC-mode-text .DC-action-mode-text { 386 | background-color: $dcBrandBlueLight; 387 | } 388 | 389 | .DC-embed-menubar { 390 | @include menubar; 391 | 392 | > .DC-actions-mode { 393 | @include actions-left; 394 | } 395 | 396 | > .DC-actions-nav { 397 | @include actions-center; 398 | 399 | .DC-action-text 400 | { 401 | display: none; 402 | } 403 | } 404 | 405 | > .DC-actions-meta { 406 | @include actions-right; 407 | 408 | .DC-action-text { 409 | display: none; 410 | } 411 | } 412 | } 413 | 414 | 415 | // Page navigator 416 | 417 | .DC-action-nav-prev, 418 | .DC-action-nav-next { 419 | padding-left: $buttonPaddingMin !important; 420 | padding-right: $buttonPaddingMin !important; 421 | 422 | &[disabled] { 423 | opacity: 0.2; 424 | cursor: default; 425 | 426 | .DC-m-no-touch &:hover { 427 | background-color: inherit; 428 | } 429 | } 430 | } 431 | 432 | .DC-action-nav-select { 433 | background-color: transparent; 434 | display: block; 435 | height: $touchHeightMin; 436 | line-height: $touchHeightMin; 437 | vertical-align: middle; 438 | padding: 0 $buttonPaddingMin; 439 | border: 0; 440 | border-radius: 0; 441 | text-transform: inherit; 442 | font-size: inherit; 443 | font-weight: inherit; 444 | color: inherit; 445 | @include antialiased; 446 | @include appearance; 447 | 448 | &::-ms-expand { 449 | display: none; 450 | } 451 | 452 | .DC-m-no-touch & { 453 | height: $clickHeightMin; 454 | line-height: $clickHeightMin; 455 | 456 | &:hover { 457 | background-color: $dcBrandBlueLight; 458 | } 459 | } 460 | 461 | } 462 | 463 | 464 | // Notes 465 | 466 | .DC-note-overlay { 467 | @include full-size(); 468 | z-index: 1; 469 | } 470 | &.open .DC-note-overlay { 471 | background-color: $grayDim; 472 | 473 | .DC-m-no-touch & { 474 | cursor: pointer; 475 | } 476 | } 477 | 478 | .DC-note-wrapper { 479 | position: absolute; 480 | z-index: 1; 481 | overflow: visible; 482 | 483 | &.open { 484 | z-index: 1000; 485 | } 486 | 487 | } 488 | 489 | .DC-note-region { 490 | position: relative; 491 | width: 100%; 492 | height: 100%; 493 | background-color: $highlight; 494 | z-index: 1; 495 | 496 | .DC-m-no-touch & { 497 | cursor: pointer; 498 | 499 | &:hover { 500 | box-shadow: 0 1px 3px $grayDimmer; 501 | } 502 | } 503 | } 504 | .DC-m-no-touch & .DC-note-wrapper.open .DC-note-region { 505 | box-shadow: 0 1px 3px $grayDimmer; 506 | } 507 | &.open .DC-note-wrapper:not(.open) .DC-note-region { 508 | background-color: $highlightLight; 509 | } 510 | .DC-note-wrapper.open .DC-note-region { 511 | background-color: transparent; 512 | } 513 | 514 | .DC-note-image-wrapper { 515 | @include full-size(); 516 | overflow: hidden; 517 | } 518 | 519 | .DC-note-image { 520 | position: absolute; 521 | height: auto; 522 | display: none; 523 | } 524 | .DC-note-wrapper.open .DC-note-image { 525 | display: block; 526 | } 527 | 528 | .DC-note-body { 529 | position: absolute; 530 | overflow: hidden; 531 | cursor: default; 532 | opacity: 0; 533 | 534 | max-height: 0; 535 | min-width: 200px; 536 | width: 100%; 537 | max-width: 400px; 538 | } 539 | .DC-note-wrapper.open .DC-note-body { 540 | max-height: 200px; // Keep in sync with `NoteView.NOTE_MAX_HEIGHT` 541 | opacity: 1; 542 | } 543 | 544 | .DC-note-content { 545 | max-height: 156px; // `.DC-note-body:max-height` - `menubar:height` 546 | position: relative; 547 | width: 100%; 548 | background-color: $grayLight; 549 | color: $textColor; 550 | overflow-x: hidden; 551 | overflow-y: auto; 552 | padding: 0 1em 1em 1em; 553 | } 554 | 555 | .DC-note-menubar { 556 | @include menubar; 557 | 558 | box-shadow: 0 -1px 4px $grayDimmer; 559 | 560 | > .DC-actions-meta { 561 | @include actions-right; 562 | 563 | .DC-action-text 564 | { 565 | display: none; 566 | } 567 | } 568 | 569 | } 570 | 571 | .DC-note-title { 572 | font-weight: $fontWeightBold; 573 | text-transform: uppercase; 574 | font-size: $fontSizeXSmall; 575 | margin: 1.5em 0 0 0; 576 | 577 | > a { 578 | color: $textColor; 579 | text-decoration: none; 580 | &:hover { 581 | text-decoration: underline; 582 | } 583 | } 584 | 585 | &:empty { 586 | margin-top: 0; 587 | } 588 | 589 | } 590 | 591 | .DC-note-text { 592 | margin: 1em 0 0 0; 593 | &:empty { 594 | margin-top: 0; 595 | } 596 | } 597 | 598 | } 599 | 600 | // Size breakpoints: 601 | // For iframes, we use media queries. This is preferable, but we have to 602 | // hard-code the breakpoints into CSS instead of describing on the Page view. 603 | // For inline, we observe window resizing and set a class. This degrades 604 | // performance, but doesn't require hard-coding the breakpoints here in CSS. 605 | 606 | @mixin embedSize0() { 607 | .DC-page-embed { 608 | $margin: 0.5em; 609 | 610 | font-size: $fontSizeXSmall; 611 | line-height: $lineHeightXSmall; 612 | 613 | .DC-page-number, 614 | .DC-credit, 615 | .DC-resource-logomark-link, 616 | .DC-embed-menubar, 617 | .DC-note-overlay, 618 | .DC-page-text 619 | { 620 | display: none; 621 | } 622 | 623 | .DC-m-no-touch & { 624 | .DC-page { 625 | cursor: pointer; 626 | } 627 | } 628 | 629 | .DC-meta { 630 | padding-right: 0; 631 | margin-bottom: $margin; 632 | min-height: auto; 633 | } 634 | 635 | .DC-title { 636 | font-size: $fontSizeXSmall; 637 | line-height: $lineHeightXSmall; 638 | } 639 | } 640 | } 641 | 642 | @mixin embedSize1() { 643 | .DC-page-embed { 644 | $margin: 0.5em; 645 | 646 | font-size: $fontSizeSmall; 647 | line-height: $lineHeightSmall; 648 | 649 | .DC-resource-logomark-link, 650 | .DC-action-nav-pagecount, 651 | .DC-action-text 652 | { 653 | display: none; 654 | } 655 | 656 | .DC-meta { 657 | padding-right: 0; 658 | margin-bottom: $margin; 659 | min-height: auto; 660 | } 661 | 662 | .DC-title { 663 | font-size: $fontSizeSmall; 664 | line-height: $lineHeightSmall; 665 | } 666 | 667 | .DC-credit { 668 | font-size: $fontSizeXSmall; 669 | line-height: $lineHeightXSmall; 670 | margin-top: $margin; 671 | } 672 | } 673 | } 674 | 675 | .DC-embed-inline { 676 | &.DC-embed-size-0 { 677 | @include embedSize0; 678 | } 679 | 680 | &.DC-embed-size-1 { 681 | @include embedSize1; 682 | } 683 | } 684 | 685 | @media (max-width: 199px) { 686 | @include embedSize0; 687 | } 688 | 689 | @media (min-width: 200px) and (max-width: 399px) { 690 | @include embedSize1; 691 | } 692 | 693 | // In an iframe 694 | 695 | .DC-embed-iframe { 696 | margin: 0; 697 | padding: 0; 698 | overflow: hidden; 699 | > body { 700 | margin: 0; 701 | padding: 0; 702 | overflow: hidden; 703 | 704 | *, *:before, *:after { 705 | -webkit-box-sizing: border-box; 706 | box-sizing: border-box; 707 | } 708 | } 709 | 710 | .DC-embed-unloadable { 711 | position: absolute; 712 | top: 0; 713 | right: 0; 714 | left: 0; 715 | bottom: 0; 716 | width: 100%; 717 | height: 100%; 718 | } 719 | 720 | .DC-page-embed { 721 | @include full-size(); 722 | overflow: hidden; 723 | } 724 | 725 | .DC-meta, 726 | .DC-credit { 727 | display: none; 728 | } 729 | 730 | .DC-embed-menubar { 731 | @include full-width(); 732 | z-index: 10; 733 | } 734 | 735 | .DC-page { 736 | @include full-size(); 737 | border: 0; 738 | } 739 | } -------------------------------------------------------------------------------- /src/css/vendor/fontello/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Font license info 2 | 3 | 4 | ## Font Awesome 5 | 6 | Copyright (C) 2012 by Dave Gandy 7 | 8 | Author: Dave Gandy 9 | License: SIL () 10 | Homepage: http://fortawesome.github.com/Font-Awesome/ 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/css/vendor/fontello/animation.css: -------------------------------------------------------------------------------- 1 | /* 2 | Animation example, for spinners 3 | */ 4 | .animate-spin { 5 | -moz-animation: spin 2s infinite linear; 6 | -o-animation: spin 2s infinite linear; 7 | -webkit-animation: spin 2s infinite linear; 8 | animation: spin 2s infinite linear; 9 | display: inline-block; 10 | } 11 | @-moz-keyframes spin { 12 | 0% { 13 | -moz-transform: rotate(0deg); 14 | -o-transform: rotate(0deg); 15 | -webkit-transform: rotate(0deg); 16 | transform: rotate(0deg); 17 | } 18 | 19 | 100% { 20 | -moz-transform: rotate(359deg); 21 | -o-transform: rotate(359deg); 22 | -webkit-transform: rotate(359deg); 23 | transform: rotate(359deg); 24 | } 25 | } 26 | @-webkit-keyframes spin { 27 | 0% { 28 | -moz-transform: rotate(0deg); 29 | -o-transform: rotate(0deg); 30 | -webkit-transform: rotate(0deg); 31 | transform: rotate(0deg); 32 | } 33 | 34 | 100% { 35 | -moz-transform: rotate(359deg); 36 | -o-transform: rotate(359deg); 37 | -webkit-transform: rotate(359deg); 38 | transform: rotate(359deg); 39 | } 40 | } 41 | @-o-keyframes spin { 42 | 0% { 43 | -moz-transform: rotate(0deg); 44 | -o-transform: rotate(0deg); 45 | -webkit-transform: rotate(0deg); 46 | transform: rotate(0deg); 47 | } 48 | 49 | 100% { 50 | -moz-transform: rotate(359deg); 51 | -o-transform: rotate(359deg); 52 | -webkit-transform: rotate(359deg); 53 | transform: rotate(359deg); 54 | } 55 | } 56 | @-ms-keyframes spin { 57 | 0% { 58 | -moz-transform: rotate(0deg); 59 | -o-transform: rotate(0deg); 60 | -webkit-transform: rotate(0deg); 61 | transform: rotate(0deg); 62 | } 63 | 64 | 100% { 65 | -moz-transform: rotate(359deg); 66 | -o-transform: rotate(359deg); 67 | -webkit-transform: rotate(359deg); 68 | transform: rotate(359deg); 69 | } 70 | } 71 | @keyframes spin { 72 | 0% { 73 | -moz-transform: rotate(0deg); 74 | -o-transform: rotate(0deg); 75 | -webkit-transform: rotate(0deg); 76 | transform: rotate(0deg); 77 | } 78 | 79 | 100% { 80 | -moz-transform: rotate(359deg); 81 | -o-transform: rotate(359deg); 82 | -webkit-transform: rotate(359deg); 83 | transform: rotate(359deg); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/css/vendor/fontello/font/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentcloud/documentcloud-pages/178c7ee16f7b74e3cf9b0ffd46ded6d9521463ff/src/css/vendor/fontello/font/fontello.eot -------------------------------------------------------------------------------- /src/css/vendor/fontello/font/fontello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2015 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/css/vendor/fontello/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentcloud/documentcloud-pages/178c7ee16f7b74e3cf9b0ffd46ded6d9521463ff/src/css/vendor/fontello/font/fontello.ttf -------------------------------------------------------------------------------- /src/css/vendor/fontello/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentcloud/documentcloud-pages/178c7ee16f7b74e3cf9b0ffd46ded6d9521463ff/src/css/vendor/fontello/font/fontello.woff -------------------------------------------------------------------------------- /src/css/vendor/fontello/fontello-codes.css: -------------------------------------------------------------------------------- 1 | 2 | .DC-icon-list:before { content: '\e800'; } /* '' */ 3 | .DC-icon-list-bullet:before { content: '\e801'; } /* '' */ 4 | .DC-icon-share:before { content: '\e802'; } /* '' */ 5 | .DC-icon-external:before { content: '\e803'; } /* '' */ 6 | .DC-icon-link:before { content: '\e804'; } /* '' */ 7 | .DC-icon-info:before { content: '\e805'; } /* '' */ 8 | .DC-icon-info-circled:before { content: '\e806'; } /* '' */ 9 | .DC-icon-doc-text:before { content: '\e807'; } /* '' */ 10 | .DC-icon-doc-inv:before { content: '\e808'; } /* '' */ 11 | .DC-icon-doc-text-inv:before { content: '\e809'; } /* '' */ 12 | .DC-icon-text:before { content: '\e80a'; } /* '' */ 13 | .DC-icon-left:before { content: '\e80b'; } /* '' */ 14 | .DC-icon-right:before { content: '\e80c'; } /* '' */ 15 | .DC-icon-arrows-cw:before { content: '\e80d'; } /* '' */ 16 | .DC-icon-lock:before { content: '\e80e'; } /* '' */ 17 | .DC-icon-unlink:before { content: '\e80f'; } /* '' */ 18 | .DC-icon-help:before { content: '\e810'; } /* '' */ 19 | .DC-icon-cancel:before { content: '\e812'; } /* '' */ 20 | .DC-icon-attention:before { content: '\e813'; } /* '' */ -------------------------------------------------------------------------------- /src/css/vendor/fontello/fontello-embedded.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'DC-fontello'; 3 | src: url('font/fontello.eot?95584711'); 4 | src: url('font/fontello.eot?95584711#iefix') format('embedded-opentype'), 5 | url('font/fontello.svg?95584711#fontello') format('svg'); 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | @font-face { 10 | font-family: 'DC-fontello'; 11 | src: url('data:application/octet-stream;base64,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') format('woff'), 12 | url('data:application/octet-stream;base64,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RSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKxAAAqsQAFQrEACCqxAAVCsQAIKrEABUK5AAAACSqxAAVCuQAAAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbEADCq4Af+FsASNsQIARAA=') format('truetype'); 13 | } 14 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ 15 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ 16 | /* 17 | @media screen and (-webkit-min-device-pixel-ratio:0) { 18 | @font-face { 19 | font-family: 'DC-fontello'; 20 | src: url('font/fontello.svg?95584711#fontello') format('svg'); 21 | } 22 | } 23 | */ 24 | 25 | [class^="DC-icon-"]:before, [class*=" DC-icon-"]:before { 26 | font-family: "DC-fontello"; 27 | font-style: normal; 28 | font-weight: normal; 29 | speak: none; 30 | 31 | display: inline-block; 32 | text-decoration: inherit; 33 | width: 1em; 34 | margin-right: .2em; 35 | text-align: center; 36 | /* opacity: .8; */ 37 | 38 | /* For safety - reset parent styles, that can break glyph codes*/ 39 | font-variant: normal; 40 | text-transform: none; 41 | 42 | /* fix buttons height, for twitter bootstrap */ 43 | line-height: 1em; 44 | 45 | /* Animation center compensation - margins should be symmetric */ 46 | /* remove if not needed */ 47 | margin-left: .2em; 48 | 49 | /* you can be more comfortable with increased icons size */ 50 | /* font-size: 120%; */ 51 | 52 | /* Uncomment for 3D effect */ 53 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 54 | } 55 | .DC-icon-list:before { content: '\e800'; } /* '' */ 56 | .DC-icon-list-bullet:before { content: '\e801'; } /* '' */ 57 | .DC-icon-share:before { content: '\e802'; } /* '' */ 58 | .DC-icon-external:before { content: '\e803'; } /* '' */ 59 | .DC-icon-link:before { content: '\e804'; } /* '' */ 60 | .DC-icon-info:before { content: '\e805'; } /* '' */ 61 | .DC-icon-info-circled:before { content: '\e806'; } /* '' */ 62 | .DC-icon-doc-text:before { content: '\e807'; } /* '' */ 63 | .DC-icon-doc-inv:before { content: '\e808'; } /* '' */ 64 | .DC-icon-doc-text-inv:before { content: '\e809'; } /* '' */ 65 | .DC-icon-text:before { content: '\e80a'; } /* '' */ 66 | .DC-icon-left:before { content: '\e80b'; } /* '' */ 67 | .DC-icon-right:before { content: '\e80c'; } /* '' */ 68 | .DC-icon-arrows-cw:before { content: '\e80d'; } /* '' */ 69 | .DC-icon-lock:before { content: '\e80e'; } /* '' */ 70 | .DC-icon-unlink:before { content: '\e80f'; } /* '' */ 71 | .DC-icon-help:before { content: '\e810'; } /* '' */ 72 | .DC-icon-cancel:before { content: '\e812'; } /* '' */ 73 | .DC-icon-attention:before { content: '\e813'; } /* '' */ -------------------------------------------------------------------------------- /src/css/vendor/fontello/fontello-ie7-codes.css: -------------------------------------------------------------------------------- 1 | 2 | .DC-icon-list { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 3 | .DC-icon-list-bullet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 4 | .DC-icon-share { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 5 | .DC-icon-external { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 6 | .DC-icon-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 7 | .DC-icon-info { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 8 | .DC-icon-info-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 9 | .DC-icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 10 | .DC-icon-doc-inv { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 11 | .DC-icon-doc-text-inv { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 12 | .DC-icon-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 13 | .DC-icon-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 14 | .DC-icon-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 15 | .DC-icon-arrows-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 16 | .DC-icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 17 | .DC-icon-unlink { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 18 | .DC-icon-help { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 19 | .DC-icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 20 | .DC-icon-attention { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -------------------------------------------------------------------------------- /src/css/vendor/fontello/fontello-ie7.css: -------------------------------------------------------------------------------- 1 | [class^="DC-icon-"], [class*=" DC-icon-"] { 2 | font-family: 'DC-fontello'; 3 | font-style: normal; 4 | font-weight: normal; 5 | 6 | /* fix buttons height */ 7 | line-height: 1em; 8 | 9 | /* you can be more comfortable with increased icons size */ 10 | /* font-size: 120%; */ 11 | } 12 | 13 | .DC-icon-list { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 14 | .DC-icon-list-bullet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 15 | .DC-icon-share { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 16 | .DC-icon-external { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 17 | .DC-icon-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 18 | .DC-icon-info { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 19 | .DC-icon-info-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 20 | .DC-icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 21 | .DC-icon-doc-inv { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 22 | .DC-icon-doc-text-inv { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 23 | .DC-icon-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 24 | .DC-icon-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 25 | .DC-icon-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 26 | .DC-icon-arrows-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 27 | .DC-icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 28 | .DC-icon-unlink { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 29 | .DC-icon-help { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 30 | .DC-icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } 31 | .DC-icon-attention { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -------------------------------------------------------------------------------- /src/css/vendor/fontello/fontello.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'DC-fontello'; 3 | src: url('font/fontello.eot?31427133'); 4 | src: url('font/fontello.eot?31427133#iefix') format('embedded-opentype'), 5 | url('font/fontello.woff?31427133') format('woff'), 6 | url('font/fontello.ttf?31427133') format('truetype'), 7 | url('font/fontello.svg?31427133#fontello') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ 12 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ 13 | /* 14 | @media screen and (-webkit-min-device-pixel-ratio:0) { 15 | @font-face { 16 | font-family: 'DC-fontello'; 17 | src: url('font/fontello.svg?31427133#fontello') format('svg'); 18 | } 19 | } 20 | */ 21 | 22 | [class^="DC-icon-"]:before, [class*=" DC-icon-"]:before { 23 | font-family: "DC-fontello"; 24 | font-style: normal; 25 | font-weight: normal; 26 | speak: none; 27 | 28 | display: inline-block; 29 | text-decoration: inherit; 30 | width: 1em; 31 | margin-right: .2em; 32 | text-align: center; 33 | /* opacity: .8; */ 34 | 35 | /* For safety - reset parent styles, that can break glyph codes*/ 36 | font-variant: normal; 37 | text-transform: none; 38 | 39 | /* fix buttons height, for twitter bootstrap */ 40 | line-height: 1em; 41 | 42 | /* Animation center compensation - margins should be symmetric */ 43 | /* remove if not needed */ 44 | margin-left: .2em; 45 | 46 | /* you can be more comfortable with increased icons size */ 47 | /* font-size: 120%; */ 48 | 49 | /* Font smoothing. That was taken from TWBS */ 50 | -webkit-font-smoothing: antialiased; 51 | -moz-osx-font-smoothing: grayscale; 52 | 53 | /* Uncomment for 3D effect */ 54 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 55 | } 56 | 57 | .DC-icon-list:before { content: '\e800'; } /* '' */ 58 | .DC-icon-list-bullet:before { content: '\e801'; } /* '' */ 59 | .DC-icon-share:before { content: '\e802'; } /* '' */ 60 | .DC-icon-external:before { content: '\e803'; } /* '' */ 61 | .DC-icon-link:before { content: '\e804'; } /* '' */ 62 | .DC-icon-info:before { content: '\e805'; } /* '' */ 63 | .DC-icon-info-circled:before { content: '\e806'; } /* '' */ 64 | .DC-icon-doc-text:before { content: '\e807'; } /* '' */ 65 | .DC-icon-doc-inv:before { content: '\e808'; } /* '' */ 66 | .DC-icon-doc-text-inv:before { content: '\e809'; } /* '' */ 67 | .DC-icon-text:before { content: '\e80a'; } /* '' */ 68 | .DC-icon-left:before { content: '\e80b'; } /* '' */ 69 | .DC-icon-right:before { content: '\e80c'; } /* '' */ 70 | .DC-icon-arrows-cw:before { content: '\e80d'; } /* '' */ 71 | .DC-icon-lock:before { content: '\e80e'; } /* '' */ 72 | .DC-icon-unlink:before { content: '\e80f'; } /* '' */ 73 | .DC-icon-help:before { content: '\e810'; } /* '' */ 74 | .DC-icon-cancel:before { content: '\e812'; } /* '' */ 75 | .DC-icon-attention:before { content: '\e813'; } /* '' */ -------------------------------------------------------------------------------- /src/images/documentcloud-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentcloud/documentcloud-pages/178c7ee16f7b74e3cf9b0ffd46ded6d9521463ff/src/images/documentcloud-logo.png -------------------------------------------------------------------------------- /src/images/documentcloud-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 14 | 16 | 18 | 21 | 24 | 26 | 28 | 31 | 32 | 35 | 37 | 40 | 41 | 42 | 43 | 52 | 53 | 54 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/images/documentcloud-logotype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/documentcloud/documentcloud-pages/178c7ee16f7b74e3cf9b0ffd46ded6d9521463ff/src/images/documentcloud-logotype.png -------------------------------------------------------------------------------- /src/js/config/config.js.erb: -------------------------------------------------------------------------------- 1 | <% asset_root = DC.asset_root(:agnostic => true) %> 2 | (function(){ 3 | var ENV = window.ENV = window.ENV || {}; 4 | ENV.config = ENV.config || {}; 5 | ENV.config.embed = ENV.config.embed || { doc: {}, page: {}, note: {}, search: {} }; 6 | ENV.config.embed.page.assetPaths = { 7 | style: "<%= asset_root %>/embed/page/page_embed.css", 8 | app: "<%= asset_root %>/embed/page/page_embed.js" 9 | }; 10 | })(); 11 | -------------------------------------------------------------------------------- /src/js/enhance.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Enhance 3 | * A small library of components shared between both the enhance.js embed loader 4 | * and actual embed libraries. It should remain very small. 5 | * 6 | * Depends on either `setup.js` (for Underscore) or Penny. 7 | * 8 | * @license (c) 2015 Justin Reese, DocumentCloud 9 | * Enhance may be freely distributed under the MIT license. 10 | * 11 | */ 12 | 13 | (function(){ 14 | 15 | Penny.ready(function(){ 16 | 17 | if (!window.DCEmbedToolbelt) { 18 | console.error("DocumentCloud embed can't load because of missing components."); 19 | return; 20 | } 21 | 22 | // Insert the necessary stylesheet into the head, unless it's already there. 23 | var insertStylesheet = function(href) { 24 | if (!document.querySelector('link[href$="' + href + '"]')) { 25 | var stylesheet = document.createElement('link'); 26 | stylesheet.rel = 'stylesheet'; 27 | stylesheet.type = 'text/css'; 28 | stylesheet.media = 'screen'; 29 | stylesheet.href = href; 30 | document.querySelector('head').appendChild(stylesheet); 31 | } 32 | }; 33 | 34 | // Insert the necessary JavaScript into the body, unless it's already there. 35 | var insertJavaScript = function(src, onLoadCallback) { 36 | if (!document.querySelector('script[src$="' + src + '"]')) { 37 | var script = document.createElement('script'); 38 | script.src = src; 39 | script.async = true; 40 | Penny.on(script, 'load', onLoadCallback); 41 | document.querySelector('body').appendChild(script); 42 | } 43 | }; 44 | 45 | // Takes an embed stub DOM element and looks for a `data-options` attribute 46 | // that contains a JSON representation of options. 47 | var extractOptionsFromStub = function(stub) { 48 | var options = stub.getAttribute('data-options'); 49 | if (options) { 50 | try { 51 | options = JSON.parse(options); 52 | } 53 | catch(err) { 54 | console.error("Inline DocumentCloud embed options must be valid JSON. See https://www.documentcloud.org/help/publishing."); 55 | options = {}; 56 | } 57 | } else { 58 | options = {}; 59 | } 60 | return options; 61 | }; 62 | 63 | // Convert embed stubs to real live embeds. If this fails, stubs remain 64 | // usable as effectively `noscript` representations of the embed. 65 | var enhanceStubs = function() { 66 | // `enhanceStubs() will only be called after `window.DocumentCloud` is 67 | // confirmed available. 68 | var DocumentCloud = window.DocumentCloud; 69 | 70 | var stubs = document.querySelectorAll('.DC-embed[data-version^="1."]'); 71 | Penny.each(stubs, function (stub, i) { 72 | if (stub.className.indexOf('DC-embed-enhanced') != -1) { return; } 73 | var resourceElement = stub.querySelector('.DC-embed-resource'); 74 | var resourceUrl = resourceElement.getAttribute('href'); 75 | var resource = DCEmbedToolbelt.recognizeResource(resourceUrl); 76 | if (!Penny.isEmpty(resource)) { 77 | // Changing the class name means subsequent runs of the loader will 78 | // recognize this element has already been enhanced and won't redo it. 79 | stub.className += ' DC-embed-enhanced'; 80 | stub.setAttribute('data-resource-type', resource.resourceType); 81 | 82 | // Options come from three places: 83 | // 1. JSON hash passed in via the stub's `data-options` attribute 84 | // 2. Resource-specific options composed in `recognizeResource()` 85 | // 3. Resource-agnostic options 86 | var embedOptions = Penny.extend({}, 87 | extractOptionsFromStub(stub), 88 | resource.embedOptions, 89 | { container: stub } 90 | ); 91 | 92 | DocumentCloud.embed.load(resource, embedOptions); 93 | } else { 94 | console.error("The DocumentCloud URL you're trying to embed doesn't look right. Please generate a new embed code."); 95 | } 96 | }); 97 | }; 98 | 99 | // Combine default config with environment-set config 100 | var loadConfig = function() { 101 | var defaultConfig = { 102 | page: { 103 | assetPaths: { 104 | app: "../dist/page_embed.js", 105 | style: "../dist/page_embed.css" 106 | } 107 | } 108 | }; 109 | // Safely try to access potentially undefined config options 110 | try { var envConfig = window.ENV.config.embed; } 111 | catch (e) { var envConfig = {}; } 112 | return Penny.extend({}, defaultConfig, envConfig); 113 | }; 114 | 115 | // Definitions are complete. Do things! 116 | 117 | // TODO: Support more resource types; will have to scan the DOM for all 118 | // embed types before enhancing. 119 | var config = loadConfig(); 120 | insertStylesheet(config.page.assetPaths.style); 121 | if (window.DocumentCloud) { 122 | enhanceStubs(); 123 | } else { 124 | insertJavaScript(config.page.assetPaths.app, enhanceStubs); 125 | } 126 | 127 | }); 128 | 129 | })(); 130 | -------------------------------------------------------------------------------- /src/js/legacy/modernizr.custom.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.8.3 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-svg-touch-shiv-cssclasses-cssclassprefix:DC!m! 3 | */ 4 | ; 5 | 6 | 7 | 8 | window.Modernizr = (function( window, document, undefined ) { 9 | 10 | var version = '2.8.3', 11 | 12 | Modernizr = {}, 13 | 14 | enableClasses = true, 15 | 16 | docElement = document.documentElement, 17 | 18 | mod = 'modernizr', 19 | modElem = document.createElement(mod), 20 | mStyle = modElem.style, 21 | 22 | inputElem , 23 | 24 | 25 | toString = {}.toString, 26 | 27 | prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), 28 | 29 | 30 | ns = {'svg': 'http://www.w3.org/2000/svg'}, 31 | 32 | tests = {}, 33 | inputs = {}, 34 | attrs = {}, 35 | 36 | classes = [], 37 | 38 | slice = classes.slice, 39 | 40 | featureName, 41 | 42 | 43 | injectElementWithStyles = function( rule, callback, nodes, testnames ) { 44 | 45 | var style, ret, node, docOverflow, 46 | div = document.createElement('div'), 47 | body = document.body, 48 | fakeBody = body || document.createElement('body'); 49 | 50 | if ( parseInt(nodes, 10) ) { 51 | while ( nodes-- ) { 52 | node = document.createElement('div'); 53 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1); 54 | div.appendChild(node); 55 | } 56 | } 57 | 58 | style = ['­',''].join(''); 59 | div.id = mod; 60 | (body ? div : fakeBody).innerHTML += style; 61 | fakeBody.appendChild(div); 62 | if ( !body ) { 63 | fakeBody.style.background = ''; 64 | fakeBody.style.overflow = 'hidden'; 65 | docOverflow = docElement.style.overflow; 66 | docElement.style.overflow = 'hidden'; 67 | docElement.appendChild(fakeBody); 68 | } 69 | 70 | ret = callback(div, rule); 71 | if ( !body ) { 72 | fakeBody.parentNode.removeChild(fakeBody); 73 | docElement.style.overflow = docOverflow; 74 | } else { 75 | div.parentNode.removeChild(div); 76 | } 77 | 78 | return !!ret; 79 | 80 | }, 81 | _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; 82 | 83 | if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { 84 | hasOwnProp = function (object, property) { 85 | return _hasOwnProperty.call(object, property); 86 | }; 87 | } 88 | else { 89 | hasOwnProp = function (object, property) { 90 | return ((property in object) && is(object.constructor.prototype[property], 'undefined')); 91 | }; 92 | } 93 | 94 | 95 | if (!Function.prototype.bind) { 96 | Function.prototype.bind = function bind(that) { 97 | 98 | var target = this; 99 | 100 | if (typeof target != "function") { 101 | throw new TypeError(); 102 | } 103 | 104 | var args = slice.call(arguments, 1), 105 | bound = function () { 106 | 107 | if (this instanceof bound) { 108 | 109 | var F = function(){}; 110 | F.prototype = target.prototype; 111 | var self = new F(); 112 | 113 | var result = target.apply( 114 | self, 115 | args.concat(slice.call(arguments)) 116 | ); 117 | if (Object(result) === result) { 118 | return result; 119 | } 120 | return self; 121 | 122 | } else { 123 | 124 | return target.apply( 125 | that, 126 | args.concat(slice.call(arguments)) 127 | ); 128 | 129 | } 130 | 131 | }; 132 | 133 | return bound; 134 | }; 135 | } 136 | 137 | function setCss( str ) { 138 | mStyle.cssText = str; 139 | } 140 | 141 | function setCssAll( str1, str2 ) { 142 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); 143 | } 144 | 145 | function is( obj, type ) { 146 | return typeof obj === type; 147 | } 148 | 149 | function contains( str, substr ) { 150 | return !!~('' + str).indexOf(substr); 151 | } 152 | 153 | 154 | function testDOMProps( props, obj, elem ) { 155 | for ( var i in props ) { 156 | var item = obj[props[i]]; 157 | if ( item !== undefined) { 158 | 159 | if (elem === false) return props[i]; 160 | 161 | if (is(item, 'function')){ 162 | return item.bind(elem || obj); 163 | } 164 | 165 | return item; 166 | } 167 | } 168 | return false; 169 | } 170 | tests['touch'] = function() { 171 | var bool; 172 | 173 | if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { 174 | bool = true; 175 | } else { 176 | injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { 177 | bool = node.offsetTop === 9; 178 | }); 179 | } 180 | 181 | return bool; 182 | }; 183 | tests['svg'] = function() { 184 | return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; 185 | }; 186 | for ( var feature in tests ) { 187 | if ( hasOwnProp(tests, feature) ) { 188 | featureName = feature.toLowerCase(); 189 | Modernizr[featureName] = tests[feature](); 190 | 191 | classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); 192 | } 193 | } 194 | 195 | 196 | 197 | Modernizr.addTest = function ( feature, test ) { 198 | if ( typeof feature == 'object' ) { 199 | for ( var key in feature ) { 200 | if ( hasOwnProp( feature, key ) ) { 201 | Modernizr.addTest( key, feature[ key ] ); 202 | } 203 | } 204 | } else { 205 | 206 | feature = feature.toLowerCase(); 207 | 208 | if ( Modernizr[feature] !== undefined ) { 209 | return Modernizr; 210 | } 211 | 212 | test = typeof test == 'function' ? test() : test; 213 | 214 | if (typeof enableClasses !== "undefined" && enableClasses) { 215 | docElement.className+=" DC-m-" + (test ? '' : 'no-') + feature; 216 | } 217 | Modernizr[feature] = test; 218 | 219 | } 220 | 221 | return Modernizr; 222 | }; 223 | 224 | 225 | setCss(''); 226 | modElem = inputElem = null; 227 | 228 | ;(function(window, document) { 229 | var version = '3.7.0'; 230 | 231 | var options = window.html5 || {}; 232 | 233 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 234 | 235 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; 236 | 237 | var supportsHtml5Styles; 238 | 239 | var expando = '_html5shiv'; 240 | 241 | var expanID = 0; 242 | 243 | var expandoData = {}; 244 | 245 | var supportsUnknownElements; 246 | 247 | (function() { 248 | try { 249 | var a = document.createElement('a'); 250 | a.innerHTML = ''; 251 | supportsHtml5Styles = ('hidden' in a); 252 | 253 | supportsUnknownElements = a.childNodes.length == 1 || (function() { 254 | (document.createElement)('a'); 255 | var frag = document.createDocumentFragment(); 256 | return ( 257 | typeof frag.cloneNode == 'undefined' || 258 | typeof frag.createDocumentFragment == 'undefined' || 259 | typeof frag.createElement == 'undefined' 260 | ); 261 | }()); 262 | } catch(e) { 263 | supportsHtml5Styles = true; 264 | supportsUnknownElements = true; 265 | } 266 | 267 | }()); 268 | 269 | function addStyleSheet(ownerDocument, cssText) { 270 | var p = ownerDocument.createElement('p'), 271 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 272 | 273 | p.innerHTML = 'x'; 274 | return parent.insertBefore(p.lastChild, parent.firstChild); 275 | } 276 | 277 | function getElements() { 278 | var elements = html5.elements; 279 | return typeof elements == 'string' ? elements.split(' ') : elements; 280 | } 281 | 282 | function getExpandoData(ownerDocument) { 283 | var data = expandoData[ownerDocument[expando]]; 284 | if (!data) { 285 | data = {}; 286 | expanID++; 287 | ownerDocument[expando] = expanID; 288 | expandoData[expanID] = data; 289 | } 290 | return data; 291 | } 292 | 293 | function createElement(nodeName, ownerDocument, data){ 294 | if (!ownerDocument) { 295 | ownerDocument = document; 296 | } 297 | if(supportsUnknownElements){ 298 | return ownerDocument.createElement(nodeName); 299 | } 300 | if (!data) { 301 | data = getExpandoData(ownerDocument); 302 | } 303 | var node; 304 | 305 | if (data.cache[nodeName]) { 306 | node = data.cache[nodeName].cloneNode(); 307 | } else if (saveClones.test(nodeName)) { 308 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 309 | } else { 310 | node = data.createElem(nodeName); 311 | } 312 | 313 | return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; 314 | } 315 | 316 | function createDocumentFragment(ownerDocument, data){ 317 | if (!ownerDocument) { 318 | ownerDocument = document; 319 | } 320 | if(supportsUnknownElements){ 321 | return ownerDocument.createDocumentFragment(); 322 | } 323 | data = data || getExpandoData(ownerDocument); 324 | var clone = data.frag.cloneNode(), 325 | i = 0, 326 | elems = getElements(), 327 | l = elems.length; 328 | for(;i',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},v={}.hasOwnProperty,w;!z(v,"undefined")&&!z(v.call,"undefined")?w=function(a,b){return v.call(a,b)}:w=function(a,b){return b in a&&z(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=s.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(s.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(s.call(arguments)))};return e}),o.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:u(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},o.svg=function(){return!!b.createElementNS&&!!b.createElementNS(n.svg,"svg").createSVGRect};for(var C in o)w(o,C)&&(t=C.toLowerCase(),e[t]=o[C](),r.push((e[t]?"":"no-")+t));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)w(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" DC-m-"+(b?"":"no-")+a),e[a]=b}return e},x(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._prefixes=m,e.testStyles=u,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" DC-m-js DC-m-"+r.join(" DC-m-"):""),e}(this,this.document); -------------------------------------------------------------------------------- /src/js/loaders/page.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var DCEmbedToolbelt = window.DCEmbedToolbelt; 3 | var DocumentCloud = window.DocumentCloud; 4 | var $ = DocumentCloud.$; 5 | var _ = DocumentCloud._; 6 | 7 | var definition = DocumentCloud.embed.definition; 8 | var data = DocumentCloud.embed.data; 9 | var views = DocumentCloud.embed.views; 10 | 11 | data.documents = data.documents || new definition.DocumentSet(); 12 | // `views.pages` is a nested list of page views, keyed at the top level by 13 | // document ID and then by element ID (sans `#`), e.g.: 14 | // `views.pages['1234']['foo']`. You could target the element with 15 | // `document.getElementById('foo')`. 16 | views.pages = views.pages || {}; 17 | 18 | if (!_.isFunction(DocumentCloud.embed.load)) { 19 | DocumentCloud.embed.load = function(resource, options) { 20 | options = options || {}; 21 | resource = DCEmbedToolbelt.recognizeResource(resource); 22 | 23 | var container = DCEmbedToolbelt.toDomElement(options.container); 24 | if (!container) { 25 | console.error("DocumentCloud can't be embedded without specifying a container."); 26 | return; 27 | } 28 | 29 | var isIframed = DCEmbedToolbelt.isIframed(); 30 | if (isIframed) { 31 | $('html').addClass('DC-embed-iframe'); 32 | } 33 | 34 | var documentId = resource.documentId; 35 | var doc = new definition.Document({id: documentId}); 36 | doc.fetch({ 37 | url: resource.dataUrl, 38 | success: function() { 39 | // We don't want the top-level container to be our view element, so 40 | // create one. 41 | var viewElementId = DCEmbedToolbelt.generateUniqueElementId(resource); 42 | container.innerHTML = '
'; 43 | var viewElement = document.getElementById(viewElementId); 44 | 45 | // Create page view 46 | var pagePrototype = definition.PageView.prototype; 47 | var validOptionKeys = pagePrototype.validOptionKeys; 48 | var embedOptions = _.extend({}, _.pick(options, validOptionKeys), 49 | resource.embedOptions, 50 | {model: doc, el: viewElement}); 51 | var view = new definition.PageView(embedOptions); 52 | 53 | // Store doc model and page view on globals 54 | data.documents.add(doc); 55 | views.pages[documentId] = views.pages[documentId] || {}; 56 | views.pages[documentId][viewElementId] = view; 57 | 58 | // Track where the embed is loaded from 59 | if (options.preview !== true) { 60 | DCEmbedToolbelt.pixelPing(resource, viewElement); 61 | } 62 | 63 | if (!isIframed) { 64 | // We tweak the interface lightly based on the width of the embed; 65 | // in non-iframe contexts, this requires observing window resizes. 66 | var $el = $(viewElement); 67 | var sizeBreakpoints = pagePrototype.sizeBreakpoints; 68 | var setEmbedSizeClasses = _.debounce(function(e) { 69 | var width = $el.width(); 70 | _.each(sizeBreakpoints, function(breakpoints, i) { 71 | $el.toggleClass('DC-embed-size-' + i, (width >= breakpoints[0] && width <= breakpoints[1])); 72 | }); 73 | }, 250); 74 | $(window).on('resize', setEmbedSizeClasses); 75 | setEmbedSizeClasses(); 76 | } 77 | }, 78 | error: function(model, response) { 79 | var icon, message, replace; 80 | switch (response.status) { 81 | case 403: 82 | icon = 'lock'; 83 | message = 'This DocumentCloud document is private and can only be viewed by its owner.'; 84 | replace = 'all'; 85 | break; 86 | case 404: 87 | icon = 'help'; 88 | message = 'DocumentCloud can’t find this document.'; 89 | replace = 'image'; 90 | break; 91 | default: 92 | icon = 'cancel'; 93 | message = 'DocumentCloud can’t load this document.'; 94 | replace = 'image'; 95 | break; 96 | } 97 | message = '
' + message + '
'; 98 | 99 | switch (replace) { 100 | case 'all': 101 | container.innerHTML = message; 102 | break; 103 | default: 104 | container.querySelector('img').outerHTML = message; 105 | break; 106 | } 107 | // TODO: Notify us of the load error via pixel ping or something [JR] 108 | } 109 | }); 110 | }; 111 | } 112 | 113 | })(); 114 | -------------------------------------------------------------------------------- /src/js/models/document.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var DocumentCloud = window.DocumentCloud; 3 | var $ = DocumentCloud.$; 4 | var _ = DocumentCloud._; 5 | var Backbone = DocumentCloud.Backbone; 6 | 7 | var definition = DocumentCloud.embed.definition; 8 | var data = DocumentCloud.embed.data; 9 | var views = DocumentCloud.embed.views; 10 | 11 | definition.Document = definition.Document || Backbone.Model.extend({ 12 | initialize: function(attributes){ 13 | this.notes = new definition.NoteSet(); 14 | //this.resources = new definition.ResourceSet(); // doesn't exist yet. 15 | this.on('sync', this.updateCollections, this); 16 | }, 17 | 18 | updateCollections: function() { 19 | this.notes.reset(this.get('annotations')); 20 | }, 21 | 22 | imageUrl : function(pageNumber, size) { 23 | size = size || 'normal'; 24 | var resources = this.get('resources'); 25 | var urlTemplate = resources['page']['image']; 26 | return urlTemplate.replace('{size}', size).replace('{page}', pageNumber).replace(/^https?:/, ''); 27 | }, 28 | 29 | textUrl : function(pageNumber) { 30 | var resources = this.get('resources'); 31 | return resources['text']; 32 | }, 33 | 34 | hasMultiplePages: function() { 35 | return this.get('pages') > 1; 36 | }, 37 | 38 | publishedUrl: function() { 39 | var resources = this.get('resources'); 40 | return resources['published_url'] || this.get('canonical_url'); 41 | }, 42 | 43 | pagePublishedUrl: function(pageNumber) { 44 | return this.publishedUrl() + '#document/p' + pageNumber; 45 | }, 46 | 47 | pageContextualUrl: function(pageNumber) { 48 | return this.get('canonical_url') + '#document/p' + pageNumber; 49 | }, 50 | 51 | // Link to page text within platform. Composed with `canonical_url`, not 52 | // `published_url`, because `published_url` might land us on remote page 53 | // where text mode is disabled. 54 | pageTextUrl: function(pageNumber) { 55 | return this.get('canonical_url') + '#text/p' + pageNumber; 56 | }, 57 | 58 | // URL for page text file 59 | pageTextFileUrl: function(pageNumber) { 60 | var resources = this.get('resources'); 61 | return resources['page']['text'].replace('{page}', pageNumber); 62 | }, 63 | 64 | }); 65 | 66 | definition.DocumentSet = definition.DocumentSet || Backbone.Collection.extend({ 67 | model: definition.Document 68 | }); 69 | 70 | })(); 71 | -------------------------------------------------------------------------------- /src/js/models/note.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var DocumentCloud = window.DocumentCloud; 3 | var $ = DocumentCloud.$; 4 | var _ = DocumentCloud._; 5 | var Backbone = DocumentCloud.Backbone; 6 | 7 | var definition = DocumentCloud.embed.definition; 8 | var data = DocumentCloud.embed.data; 9 | var views = DocumentCloud.embed.views; 10 | 11 | definition.Note = Backbone.Model.extend({ 12 | 13 | pageAnchor: function() { 14 | var id = this.get('id'); 15 | var page = this.get('page'); 16 | return '#document/p' + page + '/a' + id; 17 | }, 18 | 19 | // Parses the coordinates in pixel value and calculates pixel width/height 20 | coordinates: function(force){ 21 | if (!this._coordinates || force) { 22 | var css = _.map(this.get('location').image.split(','), function(num){ return parseInt(num, 10); }); 23 | this._coordinates = { 24 | top: css[0], 25 | left: css[3], 26 | right: css[1], 27 | height: css[2] - css[0], 28 | width: css[1] - css[3], 29 | }; 30 | this.transformCoordinatesToLegacy(); 31 | } 32 | return this._coordinates; 33 | }, 34 | 35 | // The existing note viewer transforms stored note dimensions before 36 | // rendering. Replicate those transformations here for compatibility. 37 | transformCoordinatesToLegacy: function() { 38 | var adjustments = { 39 | top: 1, 40 | left: -2, 41 | width: -8, 42 | }; 43 | this._coordinates = _.mapObject(this._coordinates, function(val, key) { 44 | return _.has(adjustments, key) ? val + adjustments[key] : val; 45 | }); 46 | }, 47 | 48 | // Calculate the coordinates as a fraction of the parent. E.g. a 100px wide 49 | // note on a 500px wide page has a width of `0.2`. 50 | fractionalCoordinates: function(pageDimensions) { 51 | var _coordinates = this.coordinates(); 52 | return { 53 | top: (_coordinates.top / pageDimensions.height), 54 | left: (_coordinates.left / pageDimensions.width), 55 | right: (_coordinates.right / pageDimensions.width), 56 | height: (_coordinates.height / pageDimensions.height), 57 | width: (_coordinates.width / pageDimensions.width), 58 | }; 59 | }, 60 | 61 | // Convert the fractional coordinates (e.g., `0.2`) into percentage strings 62 | // (e.g., `'200%'`). 63 | percentageCoordinates: function(pageDimensions) { 64 | var _coordinates = this.fractionalCoordinates(pageDimensions); 65 | return _.mapObject(_coordinates, function(coordinate) { 66 | return coordinate * 100 + '%'; 67 | }); 68 | }, 69 | 70 | // Compose coordinates necessary to position the note excerpt image. Tricky 71 | // math. 72 | imageCoordinates: function(pageDimensions) { 73 | var _coordinates = this.fractionalCoordinates(pageDimensions); 74 | return { 75 | width: 1 / _coordinates.width * 100 + '%', 76 | left: _coordinates.left/_coordinates.width * -100 + '%', 77 | top: _coordinates.top/_coordinates.height * -100 + '%', 78 | }; 79 | }, 80 | 81 | }); 82 | 83 | definition.NoteSet = Backbone.Collection.extend({ 84 | model: definition.Note, 85 | forPage: function(number) { 86 | return this.select(function(note){ 87 | return note.has('location') && note.get('page') == number; 88 | }); 89 | } 90 | }); 91 | })(); 92 | -------------------------------------------------------------------------------- /src/js/util/penny.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Penny 0.0.0 3 | * Sometimes you only need a fraction of a $ or _. 4 | * Thanks to http://youmightnotneedjquery.com, Underscore, and Stack Overflow. 5 | * 6 | * The version number is not a typo, it's a statement: you should not use Penny. 7 | * It is not a library, it is a customized minimal set of functions for a very 8 | * specific use case. You should totally make your own Penny for your own case. 9 | * 10 | * @license (c) 2015 Justin Reese, DocumentCloud 11 | * Penny may be freely distributed under the MIT license, but why would you? 12 | * 13 | */ 14 | 15 | (function(){ 16 | 17 | var Penny = window.Penny = window.Penny || { 18 | 19 | VERSION: '0.0.0', 20 | 21 | on: function (el, eventName, handler) { 22 | if (el.addEventListener) { 23 | el.addEventListener(eventName, handler); 24 | } else { 25 | el.attachEvent('on' + eventName, function() { 26 | handler.call(el); 27 | }); 28 | } 29 | }, 30 | 31 | ready: function (fn) { 32 | if (document.readyState != 'loading') { 33 | fn(); 34 | } else if (document.addEventListener) { 35 | document.addEventListener('DOMContentLoaded', fn); 36 | } else { 37 | document.attachEvent('onreadystatechange', function() { 38 | if (document.readyState != 'loading') { 39 | fn(); 40 | } 41 | }); 42 | } 43 | }, 44 | 45 | each: function (collection, fn) { 46 | if (collection != null && typeof collection === 'object' && !Penny.isArrayLike(collection)) { 47 | for (var key in collection) { 48 | if (Penny.has(collection, key)) { 49 | fn(collection[key], key); 50 | } 51 | } 52 | } else { 53 | for (var i = 0, len = collection.length; i < len; i++) { 54 | fn(collection[i], i); 55 | } 56 | } 57 | }, 58 | 59 | has: function(obj, key) { 60 | return obj != null && Object.prototype.hasOwnProperty.call(obj, key); 61 | }, 62 | 63 | values: function (obj) { 64 | var values = []; 65 | for (var key in obj) { 66 | if (Penny.has(obj, key)) { 67 | values.push(obj[key]); 68 | } 69 | } 70 | return values; 71 | }, 72 | 73 | keys: function (obj) { 74 | var keys = []; 75 | for (var key in obj) { 76 | if (Penny.has(obj, key)) { 77 | keys.push(obj[key]); 78 | } 79 | } 80 | return keys; 81 | }, 82 | 83 | findKey: function(obj, fn) { 84 | for (var key in obj) { 85 | if (Penny.has(obj, key)) { 86 | if (fn(obj[key], key)) { 87 | return key; 88 | } 89 | } 90 | } 91 | return null; 92 | }, 93 | 94 | extend: function(out) { 95 | out = out || {}; 96 | 97 | for (var i = 1, len = arguments.length; i < len; i++) { 98 | if (!arguments[i]) { 99 | continue; 100 | } 101 | 102 | for (var key in arguments[i]) { 103 | if (arguments[i].hasOwnProperty(key)) { 104 | out[key] = arguments[i][key]; 105 | } 106 | } 107 | } 108 | 109 | return out; 110 | }, 111 | 112 | isString: function(thing) { 113 | return !!(typeof thing === 'string'); 114 | }, 115 | 116 | isElement: function(thing) { 117 | return !!(thing && thing.nodeType === 1); 118 | }, 119 | 120 | // Exists to work around a bug in Safari whereby some lists, notably 121 | // `querySelectorAll` NodeLists, treat `length` as an own property. 122 | isArrayLike: function(collection) { 123 | var property = function(key) { 124 | return function(obj) { 125 | return obj == null ? void 0 : obj[key]; 126 | }; 127 | }; 128 | var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; 129 | var getLength = property('length'); 130 | var length = getLength(collection); 131 | return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; 132 | }, 133 | 134 | // http://stackoverflow.com/a/4994244/5071070 135 | isEmpty: function(obj) { 136 | // null and undefined are "empty" 137 | if (obj == null) { return true; } 138 | 139 | // Assume if it has a length property with a non-zero value 140 | // that that property is correct. 141 | if (obj.length > 0) { return false; } 142 | if (obj.length === 0) { return true; } 143 | 144 | // Otherwise, does it have any properties of its own? 145 | // Note that this doesn't handle 146 | // toString and valueOf enumeration bugs in IE < 9 147 | for (var key in obj) { 148 | if (Penny.has(obj, key)) { return false; } 149 | } 150 | 151 | return true; 152 | }, 153 | 154 | }; 155 | 156 | }()); 157 | -------------------------------------------------------------------------------- /src/js/util/setup.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var DocumentCloud = window.DocumentCloud = window.DocumentCloud || {}; 3 | 4 | DocumentCloud.$ = DocumentCloud.$ || window.jQuery.noConflict(); 5 | DocumentCloud._ = DocumentCloud._ || window._.noConflict(); 6 | DocumentCloud.Backbone = DocumentCloud.Backbone || window.Backbone.noConflict(); 7 | DocumentCloud.embed = DocumentCloud.embed || {data: {},definition: {},views: {}}; 8 | })(); 9 | -------------------------------------------------------------------------------- /src/js/util/toolbelt.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * DCEmbedToolbelt 3 | * A small library of components shared between DocumentCloud embed loaders and 4 | * enhancers. It should remain very small. 5 | * 6 | * Depends on either `setup.js` (for Underscore) or Penny. 7 | * 8 | * @license (c) 2015 Justin Reese, DocumentCloud 9 | * DCEmbedToolbelt may be freely distributed under the MIT license. 10 | * 11 | */ 12 | 13 | (function(){ 14 | 15 | // For browsers that don't support `console`, swallow calls. 16 | if (!window.console) { 17 | window.console = { 18 | log: function(){}, 19 | info: function(){}, 20 | warn: function(){}, 21 | error: function(){}, 22 | }; 23 | } 24 | 25 | var DocumentCloud = window.DocumentCloud || {}; 26 | var Penny = window.Penny; 27 | 28 | if (DocumentCloud._) { 29 | // Use Underscore if it's available... 30 | var _ = DocumentCloud._; 31 | } else if (Penny) { 32 | // ...but fall back to Penny, which has the bits of Underscore we need here. 33 | var _ = Penny; 34 | } else { 35 | console.error("DocumentCloud embed can't load because of missing components."); 36 | return false; 37 | } 38 | 39 | var DCEmbedToolbelt = window.DCEmbedToolbelt = window.DCEmbedToolbelt || { 40 | 41 | // Equivalent to `\p{Letter}` and `\p{Number}` 42 | unicodeLetter: 'A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', 43 | unicodeNumber: '0-9\xB2\xB3\xB9\xBC-\xBE\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F\u0C78-\u0C7E\u0CE6-\u0CEF\u0D66-\u0D75\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19', 44 | 45 | // Sugar for making sure we've recognized a thing as a resource. 46 | isResource: function(thing) { 47 | return !!(_.has(thing, 'resourceType')); 48 | }, 49 | 50 | // Given a valid URL to a DocumentCloud resource, returns an object 51 | // identifying the resource's type, the environment of the resource being 52 | // requested, and some resource-specific URL components. 53 | recognizeResource: function(originalResource) { 54 | 55 | if (this.isResource(originalResource)) { 56 | return originalResource; 57 | } 58 | 59 | var domainEnvPatterns = { 60 | production: 'www\.documentcloud\.org', 61 | staging: 'staging\.documentcloud\.org', 62 | development: 'dev\.dcloud\.org' 63 | }; 64 | var domains = _.values(domainEnvPatterns).join('|'); 65 | var unicodeSlug = this.unicodeLetter + this.unicodeNumber + '%-'; 66 | var docBase = '(' + domains + ')\/documents\/([0-9]+)-([' + unicodeSlug + ']+)'; 67 | var resourceTypePatterns = { 68 | 'document': [ 69 | docBase + '\.(?:html|js|json)$' 70 | ], 71 | page: [ 72 | docBase + '\.html#document\/p([0-9]+)$', 73 | docBase + '\/pages\/([0-9]+)\.(?:html|js|json)$' 74 | ], 75 | note: [ 76 | docBase + '\/annotations\/([0-9]+)\.(?:html|js|json)$', 77 | docBase + '\.html#document\/p[0-9]+\/a([0-9]+)$', 78 | docBase + '\.html#annotation\/a([0-9]+)$' 79 | ] 80 | }; 81 | 82 | // Figure out which JSON endpoint to hit to access data for a given resource 83 | var makeDataUrl = function(resource) { 84 | var urlComponents; 85 | 86 | switch (resource.resourceType) { 87 | case 'document': 88 | urlComponents = [resource.domain, 'documents', resource.documentSlug]; 89 | break; 90 | case 'page': 91 | urlComponents = [resource.domain, 'documents', resource.documentSlug]; 92 | break; 93 | case 'note': 94 | urlComponents = [resource.domain, 'documents', resource.documentSlug, 'annotations', resource.noteId]; 95 | break; 96 | } 97 | 98 | return 'https://' + urlComponents.join('/') + '.json'; 99 | }; 100 | 101 | var resource = {}; 102 | _.each(resourceTypePatterns, function(patterns, resourceType) { 103 | if (!_.isEmpty(resource)) { return; } // `_.each` can't be broken out of 104 | _.each(patterns, function(pattern) { 105 | if (!_.isEmpty(resource)) { return; } // `_.each` can't be broken out of 106 | var match = originalResource.match(pattern); 107 | if (match) { 108 | // Resource-agnostic properties 109 | resource = { 110 | resourceUrl: originalResource, 111 | resourceType: resourceType, 112 | environment: _.findKey(domainEnvPatterns, function(domain, env) { 113 | return originalResource.match(domain); 114 | }), 115 | domain: match[1], 116 | documentId: match[2], 117 | documentSlug: match[2] + '-' + match[3], 118 | }; 119 | // Resource-specific properties 120 | switch (resourceType) { 121 | case 'document': 122 | resource.trackingId = resource.documentId; 123 | break; 124 | case 'page': 125 | resource.pageNumber = match[4]; 126 | resource.trackingId = resource.documentId + 'p' + resource.pageNumber; 127 | resource.embedOptions = { 128 | page: resource.pageNumber 129 | }; 130 | break; 131 | case 'note': 132 | resource.trackingId = resource.noteId = match[4]; 133 | break; 134 | } 135 | resource.dataUrl = makeDataUrl(resource); 136 | } 137 | }); 138 | }); 139 | return resource; 140 | }, 141 | 142 | // Many times, we want a function to be able to receive either a string 143 | // selector or a DOM element (or even a jQuery element). This function lets 144 | // you pass a parameter in and get out a DOM element. 145 | toDomElement: function(thing) { 146 | if (_.isElement(thing)) { 147 | // Is DOM element already; return it 148 | return thing; 149 | } else if (_.isString(thing)) { 150 | // Is a selector; find and return DOM element 151 | return document.querySelector(thing); 152 | } else if (thing instanceof jQuery && _.isElement(thing[0])) { 153 | // Is jQuery element; pluck out and return DOM element 154 | return thing[0]; 155 | } 156 | return null; 157 | }, 158 | 159 | // Generates a unique ID for a resource, checks the DOM for any existing 160 | // element with that ID, then increments and tries again if it finds one. 161 | generateUniqueElementId: function(resource) { 162 | var i = 1; 163 | var id = 'DC-' + resource.documentSlug; 164 | switch (resource.resourceType) { 165 | case 'document': 166 | id += '-i' + i; 167 | break; 168 | case 'page': 169 | id += '-p' + resource.pageNumber + '-i' + i; 170 | break; 171 | case 'note': 172 | id += '-a' + resource.noteId + '-i' + i; 173 | break; 174 | } 175 | while (document.getElementById(id)) { 176 | id = id.replace(/-i[0-9]+$/, '-i' + i++); 177 | } 178 | return id; 179 | }, 180 | 181 | // http://stackoverflow.com/a/21965342/5071070 182 | isIframed: function() { 183 | try { 184 | return window.self !== window.top; 185 | } catch (e) { 186 | return true; 187 | } 188 | }, 189 | 190 | // We want to know where the resource is embedded in an iframe-safe way. 191 | // I.e., when wrapped in an iframe, we want the parent page's URL, not the 192 | // iframe's URL. We also want to strip off any hash, query params, and 193 | // ending slash to unify the results. 194 | getSourceUrl: function() { 195 | var source, sourceUrl; 196 | 197 | if (this.isIframed()) { 198 | // NB: Since we can't rely on accessing the parent page's URL using 199 | // `window.top` or `window.parent` thanks to same-origin requirements, 200 | // we have to use `document.referrer`, which has two limitations: 201 | // - Deep-iframed pages (grandchildren) can't access grandparents, and 202 | // so will return the URL of their own parent 203 | // - If the child page navigates away, the `referrer` is now the 204 | // original child, not the parent 205 | // Despite these limitations, it's still the most practical answer. 206 | source = document.createElement('A'); 207 | source.href = document.referrer; 208 | } else { 209 | source = window.location; 210 | } 211 | sourceUrl = source.protocol + '//' + source.host; 212 | 213 | // Use `pathname` to effectively strip off hash and query params. But! In 214 | // IE, `createElement`-generated URLs won't have an absolute `pathname`, 215 | // so we must prefix it manually. 216 | if (source.pathname.indexOf('/') !== 0) { 217 | sourceUrl += '/'; 218 | }; 219 | sourceUrl += source.pathname; 220 | 221 | // Treat `foo.com/bar/` and `foo.com/bar` as the same URL 222 | sourceUrl = sourceUrl.replace(/[\/]+$/, ''); 223 | 224 | return sourceUrl; 225 | }, 226 | 227 | // Given a resource or resource URL, composes and inserts a tracking pixel 228 | // adjacent to the specified container (which can be a DOM element or 229 | // selector string). 230 | pixelPing: function(resource, container) { 231 | resource = this.recognizeResource(resource); 232 | container = this.toDomElement(container); 233 | 234 | var pingUrl = '//' + resource.domain + '/pixel.gif'; 235 | var sourceUrl = this.getSourceUrl(); 236 | var key = encodeURIComponent(resource.resourceType + ':' + resource.trackingId + ':' + sourceUrl); 237 | var image = 'Anonymous hit counter for DocumentCloud'; 238 | container.insertAdjacentHTML('afterend', image); 239 | } 240 | }; 241 | 242 | })(); 243 | -------------------------------------------------------------------------------- /src/js/views/note.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var DCEmbedToolbelt = window.DCEmbedToolbelt; 3 | var DocumentCloud = window.DocumentCloud; 4 | var $ = DocumentCloud.$; 5 | var _ = DocumentCloud._; 6 | var Backbone = DocumentCloud.Backbone; 7 | 8 | var definition = DocumentCloud.embed.definition; 9 | var data = DocumentCloud.embed.data; 10 | var views = DocumentCloud.embed.views; 11 | 12 | definition.NoteView = definition.NoteView || Backbone.View.extend({ 13 | NOTE_MAX_HEIGHT: 200, 14 | 15 | // Appending `-wrapper` to avoid conflict with note embed 16 | className: "DC-note-wrapper", 17 | 18 | events: { 19 | 'click .DC-note-region': 'toggle', 20 | }, 21 | 22 | initialize: function(options) { 23 | this.imageUrl = options.imageUrl; 24 | this.imageUrlLarge = options.imageUrlLarge; 25 | }, 26 | 27 | // FIXME: It is immensely gross that I'm passing `documentCanonicalUrl` in 28 | // here; I hope to work around that ASAP. 29 | render: function(dimensions, documentCanonicalUrl) { 30 | this.$el.html(JST["note"]({ 31 | title: this.model.get('title'), 32 | text: this.model.get('content'), 33 | noteContextualUrl: documentCanonicalUrl + this.model.pageAnchor(), 34 | imageUrl: this.imageUrl, 35 | imageUrlLarge: this.imageUrlLarge, 36 | showNoteMenuBar: false, 37 | })); 38 | this.cacheDomReferences(); 39 | 40 | var coordinates = this.model.percentageCoordinates(dimensions); 41 | var cssCoordinates = _.pick(coordinates, 'top', 'left', 'width', 'height'); 42 | this.$el.css(cssCoordinates); 43 | 44 | var imageCoordinates = this.model.imageCoordinates(dimensions); 45 | var cssImageCoordinates = _.pick(imageCoordinates, 'top', 'left', 'width'); 46 | this.$noteImage.css(cssImageCoordinates); 47 | 48 | return this; 49 | }, 50 | 51 | cacheDomReferences: function() { 52 | this.$noteRegion = this.$el.find('.DC-note-region'); 53 | this.$noteImage = this.$el.find('.DC-note-image'); 54 | this.$noteBody = this.$el.find('.DC-note-body'); 55 | }, 56 | 57 | toggle: function() { 58 | if (this.$el.hasClass('open')) { 59 | this.close(); 60 | } else { 61 | this.open(); 62 | } 63 | }, 64 | 65 | open: function() { 66 | this.$el.addClass('open'); 67 | this.trigger('opened', this); 68 | this.$el.closest('.DC-page-embed').addClass('open'); 69 | this.repositionIfNecessary(); 70 | }, 71 | 72 | close: function() { 73 | this.$el.removeClass('open'); 74 | this.trigger('closed', this); 75 | this.$el.closest('.DC-page-embed').removeClass('open'); 76 | this.$noteBody.css({top: 'auto', bottom: 'auto'}); 77 | }, 78 | 79 | // Note might drip off right or bottom edge; reposition in that case. 80 | repositionIfNecessary: function() { 81 | var $overlay = this.$el.closest('.DC-note-overlay'); 82 | 83 | var newCSS = {}; 84 | var notePosition = this.$el.position(); 85 | 86 | var noteLeft = notePosition.left; 87 | var bodyWidth = this.$noteBody.width(); 88 | var bodyRight = noteLeft + bodyWidth; 89 | var overlayWidth = $overlay.width(); 90 | 91 | // Never drip off horizontal edges, even in direct (non-iframe) contexts 92 | if (bodyRight > overlayWidth) { 93 | var regionRight = noteLeft + this.$noteRegion.width(); 94 | var noteBodyRight = (regionRight > bodyWidth) ? 0 : ((overlayWidth - regionRight) * -1); 95 | newCSS.right = noteBodyRight + 'px'; 96 | } 97 | 98 | // In the iframe context, never drip off the bottom edge. 99 | if (DCEmbedToolbelt.isIframed()) { 100 | var noteHeight = this.$el.height(); 101 | var noteBottom = notePosition.top + noteHeight; 102 | var overlayHeight = $overlay.height(); 103 | var bodyHeight = this.$noteBody.height(); 104 | var bodyBottom = noteBottom + bodyHeight; 105 | 106 | // Is the note bottom outside of the overlay? 107 | if (bodyBottom > overlayHeight) { 108 | var noteBottomToOverlayBottom = overlayHeight - noteBottom; 109 | var noteTopToOverlayTop = notePosition.top; 110 | 111 | // Do we have more room above the note than below? 112 | if (noteTopToOverlayTop > noteBottomToOverlayBottom) { 113 | newCSS.bottom = noteHeight + 'px'; 114 | 115 | // Do we need to limit max height? 116 | if (noteTopToOverlayTop < this.NOTE_MAX_HEIGHT) { 117 | newCSS.maxHeight = noteTopToOverlayTop + 'px'; 118 | } 119 | } else { 120 | // Below-note positioning is still our best bet, so limit max height 121 | newCSS.maxHeight = noteBottomToOverlayBottom + 'px'; 122 | } 123 | } 124 | } 125 | 126 | this.$noteBody.css(newCSS); 127 | }, 128 | 129 | }); 130 | })(); -------------------------------------------------------------------------------- /src/js/views/page.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var DCEmbedToolbelt = window.DCEmbedToolbelt; 3 | var DocumentCloud = window.DocumentCloud; 4 | var $ = DocumentCloud.$; 5 | var _ = DocumentCloud._; 6 | var Backbone = DocumentCloud.Backbone; 7 | 8 | var definition = DocumentCloud.embed.definition; 9 | var data = DocumentCloud.embed.data; 10 | var views = DocumentCloud.embed.views; 11 | 12 | definition.PageView = definition.PageView || Backbone.View.extend({ 13 | 14 | events: { 15 | 'click.dcPage .DC-page': 'clickPage', 16 | 'click.dcPage .DC-action-nav-prev': 'goToPrevPage', 17 | 'click.dcPage .DC-action-nav-next': 'goToNextPage', 18 | 'change.dcPage .DC-action-nav-select': 'selectPage', 19 | 'click.dcPage .DC-action-mode-image': 'switchToImage', 20 | 'click.dcPage .DC-action-mode-text': 'switchToText', 21 | 'click.dcPage .DC-note-overlay': 'clickNoteOverlay', 22 | }, 23 | 24 | validOptionKeys: ['credit', 'page', 'pageNavigator', 'text', 'preview'], 25 | 26 | defaultOptions: { 27 | credit: true, 28 | page: 1, 29 | pageNavigator: false, 30 | preview: false, 31 | text: false 32 | }, 33 | 34 | sizeBreakpoints: [ 35 | [0, 199], 36 | [200, 399] 37 | ], // Keep in sync with media queries in `page_embed.scss` 38 | 39 | initialize: function(options) { 40 | this.options = _.extend({}, this.defaultOptions, options); 41 | 42 | this.currentPageNumber = this.options.page; 43 | this.noteViews = {}; 44 | this.cachedText = {}; 45 | this.iframed = DCEmbedToolbelt.isIframed() 46 | 47 | this.listenTo(this.model, 'sync', this.render); 48 | }, 49 | 50 | render: function() { 51 | this.verifyPageNumber(); 52 | this.makeTemplateData(); 53 | this.prepareNotes(); // Requires `makeTemplateData()` be run first 54 | this.$el.html(JST['page'](this.templateData)); 55 | this.cacheDomReferences(); 56 | this.classifyEmbedContext(); 57 | this.$image.on('load', _.bind(this.renderNoteOverlay, this)); 58 | if (this.mode == 'text') { 59 | this.switchToText(); 60 | } else { 61 | this.switchToImage(); 62 | } 63 | }, 64 | 65 | verifyPageNumber: function() { 66 | if (this.currentPageNumber > this.model.get('pages')) { 67 | console.warn("Showing the first page of the DocumentCloud document because page " + this.currentPageNumber + " doesn't exist."); 68 | this.currentPageNumber = 1; 69 | } 70 | }, 71 | 72 | prepareNotes: function() { 73 | if (!_.has(this.noteViews, this.currentPageNumber)) { 74 | this.noteViews[this.currentPageNumber] = {} 75 | } 76 | // TODO: Try to save this and not regenerate every time 77 | var notes = this.model.notes.forPage(this.currentPageNumber); 78 | _.each(notes, function(note){ 79 | var noteView = new definition.NoteView({ 80 | model: note, 81 | imageUrl: this.templateData.imageUrl, 82 | imageUrlLarge: this.templateData.imageUrlLarge 83 | }); 84 | this.noteViews[this.currentPageNumber][note.id] = noteView; 85 | this.listenTo(noteView, 'opened', this.updateOpenNote); 86 | this.listenTo(noteView, 'closed', this.closeOpenNote); 87 | }, this); 88 | }, 89 | 90 | makeTemplateData: function() { 91 | var model = this.model; 92 | var pageCount = model.get('pages'); 93 | var pageNumber = this.currentPageNumber; 94 | var creditData = { 95 | contributor: this.model.get('contributor'), 96 | contributorDocumentsUrl: this.model.get('contributor_documents_url'), 97 | organization: this.model.get('contributor_organization'), 98 | organizationDocumentsUrl: this.model.get('contributor_organization_documents_url'), 99 | }; 100 | // We need to know at least the contributor or organization name 101 | var hasCreditData = creditData.contributor || creditData.organization; 102 | 103 | this.templateData = { 104 | showMeta: !this.iframed, 105 | showCredit: !this.iframed && this.options.credit && hasCreditData, 106 | showTextMode: this.options.text, 107 | showPageNavigator: this.options.pageNavigator, 108 | showPageMenuBar: this.options.pageNavigator || this.options.text, 109 | model: model, 110 | imageUrl: model.imageUrl(pageNumber), 111 | imageUrlLarge: model.imageUrl(pageNumber, 'large'), 112 | pageContextualUrl: model.pageContextualUrl(pageNumber), 113 | pageTextUrl: model.pageTextUrl(pageNumber), 114 | pageTextFileUrl: model.pageTextFileUrl(pageNumber), 115 | pageCount: pageCount, 116 | hasMultiplePages: model.hasMultiplePages(), 117 | pageNumber: pageNumber, 118 | hasPrevPage: pageNumber > 1, 119 | hasNextPage: pageNumber < pageCount, 120 | }; 121 | this.templateData.prevPageHref = this.templateData.hasPrevPage ? model.pageContextualUrl(pageNumber - 1) : '#'; 122 | this.templateData.nextPageHref = this.templateData.hasNextPage ? model.pageContextualUrl(pageNumber + 1) : '#'; 123 | // Don't compile template if we don't have to 124 | this.templateData.contributorCredit = this.templateData.showCredit ? JST['credit'](creditData) : ''; 125 | }, 126 | 127 | cacheDomReferences: function() { 128 | this.$embed = this.$el.find('.DC-page-embed'); 129 | this.$image = this.$el.find('.DC-page-image'); 130 | this.$text = this.$el.find('.DC-page-text'); 131 | this.$overlay = this.$el.find('.DC-note-overlay'); 132 | this.$pageSelector = this.$el.find('.DC-action-nav-select'); 133 | }, 134 | 135 | renderNoteOverlay: function() { 136 | if (this.notesLoaded) { 137 | return false; 138 | } 139 | 140 | var view = this; 141 | 142 | // Cache this function internally 143 | var _renderOverlay = function() { 144 | view.$overlay.empty(); 145 | var noteViews = _.map(view.noteViews[view.currentPageNumber], 146 | function(noteView) { 147 | return noteView.render(view.dimensions, view.model.get('canonical_url')); 148 | }); 149 | view.$overlay.append(_.map(noteViews, function(v) { return v.$el; })); 150 | view.notesLoaded = true; 151 | } 152 | 153 | // If dimensions are already cached, just straight re-render 154 | if (view.dimensions) { 155 | _renderOverlay(); 156 | } else { 157 | view.dimensions = { 158 | width: 700, 159 | // TODO: If the document JSON returns page aspect ratio, we won't 160 | // have to do this calculation, and in fact won't have to observe 161 | // image loading at all 162 | aspectRatio: view.$image.width() / view.$image.height() 163 | }; 164 | view.dimensions.height = view.dimensions.width / view.dimensions.aspectRatio; 165 | _renderOverlay(); 166 | } 167 | }, 168 | 169 | clickNoteOverlay: function(event) { 170 | if ($(event.target).is('.DC-note-overlay') && this.openNote) { 171 | this.openNote.close(); 172 | } 173 | }, 174 | 175 | currentScale: function() { return this.$image.width() / this.dimensions.width; }, 176 | 177 | switchToImage: function(event) { 178 | if (!_.isUndefined(event)) { 179 | event.preventDefault(); 180 | } 181 | this.$embed.removeClass('DC-mode-text').addClass('DC-mode-image'); 182 | this.mode = 'page'; 183 | }, 184 | 185 | switchToText: function(event) { 186 | if (!_.isUndefined(event)) { 187 | event.preventDefault(); 188 | } 189 | this.$embed.removeClass('DC-mode-image').addClass('DC-mode-text'); 190 | this.mode = 'text'; 191 | if (_.isUndefined(this.cachedText[this.currentPageNumber])) { 192 | this.$text.removeClass('error').addClass('fetching') 193 | .html(' Fetching page text…'); 194 | var _this = this; 195 | $.get(this.model.pageTextFileUrl(this.currentPageNumber), function(data) { 196 | _this.cachedText[_this.currentPageNumber] = data; 197 | _this.$text.text(data); 198 | }).fail(function(){ 199 | _this.$text.addClass('error').text('Unable to fetch page text.'); 200 | }).always(function(){ 201 | _this.$text.removeClass('fetching'); 202 | }); 203 | } else { 204 | this.$text.text(this.cachedText[this.currentPageNumber]); 205 | } 206 | }, 207 | 208 | updateOpenNote: function(justOpened) { 209 | if (this.openNote && this.openNote != justOpened) { 210 | this.openNote.close(); 211 | } 212 | this.openNote = justOpened; 213 | }, 214 | 215 | closeOpenNote: function() { 216 | this.openNote = undefined; 217 | }, 218 | 219 | selectPage: function() { 220 | var newPageNumber = this.$pageSelector.val(); 221 | this.goToPage(newPageNumber); 222 | }, 223 | 224 | goToPrevPage: function(event) { 225 | event.preventDefault(); 226 | var $prevPage = this.$pageSelector.find('option:selected').prev('option'); 227 | if ($prevPage.length) { 228 | this.goToPage($prevPage.attr('value')); 229 | } 230 | }, 231 | 232 | goToNextPage: function(event) { 233 | event.preventDefault(); 234 | var $nextPage = this.$pageSelector.find('option:selected').next('option'); 235 | if ($nextPage.length) { 236 | this.goToPage($nextPage.attr('value')); 237 | } 238 | }, 239 | 240 | goToPage: function(pageNumber) { 241 | if (pageNumber <= this.model.get('pages') && pageNumber != this.currentPageNumber) { 242 | if (this.openNote) { 243 | this.openNote.close(); 244 | }; 245 | this.currentPageNumber = pageNumber; 246 | this.notesLoaded = false; 247 | this.undelegateEvents(); 248 | this.$el.html(this.render()); 249 | this.delegateEvents(); 250 | } 251 | }, 252 | 253 | clickPage: function() { 254 | var weAreTiny = this.iframed ? (this.$el.width() <= this.sizeBreakpoints[0][1]) : this.$el.hasClass('DC-embed-size-0'); 255 | if (weAreTiny) { 256 | window.open(this.model.pageContextualUrl(this.currentPageNumber)); 257 | } 258 | }, 259 | 260 | classifyEmbedContext: function() { 261 | this.$el.addClass('DC-embed-' + (this.iframed ? 'iframed' : 'inline')); 262 | }, 263 | 264 | }); 265 | })(); 266 | -------------------------------------------------------------------------------- /src/templates/credit.jst: -------------------------------------------------------------------------------- 1 | Contributed to 2 | DocumentCloud by 3 | <% if (contributor) { %> 4 | <% if (contributorDocumentsUrl) { %><% } %><%- contributor %><% if (contributorDocumentsUrl) { %><% } %> of 5 | <% } %> 6 | <% if (organization) { %> 7 | <% if (organizationDocumentsUrl) { %><% } %><%- organization %><% if (organizationDocumentsUrl) { %><% } %> 8 | <% } %> 9 | • -------------------------------------------------------------------------------- /src/templates/note.jst: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%- title %> 4 |
5 |
6 | 7 |
8 | 9 |
10 |
11 | <%- title %> 12 |
13 |
<%= text %>
14 |
15 | 16 | <% if (showNoteMenuBar) { %> 17 |
18 | 24 |
25 | <% } %> 26 | 27 |
28 | -------------------------------------------------------------------------------- /src/templates/page.jst: -------------------------------------------------------------------------------- 1 |
2 | 3 | <% if (showMeta) { %> 4 |
5 |
6 | <% if (hasMultiplePages && !showPageNavigator) { %> 7 | Page <%= pageNumber %> of 8 | <% } %> 9 | <%- model.attributes.title %> 10 |
11 | 12 | DocumentCloud 13 | 14 |
15 | <% } %> 16 | 17 | <% if (showPageMenuBar) { %> 18 |
19 | 20 | <% if (showTextMode) { %> 21 | 31 | <% } %> 32 | 33 | <% if (hasMultiplePages && showPageNavigator) { %> 34 | 55 | <% } %> 56 | 57 | 63 | 64 |
65 | <% } %> 66 | 67 |
68 |
69 | Page <%= pageNumber %> of <%- model.attributes.title %> 70 |
71 |
72 | 73 | <% if (showCredit) { %> 74 |
75 | <%= contributorCredit %> 76 | View document 77 | <% if (showTextMode) { %> 78 | or read text 79 | <% } %> 80 |
81 | <% } %> 82 | 83 |
84 | --------------------------------------------------------------------------------