├── LICENSE ├── index.css ├── README.md ├── spritz.html ├── style.css ├── index.html └── spritz.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Rich Jones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | #container{ 2 | width: 50%; 3 | margin-left: 25%; 4 | margin-right: 25%; 5 | } 6 | 7 | /* This button was generated using CSSButtonGenerator.com */ 8 | .button { 9 | background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #ededed), color-stop(1, #dfdfdf) ); 10 | background:-moz-linear-gradient( center top, #ededed 5%, #dfdfdf 100% ); 11 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed', endColorstr='#dfdfdf'); 12 | background-color:#ededed; 13 | -webkit-border-top-left-radius:6px; 14 | -moz-border-radius-topleft:6px; 15 | border-top-left-radius:6px; 16 | -webkit-border-top-right-radius:6px; 17 | -moz-border-radius-topright:6px; 18 | border-top-right-radius:6px; 19 | -webkit-border-bottom-right-radius:6px; 20 | -moz-border-radius-bottomright:6px; 21 | border-bottom-right-radius:6px; 22 | -webkit-border-bottom-left-radius:6px; 23 | -moz-border-radius-bottomleft:6px; 24 | border-bottom-left-radius:6px; 25 | text-indent:0; 26 | border:1px solid #dcdcdc; 27 | display:inline-block; 28 | color:#777777; 29 | font-family:arial; 30 | font-size:12px; 31 | font-weight:bold; 32 | font-style:normal; 33 | height:38px; 34 | line-height:38px; 35 | width:125px; 36 | text-decoration:none; 37 | text-align:center; 38 | } 39 | 40 | .button a { 41 | color:#777777; 42 | text-decoration:none; 43 | } 44 | 45 | .button:hover { 46 | background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #dfdfdf), color-stop(1, #ededed) ); 47 | background:-moz-linear-gradient( center top, #dfdfdf 5%, #ededed 100% ); 48 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#dfdfdf', endColorstr='#ededed'); 49 | background-color:#dfdfdf; 50 | }.button:active { 51 | position:relative; 52 | top:1px; 53 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![OpenSpritz](http://i.imgur.com/LOtmyf9.gif) 2 | 3 | # OpenSpritz 4 | 5 | OpenSpritz is an extremely crude implementation of [Spritz](http://www.spritzinc.com/) in JavaScript. It works as a bookmarklet to add Spritz-type speed reading to every page. 6 | 7 | ## Installation 8 | 9 | To install OpenSpritz, follow [this guide at **gun.io**](https://gun.io/blog/openspritz-a-free-speed-reading-bookmarklet). 10 | 11 | ## Features 12 | 13 | * Spritz-type speed reading 14 | * Word length and grammar-aware speed reading 15 | * WPM selector 16 | * Cross-browser bookmarklet 17 | * Text-selection aware 18 | * Readabilty-based article extraction 19 | 20 | ## Contributing 21 | 22 | OpenSpritz needs you! If you find bugs, have feature requests, or have other needs, please help out! 23 | 24 | The best way to contribute is to start a new ticket, or to work on an existing ticket. If you find a website which doesn't work with OpenSpritz, [add it to this ticket](https://github.com/Miserlou/OpenSpritz/issues/8) so that it can be diagnosed and fixed. 25 | 26 | OpenSpritz currently needs a little more love to make it work with JSONP and needs a better method of detecting the presence of jQuery, so those are some good places to start. 27 | 28 | Once you have tested your changes and confirmed they work, send a pull request. Add yourself to the list of contributors below as well! 29 | 30 | ### Contributors 31 | 32 | * [Rich Jones](https://github.com/Miserlou) 33 | * [Nick R](https://github.com/niroyb) 34 | 35 | ## Sister Projects 36 | 37 | * [OpenSpritz-Android](https://github.com/OnlyInAmerica/OpenSpritz-Android) - An Android Spritz ePub Reader by [@OnlyInAmerica](https://github.com/OnlyInAmerica) 38 | * [SpritzerTextView](https://github.com/andrewgiang/SpritzerTextView) - An Android Spritz View by [@andrewgiang](https://github.com/andrewgiang) 39 | * [speedread](https://github.com/pasky/speedread) - A terminal Spritzer. by [@pasky](https://github.com/pasky) 40 | 41 | #### A Note About the Name 42 | 43 | OpenSpritz has nothing to do with [Spritz Incorporated](http://www.spritzinc.com/). This is an open source, community created project, made with love because Spritz is such an awesome technique for reading with. _(Please don't send us a DMCA take down request! That'll only backfire on you anyway. We love you.)_ 44 | -------------------------------------------------------------------------------- /spritz.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 | 7 |
8 | ――――――――――ф――――――――――― 9 |
10 | 11 |
12 | Choose a WPM to start. 13 |
14 | 15 |
16 | ―――――――――――――――――――――― 17 |
18 | 19 | 37 | 38 | 39 | 40 | OpenSpritz 41 | 42 | 43 | [x] 44 | 45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0px; 3 | } 4 | 5 | #spritz_holder{ 6 | width: 100%; 7 | box-shadow: 2px 2px 4px rgba(0, 0, 0, .2); 8 | padding-top: 12px; 9 | padding-bottom: 12px; 10 | margin-bottom: 12px; 11 | background-color: #ffffff; 12 | min-height: 166px; 13 | position: fixed; 14 | top: 0; 15 | left: 0; 16 | z-index: 99999999999999999; 17 | } 18 | 19 | #spritz_spacer{ 20 | width: 100%; 21 | min-height: 210px; 22 | } 23 | 24 | #spritz_container{ 25 | width: 50%; 26 | margin-left: 25%; 27 | margin-right: 25%; 28 | background-color: #ffffff; 29 | line-height: 43px; 30 | 31 | } 32 | 33 | #spritz_result{ 34 | text-align: center; 35 | font-size: 32px; 36 | font-family: 'Droid Sans Mono', sans-serif; 37 | padding-top: 9px; 38 | padding-bottom: 9px; 39 | min-height: 40px; 40 | } 41 | 42 | #guide_top{ 43 | text-align: center; 44 | font-size: 32px; 45 | font-family: 'Droid Sans Mono', sans-serif; 46 | color: #DDDDDD; 47 | } 48 | 49 | #guide_bottom{ 50 | text-align: center; 51 | font-size: 32px; 52 | font-family: 'Droid Sans Mono', sans-serif; 53 | color: #DDDDDD; 54 | } 55 | 56 | #notch{ 57 | text-align: center; 58 | font-size: 32px; 59 | font-family: 'Droid Sans Mono', sans-serif; 60 | color: #DDDDDD; 61 | margin-top: 9px; 62 | padding-top: 12px; 63 | } 64 | 65 | #spritz_credits{ 66 | float: right; 67 | font-family: 'Droid Sans Mono', sans-serif; 68 | font-size: 13px; 69 | } 70 | 71 | #spritz_credits a{ 72 | color: #dddddd; 73 | text-decoration: none; 74 | } 75 | 76 | #spritz_selector{ 77 | float: left; 78 | width: auto; 79 | } 80 | 81 | .invisible{ 82 | font-family: 'Droid Sans Mono', sans-serif; 83 | color: #ffffff; 84 | } 85 | 86 | .start{ 87 | color: #333333; 88 | text-align: center; 89 | font-size: 32px; 90 | font-family: 'Droid Sans Mono', sans-serif; 91 | } 92 | .pivot{ 93 | color: #DE0000; 94 | text-align: center; 95 | font-size: 32px; 96 | font-family: 'Droid Sans Mono', sans-serif; 97 | } 98 | .end{ 99 | color: #333333; 100 | text-align: center; 101 | font-size: 32px; 102 | font-family: 'Droid Sans Mono', sans-serif; 103 | } 104 | 105 | .spritz_start{ 106 | color: #333333; 107 | text-align: center; 108 | font-size: 32px; 109 | font-family: 'Droid Sans Mono', sans-serif; 110 | } 111 | .spritz_pivot{ 112 | color: #DE0000; 113 | text-align: center; 114 | font-size: 32px; 115 | font-family: 'Droid Sans Mono', sans-serif; 116 | } 117 | .spritz_end{ 118 | color: #333333; 119 | text-align: center; 120 | font-size: 32px; 121 | font-family: 'Droid Sans Mono', sans-serif; 122 | } 123 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OpenSpritz 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |

A Nice Cup of Tea

14 |

George Orwell, 12 January 1946

15 | 16 | 24 | 25 |
26 | 27 | OpenSpritz this! 28 | 29 |
30 | 31 |

32 | 33 |
34 | If you look up 'tea' in the first cookery book that comes to hand you will probably find that it is unmentioned; or at most you will find a few lines of sketchy instructions which give no ruling on several of the most important points. This is curious, not only because tea is one of the main stays of civilization in this country, as well as in Eire, Australia and New Zealand, but because the best manner of making it is the subject of violent disputes. When I look through my own recipe for the perfect cup of tea, I find no fewer than eleven outstanding points. On perhaps two of them there would be pretty general agreement, but at least four others are acutely controversial. Here are my own eleven rules, every one of which I regard as golden: First of all, one should use Indian or Ceylonese tea. China tea has virtues which are not to be despised nowadays — it is economical, and one can drink it without milk — but there is not much stimulation in it. One does not feel wiser, braver or more optimistic after drinking it. Anyone who has used that comforting phrase 'a nice cup of tea' invariably means Indian tea. Secondly, tea should be made in small quantities — that is, in a teapot. Tea out of an urn is always tasteless, while army tea, made in a cauldron, tastes of grease and whitewash. The teapot should be made of china or earthenware. Silver or Britanniaware teapots produce inferior tea and enamel pots are worse; though curiously enough a pewter teapot (a rarity nowadays) is not so bad. Thirdly, the pot should be warmed beforehand. This is better done by placing it on the hob than by the usual method of swilling it out with hot water. Fourthly, the tea should be strong. For a pot holding a quart, if you are going to fill it nearly to the brim, six heaped teaspoons would be about right. In a time of rationing, this is not an idea that can be realized on every day of the week, but I maintain that one strong cup of tea is better than twenty weak ones. All true tea lovers not only like their tea strong, but like it a little stronger with each year that passes — a fact which is recognized in the extra ration issued to old-age pensioners. Fifthly, the tea should be put straight into the pot. No strainers, muslin bags or other devices to imprison the tea. In some countries teapots are fitted with little dangling baskets under the spout to catch the stray leaves, which are supposed to be harmful. Actually one can swallow tea-leaves in considerable quantities without ill effect, and if the tea is not loose in the pot it never infuses properly. Sixthly, one should take the teapot to the kettle and not the other way about. The water should be actually boiling at the moment of impact, which means that one should keep it on the flame while one pours. Some people add that one should only use water that has been freshly brought to the boil, but I have never noticed that it makes any difference. Seventhly, after making the tea, one should stir it, or better, give the pot a good shake, afterwards allowing the leaves to settle. Eighthly, one should drink out of a good breakfast cup — that is, the cylindrical type of cup, not the flat, shallow type. The breakfast cup holds more, and with the other kind one's tea is always half cold before one has well started on it. Ninthly, one should pour the cream off the milk before using it for tea. Milk that is too creamy always gives tea a sickly taste. Tenthly, one should pour tea into the cup first. This is one of the most controversial points of all; indeed in every family in Britain there are probably two schools of thought on the subject. The milk-first school can bring forward some fairly strong arguments, but I maintain that my own argument is unanswerable. This is that, by putting the tea in first and stirring as one pours, one can exactly regulate the amount of milk whereas one is liable to put in too much milk if one does it the other way round. Lastly, tea — unless one is drinking it in the Russian style — should be drunk without sugar. I know very well that I am in a minority here. But still, how can you call yourself a true tealover if you destroy the flavour of your tea by putting sugar in it? It would be equally reasonable to put in pepper or salt. Tea is meant to be bitter, just as beer is meant to be bitter. If you sweeten it, you are no longer tasting the tea, you are merely tasting the sugar; you could make a very similar drink by dissolving sugar in plain hot water. Some people would answer that they don't like tea in itself, that they only drink it in order to be warmed and stimulated, and they need sugar to take the taste away. To those misguided people I would say: Try drinking tea without sugar for, say, a fortnight and it is very unlikely that you will ever want to ruin your tea by sweetening it again. These are not the only controversial points to arise in connexion with tea drinking, but they are sufficient to show how subtilized the whole business has become. There is also the mysterious social etiquette surrounding the teapot (why is it considered vulgar to drink out of your saucer, for instance?) and much might be written about the subsidiary uses of tealeaves, such as telling fortunes, predicting the arrival of visitors, feeding rabbits, healing burns and sweeping the carpet. It is worth paying attention to such details as warming the pot and using water that is really boiling, so as to make quite sure of wringing out of one's ration the twenty good, strong cups of that two ounces, properly handled, ought to represent. 35 |
36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /spritz.js: -------------------------------------------------------------------------------- 1 | 2 | // spritz.js 3 | // A JavaScript Speed Reader 4 | // rich@gun.io 5 | // https://github.com/Miserlou/OpenSpritz 6 | 7 | // Please don't abuse this. 8 | var readability_token = '172b057cd7cfccf27b60a36f16b1acde12783893'; 9 | 10 | // Create the view from the remote resource. 11 | function create_spritz(){ 12 | 13 | spritz_loader = function() { 14 | 15 | $.get("https://rawgithub.com/Miserlou/OpenSpritz/master/spritz.html", function(data){ 16 | 17 | if (!($("#spritz_container").length) ) { 18 | $("body").prepend(data); 19 | } 20 | },'html'); 21 | }; 22 | 23 | load_jq(spritz_loader); 24 | } 25 | 26 | // jQuery loader: http://coding.smashingmagazine.com/2010/05/23/make-your-own-bookmarklets-with-jquery/ 27 | // This is pretty fucked and should be replaced. Is there anyway we can just force 28 | // the latest jQ? I wouldn't have a problem with that. 29 | function load_jq(spritz_loader){ 30 | 31 | // the minimum version of jQuery we want 32 | var v = "1.11.0"; 33 | 34 | // check prior inclusion and version 35 | if (window.jQuery === undefined || window.jQuery.fn.jquery < v) { 36 | var done = false; 37 | var script = document.createElement("script"); 38 | script.src = "https://ajax.googleapis.com/ajax/libs/jquery/" + v + "/jquery.min.js"; 39 | script.onload = script.onreadystatechange = function(){ 40 | if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) { 41 | done = true; 42 | spritz_loader(); 43 | } 44 | }; 45 | document.getElementsByTagName("head")[0].appendChild(script); 46 | } else{ 47 | spritz_loader(); 48 | } 49 | } 50 | 51 | function hide_spritz(){ 52 | $('#spritz_spacer').slideUp(); 53 | $('#spritz_container').slideUp(); 54 | $('#spritz_holder').slideUp(); 55 | } 56 | 57 | // Entry point to the beef. 58 | // Gets the WPM and the selected text, if any. 59 | function spritz(){ 60 | 61 | var wpm = parseInt($("#spritz_selector").val(), 10); 62 | if(wpm < 1){ 63 | return; 64 | } 65 | 66 | var selection = getSelectionText(); 67 | if(selection){ 68 | spritzify(selection); 69 | } 70 | else{ 71 | spritzifyURL(); 72 | } 73 | } 74 | 75 | // The meat! 76 | function spritzify(input){ 77 | 78 | var wpm = parseInt($("#spritz_selector").val(), 10); 79 | var ms_per_word = 60000/wpm; 80 | 81 | // Split on any spaces. 82 | var all_words = input.split(/\s+/); 83 | 84 | var word = ''; 85 | var result = ''; 86 | 87 | 88 | // Preprocess words 89 | var temp_words = all_words.slice(0); // copy Array 90 | var t = 0; 91 | 92 | for (var i=0; i 8) && all_words[i].indexOf('.') == -1){ 100 | temp_words.splice(t+1, 0, all_words[i]); 101 | temp_words.splice(t+1, 0, all_words[i]); 102 | t++; 103 | t++; 104 | } 105 | 106 | // Add an additional space after punctuation. 107 | if(all_words[i].indexOf('.') != -1 || all_words[i].indexOf('!') != -1 || all_words[i].indexOf('?') != -1 || all_words[i].indexOf(':') != -1 || all_words[i].indexOf(';') != -1|| all_words[i].indexOf(')') != -1){ 108 | temp_words.splice(t+1, 0, "."); 109 | temp_words.splice(t+1, 0, "."); 110 | temp_words.splice(t+1, 0, "."); 111 | t++; 112 | t++; 113 | t++; 114 | } 115 | 116 | t++; 117 | 118 | } 119 | all_words = temp_words.slice(0); 120 | 121 | // Set the timers! 122 | for (var i=0; i 0){ 144 | word = word + '.'; 145 | } 146 | else{ 147 | word = '.' + word; 148 | } 149 | bit = bit * -1; 150 | } 151 | 152 | var start = ''; 153 | var end = ''; 154 | if((length % 2) === 0){ 155 | start = word.slice(0, word.length/2); 156 | end = word.slice(word.length/2, word.length); 157 | } else{ 158 | start = word.slice(0, word.length/2); 159 | end = word.slice(word.length/2, word.length); 160 | } 161 | 162 | var result; 163 | result = "" + start.slice(0, start.length -1); 164 | result = result + ""; 165 | result = result + start.slice(start.length-1, start.length); 166 | result = result + ""; 167 | result = result + end; 168 | result = result + ""; 169 | } 170 | 171 | else{ 172 | 173 | var tail = 22 - (word.length + 7); 174 | word = '.......' + word + ('.'.repeat(tail)); 175 | 176 | var start = word.slice(0, word.length/2); 177 | var end = word.slice(word.length/2, word.length); 178 | 179 | var result; 180 | result = "" + start.slice(0, start.length -1); 181 | result = result + ""; 182 | result = result + start.slice(start.length-1, start.length); 183 | result = result + ""; 184 | result = result + end; 185 | result = result + ""; 186 | 187 | } 188 | 189 | result = result.replace(/\./g, ""); 190 | 191 | return result; 192 | } 193 | 194 | // Get the currently selected text, if any. 195 | // Shameless pinched from StackOverflow. 196 | function getSelectionText() { 197 | var text = ""; 198 | if (typeof window.getSelection != "undefined") { 199 | var sel = window.getSelection(); 200 | if (sel.rangeCount) { 201 | var container = document.createElement("div"); 202 | for (var i = 0, len = sel.rangeCount; i < len; ++i) { 203 | container.appendChild(sel.getRangeAt(i).cloneContents()); 204 | } 205 | text = container.innerText || container.textContent; 206 | } 207 | } else if (typeof document.selection != "undefined") { 208 | if (document.selection.type == "Text") { 209 | text = document.selection.createRange().text; 210 | } 211 | } 212 | if(text === ""){ 213 | return false; 214 | } 215 | else{ 216 | return text; 217 | } 218 | } 219 | 220 | // Uses the Readability API to get the juicy content of the current page. 221 | function spritzifyURL(){ 222 | var url = document.URL; 223 | 224 | $.getJSON("https://www.readability.com/api/content/v1/parser?url="+ url +"&token=" + readability_token +"&callback=?", 225 | function (data) { 226 | 227 | if(data.error){ 228 | $('#spritz_result').html("Article extraction failed. Try selecting text instead."); 229 | return; 230 | } 231 | 232 | var title = ''; 233 | if(data.title !== ""){ 234 | title = data.title + ". "; 235 | } 236 | 237 | var author = ''; 238 | if(data.author !== null){ 239 | author = "By " + data.author + ". "; 240 | } 241 | 242 | var body = jQuery(data.content).text(); // Textify HTML content. 243 | body = $.trim(body); // Trip trailing and leading whitespace. 244 | body = body.replace(/\s+/g, ' '); // Shrink long whitespaces. 245 | 246 | var text_content = title + author + body; 247 | text_content = text_content.replace(/\./g, '. '); // Make sure punctuation is apprpriately spaced. 248 | text_content = text_content.replace(/\?/g, '? '); 249 | text_content = text_content.replace(/\!/g, '! '); 250 | spritzify(text_content); 251 | }); 252 | 253 | } 254 | 255 | ////// 256 | // Helpers 257 | ////// 258 | 259 | // This is a hack using the fact that browers sequentially id the timers. 260 | function clearTimeouts(){ 261 | var id = window.setTimeout(function() {}, 0); 262 | 263 | while (id--) { 264 | window.clearTimeout(id); 265 | } 266 | } 267 | 268 | // Let strings repeat themselves, 269 | // because JavaScript isn't as awesome as Python. 270 | String.prototype.repeat = function( num ){ 271 | return new Array( num + 1 ).join( this ); 272 | } 273 | 274 | --------------------------------------------------------------------------------