├── app ├── .htaccess ├── templates │ ├── error.html │ └── sql_table.html ├── data │ ├── base.sqlite3 │ ├── session.sqlite3 │ └── base.dump.sql ├── config.json ├── INSTALL ├── session.php ├── user_data.php └── common.php ├── session └── .htaccess ├── dev ├── img │ ├── .gitignore │ ├── sqmight.png │ ├── sqmight.pxm │ ├── throbber_big.gif │ ├── throbber_small.gif │ ├── dark_linen_brown1.png │ ├── dark_stripes_red1.png │ └── dark_leather_brown1.gif ├── js │ ├── mylibs │ │ └── .gitignore │ ├── libs │ │ └── prettify.min │ │ │ ├── lang-sql.js │ │ │ └── prettify.js │ ├── plugins.js │ └── script.js ├── dump.php ├── schema.pdf ├── favicon.ico ├── css │ ├── style.css │ ├── prettify.css │ ├── sqmight.css │ └── base.css ├── robots.txt ├── create_db.php ├── .htaccess ├── test │ ├── tests.js │ ├── index.html │ └── qunit │ │ ├── qunit.css │ │ └── qunit.js ├── crossdomain.xml ├── 404.html ├── humans.txt ├── query.php ├── about.md ├── index.html └── about.html ├── .htaccess ├── .gitignore ├── LICENSE ├── TODO └── Rakefile /app/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all 2 | -------------------------------------------------------------------------------- /session/.htaccess: -------------------------------------------------------------------------------- 1 | deny from all 2 | -------------------------------------------------------------------------------- /dev/img/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | 3 | -------------------------------------------------------------------------------- /dev/js/mylibs/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | 3 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | #TODO: deny from all 2 | php_value open_basedir "/" -------------------------------------------------------------------------------- /dev/dump.php: -------------------------------------------------------------------------------- 1 |

-------------------------------------------------------------------------------- /dev/schema.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/schema.pdf -------------------------------------------------------------------------------- /dev/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/favicon.ico -------------------------------------------------------------------------------- /dev/img/sqmight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/img/sqmight.png -------------------------------------------------------------------------------- /dev/img/sqmight.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/img/sqmight.pxm -------------------------------------------------------------------------------- /app/data/base.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/app/data/base.sqlite3 -------------------------------------------------------------------------------- /app/data/session.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/app/data/session.sqlite3 -------------------------------------------------------------------------------- /dev/img/throbber_big.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/img/throbber_big.gif -------------------------------------------------------------------------------- /dev/img/throbber_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/img/throbber_small.gif -------------------------------------------------------------------------------- /dev/img/dark_linen_brown1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/img/dark_linen_brown1.png -------------------------------------------------------------------------------- /dev/img/dark_stripes_red1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/img/dark_stripes_red1.png -------------------------------------------------------------------------------- /dev/css/style.css: -------------------------------------------------------------------------------- 1 | @import url("base.css"); 2 | @import url("sqmight.css"); 3 | @import url("prettify.css"); 4 | -------------------------------------------------------------------------------- /dev/img/dark_leather_brown1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/img/dark_leather_brown1.gif -------------------------------------------------------------------------------- /dev/js/libs/prettify.min/lang-sql.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tag/sqmight/master/dev/js/libs/prettify.min/lang-sql.js -------------------------------------------------------------------------------- /dev/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | 6 | -------------------------------------------------------------------------------- /app/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": "development", 3 | "debug": true, 4 | 5 | "user_data_path": "./session/", 6 | "php": { 7 | "date.timezone":"America/New_York", 8 | "display_errors": "On" 9 | } 10 | } -------------------------------------------------------------------------------- /app/INSTALL: -------------------------------------------------------------------------------- 1 | 2 | The following folders must be writeable by the server: 3 | + /app/log 4 | + /app/session 5 | 6 | The following files must be writable by the server: 7 | + /app/data/sessions.sqlite3 8 | 9 | All other files/folders should NOT be writeable by the server 10 | 11 | Some hosts don' allow PHP directives in .htaccess. If this is the case, use the php.ini; if not, delete php.ini. -------------------------------------------------------------------------------- /app/templates/sql_table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | '.H($val).''; 6 | } 7 | ?> 8 | 9 | "; 12 | foreach($T['columns'] as $key) { 13 | echo ''; 14 | } 15 | echo "\n"; 16 | } 17 | ?> 18 | 19 |
'.H($row[$key]).'
-------------------------------------------------------------------------------- /dev/create_db.php: -------------------------------------------------------------------------------- 1 | createUserDb(); 17 | //$userDb = $userDbHandler->getDbConnection(); 18 | 19 | //header('Content-type: application/json'); 20 | 21 | echo '{"response":"created","session":'; 22 | echo json_encode($_SESSION); 23 | echo '}'; 24 | -------------------------------------------------------------------------------- /dev/.htaccess: -------------------------------------------------------------------------------- 1 | php_flag register_globals off 2 | php_flag magic_quotes_gpc off 3 | php_flag magic_quotes_runtime off 4 | 5 | php_flag display_startup_errors off 6 | php_flag display_errors off 7 | php_flag log_errors on 8 | 9 | php_value error_reporting 32767 10 | 11 | php_flag session.use_cookies on 12 | php_flag session.use_only_cookies on 13 | 14 | php_value error_log "../app/log/error.log" 15 | 16 | php_value session.name "SQM_SID" 17 | php_value session.save_path "../session" 18 | # 10800 == 3 hrs 19 | php_value session.gc_maxlifetime 10800 20 | php_value session.cookie_lifetime 10800 21 | 22 | php_value open_basedir "../" 23 | 24 | Options MultiViews 25 | 26 | BrowserMatch MSIE ie 27 | Header set X-UA-Compatible "IE=Edge,chrome=1" env=ie 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | app/data/scripts 2 | dev/js/libs/prettify.min/lang-apollo.js 3 | dev/js/libs/prettify.min/lang-clj.js 4 | dev/js/libs/prettify.min/lang-css.js 5 | dev/js/libs/prettify.min/lang-go.js 6 | dev/js/libs/prettify.min/lang-hs.js 7 | dev/js/libs/prettify.min/lang-lisp.js 8 | dev/js/libs/prettify.min/lang-lua.js 9 | dev/js/libs/prettify.min/lang-ml.js 10 | dev/js/libs/prettify.min/lang-n.js 11 | dev/js/libs/prettify.min/lang-proto.js 12 | dev/js/libs/prettify.min/lang-scala.js 13 | dev/js/libs/prettify.min/lang-tex.js 14 | dev/js/libs/prettify.min/lang-vb.js 15 | dev/js/libs/prettify.min/lang-vhdl.js 16 | dev/js/libs/prettify.min/lang-wiki.js 17 | dev/js/libs/prettify.min/lang-xq.js 18 | dev/js/libs/prettify.min/lang-yaml.js 19 | intermediate 20 | publish 21 | -------------------------------------------------------------------------------- /dev/test/tests.js: -------------------------------------------------------------------------------- 1 | 2 | // documentation on writing tests here: http://docs.jquery.com/QUnit 3 | // example tests: https://github.com/jquery/qunit/blob/master/test/same.js 4 | 5 | // below are some general tests but feel free to delete them. 6 | 7 | module("example tests"); 8 | test("HTML5 Boilerplate is sweet",function(){ 9 | expect(1); 10 | equals("boilerplate".replace("boilerplate","sweet"),"sweet","Yes. HTML5 Boilerplate is, in fact, sweet"); 11 | 12 | }) 13 | 14 | // these test things from plugins.js 15 | test("Environment is good",function(){ 16 | expect(3); 17 | ok( !!window.log, "log function present"); 18 | 19 | var history = log.history && log.history.length || 0; 20 | log("logging from the test suite.") 21 | equals( log.history.length - history, 1, "log history keeps track" ) 22 | 23 | ok( !!window.Modernizr, "Modernizr global is present") 24 | }) 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /dev/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /dev/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit Tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

QUnit Test Suite

25 |

26 |
27 |

28 |
    29 |
    test markup
    30 | 31 | 32 | -------------------------------------------------------------------------------- /dev/js/plugins.js: -------------------------------------------------------------------------------- 1 | 2 | // usage: log('inside coolFunc', this, arguments); 3 | // paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/ 4 | window.log = function(){ 5 | log.history = log.history || []; // store logs to an array for reference 6 | log.history.push(arguments); 7 | if(this.console) { 8 | arguments.callee = arguments.callee.caller; 9 | var newarr = [].slice.call(arguments); 10 | (typeof console.log === 'object') ? log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr); 11 | } 12 | }; 13 | 14 | // make it safe to use console.log always 15 | (function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try 16 | {console.log();return window.console;}catch(err){return window.console={};}})()); 17 | 18 | 19 | // place any jQuery/helper plugins in here, instead of separate, slower script files. 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, 2013 Tom Gregory 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /dev/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 18 | 19 | 20 |
    21 |

    Not found :(

    22 |
    23 |

    Sorry, but the page you were trying to view does not exist.

    24 |

    It looks like this was the result of either:

    25 | 29 |
    30 | 31 | 35 | 36 |
    37 | 38 | -------------------------------------------------------------------------------- /dev/humans.txt: -------------------------------------------------------------------------------- 1 | /* the humans responsible & colophon */ 2 | 3 | 4 | /* TEAM */ 5 | Some Guy: Tom Gregory 6 | Site: http://alt-tag.com 7 | Location: USA 8 | 9 | /* THANKS */ 10 | Names (& URL): 11 | 12 | /* SITE */ 13 | Standards: HTML5, CSS3 14 | Components: Modernizr, Prototype, Scriptaculous, HTML5Boilerplate 15 | Software: Apache2, PHP, SQLite3 16 | 17 | 18 | 19 | -o/- 20 | +oo//- 21 | :ooo+//: 22 | -ooooo///- 23 | /oooooo//: 24 | :ooooooo+//- 25 | -+oooooooo///- 26 | -://////////////+oooooooooo++////////////:: 27 | :+ooooooooooooooooooooooooooooooooooooo+:::- 28 | -/+ooooooooooooooooooooooooooooooo+/::////:- 29 | -:+oooooooooooooooooooooooooooo/::///////:- 30 | --/+ooooooooooooooooooooo+::://////:- 31 | -:+ooooooooooooooooo+:://////:-- 32 | /ooooooooooooooooo+//////:- 33 | -ooooooooooooooooooo////- 34 | /ooooooooo+oooooooooo//: 35 | :ooooooo+/::/+oooooooo+//- 36 | -oooooo/::///////+oooooo///- 37 | /ooo+::://////:---:/+oooo//: 38 | -o+/::///////:- -:/+o+//- 39 | :-:///////:- -:/:// 40 | -////:- --//: 41 | -- -: 42 | -------------------------------------------------------------------------------- /dev/query.php: -------------------------------------------------------------------------------- 1 | render('error', array('error'=>'Empty query.')); 12 | exit; 13 | } 14 | 15 | if (strlen($sql) > 1024) { 16 | echo $app->render('error', array('error'=>'Query too long. 1024 character limit.')); 17 | exit; 18 | } 19 | 20 | #TODO: Validate query — Input checking 21 | 22 | ### Open User's Session and DbConnection 23 | require_once('app/session.php'); 24 | require_once('app/user_data.php'); 25 | 26 | $app = new ApplicationController(); 27 | 28 | $userDbHandler = new SQM_UserDbHandler(); 29 | $userDb = $userDbHandler->getDbConnection(); 30 | 31 | if (!$userDb) { 32 | echo $app->render('error', array('error'=>'No database instance found for session.')); 33 | exit; 34 | } 35 | 36 | $result = $userDb->query($sql); # $result is PDOStatement object or FALSE on error 37 | 38 | ### Handle db/query, errors 39 | if (!$result) { 40 | #TODO: better error handling 41 | $err = $userDb->errorInfo(); 42 | echo $app->render('error', array('error'=>'Database error: '.H($err[2]))); 43 | exit; 44 | } 45 | 46 | ### Output PDOStatement 47 | ob_start(); 48 | $data = $result->fetchAll(PDO::FETCH_ASSOC); 49 | $columns = array(); 50 | 51 | if (empty($data)) { 52 | echo '(empty)'; 53 | } else { 54 | $columns = array_keys($data[0]); 55 | echo $app->render('sql_table', array('data'=>$data, 'columns'=>$columns)); 56 | } 57 | ob_end_flush(); -------------------------------------------------------------------------------- /dev/js/script.js: -------------------------------------------------------------------------------- 1 | /* Author: 2 | 3 | */ 4 | 5 | var SQM = { 6 | push: function(query, qid) { 7 | /** if ($F('sql').length > 1024) { 8 | $('output').update('

    Query too long (1024 character limit).

    '); 9 | return; 10 | }**/ 11 | 12 | $('user_sql').request({ 13 | onComplete: function(response){ 14 | $(qid+'-response').update(response.responseText); 15 | } 16 | }); 17 | 18 | var ppQuery = prettyPrintOne(query.escapeHTML(), 'sql', false); 19 | 20 | var li = new Element('li'); 21 | li.id = qid; 22 | li.update('
    '
    23 | 			+ ppQuery + '
    '); 24 | // style="display:none" 25 | //X 26 | 27 | //TODO slide down new inertion 28 | $('history').insert({top:li}); 29 | 30 | // TODO: pop if a size limit is reached? Mask? 31 | }, 32 | 33 | delete_history: function () { 34 | if (!confirm("really?")) {return;} 35 | alert(this); 36 | } 37 | }; 38 | 39 | document.observe("dom:loaded", function() { 40 | $('user_sql').observe('submit', function(evt) { 41 | evt.stop(); 42 | 43 | if (!$('results').visible()) { 44 | Effect.SlideDown('results', { duration: 0.5}); 45 | } 46 | 47 | $('sql').value = $F('sql').strip(); 48 | 49 | //push sql statement to history and execute 50 | SQM.push($F('sql'), 'output-' +new Date().getTime()); 51 | }); 52 | 53 | $('user_create_db').show(); 54 | $('user_create_db').observe('submit', function(evt) { 55 | evt.stop(); 56 | 57 | // Show throbber. 58 | $('create').hide(); 59 | $('throbber_create').show(); 60 | 61 | //TODO: Should check to see if db is valid, first 62 | $('user_create_db').request({ 63 | onSuccess: function(response){ 64 | $('entry').remove(); 65 | Effect.SlideDown('user_sql', { duration: 0.5}); 66 | }, 67 | onFailure: function(response){ 68 | $('user_create_db').replace( response.responseText 69 | || '

    An unknown error occurred.

    '); 70 | } 71 | }); 72 | }); 73 | 74 | }); -------------------------------------------------------------------------------- /dev/css/prettify.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Derived from einaros's Sons of Obsidian theme at 3 | * http://studiostyl.es/schemes/son-of-obsidian by 4 | * Alex Ford of CodeTunnel: 5 | * http://CodeTunnel.com/blog/post/71/google-code-prettify-obsidian-theme 6 | * Further tweaks by Tom Gregory, http://alt-tag.com 7 | */ 8 | 9 | .str 10 | { 11 | color: #EC7600; 12 | } 13 | .kwd 14 | { 15 | color: #93C763; 16 | font-weight: bold; 17 | } 18 | .com 19 | { 20 | color: #66747B; 21 | } 22 | .typ 23 | { 24 | color: #678CB1; 25 | } 26 | .lit 27 | { 28 | color: #FACD22; 29 | } 30 | .pun 31 | { 32 | color: #ccc; 33 | } 34 | .pln 35 | { 36 | color: #F1F2F3; 37 | } 38 | .tag 39 | { 40 | color: #8AC763; 41 | } 42 | .atn 43 | { 44 | color: #E0E2E4; 45 | } 46 | .atv 47 | { 48 | color: #EC7600; 49 | } 50 | .dec 51 | { 52 | color: purple; 53 | } 54 | pre.prettyprint 55 | { 56 | border: 0px solid #888; 57 | } 58 | ol.linenums 59 | { 60 | margin-top: 0; 61 | margin-bottom: 0; 62 | } 63 | .prettyprint { 64 | background: #111; 65 | } 66 | li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9 67 | { 68 | color: #555; 69 | } 70 | li.L1, li.L3, li.L5, li.L7, li.L9 { 71 | background: #111; 72 | } 73 | @media print 74 | { 75 | .str 76 | { 77 | color: #060; 78 | } 79 | .kwd 80 | { 81 | color: #006; 82 | font-weight: bold; 83 | } 84 | .com 85 | { 86 | color: #600; 87 | font-style: italic; 88 | } 89 | .typ 90 | { 91 | color: #404; 92 | font-weight: bold; 93 | } 94 | .lit 95 | { 96 | color: #044; 97 | } 98 | .pun 99 | { 100 | color: #440; 101 | } 102 | .pln 103 | { 104 | color: #000; 105 | } 106 | .tag 107 | { 108 | color: #006; 109 | font-weight: bold; 110 | } 111 | .atn 112 | { 113 | color: #404; 114 | } 115 | .atv 116 | { 117 | color: #060; 118 | } 119 | } -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | + get it working on Bluehost 2 | + Remove error reporting code from common.php (on Bluehost) 3 | + db create more dummy data! 4 | + sha1 db password 5 | + lower case db email 6 | 7 | + Build automation: Markdown, About page 8 | + About page 9 | + Schema printing 10 | + Remove index's js from md template file 11 | + Create index.html off of md template 12 | + Update Redcloth (instead of PHP-markdown) in about 13 | + update md template to include main, text align left, fix links to amazon, etc. 14 | + Make header a link home (if not index!) 15 | 16 | + remove associated db on session destroy (the whole point of a custom session handler!) 17 | 18 | + Install optipng to enable png optimization in build process. For instructions see 'Dependencies' at: http://html5boilerplate.com/docs/#Build-script#dependencies 19 | 20 | + db reset 21 | 22 | + Add behavior to delete history queries from DOM 23 | + Build automation (rake): minify & concat prettify/lang-sql; include developer-readable js in project 24 | + Trim preffity (e.g., so unused onload event doesn't fire) 25 | + SQL parsing/verification 26 | + sql dump export 27 | + Include query run time in output 28 | + Include character count below query box 29 | 30 | + Add LIMIT to keywords in pretify sql-lang (temporarily added; test/submit ticket/pull request) 31 | + Handle printing of multiline queries; e.g. "SELECT * FROM users -- foo \n LIMIT 5;" 32 | + When packaging: create dummy config.js.example 33 | 34 | + Add html, css, .htaccess comments TODOs to rakefile :todo 35 | + favicon 36 | 37 | AUTO-GENERATED 38 | -------------- 39 | + slide down new inertion (./dev/js/script.js:27) 40 | + pop if a size limit is reached? Mask? (./dev/js/script.js:30) 41 | + Should check to see if db is valid, first (./dev/js/script.js:61) 42 | + implement dump.php (./dev/dump.php:2) 43 | + Validate query — Input checking (./dev/query.php:20) 44 | + better error handling (./dev/query.php:40) 45 | + if not XHR, show full page. (./app/common.php:135) 46 | + fix HTTP status code (./app/common.php:170) 47 | + Verify key, remove associated user db (./app/session.php:112) 48 | + Verify key, remove associated user db (./app/session.php:126) 49 | + Add error logging (./app/user_data.php:17) 50 | + exportUserDb not yet completed (./app/user_data.php:72) 51 | -------------------------------------------------------------------------------- /dev/about.md: -------------------------------------------------------------------------------- 1 | About **sqMight** 2 | ================= 3 | 4 | *sqMight* uses [sqLite](http://www.sqlite.org/) as a backend, and offers each user a unique, temporary database instance. So, yes, go ahead and write those INSERT and UPDATE queries. CREATE or DROP tables. No one will know but you. Your personal database instance will stick around for a couple of hours, then it's kaput. 5 | 6 | *sqMight* is intended for use in learning SQL, and is available under an MIT-style license. 7 | 8 | Items listed in the 'products' table are real items found online. Many are listed at [hackerthings.com](http://hackerthings.com), [thinkgeek.com](http://thinkgeek.com), or [amazon.com](http://amazon.com). None of these shopping sites have any affiliation with this site. 9 | 10 | If you wish to incorporate *sqMight* into your classroom curriculum or research, or otherwise use it commercially, please contact Tom Gregory at tom@alt-tag.com. 11 | 12 | *sqMight* was conceived and built by [Tom Gregory](http://alt-tag.com), based on an idea from [Gove Allen](http://gove.net/home/page1.html) in 2009. 13 | 14 | *sqMight* uses code, ideas, images, or inspiration from the following publicly avaible sources: 15 | 16 | + [Prototype](http://prototypejs.org), by Sam Stephenson 17 | Copyright © 2005–2010; [MIT-style license](https://raw.github.com/sstephenson/prototype/master/LICENSE) 18 | + [Scriptaculous](http://script.aculo.us/), by Thomas Fuchs 19 | Copyright © 2005-2008; [MIT-style license](http://madrobby.github.com/scriptaculous/license/) 20 | + [php-markdown](https://github.com/wolfie/php-markdown), by [Michel Fortin](http://www.michelf.com/) 21 | Copyright © 2004, All rights reserved; BSD-style open license. 22 | + [Markdown](http://daringfireball.net/projects/markdown/), by [John Gruber](http://daringfireball.net/ "Daring Fireball") 23 | Copyright © 2004, All rights reserved; BSD-style open license. 24 | + [Google Code Pretify](http://code.google.com/p/google-code-prettify/), Google 25 | Copyright © 2006; [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0) 26 | + [HTML5 Boilerplate](http://html5boilerplate.com/), Public Domain 27 | + [Modernizr](http://www.modernizr.com/license/), MIT/BSD license 28 | + [Respond.js](https://github.com/scottjehl/Respond/blob/master/README.md), MIT/GPL license 29 | + [Normalize.css](https://github.com/necolas/normalize.css/blob/master/README.md), Public Domain 30 | + Background textures adapted from [subtlepatterns.com](http://subtlepatterns.com) 31 | + Throbbers from [ajaxloader.info](http://ajaxloader.info). 32 | -------------------------------------------------------------------------------- /app/session.php: -------------------------------------------------------------------------------- 1 | , © 2012 5 | * 6 | * Custom session handler; creates/destroys user db instances and session data. 7 | * Implemented as a Singlton. 8 | */ 9 | class SQM_SessionHandler 10 | { 11 | private static $instance; // Singleton 12 | static private $dbPath = './app/data/session.sqlite3'; 13 | private $db; 14 | 15 | private function __construct() { 16 | session_set_save_handler( 17 | array($this, "open"), 18 | array($this, "close"), 19 | array($this, "read"), 20 | array($this, "write"), 21 | array($this, "destroy"), 22 | array($this, "clean")); 23 | 24 | register_shutdown_function('session_write_close'); // Triggers session shutdown before objects are unloaded 25 | session_start(); 26 | } 27 | 28 | public static function singleton() 29 | { 30 | if (!isset(self::$instance)) { 31 | $className = __CLASS__; 32 | self::$instance = new $className; 33 | } 34 | return self::$instance; 35 | } 36 | 37 | public function __clone() { 38 | trigger_error('Clone is not allowed.', E_USER_ERROR); 39 | } 40 | 41 | public function __wakeup() { 42 | trigger_error('Unserializing is not allowed.', E_USER_ERROR); 43 | } 44 | 45 | /*** 46 | * Opens session database for reading. Params not used, but provided per spec. 47 | * See e.g., http://us2.php.net/manual/en/function.session-set-save-handler.php 48 | * 49 | * @param string $save_path 50 | * @param string $session_name 51 | * @return bool success 52 | */ 53 | function open($save_path, $session_name) { 54 | $this->db = new PDO('sqlite:'.self::$dbPath); 55 | 56 | assert($this->db); 57 | 58 | return TRUE; 59 | } 60 | 61 | /*** 62 | * Closes db connection by nulling PDO object 63 | * 64 | * @return bool success 65 | */ 66 | function close() { 67 | $this->db = null; 68 | return TRUE; 69 | } 70 | 71 | /** 72 | * Reads session data. Parsed automatically by PHP 73 | * 74 | * @return string encoded session data; empty string if no data 75 | */ 76 | public function read($id) { 77 | assert($this->db); 78 | 79 | $sql = $this->db->prepare('SELECT data FROM sessions WHERE id = ?'); 80 | $sql->execute(array($id)); 81 | 82 | $data = $sql->fetch(PDO::FETCH_ASSOC); 83 | 84 | return $data === FALSE ? '' : $data['data']; 85 | } 86 | 87 | /** 88 | * Writes session data, which is already encoded by PHP 89 | * 90 | * @param string session id 91 | * @return bool success 92 | */ 93 | 94 | public function write($id, $data) { 95 | assert($this->db); 96 | 97 | $sql = $this->db->prepare('REPLACE INTO sessions (id, access_time, data) VALUES (?, ?, ?)'); 98 | return $sql->execute(array($id, time(), $data)); 99 | } 100 | 101 | /** 102 | * Destroys session data 103 | * 104 | * 105 | * @return bool success; no return value specificed by PHP docs 106 | */ 107 | 108 | public function destroy($id) { 109 | assert($this->db); 110 | 111 | $sql = $this->db->prepare('DELETE FROM sessions WHERE id = ?'); 112 | #TODO: Verify key, remove associated user db 113 | return $sql->execute(array($id)); 114 | } 115 | 116 | /** 117 | * @param int max session lifetime (seconds) 118 | */ 119 | public function clean($max) { 120 | assert($this->db); 121 | 122 | $old = time() - $max; 123 | 124 | $getData = $this->db->prepare('DELETE FROM sessions WHERE access_time < ?'); 125 | 126 | #TODO: Verify key, remove associated user db 127 | 128 | return $getData->execute(array($old)); 129 | } 130 | } 131 | 132 | /*** 133 | 134 | CREATE TABLE sessions 135 | ( 136 | id text NOT NULL, 137 | access_time int DEFAULT 0, 138 | data text DEFAULT '', 139 | PRIMARY KEY (id) 140 | ); 141 | 142 | ***/ 143 | 144 | $GLOBALS['session_handler'] = SQM_SessionHandler::singleton(); 145 | 146 | 147 | -------------------------------------------------------------------------------- /dev/test/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** Font Family and Sizes */ 2 | 3 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 4 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 5 | } 6 | 7 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 8 | #qunit-tests { font-size: smaller; } 9 | 10 | 11 | /** Resets */ 12 | 13 | #qunit-tests, #qunit-tests li ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | 19 | /** Header */ 20 | 21 | #qunit-header { 22 | padding: 0.5em 0 0.5em 1em; 23 | 24 | color: #fff; 25 | text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; 26 | background-color: #0d3349; 27 | 28 | border-radius: 15px 15px 0 0; 29 | -moz-border-radius: 15px 15px 0 0; 30 | -webkit-border-top-right-radius: 15px; 31 | -webkit-border-top-left-radius: 15px; 32 | } 33 | 34 | #qunit-banner { 35 | height: 5px; 36 | } 37 | 38 | #qunit-testrunner-toolbar { 39 | padding: 0em 0 0.5em 2em; 40 | } 41 | 42 | #qunit-userAgent { 43 | padding: 0.5em 0 0.5em 2.5em; 44 | background-color: #2b81af; 45 | color: #fff; 46 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 47 | } 48 | 49 | 50 | /** Tests: Pass/Fail */ 51 | 52 | #qunit-tests { 53 | list-style-position: inside; 54 | } 55 | 56 | #qunit-tests li { 57 | padding: 0.4em 0.5em 0.4em 2.5em; 58 | border-bottom: 1px solid #fff; 59 | list-style-position: inside; 60 | } 61 | 62 | #qunit-tests li strong { 63 | cursor: pointer; 64 | } 65 | 66 | #qunit-tests li ol { 67 | margin-top: 0.5em; 68 | padding: 0.5em; 69 | 70 | background-color: #fff; 71 | 72 | border-radius: 15px; 73 | -moz-border-radius: 15px; 74 | -webkit-border-radius: 15px; 75 | 76 | box-shadow: inset 0px 2px 13px #999; 77 | -moz-box-shadow: inset 0px 2px 13px #999; 78 | -webkit-box-shadow: inset 0px 2px 13px #999; 79 | } 80 | 81 | #qunit-tests li li { 82 | margin: 0.5em; 83 | padding: 0.4em 0.5em 0.4em 0.5em; 84 | background-color: #fff; 85 | border-bottom: none; 86 | list-style-position: inside; 87 | } 88 | 89 | /*** Passing Styles */ 90 | 91 | #qunit-tests li li.pass { 92 | color: #5E740B; 93 | background-color: #fff; 94 | border-left: 26px solid #C6E746; 95 | } 96 | 97 | #qunit-tests li.pass { color: #528CE0; background-color: #D2E0E6; } 98 | #qunit-tests li.pass span.test-name { color: #366097; } 99 | 100 | #qunit-tests li li.pass span.test-actual, 101 | #qunit-tests li li.pass span.test-expected { color: #999999; } 102 | 103 | strong b.pass { color: #5E740B; } 104 | 105 | #qunit-banner.qunit-pass { background-color: #C6E746; } 106 | 107 | /*** Failing Styles */ 108 | 109 | #qunit-tests li li.fail { 110 | color: #710909; 111 | background-color: #fff; 112 | border-left: 26px solid #EE5757; 113 | } 114 | 115 | #qunit-tests li.fail { color: #000000; background-color: #EE5757; } 116 | #qunit-tests li.fail span.test-name, 117 | #qunit-tests li.fail span.module-name { color: #000000; } 118 | 119 | #qunit-tests li li.fail span.test-actual { color: #EE5757; } 120 | #qunit-tests li li.fail span.test-expected { color: green; } 121 | 122 | strong b.fail { color: #710909; } 123 | 124 | #qunit-banner.qunit-fail, 125 | #qunit-testrunner-toolbar { background-color: #EE5757; } 126 | 127 | 128 | /** Footer */ 129 | 130 | #qunit-testresult { 131 | padding: 0.5em 0.5em 0.5em 2.5em; 132 | 133 | color: #2b81af; 134 | background-color: #D2E0E6; 135 | 136 | border-radius: 0 0 15px 15px; 137 | -moz-border-radius: 0 0 15px 15px; 138 | -webkit-border-bottom-right-radius: 15px; 139 | -webkit-border-bottom-left-radius: 15px; 140 | } 141 | 142 | /** Fixture */ 143 | 144 | #qunit-fixture { 145 | position: absolute; 146 | top: -10000px; 147 | left: -10000px; 148 | } 149 | -------------------------------------------------------------------------------- /app/user_data.php: -------------------------------------------------------------------------------- 1 | authUserDb() 12 | * 13 | * @return objec|bool PDOConnection on success, else FALSE 14 | **/ 15 | 16 | public function getDbConnection() { 17 | if (!$this->authUserDb()) {return FALSE;} #TODO: Add error logging 18 | 19 | if (!$this->db) { 20 | // Confirm dbName is alphanumeric. (It shouldn't ever change.) 21 | assert(ctype_alnum($_SESSION['dbName'])); 22 | error_log(USER_DATA_PATH.'/'.$_SESSION['dbName'].'.sqlite3'); 23 | try { 24 | $this->db = new PDO( 25 | 'sqlite:'.USER_DATA_PATH.'/'.$_SESSION['dbName'].'.sqlite3'); 26 | } catch (Exception $e) { 27 | error_log('ERROR OPENING USER DB:' . USER_DATA_PATH . 28 | '/'.$_SESSION['dbName'].'.sqlite3'); 29 | throw $e; 30 | } 31 | } 32 | return $this->db; 33 | } 34 | 35 | public function createUserDb() { 36 | // Remove old DB, if exists 37 | $this->destroyUserDb(); 38 | 39 | $_SESSION['dbName'] = $this->generateToken(); 40 | $key = hash_hmac('sha256', $this->generateToken(32) . 41 | (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? 42 | $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']) 43 | , time()); 44 | 45 | setcookie('key', $key, time()+ini_get('session.gc_maxlifetime'), '/'); 46 | 47 | # hash MUST match sha512(key, $dbName) or DB no worky 48 | $_SESSION['auth'] = $this->userHash($key); 49 | 50 | error_log('sqlite3 '.USER_DATA_PATH.'/'.$_SESSION['dbName'] 51 | .'.sqlite3 < ./app/data/base.dump.sql'); 52 | $tmp = exec('sqlite3 '.USER_DATA_PATH.'/'.$_SESSION['dbName'] 53 | .'.sqlite3 < ./app/data/base.dump.sql'); 54 | error_log('Exec output:' . $tmp); 55 | } 56 | 57 | public function destroyUserDb() { 58 | if (!$this->authUserDb()) {return FALSE;} 59 | error_log('SESSION DESTROY: destroyUserDb'); 60 | unlink(USER_DATA_PATH.'/'.$_SESSION['dbName'].'.sqlite3'); 61 | 62 | setcookie('key', '', time() - 3600, '/'); // Negative time deletes cookie 63 | 64 | $_SESSION = array(); // Clean out session variables 65 | if (session_id() != '') { 66 | session_destroy(); 67 | session_start(); 68 | } 69 | } 70 | 71 | public function exportUserDb() { 72 | assert(FALSE); //TODO: exportUserDb not yet completed 73 | if (!$this->authUserDb()) {return FALSE;} 74 | 75 | $tmp = exec('sqlite3 '.USER_DATA_PATH.'/'.$_SESSION['dbName'].'.sqlite3 .dump'); 76 | 77 | echo $tmp; 78 | } 79 | 80 | 81 | /** 82 | * Relies on $_SESSION['dbName'], session auth token, and user's 'key' cookie 83 | * @return bool success 84 | */ 85 | public function authUserDb() { 86 | if (!isset($_SESSION['dbName']) 87 | || ! isset($_SESSION['auth']) 88 | || ! isset($_COOKIE['key'])) {return FALSE;} 89 | 90 | // Update timestamp on hash cookie 91 | setcookie('key', $_COOKIE['key'], time()+ini_get('session.gc_maxlifetime'), '/'); 92 | 93 | // hash MUST match sha512(key . IP_addr, $dbName) or DB doesn't authenticate 94 | $hash = $this->userHash($_COOKIE['key']); 95 | 96 | return $hash === $_SESSION['auth']; 97 | } 98 | 99 | /** 100 | * @param string has of user supplied key + IP address using dbName 101 | * @return bool success 102 | */ 103 | protected function userHash($key) { 104 | return hash_hmac('sha512', $key . 105 | (isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? 106 | $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']) 107 | , $_SESSION['dbName']); 108 | } 109 | 110 | /** 111 | * @param int token length 112 | * @return string random token 113 | */ 114 | protected function generateToken($len = 20) { 115 | $chars = array(0,1,2,3,4,5,6,7,8,9,0, 116 | 'a','b','c','d','e','f','g','h','i','j','k','m', 117 | 'n','o','p','q','r','s','t','u','v','w','x','y','z'); 118 | shuffle($chars); 119 | return implode('', array_slice($chars, 0, $len)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 |
    34 |
    35 |

    sqMight

    36 |
    37 | 43 | 48 |
    49 | 50 |
    51 |
    52 |

    Welcome to sqMight

    53 | 54 | 61 |
    62 | 63 | 70 | 71 | 75 |
    76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 93 | 97 | 98 | 99 | 100 | Fork me on GitHub 101 | 102 | 103 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #task :default => [ :clean, :fresh_scriptaculous, :package ] 2 | 3 | # task :clean do 4 | # rm_rf PKG_DESTINATION 5 | # end 6 | 7 | #task :default => [ :jslint ] 8 | task :default => [ :md ] 9 | 10 | 11 | # task :build => [ :basics_production ] 12 | # task :test => [ :basics_test ] 13 | # task :dev => [ :basics_dev 14 | 15 | 16 | dir = { :source => '.', 17 | :publish => 'publish', 18 | :js => 'js', 19 | } 20 | dir[:js_lib] = File.join(dir[:js], 'lib') 21 | dir[:dev] = File.join(dir[:source], 'dev') 22 | dir[:build] = File.join(dir[:source], 'build') 23 | dir[:build_tools] = File.join(dir[:build], 'tools') 24 | dir[:build_templates] = File.join(dir[:build], 'templates') 25 | 26 | #http://www.jslint.com/lint.html 27 | opt = {} 28 | opt[:jslint] = 'maxerr=25,evil=true,browser=true,eqeqeq=true,immed=true,newcap=true,nomen=true,es5=true,rhino=true,undef=true,white=false' 29 | 30 | summary = [] 31 | 32 | desc "Run jslint on dev js files" 33 | task :jslint do #=> :summary 34 | fl = FileList.new(File.join( dir[:dev], dir[:js], '**', '*.js')) 35 | fl.exclude('*.min.js.'); 36 | fl.exclude(File.join(dir[:dev], dir[:js_lib])) 37 | fl.each {|file| 38 | puts "== jslint on #{file}" 39 | sh "java -jar #{File.join(dir[:build_tools], 'rhino.jar')} #{File.join(dir[:build_tools], 'fulljslint.js')} #{file} #{opt[:jslint]}" do |ok, res| 40 | if !ok 41 | summary.push "jslint: Errors found in #{file}" 42 | end 43 | end 44 | } 45 | end 46 | 47 | # task :summary do 48 | # puts 'SUMMARY' 49 | # summary.each {|s| puts s} 50 | # end 51 | 52 | desc "Searches code files for TODOs, appends to TODO file" 53 | task :todo do 54 | todo = File.join(dir[:source],'TODO') 55 | content = File.read(todo) 56 | content.gsub!(/\nAUTO(-GENERATED)?\n-+\n.*/m, "\n") 57 | 58 | puts content 59 | File.open(todo, 'w') do |f| 60 | f.puts content, "AUTO-GENERATED\n--------------\n" 61 | 62 | fl = FileList.new(File.join( dir[:dev], '**', '*.{js,php,rb,bash}')) 63 | fl.exclude('*.min.js.') 64 | fl.exclude(File.join(dir[:dev], dir[:js_lib])) 65 | fl.include(File.join(dir[:source], '*.{js,php,rb,bash}')) 66 | 67 | fl.include(File.join(dir[:source], 'app','**','*.{js,php,rb,bash}')) 68 | 69 | pattern = /(\/\/|#)\s*TODO:?\s+(.+)$/ 70 | fl.each do |file| 71 | File.open(file) do |grep_file| 72 | grep_file.each do |line| 73 | f.puts "+ #{$2} (#{file}:#{grep_file.lineno.to_s})" if pattern.match(line) 74 | end 75 | end 76 | end 77 | end 78 | end 79 | 80 | # jshint 81 | # csslint 82 | # build => -build.production 83 | # -build.production => ["-rev, 84 | # -usemin, 85 | # -js.all.minify, 86 | # -js.main.concat, 87 | # -js.mylibs.concat, 88 | # -js.scripts.concat, 89 | # -css, 90 | # -manifest, 91 | # -htmlclean, 92 | # -imagespng, 93 | # -imagesjpg, 94 | # -copy] 95 | 96 | # minify => -minify.production 97 | 98 | # namespace :js 99 | # task :minify_prototype do 100 | # end 101 | # end 102 | 103 | # namespace :md 104 | # require 'rubygems' 105 | # require 'redcarpet' 106 | # markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, 107 | # :autolink => true, :strikethrough => true) 108 | # 109 | 110 | # rule ".html" => [".markdown", ".md"] do |t| 111 | MD = FileList[File.join( dir[:dev], '*.{md,markdown}')] 112 | HTML = MD.ext('html') 113 | # 114 | # #rule File.join(dir[:dev], 'about.html') => [File.join(dir[:dev], 'about.md')] do |t| 115 | #rule HTML => MD do 116 | rule File.join( dir[:dev], 'about.html') => File.join( dir[:dev], 'about.md') do |t| 117 | puts "#{t.source} RULE" 118 | mdfile_to_html t.source, File.join(dir[:build_templates] , "base.html.erb") 119 | # # sh "markdown #{t.source} > #{t.name}" 120 | end 121 | 122 | desc "markdown" 123 | task :md => [File.join( dir[:dev], 'about.html').to_s] do |t| 124 | # puts "#{t.source} to md" 125 | # file_to_html File.join( dir[:dev], 'about.md') 126 | end 127 | # 128 | # task :all do 129 | # 130 | # end 131 | # end 132 | 133 | require "erb" 134 | require "rubygems" 135 | #require "rdiscount" 136 | require 'redcarpet' 137 | 138 | def mdfile_to_html(file, template_file) 139 | basename = File.basename(file, ".md") 140 | # template_file = 141 | output_file = File.join(File.dirname(file), "#{basename}.html") 142 | 143 | begin 144 | @template = nil 145 | pre_body = ERB.new(File.open(file).read).result 146 | # @body = RDiscount.new(pre_body).to_html 147 | markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, 148 | :autolink => true, :strikethrough => true) 149 | @body = markdown.render(pre_body) 150 | 151 | if @template # may be defined in .md file. 152 | template_file = @template 153 | end 154 | 155 | if File.exists? template_file 156 | template = File.open(template_file).read 157 | else 158 | template = %q{ 159 | 160 | 161 | 162 | <%= @body %> 163 | 164 | 165 | } 166 | puts 'OOPS' 167 | # TODO: ERROR if template not found 168 | end 169 | 170 | File.open(output_file, "w") do |f| 171 | f.write "" 172 | f.write ERB.new(template).result 173 | end 174 | puts "#{file} => #{output_file}" 175 | rescue => e 176 | puts "Error while working with file \"#{file}\":" 177 | puts " - #{e.message}" 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /app/common.php: -------------------------------------------------------------------------------- 1 | $val) { 22 | ini_set($key, $val); 23 | } 24 | 25 | define ("DEBUG", 26 | isset($GLOBALS['CONFIG']['debug']) && $GLOBALS['CONFIG']['debug']); 27 | 28 | define ("USER_DATA_PATH", isset($GLOBALS['config']['user_data_path']) ? 29 | $GLOBALS['config']['user_data_path'] : './session'); 30 | 31 | // -------------------- 32 | // Assertions 33 | // -------------------- 34 | function _assertCallback($file, $line, $code) { 35 | $trace = print_r(debug_backtrace(), true); 36 | error_log($trace); 37 | 38 | $dt = new DateTime(); 39 | $dt_out = $dt->format(DateTime::ISO8601); 40 | 41 | $details = <<getFile()."' (".$e->getLine().")\n"; 83 | $msg .= $e->getMessage(); 84 | $code = $e->getCode(); 85 | error_log("{$msg}\n{$code}"); 86 | 87 | error_log($e->getTraceAsString()); 88 | error_log(_getErrorState()); 89 | 90 | errorAndDie(DEBUG ? $msg : 'Uncaught Exception'); 91 | } 92 | 93 | set_exception_handler('_exceptionHandler'); 94 | 95 | // ------------------------- 96 | // Global Functions 97 | // ------------------------- 98 | 99 | /** 100 | * Shortcut function for htmlentities 101 | * @param string 102 | * @return string HTML-ized 103 | */ 104 | function H ($str) { 105 | return htmlentities($str, ENT_COMPAT, 'UTF-8'); 106 | } 107 | 108 | /** 109 | * Prepares a string for use in JavaScript 110 | * @param string 111 | * @param string 112 | * @return string JavaScript-ized 113 | */ 114 | function J ($str, $quoteType = ENT_COMPAT) { 115 | $str = preg_replace('/\\\\/', '\\\\\\\\' ,$str); 116 | switch ($quoteType) { 117 | case ENT_COMPAT: 118 | $str = preg_replace('/"/', '\\"' ,$str); 119 | break; 120 | case ENT_QUOTES: 121 | $str = preg_replace('/"/', '\\"' ,$str); 122 | $str = preg_replace('/\'/', '\\\'' ,$str); 123 | break; 124 | } 125 | return utf8_encode($str); 126 | } 127 | 128 | function errorAndDie ($msg = 'Unknown error', $output = 'html' ) { 129 | error_log($msg); 130 | header("HTTP/1.0 500 Server Error"); 131 | if ($output == 'json') { 132 | header('Content-type: application/json'); 133 | echo '{"error":"'.J($msg).'"}'; 134 | } else { 135 | #TODO: if not XHR, show full page. 136 | echo H($msg); 137 | } 138 | exit; 139 | } 140 | 141 | // ------------------------- 142 | // Application Controller 143 | // ------------------------- 144 | 145 | class ApplicationController 146 | { 147 | /** 148 | * Cause redirect by relative path, preserving SSL state. 149 | * Strips out .. appropriately. Uses $_SERVER['REQUEST_URI']. 150 | * 151 | * @param string relative path of template from templates root 152 | * @param _vars 153 | **/ 154 | public function render ($_template = "", $_vars = null, $_type = "html") { 155 | $T = $_vars ? $_vars : array(); 156 | 157 | $_path = "./app/templates/{$_template}.{$_type}"; 158 | 159 | ob_start(); 160 | require $_path; 161 | return ob_get_clean(); 162 | } 163 | 164 | /** 165 | * Cause redirect by relative path, preserving SSL state. 166 | * Strips out .. appropriately. Uses $_SERVER['REQUEST_URI']. 167 | * 168 | * @param string relative path from current URL to next URL 169 | **/ 170 | protected function redirect($url) { #, $code = 200) { #TODO: fix HTTP status code 171 | session_write_close(); 172 | 173 | if (!preg_match('/^https?:\/\//', $url)) { 174 | if (!preg_match('/^\//', $url)) { 175 | // convert $url from relative to full path 176 | $url = preg_replace('/\/[^\/]*$/','/', $_SERVER['REQUEST_URI']).$url; 177 | 178 | $count = 0; 179 | do { 180 | $url = preg_replace('/[^\/]+\/\.\.\//', '', $url, -1, $count); 181 | } while ($count); 182 | } 183 | $newurl = 'http'; 184 | if (!empty($_SERVER['HTTPS'])) {$newurl .= 's';} 185 | $newurl .= '://' . $_SERVER['HTTP_HOST']; 186 | $url = $newurl . $url; 187 | } 188 | header("Location: {$url}"); 189 | exit(); 190 | } 191 | 192 | /** 193 | * @return bool whether request came from XMLHttpRequest, such as Prototype. 194 | * Can return true if HTTP header exists, or if HTTP Basic Auth detected 195 | */ 196 | protected function isXhr() { 197 | $xhr = g_getArrayValue(apache_request_headers(), 'X-Requested-With', ''); 198 | if ('XMLHttpRequest' == $xhr) {return true;} 199 | 200 | return g_getArrayValue($_SERVER, 'PHP_AUTH_USER') 201 | && g_getArrayValue($_SERVER, 'PHP_AUTH_PW'); 202 | } 203 | } -------------------------------------------------------------------------------- /dev/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 |
    34 |
    35 |

    sqMight

    36 |
    37 | 43 |
    44 |
      45 |
    • © 2012–2013, TAG
    • 46 |
    47 |
    48 |
    49 | 50 |
    51 |
    52 |

    About sqMight

    53 | 54 |

    sqMight uses sqLite as a backend, and offers each user a unique, temporary database instance. So, yes, go ahead and write those INSERT and UPDATE queries. CREATE or DROP tables. No one will know but you. Your personal database instance will stick around for a couple of hours, then it's kaput.

    55 | 56 |

    sqMight is intended for use in learning SQL, and is available under an MIT-style license.

    57 | 58 |

    Items listed in the 'products' table are real items found online. Many are listed at hackerthings.com, thinkgeek.com, or amazon.com. None of these shopping sites have any affiliation with this site.

    59 | 60 |

    If you wish to incorporate sqMight into your classroom curriculum or research, or otherwise use it commercially, please contact Tom Gregory at tom@alt-tag.com.

    61 | 62 |

    sqMight was conceived and built by Tom Gregory, based on an idea from Gove Allen in 2009.

    63 | 64 |

    sqMight uses code, ideas, images, or inspiration from the following publicly avaible sources:

    65 | 66 | 87 | 88 |
    89 |
    90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | Fork me on GitHub 108 | 109 | 110 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /dev/css/sqmight.css: -------------------------------------------------------------------------------- 1 | body { 2 | background:#322F0C url('../img/dark_leather_brown1.gif') top left; 3 | padding: 10px 10px 10px 160px; 4 | min-width:760px; 5 | position: relative; 6 | } 7 | 8 | #main { 9 | width: 100%; 10 | } 11 | 12 | #ribbon { 13 | position: absolute; 14 | left: 15px; 15 | top: 0; 16 | width: 120px; 17 | 18 | color: #E0DDCD; 19 | background:#7A1111 url('../img/dark_stripes_red1.png') top left; 20 | 21 | font-size: 14px; 22 | 23 | text-shadow: 0px 1px 2px #000; 24 | 25 | -moz-box-shadow: 0px 4px 12px #000; 26 | -webkit-box-shadow: 0px 4px 12px #000; 27 | box-shadow: 0px 4px 12px #000; 28 | } 29 | 30 | #ribbon ul#menu { 31 | position:relative; 32 | margin-top: 2px; 33 | border-top: 1px dotted #E0DDCD; 34 | border-bottom: 1px dotted #E0DDCD; 35 | } 36 | 37 | #ribbon ul { 38 | margin: 2px 5px 5px 5px; padding: 4px 0; 39 | list-style-type: none; 40 | text-align:center; 41 | } 42 | #ribbon li { 43 | margin: 2px 0 2px 0; padding:0; 44 | } 45 | 46 | #ribbon a:link, #ribbon a:visited { 47 | display: block; 48 | padding: 2px 0; 49 | width 100%; 50 | font-weight: bold; 51 | color: #E0DDCD; 52 | text-decoration:none; 53 | -webkit-border-radius: 6px; 54 | -moz-border-radius: 6px; 55 | border-radius: 6px; 56 | } 57 | 58 | #ribbon a:hover, #ribbon a:active { 59 | color: rgb(224,218,96); 60 | color: white; 61 | text-decoration: underline; 62 | background-color: rgba(224,221,205, .10); 63 | } 64 | 65 | #ribbon footer { 66 | font-size: 11px; 67 | } 68 | 69 | #banner img { 70 | position:relative; 71 | margin: 0 0 0 15px; 72 | } 73 | 74 | .content { 75 | width: 75%; 76 | background-color: #E0DDCD; 77 | padding: 5px 15px 15px 15px; 78 | font-size: 120%; 79 | margin: 15px auto; 80 | border: 1px solid #111; 81 | 82 | -webkit-border-radius: 10px 10px 10px 10px; 83 | -moz-border-radius: 10px 10px 10px 10px; 84 | border-radius: 10px 10px 10px 10px; 85 | 86 | -moz-box-shadow: inset 2px 2px 6px #111; 87 | -webkit-box-shadow: inset 2px 2px 6px #111; 88 | box-shadow: inset 2px 2px 6px #111; 89 | } 90 | 91 | #entry { 92 | width: 50%; 93 | background-color: #E0DDCD; 94 | padding: 5px 15px 15px 15px; 95 | text-align:center; 96 | font-size: 120%; 97 | margin: 15px auto; 98 | border: 1px solid #111; 99 | 100 | -webkit-border-radius: 10px 10px 10px 10px; 101 | -moz-border-radius: 10px 10px 10px 10px; 102 | border-radius: 10px 10px 10px 10px; 103 | 104 | -moz-box-shadow: inset 2px 2px 6px #111; 105 | -webkit-box-shadow: inset 2px 2px 6px #111; 106 | box-shadow: inset 2px 2px 6px #111; 107 | } 108 | #entry h2{ 109 | color: #1F1A07 /** #322F0C **/ ; 110 | font-size: 20pt; 111 | text-shadow: rgba(0,0,0,0.5) -1px 0, rgba(0,0,0,0.3) 0 -1px, rgba(255,255,255,0.5) 0 1px, rgba(0,0,0,0.3) -1px -1px; 112 | } 113 | #create { 114 | padding:20px; 115 | } 116 | 117 | 118 | table.sql-output td, table.sql-output th { 119 | padding: 4px; 120 | border: 1px solid #999; 121 | } 122 | 123 | table.sql-output th { 124 | background-color: #dfdfdf; 125 | } 126 | 127 | #sql { 128 | margin:0 auto; 129 | padding: 4px; 130 | width: 80%; 131 | font-family: Inconsolata, Consolas, Monaco, 'Courier New'; 132 | font-size: 12pt; 133 | -webkit-border-radius: 8px; 134 | -moz-border-radius: 8px; 135 | border-radius: 8px; 136 | } 137 | 138 | #history { 139 | margin: 2px 0; padding: 0; 140 | list-style-type: none; 141 | } 142 | 143 | #history a { 144 | float: right; 145 | } 146 | pre { 147 | padding:5px; 148 | font-size: 11pt; 149 | font-family: Inconsolata, Consolas, Monaco, 'Courier New'; 150 | } 151 | 152 | #history pre { 153 | margin: 0; 154 | -webkit-border-radius: 6px 6px 0 0; 155 | -moz-border-radius: 6px 6px 0 0; 156 | border-radius: 6px 6px 0 0; 157 | 158 | -moz-box-shadow: 0px 2px 8px #000; 159 | -webkit-box-shadow: 0px 2px 8px #000; 160 | box-shadow: 0px 2px 8px #000; 161 | } 162 | #history li { 163 | margin-bottom: 12px; 164 | 165 | } 166 | 167 | #history li div.response { 168 | padding: 4px; 169 | background-color: #E0DDCD; 170 | 171 | -webkit-border-radius: 0 0 6px 6px; 172 | -moz-border-radius: 0 0 6px 6px; 173 | border-radius: 0 0 6px 6px; 174 | 175 | -moz-box-shadow: 0px 2px 8px #000; 176 | -webkit-box-shadow: 0px 2px 8px #000; 177 | box-shadow: 0px 2px 8px #000; 178 | } 179 | 180 | #history li div.response p { 181 | padding: 0 6px; 182 | } 183 | 184 | 185 | /*** 186 | Button 187 | ***/ 188 | button, input.button { 189 | display: inline-block; 190 | white-space: nowrap; 191 | background-color: #ccc; 192 | background-image: -webkit-gradient(linear, left top, left bottom, from(#eee), to(#ccc)); 193 | background-image: -webkit-linear-gradient(top, #eee, #ccc); 194 | background-image: -moz-linear-gradient(top, #eee, #ccc); 195 | background-image: -ms-linear-gradient(top, #eee, #ccc); 196 | background-image: -o-linear-gradient(top, #eee, #ccc); 197 | background-image: linear-gradient(top, #eee, #ccc); 198 | filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#eeeeee', EndColorStr='#cccccc'); 199 | border: 1px solid #777; 200 | padding: 0 1.5em; 201 | margin: 0.5em; 202 | font-weight: bold; 203 | font-size: 14pt; 204 | text-decoration: none; 205 | color: #333; 206 | text-shadow: 0 1px 0 rgba(255,255,255,.8); 207 | -moz-border-radius: .2em; 208 | -webkit-border-radius: .2em; 209 | border-radius: .2em; 210 | -moz-box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3); 211 | -webkit-box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3); 212 | box-shadow: 0 0 1px 1px rgba(255,255,255,.8) inset, 0 1px 0 rgba(0,0,0,.3); 213 | } 214 | 215 | button:hover, input.button:hover { 216 | background-color: #ddd; 217 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fafafa), to(#ddd)); 218 | background-image: -webkit-linear-gradient(top, #fafafa, #ddd); 219 | background-image: -moz-linear-gradient(top, #fafafa, #ddd); 220 | background-image: -ms-linear-gradient(top, #fafafa, #ddd); 221 | background-image: -o-linear-gradient(top, #fafafa, #ddd); 222 | background-image: linear-gradient(top, #fafafa, #ddd); 223 | filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#fafafa', EndColorStr='#dddddd'); 224 | } 225 | 226 | button:active, input.button:active { 227 | -moz-box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset; 228 | -webkit-box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset; 229 | box-shadow: 0 0 4px 2px rgba(0,0,0,.3) inset; 230 | position: relative; 231 | top: 1px; 232 | } 233 | 234 | button:focus, input.button:focus { 235 | outline: 0; 236 | background: #fafafa; 237 | } 238 | 239 | button:before, input.button:before { 240 | background: #ccc; 241 | background: rgba(0,0,0,.1); 242 | float: left; 243 | width: 1em; 244 | text-align: center; 245 | font-size: 1.5em; 246 | margin: 0 1em 0 -1em; 247 | padding: .2em .2em; 248 | -moz-box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5); 249 | -webkit-box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5); 250 | box-shadow: 1px 0 0 rgba(0,0,0,.5), 2px 0 0 rgba(255,255,255,.5); 251 | -moz-border-radius: .15em 0 0 .15em; 252 | -webkit-border-radius: .15em 0 0 .15em; 253 | border-radius: .15em 0 0 .15em; 254 | pointer-events: none; 255 | } 256 | 257 | /* Hexadecimal entities for the icons */ 258 | button:before, input.button:before { 259 | content: "\25B6"; 260 | } 261 | 262 | .error { 263 | color:rgb(128,0,0); 264 | } 265 | 266 | noscript { 267 | padding: 6px 10px; 268 | background-color:#BBA192; 269 | background-color: rgba(128,0,0,.15); 270 | color:rgb(128,0,0); 271 | border: 2px solid rgb(128,0,0); 272 | -webkit-border-radius: 8px; 273 | -moz-border-radius: 8px; 274 | border-radius: 8px; 275 | } -------------------------------------------------------------------------------- /dev/css/base.css: -------------------------------------------------------------------------------- 1 | /* 2 | * HTML5 ✰ Boilerplate 3 | * 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | * 8 | * Detailed information about this CSS: h5bp.com/css 9 | * 10 | * ==|== normalize ========================================================== 11 | */ 12 | 13 | 14 | /* ============================================================================= 15 | HTML5 display definitions 16 | ========================================================================== */ 17 | 18 | article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } 19 | audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } 20 | audio:not([controls]) { display: none; } 21 | [hidden] { display: none; } 22 | 23 | /* ============================================================================= 24 | Base 25 | ========================================================================== */ 26 | 27 | /* 28 | * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units 29 | * 2. Force vertical scrollbar in non-IE 30 | * 3. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g 31 | */ 32 | 33 | html { font-size: 100%; overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } 34 | 35 | body { margin: 0; font-size: 13px; line-height: 1.231; } 36 | 37 | body, button, input, select, textarea { font-family: sans-serif; color: #222; } 38 | 39 | /* 40 | * Remove text-shadow in selection highlight: h5bp.com/i 41 | * These selection declarations have to be separate 42 | * Also: hot pink! (or customize the background color to match your design) 43 | */ 44 | 45 | /* #TODO: Fix selection color; for now, default to user preference 46 | ::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } 47 | ::selection { background: #fe57a1; color: #fff; text-shadow: none; } 48 | */ 49 | 50 | /* ============================================================================= 51 | Links 52 | ========================================================================== */ 53 | 54 | a { color: #00e; } 55 | a:visited { color: #551a8b; } 56 | a:hover { color: #06e; } 57 | a:focus { outline: thin dotted; } 58 | 59 | /* Improve readability when focused and hovered in all browsers: h5bp.com/h */ 60 | a:hover, a:active { outline: 0; } 61 | 62 | 63 | /* ============================================================================= 64 | Typography 65 | ========================================================================== */ 66 | 67 | abbr[title] { border-bottom: 1px dotted; } 68 | 69 | b, strong { font-weight: bold; } 70 | 71 | blockquote { margin: 1em 40px; } 72 | 73 | dfn { font-style: italic; } 74 | 75 | hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } 76 | 77 | ins { background: #ff9; color: #000; text-decoration: none; } 78 | 79 | mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } 80 | 81 | /* Redeclare monospace font family: h5bp.com/j */ 82 | pre, code, kbd, samp { font-family: monospace, monospace; _font-family: 'courier new', monospace; font-size: 1em; } 83 | 84 | /* Improve readability of pre-formatted text in all browsers */ 85 | pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } 86 | 87 | q { quotes: none; } 88 | q:before, q:after { content: ""; content: none; } 89 | 90 | small { font-size: 85%; } 91 | 92 | /* Position subscript and superscript content without affecting line-height: h5bp.com/k */ 93 | sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } 94 | sup { top: -0.5em; } 95 | sub { bottom: -0.25em; } 96 | 97 | 98 | /* ============================================================================= 99 | Lists 100 | ========================================================================== */ 101 | 102 | ul, ol { margin: 1em 0; padding: 0 0 0 40px; } 103 | dd { margin: 0 0 0 40px; } 104 | nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; } 105 | 106 | 107 | /* ============================================================================= 108 | Embedded content 109 | ========================================================================== */ 110 | 111 | /* 112 | * 1. Improve image quality when scaled in IE7: h5bp.com/d 113 | * 2. Remove the gap between images and borders on image containers: h5bp.com/e 114 | */ 115 | 116 | img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } 117 | 118 | /* 119 | * Correct overflow not hidden in IE9 120 | */ 121 | 122 | svg:not(:root) { overflow: hidden; } 123 | 124 | 125 | /* ============================================================================= 126 | Figures 127 | ========================================================================== */ 128 | 129 | figure { margin: 0; } 130 | 131 | 132 | /* ============================================================================= 133 | Forms 134 | ========================================================================== */ 135 | 136 | form { margin: 0; } 137 | fieldset { border: 0; margin: 0; padding: 0; } 138 | 139 | /* Indicate that 'label' will shift focus to the associated form element */ 140 | label { cursor: pointer; } 141 | 142 | /* 143 | * 1. Correct color not inheriting in IE6/7/8/9 144 | * 2. Correct alignment displayed oddly in IE6/7 145 | */ 146 | 147 | legend { border: 0; *margin-left: -7px; padding: 0; } 148 | 149 | /* 150 | * 1. Correct font-size not inheriting in all browsers 151 | * 2. Remove margins in FF3/4 S5 Chrome 152 | * 3. Define consistent vertical alignment display in all browsers 153 | */ 154 | 155 | button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } 156 | 157 | /* 158 | * 1. Define line-height as normal to match FF3/4 (set using !important in the UA stylesheet) 159 | * 2. Correct inner spacing displayed oddly in IE6/7 160 | */ 161 | 162 | button, input { line-height: normal; *overflow: visible; } 163 | 164 | /* 165 | * Reintroduce inner spacing in 'table' to avoid overlap and whitespace issues in IE6/7 166 | */ 167 | 168 | table button, table input { *overflow: auto; } 169 | 170 | /* 171 | * 1. Display hand cursor for clickable form elements 172 | * 2. Allow styling of clickable form elements in iOS 173 | */ 174 | 175 | button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; } 176 | 177 | /* 178 | * Consistent box sizing and appearance 179 | */ 180 | 181 | input[type="checkbox"], input[type="radio"] { box-sizing: border-box; } 182 | input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } 183 | input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } 184 | 185 | /* 186 | * Remove inner padding and border in FF3/4: h5bp.com/l 187 | */ 188 | 189 | button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } 190 | 191 | /* 192 | * 1. Remove default vertical scrollbar in IE6/7/8/9 193 | * 2. Allow only vertical resizing 194 | */ 195 | 196 | textarea { overflow: auto; vertical-align: top; resize: vertical; } 197 | 198 | /* Colors for form validity */ 199 | input:valid, textarea:valid { } 200 | input:invalid, textarea:invalid { background-color: #f0dddd; } 201 | 202 | 203 | /* ============================================================================= 204 | Tables 205 | ========================================================================== */ 206 | 207 | table { border-collapse: collapse; border-spacing: 0; } 208 | td { vertical-align: top; } 209 | 210 | 211 | /* ==|== primary styles ===================================================== 212 | Author: 213 | ========================================================================== */ 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | /* ==|== non-semantic helper classes ======================================== 231 | Please define your styles before this section. 232 | ========================================================================== */ 233 | 234 | /* For image replacement */ 235 | .ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; } 236 | .ir br { display: none; } 237 | 238 | /* Hide from both screenreaders and browsers: h5bp.com/u */ 239 | .hidden { display: none !important; visibility: hidden; } 240 | 241 | /* Hide only visually, but have it available for screenreaders: h5bp.com/v */ 242 | .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } 243 | 244 | /* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */ 245 | .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } 246 | 247 | /* Hide visually and from screenreaders, but maintain layout */ 248 | .invisible { visibility: hidden; } 249 | 250 | /* Contain floats: h5bp.com/q */ 251 | .clearfix:before, .clearfix:after { content: ""; display: table; } 252 | .clearfix:after { clear: both; } 253 | .clearfix { zoom: 1; } 254 | 255 | 256 | 257 | /* ==|== media queries ====================================================== 258 | PLACEHOLDER Media Queries for Responsive Design. 259 | These override the primary ('mobile first') styles 260 | Modify as content requires. 261 | ========================================================================== */ 262 | 263 | @media only screen and (min-width: 480px) { 264 | /* Style adjustments for viewports 480px and over go here */ 265 | 266 | } 267 | 268 | @media only screen and (min-width: 768px) { 269 | /* Style adjustments for viewports 768px and over go here */ 270 | 271 | } 272 | 273 | 274 | 275 | /* ==|== print styles ======================================================= 276 | Print styles. 277 | Inlined to avoid required HTTP connection: h5bp.com/r 278 | ========================================================================== */ 279 | 280 | @media print { 281 | * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */ 282 | a, a:visited { text-decoration: underline; } 283 | a[href]:after { content: " (" attr(href) ")"; } 284 | abbr[title]:after { content: " (" attr(title) ")"; } 285 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */ 286 | pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } 287 | thead { display: table-header-group; } /* h5bp.com/t */ 288 | tr, img { page-break-inside: avoid; } 289 | img { max-width: 100% !important; } 290 | @page { margin: 0.5cm; } 291 | p, h2, h3 { orphans: 3; widows: 3; } 292 | h2, h3 { page-break-after: avoid; } 293 | } 294 | -------------------------------------------------------------------------------- /dev/js/libs/prettify.min/prettify.js: -------------------------------------------------------------------------------- 1 | // (c) Google 2006; Apache 2 license 2 | // http://code.google.com/p/google-code-prettify 3 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 4 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 5 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 11 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 12 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 13 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 14 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 15 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 16 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 20 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 21 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 22 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 23 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 24 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 25 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 26 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 27 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 28 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p', testEnvironment, testEnvironmentArg; 43 | 44 | if ( arguments.length === 2 ) { 45 | callback = expected; 46 | expected = null; 47 | } 48 | // is 2nd argument a testEnvironment? 49 | if ( expected && typeof expected === 'object') { 50 | testEnvironmentArg = expected; 51 | expected = null; 52 | } 53 | 54 | if ( config.currentModule ) { 55 | name = '' + config.currentModule + ": " + name; 56 | } 57 | 58 | if ( !validTest(config.currentModule + ": " + testName) ) { 59 | return; 60 | } 61 | 62 | synchronize(function() { 63 | 64 | testEnvironment = extend({ 65 | setup: function() {}, 66 | teardown: function() {} 67 | }, config.moduleTestEnvironment); 68 | if (testEnvironmentArg) { 69 | extend(testEnvironment,testEnvironmentArg); 70 | } 71 | 72 | QUnit.testStart( testName, testEnvironment ); 73 | 74 | // allow utility functions to access the current test environment 75 | QUnit.current_testEnvironment = testEnvironment; 76 | 77 | config.assertions = []; 78 | config.expected = expected; 79 | 80 | var tests = id("qunit-tests"); 81 | if (tests) { 82 | var b = document.createElement("strong"); 83 | b.innerHTML = "Running " + name; 84 | var li = document.createElement("li"); 85 | li.appendChild( b ); 86 | li.id = "current-test-output"; 87 | tests.appendChild( li ) 88 | } 89 | 90 | try { 91 | if ( !config.pollution ) { 92 | saveGlobal(); 93 | } 94 | 95 | testEnvironment.setup.call(testEnvironment); 96 | } catch(e) { 97 | QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); 98 | } 99 | }); 100 | 101 | synchronize(function() { 102 | if ( async ) { 103 | QUnit.stop(); 104 | } 105 | 106 | try { 107 | callback.call(testEnvironment); 108 | } catch(e) { 109 | fail("Test " + name + " died, exception and test follows", e, callback); 110 | QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); 111 | // else next test will carry the responsibility 112 | saveGlobal(); 113 | 114 | // Restart the tests if they're blocking 115 | if ( config.blocking ) { 116 | start(); 117 | } 118 | } 119 | }); 120 | 121 | synchronize(function() { 122 | try { 123 | checkPollution(); 124 | testEnvironment.teardown.call(testEnvironment); 125 | } catch(e) { 126 | QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); 127 | } 128 | }); 129 | 130 | synchronize(function() { 131 | try { 132 | QUnit.reset(); 133 | } catch(e) { 134 | fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); 135 | } 136 | 137 | if ( config.expected && config.expected != config.assertions.length ) { 138 | QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); 139 | } 140 | 141 | var good = 0, bad = 0, 142 | tests = id("qunit-tests"); 143 | 144 | config.stats.all += config.assertions.length; 145 | config.moduleStats.all += config.assertions.length; 146 | 147 | if ( tests ) { 148 | var ol = document.createElement("ol"); 149 | 150 | for ( var i = 0; i < config.assertions.length; i++ ) { 151 | var assertion = config.assertions[i]; 152 | 153 | var li = document.createElement("li"); 154 | li.className = assertion.result ? "pass" : "fail"; 155 | li.innerHTML = assertion.message || "(no message)"; 156 | ol.appendChild( li ); 157 | 158 | if ( assertion.result ) { 159 | good++; 160 | } else { 161 | bad++; 162 | config.stats.bad++; 163 | config.moduleStats.bad++; 164 | } 165 | } 166 | if (bad == 0) { 167 | ol.style.display = "none"; 168 | } 169 | 170 | var b = document.createElement("strong"); 171 | b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; 172 | 173 | addEvent(b, "click", function() { 174 | var next = b.nextSibling, display = next.style.display; 175 | next.style.display = display === "none" ? "block" : "none"; 176 | }); 177 | 178 | addEvent(b, "dblclick", function(e) { 179 | var target = e && e.target ? e.target : window.event.srcElement; 180 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 181 | target = target.parentNode; 182 | } 183 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 184 | window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, "")); 185 | } 186 | }); 187 | 188 | var li = id("current-test-output"); 189 | li.id = ""; 190 | li.className = bad ? "fail" : "pass"; 191 | li.removeChild( li.firstChild ); 192 | li.appendChild( b ); 193 | li.appendChild( ol ); 194 | 195 | if ( bad ) { 196 | var toolbar = id("qunit-testrunner-toolbar"); 197 | if ( toolbar ) { 198 | toolbar.style.display = "block"; 199 | id("qunit-filter-pass").disabled = null; 200 | id("qunit-filter-missing").disabled = null; 201 | } 202 | } 203 | 204 | } else { 205 | for ( var i = 0; i < config.assertions.length; i++ ) { 206 | if ( !config.assertions[i].result ) { 207 | bad++; 208 | config.stats.bad++; 209 | config.moduleStats.bad++; 210 | } 211 | } 212 | } 213 | 214 | QUnit.testDone( testName, bad, config.assertions.length ); 215 | 216 | if ( !window.setTimeout && !config.queue.length ) { 217 | done(); 218 | } 219 | }); 220 | 221 | if ( window.setTimeout && !config.doneTimer ) { 222 | config.doneTimer = window.setTimeout(function(){ 223 | if ( !config.queue.length ) { 224 | done(); 225 | } else { 226 | synchronize( done ); 227 | } 228 | }, 13); 229 | } 230 | }, 231 | 232 | /** 233 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 234 | */ 235 | expect: function(asserts) { 236 | config.expected = asserts; 237 | }, 238 | 239 | /** 240 | * Asserts true. 241 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 242 | */ 243 | ok: function(a, msg) { 244 | msg = escapeHtml(msg); 245 | QUnit.log(a, msg); 246 | 247 | config.assertions.push({ 248 | result: !!a, 249 | message: msg 250 | }); 251 | }, 252 | 253 | /** 254 | * Checks that the first two arguments are equal, with an optional message. 255 | * Prints out both actual and expected values. 256 | * 257 | * Prefered to ok( actual == expected, message ) 258 | * 259 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 260 | * 261 | * @param Object actual 262 | * @param Object expected 263 | * @param String message (optional) 264 | */ 265 | equal: function(actual, expected, message) { 266 | push(expected == actual, actual, expected, message); 267 | }, 268 | 269 | notEqual: function(actual, expected, message) { 270 | push(expected != actual, actual, expected, message); 271 | }, 272 | 273 | deepEqual: function(actual, expected, message) { 274 | push(QUnit.equiv(actual, expected), actual, expected, message); 275 | }, 276 | 277 | notDeepEqual: function(actual, expected, message) { 278 | push(!QUnit.equiv(actual, expected), actual, expected, message); 279 | }, 280 | 281 | strictEqual: function(actual, expected, message) { 282 | push(expected === actual, actual, expected, message); 283 | }, 284 | 285 | notStrictEqual: function(actual, expected, message) { 286 | push(expected !== actual, actual, expected, message); 287 | }, 288 | 289 | raises: function(fn, message) { 290 | try { 291 | fn(); 292 | ok( false, message ); 293 | } 294 | catch (e) { 295 | ok( true, message ); 296 | } 297 | }, 298 | 299 | start: function() { 300 | // A slight delay, to avoid any current callbacks 301 | if ( window.setTimeout ) { 302 | window.setTimeout(function() { 303 | if ( config.timeout ) { 304 | clearTimeout(config.timeout); 305 | } 306 | 307 | config.blocking = false; 308 | process(); 309 | }, 13); 310 | } else { 311 | config.blocking = false; 312 | process(); 313 | } 314 | }, 315 | 316 | stop: function(timeout) { 317 | config.blocking = true; 318 | 319 | if ( timeout && window.setTimeout ) { 320 | config.timeout = window.setTimeout(function() { 321 | QUnit.ok( false, "Test timed out" ); 322 | QUnit.start(); 323 | }, timeout); 324 | } 325 | } 326 | 327 | }; 328 | 329 | // Backwards compatibility, deprecated 330 | QUnit.equals = QUnit.equal; 331 | QUnit.same = QUnit.deepEqual; 332 | 333 | // Maintain internal state 334 | var config = { 335 | // The queue of tests to run 336 | queue: [], 337 | 338 | // block until document ready 339 | blocking: true 340 | }; 341 | 342 | // Load paramaters 343 | (function() { 344 | var location = window.location || { search: "", protocol: "file:" }, 345 | GETParams = location.search.slice(1).split('&'); 346 | 347 | for ( var i = 0; i < GETParams.length; i++ ) { 348 | GETParams[i] = decodeURIComponent( GETParams[i] ); 349 | if ( GETParams[i] === "noglobals" ) { 350 | GETParams.splice( i, 1 ); 351 | i--; 352 | config.noglobals = true; 353 | } else if ( GETParams[i].search('=') > -1 ) { 354 | GETParams.splice( i, 1 ); 355 | i--; 356 | } 357 | } 358 | 359 | // restrict modules/tests by get parameters 360 | config.filters = GETParams; 361 | 362 | // Figure out if we're running the tests from a server or not 363 | QUnit.isLocal = !!(location.protocol === 'file:'); 364 | })(); 365 | 366 | // Expose the API as global variables, unless an 'exports' 367 | // object exists, in that case we assume we're in CommonJS 368 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 369 | extend(window, QUnit); 370 | window.QUnit = QUnit; 371 | } else { 372 | extend(exports, QUnit); 373 | exports.QUnit = QUnit; 374 | } 375 | 376 | // define these after exposing globals to keep them in these QUnit namespace only 377 | extend(QUnit, { 378 | config: config, 379 | 380 | // Initialize the configuration options 381 | init: function() { 382 | extend(config, { 383 | stats: { all: 0, bad: 0 }, 384 | moduleStats: { all: 0, bad: 0 }, 385 | started: +new Date, 386 | updateRate: 1000, 387 | blocking: false, 388 | autostart: true, 389 | autorun: false, 390 | assertions: [], 391 | filters: [], 392 | queue: [] 393 | }); 394 | 395 | var tests = id("qunit-tests"), 396 | banner = id("qunit-banner"), 397 | result = id("qunit-testresult"); 398 | 399 | if ( tests ) { 400 | tests.innerHTML = ""; 401 | } 402 | 403 | if ( banner ) { 404 | banner.className = ""; 405 | } 406 | 407 | if ( result ) { 408 | result.parentNode.removeChild( result ); 409 | } 410 | }, 411 | 412 | /** 413 | * Resets the test setup. Useful for tests that modify the DOM. 414 | */ 415 | reset: function() { 416 | if ( window.jQuery ) { 417 | jQuery("#main, #qunit-fixture").html( config.fixture ); 418 | } 419 | }, 420 | 421 | /** 422 | * Trigger an event on an element. 423 | * 424 | * @example triggerEvent( document.body, "click" ); 425 | * 426 | * @param DOMElement elem 427 | * @param String type 428 | */ 429 | triggerEvent: function( elem, type, event ) { 430 | if ( document.createEvent ) { 431 | event = document.createEvent("MouseEvents"); 432 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 433 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 434 | elem.dispatchEvent( event ); 435 | 436 | } else if ( elem.fireEvent ) { 437 | elem.fireEvent("on"+type); 438 | } 439 | }, 440 | 441 | // Safe object type checking 442 | is: function( type, obj ) { 443 | return QUnit.objectType( obj ) == type; 444 | }, 445 | 446 | objectType: function( obj ) { 447 | if (typeof obj === "undefined") { 448 | return "undefined"; 449 | 450 | // consider: typeof null === object 451 | } 452 | if (obj === null) { 453 | return "null"; 454 | } 455 | 456 | var type = Object.prototype.toString.call( obj ) 457 | .match(/^\[object\s(.*)\]$/)[1] || ''; 458 | 459 | switch (type) { 460 | case 'Number': 461 | if (isNaN(obj)) { 462 | return "nan"; 463 | } else { 464 | return "number"; 465 | } 466 | case 'String': 467 | case 'Boolean': 468 | case 'Array': 469 | case 'Date': 470 | case 'RegExp': 471 | case 'Function': 472 | return type.toLowerCase(); 473 | } 474 | if (typeof obj === "object") { 475 | return "object"; 476 | } 477 | return undefined; 478 | }, 479 | 480 | // Logging callbacks 481 | begin: function() {}, 482 | done: function(failures, total) {}, 483 | log: function(result, message) {}, 484 | testStart: function(name, testEnvironment) {}, 485 | testDone: function(name, failures, total) {}, 486 | moduleStart: function(name, testEnvironment) {}, 487 | moduleDone: function(name, failures, total) {} 488 | }); 489 | 490 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 491 | config.autorun = true; 492 | } 493 | 494 | addEvent(window, "load", function() { 495 | QUnit.begin(); 496 | 497 | // Initialize the config, saving the execution queue 498 | var oldconfig = extend({}, config); 499 | QUnit.init(); 500 | extend(config, oldconfig); 501 | 502 | config.blocking = false; 503 | 504 | var userAgent = id("qunit-userAgent"); 505 | if ( userAgent ) { 506 | userAgent.innerHTML = navigator.userAgent; 507 | } 508 | 509 | var toolbar = id("qunit-testrunner-toolbar"); 510 | if ( toolbar ) { 511 | toolbar.style.display = "none"; 512 | 513 | var filter = document.createElement("input"); 514 | filter.type = "checkbox"; 515 | filter.id = "qunit-filter-pass"; 516 | filter.disabled = true; 517 | addEvent( filter, "click", function() { 518 | var li = document.getElementsByTagName("li"); 519 | for ( var i = 0; i < li.length; i++ ) { 520 | if ( li[i].className.indexOf("pass") > -1 ) { 521 | li[i].style.display = filter.checked ? "none" : ""; 522 | } 523 | } 524 | }); 525 | toolbar.appendChild( filter ); 526 | 527 | var label = document.createElement("label"); 528 | label.setAttribute("for", "qunit-filter-pass"); 529 | label.innerHTML = "Hide passed tests"; 530 | toolbar.appendChild( label ); 531 | 532 | var missing = document.createElement("input"); 533 | missing.type = "checkbox"; 534 | missing.id = "qunit-filter-missing"; 535 | missing.disabled = true; 536 | addEvent( missing, "click", function() { 537 | var li = document.getElementsByTagName("li"); 538 | for ( var i = 0; i < li.length; i++ ) { 539 | if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { 540 | li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; 541 | } 542 | } 543 | }); 544 | toolbar.appendChild( missing ); 545 | 546 | label = document.createElement("label"); 547 | label.setAttribute("for", "qunit-filter-missing"); 548 | label.innerHTML = "Hide missing tests (untested code is broken code)"; 549 | toolbar.appendChild( label ); 550 | } 551 | 552 | var main = id('main') || id('qunit-fixture'); 553 | if ( main ) { 554 | config.fixture = main.innerHTML; 555 | } 556 | 557 | if (config.autostart) { 558 | QUnit.start(); 559 | } 560 | }); 561 | 562 | function done() { 563 | if ( config.doneTimer && window.clearTimeout ) { 564 | window.clearTimeout( config.doneTimer ); 565 | config.doneTimer = null; 566 | } 567 | 568 | if ( config.queue.length ) { 569 | config.doneTimer = window.setTimeout(function(){ 570 | if ( !config.queue.length ) { 571 | done(); 572 | } else { 573 | synchronize( done ); 574 | } 575 | }, 13); 576 | 577 | return; 578 | } 579 | 580 | config.autorun = true; 581 | 582 | // Log the last module results 583 | if ( config.currentModule ) { 584 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 585 | } 586 | 587 | var banner = id("qunit-banner"), 588 | tests = id("qunit-tests"), 589 | html = ['Tests completed in ', 590 | +new Date - config.started, ' milliseconds.
    ', 591 | '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); 592 | 593 | if ( banner ) { 594 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 595 | } 596 | 597 | if ( tests ) { 598 | var result = id("qunit-testresult"); 599 | 600 | if ( !result ) { 601 | result = document.createElement("p"); 602 | result.id = "qunit-testresult"; 603 | result.className = "result"; 604 | tests.parentNode.insertBefore( result, tests.nextSibling ); 605 | } 606 | 607 | result.innerHTML = html; 608 | } 609 | 610 | QUnit.done( config.stats.bad, config.stats.all ); 611 | } 612 | 613 | function validTest( name ) { 614 | var i = config.filters.length, 615 | run = false; 616 | 617 | if ( !i ) { 618 | return true; 619 | } 620 | 621 | while ( i-- ) { 622 | var filter = config.filters[i], 623 | not = filter.charAt(0) == '!'; 624 | 625 | if ( not ) { 626 | filter = filter.slice(1); 627 | } 628 | 629 | if ( name.indexOf(filter) !== -1 ) { 630 | return !not; 631 | } 632 | 633 | if ( not ) { 634 | run = true; 635 | } 636 | } 637 | 638 | return run; 639 | } 640 | 641 | function escapeHtml(s) { 642 | s = s === null ? "" : s + ""; 643 | return s.replace(/[\&"<>\\]/g, function(s) { 644 | switch(s) { 645 | case "&": return "&"; 646 | case "\\": return "\\\\"; 647 | case '"': return '\"'; 648 | case "<": return "<"; 649 | case ">": return ">"; 650 | default: return s; 651 | } 652 | }); 653 | } 654 | 655 | function push(result, actual, expected, message) { 656 | message = escapeHtml(message) || (result ? "okay" : "failed"); 657 | message = '' + message + ""; 658 | expected = escapeHtml(QUnit.jsDump.parse(expected)); 659 | actual = escapeHtml(QUnit.jsDump.parse(actual)); 660 | var output = message + ', expected: ' + expected + ''; 661 | if (actual != expected) { 662 | output += ' result: ' + actual + ', diff: ' + QUnit.diff(expected, actual); 663 | } 664 | 665 | // can't use ok, as that would double-escape messages 666 | QUnit.log(result, output); 667 | config.assertions.push({ 668 | result: !!result, 669 | message: output 670 | }); 671 | } 672 | 673 | function synchronize( callback ) { 674 | config.queue.push( callback ); 675 | 676 | if ( config.autorun && !config.blocking ) { 677 | process(); 678 | } 679 | } 680 | 681 | function process() { 682 | var start = (new Date()).getTime(); 683 | 684 | while ( config.queue.length && !config.blocking ) { 685 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { 686 | config.queue.shift()(); 687 | 688 | } else { 689 | setTimeout( process, 13 ); 690 | break; 691 | } 692 | } 693 | } 694 | 695 | function saveGlobal() { 696 | config.pollution = []; 697 | 698 | if ( config.noglobals ) { 699 | for ( var key in window ) { 700 | config.pollution.push( key ); 701 | } 702 | } 703 | } 704 | 705 | function checkPollution( name ) { 706 | var old = config.pollution; 707 | saveGlobal(); 708 | 709 | var newGlobals = diff( old, config.pollution ); 710 | if ( newGlobals.length > 0 ) { 711 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 712 | config.expected++; 713 | } 714 | 715 | var deletedGlobals = diff( config.pollution, old ); 716 | if ( deletedGlobals.length > 0 ) { 717 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 718 | config.expected++; 719 | } 720 | } 721 | 722 | // returns a new Array with the elements that are in a but not in b 723 | function diff( a, b ) { 724 | var result = a.slice(); 725 | for ( var i = 0; i < result.length; i++ ) { 726 | for ( var j = 0; j < b.length; j++ ) { 727 | if ( result[i] === b[j] ) { 728 | result.splice(i, 1); 729 | i--; 730 | break; 731 | } 732 | } 733 | } 734 | return result; 735 | } 736 | 737 | function fail(message, exception, callback) { 738 | if ( typeof console !== "undefined" && console.error && console.warn ) { 739 | console.error(message); 740 | console.error(exception); 741 | console.warn(callback.toString()); 742 | 743 | } else if ( window.opera && opera.postError ) { 744 | opera.postError(message, exception, callback.toString); 745 | } 746 | } 747 | 748 | function extend(a, b) { 749 | for ( var prop in b ) { 750 | a[prop] = b[prop]; 751 | } 752 | 753 | return a; 754 | } 755 | 756 | function addEvent(elem, type, fn) { 757 | if ( elem.addEventListener ) { 758 | elem.addEventListener( type, fn, false ); 759 | } else if ( elem.attachEvent ) { 760 | elem.attachEvent( "on" + type, fn ); 761 | } else { 762 | fn(); 763 | } 764 | } 765 | 766 | function id(name) { 767 | return !!(typeof document !== "undefined" && document && document.getElementById) && 768 | document.getElementById( name ); 769 | } 770 | 771 | // Test for equality any JavaScript type. 772 | // Discussions and reference: http://philrathe.com/articles/equiv 773 | // Test suites: http://philrathe.com/tests/equiv 774 | // Author: Philippe Rathé 775 | QUnit.equiv = function () { 776 | 777 | var innerEquiv; // the real equiv function 778 | var callers = []; // stack to decide between skip/abort functions 779 | var parents = []; // stack to avoiding loops from circular referencing 780 | 781 | // Call the o related callback with the given arguments. 782 | function bindCallbacks(o, callbacks, args) { 783 | var prop = QUnit.objectType(o); 784 | if (prop) { 785 | if (QUnit.objectType(callbacks[prop]) === "function") { 786 | return callbacks[prop].apply(callbacks, args); 787 | } else { 788 | return callbacks[prop]; // or undefined 789 | } 790 | } 791 | } 792 | 793 | var callbacks = function () { 794 | 795 | // for string, boolean, number and null 796 | function useStrictEquality(b, a) { 797 | if (b instanceof a.constructor || a instanceof b.constructor) { 798 | // to catch short annotaion VS 'new' annotation of a declaration 799 | // e.g. var i = 1; 800 | // var j = new Number(1); 801 | return a == b; 802 | } else { 803 | return a === b; 804 | } 805 | } 806 | 807 | return { 808 | "string": useStrictEquality, 809 | "boolean": useStrictEquality, 810 | "number": useStrictEquality, 811 | "null": useStrictEquality, 812 | "undefined": useStrictEquality, 813 | 814 | "nan": function (b) { 815 | return isNaN(b); 816 | }, 817 | 818 | "date": function (b, a) { 819 | return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); 820 | }, 821 | 822 | "regexp": function (b, a) { 823 | return QUnit.objectType(b) === "regexp" && 824 | a.source === b.source && // the regex itself 825 | a.global === b.global && // and its modifers (gmi) ... 826 | a.ignoreCase === b.ignoreCase && 827 | a.multiline === b.multiline; 828 | }, 829 | 830 | // - skip when the property is a method of an instance (OOP) 831 | // - abort otherwise, 832 | // initial === would have catch identical references anyway 833 | "function": function () { 834 | var caller = callers[callers.length - 1]; 835 | return caller !== Object && 836 | typeof caller !== "undefined"; 837 | }, 838 | 839 | "array": function (b, a) { 840 | var i, j, loop; 841 | var len; 842 | 843 | // b could be an object literal here 844 | if ( ! (QUnit.objectType(b) === "array")) { 845 | return false; 846 | } 847 | 848 | len = a.length; 849 | if (len !== b.length) { // safe and faster 850 | return false; 851 | } 852 | 853 | //track reference to avoid circular references 854 | parents.push(a); 855 | for (i = 0; i < len; i++) { 856 | loop = false; 857 | for(j=0;j= 0) { 1002 | type = "array"; 1003 | } else { 1004 | type = typeof obj; 1005 | } 1006 | return type; 1007 | }, 1008 | separator:function() { 1009 | return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; 1010 | }, 1011 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1012 | if ( !this.multiline ) 1013 | return ''; 1014 | var chr = this.indentChar; 1015 | if ( this.HTML ) 1016 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1017 | return Array( this._depth_ + (extra||0) ).join(chr); 1018 | }, 1019 | up:function( a ) { 1020 | this._depth_ += a || 1; 1021 | }, 1022 | down:function( a ) { 1023 | this._depth_ -= a || 1; 1024 | }, 1025 | setParser:function( name, parser ) { 1026 | this.parsers[name] = parser; 1027 | }, 1028 | // The next 3 are exposed so you can use them 1029 | quote:quote, 1030 | literal:literal, 1031 | join:join, 1032 | // 1033 | _depth_: 1, 1034 | // This is the list of parsers, to modify them, use jsDump.setParser 1035 | parsers:{ 1036 | window: '[Window]', 1037 | document: '[Document]', 1038 | error:'[ERROR]', //when no parser is found, shouldn't happen 1039 | unknown: '[Unknown]', 1040 | 'null':'null', 1041 | undefined:'undefined', 1042 | 'function':function( fn ) { 1043 | var ret = 'function', 1044 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1045 | if ( name ) 1046 | ret += ' ' + name; 1047 | ret += '('; 1048 | 1049 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); 1050 | return join( ret, this.parse(fn,'functionCode'), '}' ); 1051 | }, 1052 | array: array, 1053 | nodelist: array, 1054 | arguments: array, 1055 | object:function( map ) { 1056 | var ret = [ ]; 1057 | this.up(); 1058 | for ( var key in map ) 1059 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); 1060 | this.down(); 1061 | return join( '{', ret, '}' ); 1062 | }, 1063 | node:function( node ) { 1064 | var open = this.HTML ? '<' : '<', 1065 | close = this.HTML ? '>' : '>'; 1066 | 1067 | var tag = node.nodeName.toLowerCase(), 1068 | ret = open + tag; 1069 | 1070 | for ( var a in this.DOMAttrs ) { 1071 | var val = node[this.DOMAttrs[a]]; 1072 | if ( val ) 1073 | ret += ' ' + a + '=' + this.parse( val, 'attribute' ); 1074 | } 1075 | return ret + close + open + '/' + tag + close; 1076 | }, 1077 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1078 | var l = fn.length; 1079 | if ( !l ) return ''; 1080 | 1081 | var args = Array(l); 1082 | while ( l-- ) 1083 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1084 | return ' ' + args.join(', ') + ' '; 1085 | }, 1086 | key:quote, //object calls it internally, the key part of an item in a map 1087 | functionCode:'[code]', //function calls it internally, it's the content of the function 1088 | attribute:quote, //node calls it internally, it's an html attribute value 1089 | string:quote, 1090 | date:quote, 1091 | regexp:literal, //regex 1092 | number:literal, 1093 | 'boolean':literal 1094 | }, 1095 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1096 | id:'id', 1097 | name:'name', 1098 | 'class':'className' 1099 | }, 1100 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1101 | indentChar:' ',//indentation unit 1102 | multiline:false //if true, items in a collection, are separated by a \n, else just a space. 1103 | }; 1104 | 1105 | return jsDump; 1106 | })(); 1107 | 1108 | // from Sizzle.js 1109 | function getText( elems ) { 1110 | var ret = "", elem; 1111 | 1112 | for ( var i = 0; elems[i]; i++ ) { 1113 | elem = elems[i]; 1114 | 1115 | // Get the text from text nodes and CDATA nodes 1116 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1117 | ret += elem.nodeValue; 1118 | 1119 | // Traverse everything else, except comment nodes 1120 | } else if ( elem.nodeType !== 8 ) { 1121 | ret += getText( elem.childNodes ); 1122 | } 1123 | } 1124 | 1125 | return ret; 1126 | }; 1127 | 1128 | /* 1129 | * Javascript Diff Algorithm 1130 | * By John Resig (http://ejohn.org/) 1131 | * Modified by Chu Alan "sprite" 1132 | * 1133 | * Released under the MIT license. 1134 | * 1135 | * More Info: 1136 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1137 | * 1138 | * Usage: QUnit.diff(expected, actual) 1139 | * 1140 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1141 | */ 1142 | QUnit.diff = (function() { 1143 | function diff(o, n){ 1144 | var ns = new Object(); 1145 | var os = new Object(); 1146 | 1147 | for (var i = 0; i < n.length; i++) { 1148 | if (ns[n[i]] == null) 1149 | ns[n[i]] = { 1150 | rows: new Array(), 1151 | o: null 1152 | }; 1153 | ns[n[i]].rows.push(i); 1154 | } 1155 | 1156 | for (var i = 0; i < o.length; i++) { 1157 | if (os[o[i]] == null) 1158 | os[o[i]] = { 1159 | rows: new Array(), 1160 | n: null 1161 | }; 1162 | os[o[i]].rows.push(i); 1163 | } 1164 | 1165 | for (var i in ns) { 1166 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1167 | n[ns[i].rows[0]] = { 1168 | text: n[ns[i].rows[0]], 1169 | row: os[i].rows[0] 1170 | }; 1171 | o[os[i].rows[0]] = { 1172 | text: o[os[i].rows[0]], 1173 | row: ns[i].rows[0] 1174 | }; 1175 | } 1176 | } 1177 | 1178 | for (var i = 0; i < n.length - 1; i++) { 1179 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1180 | n[i + 1] == o[n[i].row + 1]) { 1181 | n[i + 1] = { 1182 | text: n[i + 1], 1183 | row: n[i].row + 1 1184 | }; 1185 | o[n[i].row + 1] = { 1186 | text: o[n[i].row + 1], 1187 | row: i + 1 1188 | }; 1189 | } 1190 | } 1191 | 1192 | for (var i = n.length - 1; i > 0; i--) { 1193 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1194 | n[i - 1] == o[n[i].row - 1]) { 1195 | n[i - 1] = { 1196 | text: n[i - 1], 1197 | row: n[i].row - 1 1198 | }; 1199 | o[n[i].row - 1] = { 1200 | text: o[n[i].row - 1], 1201 | row: i - 1 1202 | }; 1203 | } 1204 | } 1205 | 1206 | return { 1207 | o: o, 1208 | n: n 1209 | }; 1210 | } 1211 | 1212 | return function(o, n){ 1213 | o = o.replace(/\s+$/, ''); 1214 | n = n.replace(/\s+$/, ''); 1215 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1216 | 1217 | var str = ""; 1218 | 1219 | var oSpace = o.match(/\s+/g); 1220 | if (oSpace == null) { 1221 | oSpace = [" "]; 1222 | } 1223 | else { 1224 | oSpace.push(" "); 1225 | } 1226 | var nSpace = n.match(/\s+/g); 1227 | if (nSpace == null) { 1228 | nSpace = [" "]; 1229 | } 1230 | else { 1231 | nSpace.push(" "); 1232 | } 1233 | 1234 | if (out.n.length == 0) { 1235 | for (var i = 0; i < out.o.length; i++) { 1236 | str += '' + out.o[i] + oSpace[i] + ""; 1237 | } 1238 | } 1239 | else { 1240 | if (out.n[0].text == null) { 1241 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1242 | str += '' + out.o[n] + oSpace[n] + ""; 1243 | } 1244 | } 1245 | 1246 | for (var i = 0; i < out.n.length; i++) { 1247 | if (out.n[i].text == null) { 1248 | str += '' + out.n[i] + nSpace[i] + ""; 1249 | } 1250 | else { 1251 | var pre = ""; 1252 | 1253 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1254 | pre += '' + out.o[n] + oSpace[n] + ""; 1255 | } 1256 | str += " " + out.n[i].text + nSpace[i] + pre; 1257 | } 1258 | } 1259 | } 1260 | 1261 | return str; 1262 | } 1263 | })(); 1264 | 1265 | })(this); 1266 | -------------------------------------------------------------------------------- /app/data/base.dump.sql: -------------------------------------------------------------------------------- 1 | PRAGMA foreign_keys=OFF; 2 | BEGIN TRANSACTION; 3 | CREATE TABLE 'users' ( 4 | "id" INTEGER PRIMARY KEY AUTOINCREMENT, 5 | "email" TEXT NOT NULL UNIQUE, 6 | "password" NOT NULL, 7 | "salt" NOT NULL, 8 | "created" DEFAULT (DATETIME('now')), 9 | "first_name" DEFAULT '', 10 | "last_name" DEFAULT '' 11 | ); 12 | INSERT INTO "users" VALUES(1,'hue@example.com','ArrGeeBee','#000','2009-02-13 23:31:30','Hugh','Engleman'); 13 | INSERT INTO "users" VALUES(2,'ldubin@example.com','vowelcap5','Bs;M','2009-02-21 08:11:47','Liza','Dubin'); 14 | INSERT INTO "users" VALUES(3,'DIacovelli@example.com','hen3tell4','6Vt`','2009-02-27 06:19:58','Darren','Iacovelli'); 15 | INSERT INTO "users" VALUES(4,'link@example.com','iplaymario','h3ro','2009-03-04 18:24:11','Zelma','Gaebora'); 16 | INSERT INTO "users" VALUES(5,'couch@example.com','sock8exist8','H0t#','2009-03-08 04:23:19','Sofia','Kleiman'); 17 | INSERT INTO "users" VALUES(6,'inky@example.com','wrist2skirt8','eaBX','2009-03-08 08:20:08','Earnestine','Varnes'); 18 | INSERT INTO "users" VALUES(7,'firesomething@example.com','iprefersafari','!IE6','2009-03-12 12:50:46','Moe','Zilla'); 19 | INSERT INTO "users" VALUES(8,'JHeinen@example.com','bar8half','M}w%','2009-03-19 00:22:26','Jeanie','Heinen'); 20 | INSERT INTO "users" VALUES(9,'LLeander@example.com','hastypress','0e;Y','2009-03-22 11:29:47','Loraine','Leander'); 21 | INSERT INTO "users" VALUES(10,'noreply@example.com','1/4imaginarybase:=2i',2.56,'2009-03-26 09:45:27','Donald','Knuth'); 22 | INSERT INTO "users" VALUES(11,'LWoodside@example.com','spoon6part9','sQ(,','2009-04-01 09:07:04','Liza','Woodside'); 23 | INSERT INTO "users" VALUES(12,'kettle@example.com','no3laws','at0m','2009-04-01 22:12:14','Andrew','Harlan'); 24 | INSERT INTO "users" VALUES(13,'EMishler@example.com','room3metal6','*mlP','2009-04-04 10:47:59','Erik','Mishler'); 25 | INSERT INTO "users" VALUES(14,'SLappin@example.com','existpoem3',']y4C','2009-04-07 13:44:29','Sofia','Lappin'); 26 | INSERT INTO "users" VALUES(15,'alttag@example.com','bake5glad',']x8P','2009-04-12 03:22:47','Tom','Gregory'); 27 | INSERT INTO "users" VALUES(16,'blinky@example.com','readsleep6','GJBy','2009-04-13 17:49:42','Louisa','Getter'); 28 | INSERT INTO "users" VALUES(17,'math@example.com','rithmaticrulez','2+2=','2009-04-16 20:44:49','Tuatua','Isafoa'); 29 | INSERT INTO "users" VALUES(18,'EAshlock@example.com','replyjust9','YNi>','2009-04-17 08:27:20','Emilia','Ashlock'); 30 | INSERT INTO "users" VALUES(19,'LBrion@example.com','agojelly','VvBs','2009-04-19 22:15:58','Lenore','Brion'); 31 | INSERT INTO "users" VALUES(20,'JManjarrez@example.com','cap5their','l''|V','2009-04-23 01:12:55','Jerri','Manjarrez'); 32 | INSERT INTO "users" VALUES(21,'notvincent@example.com','speak3wake6','!l&o','2009-04-30 06:08:35','Lonnie','D''Onofrio'); 33 | INSERT INTO "users" VALUES(22,'MGosser@example.com','belt8nylon','rY7q','2009-05-03 20:31:28','Malinda','Gosser'); 34 | INSERT INTO "users" VALUES(23,'gnuworldorder@example.com','dualblades344','foss','2009-05-09 03:55:38','Rich','Stallman'); 35 | INSERT INTO "users" VALUES(24,'wildeep@example.com','bringbackflyingtoasters','m7-9','2009-05-16 06:31:22','Haun','Reekes'); 36 | INSERT INTO "users" VALUES(25,'MAraki@example.com','cheap6scene3',']r}H','2009-05-18 10:32:41','Margery','Araki'); 37 | INSERT INTO "users" VALUES(26,'uber@example.com','It wasn''t Al Gore','1KBE','2009-05-22 05:58:39','Tim','Lee'); 38 | INSERT INTO "users" VALUES(27,'ESallis@example.com','urgeyet3','kz3z','2009-05-25 18:05:21','Erik','Sallis'); 39 | INSERT INTO "users" VALUES(28,'pinky@example.com','sheep5ship5','y8g`','2009-05-30 01:26:37','Lilia','Fullbright'); 40 | INSERT INTO "users" VALUES(29,'ALanctot@example.com','cartunite3','Y1a7','2009-05-31 20:19:47','Alejandra','Lanctot'); 41 | INSERT INTO "users" VALUES(30,'NOgara@example.com','rush9sixth4','.PP0','2009-06-01 06:38:20','Neil','Ogara'); 42 | INSERT INTO "users" VALUES(31,'sly@example.com','towerpiece5','K>NA','2009-06-02 10:29:01','Hugh','Sangster'); 43 | INSERT INTO "users" VALUES(32,'RFrancis@example.com','wall6king4','N&+*','2009-06-10 05:42:35','Randy','Francis'); 44 | INSERT INTO "users" VALUES(33,'JOsborn@example.com','taxi3means','}"]H','2009-06-18 00:41:24','Johnny','Osborn'); 45 | INSERT INTO "users" VALUES(34,'JWilson@example.com','track8eager8','Kvh0','2009-06-25 02:40:15','Jonathan','Wilson'); 46 | INSERT INTO "users" VALUES(35,'injection@example.com','exploitsofamom327',1337,'2009-06-30 09:58:02','Bobby','Tables'); 47 | INSERT INTO "users" VALUES(36,'etheregg@example.com','putyou2sleep','n2o!','2009-07-03 14:55:59','Joseph','Clover'); 48 | INSERT INTO "users" VALUES(37,'MSantacruz@example.com','giftsuit4','P98&','2009-07-10 20:51:39','Marie','Santacruz'); 49 | INSERT INTO "users" VALUES(38,'JEdmondson@example.com','beak3wish7','