diff --git a/.gitignore b/.gitignore index d4db9aa..ad861ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/workspace.xml .idea/tasks.xml .DS_Store -bower_components +config/ +log/ \ No newline at end of file diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index fad7b72..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -josh.js \ No newline at end of file diff --git a/.idea/dictionaries/Administrator.xml b/.idea/dictionaries/Administrator.xml deleted file mode 100644 index a70526e..0000000 --- a/.idea/dictionaries/Administrator.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - claassen - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index e206d70..7c62b52 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 883ae4e..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 6933c1e..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/josh.js.iml b/.idea/josh.js-pages.iml similarity index 96% rename from .idea/josh.js.iml rename to .idea/josh.js-pages.iml index 6b8184f..3b09ca3 100644 --- a/.idea/josh.js.iml +++ b/.idea/josh.js-pages.iml @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/.idea/jsLinters/jshint.xml b/.idea/jsLinters/jshint.xml deleted file mode 100644 index 07f2069..0000000 --- a/.idea/jsLinters/jshint.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/.idea/jsLinters/jslint.xml b/.idea/jsLinters/jslint.xml deleted file mode 100644 index 5b9d1d1..0000000 --- a/.idea/jsLinters/jslint.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/.idea/misc.xml b/.idea/misc.xml index 1162f43..262e5d3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml index 3630cb8..798cf33 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1,9 +1,9 @@ - - - - - - - - - + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml index 922003b..0d5175c 100644 --- a/.idea/scopes/scope_settings.xml +++ b/.idea/scopes/scope_settings.xml @@ -1,5 +1,5 @@ - - - + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 9d32e50..ab55cf1 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,8 +1,7 @@ - - - - - - - - + + + + + + + diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 2cc73f8..0000000 --- a/LICENSE +++ /dev/null @@ -1,53 +0,0 @@ -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index c2379c2..0000000 --- a/README.md +++ /dev/null @@ -1,191 +0,0 @@ -josh.js 0.2 -=========== - -http://sdether.github.io/josh.js/ - -***Javascript Online SHell*** provides a toolkit for building bash-like command line consoles for web pages. JOSH enables the visitor who prefers the bash-like command to maneuver through web content using a console rather than clicking with a mouse. This toolkit is most useful for people who like to use command line because it is faster and more effective than using a mouse. It is easier and convienent to access hierarchical information using JOSH command history, change or display current directory. It consists of the following components: - -* `readline.js` - full readline support for ctrl sequences, tab, history, etc. -* `shell.js` - visual presentation of the shell and command handling -* `pathhandler.js` - provide cd, ls, pwd and path completion toolikit -* `history.js` - localStorage backed command history -* `killring.js` - killring for kill & yank handling in readline - -## What to use Josh for and when to use it - -Josh allows developers to build their own command line interface to any sites. It supports full CLI Readline in the browser like TAB completion, emacs-style line editing, killring and history with reverse search. When you are tired of clicking your way through a hierachy tree, Josh will come in handy. It will helps you browse or navigate text files quickly and minimal the using of mouse click. - -## Tutorials -* Hello world - put a console on a web page and add a new custom command with completion -* Quake Console - Create a quake-style console with ls, - cd, pwd and bash filename tab-completion -* GitHub Console - Extend the Quake Console to talk to GitHub's REST API to navigate repositories, their branches and file system - -## Articles -* CLI all the things: Introducing Josh.js Article about the origins of Josh.js with an example console for wordpress sites. - -## License -josh.js is licensed under the Apache 2.0 License - -## Status - -* code is ready for experimental use - * Tested under Chrome, Firefox, Safari and IE9 - * API may not yet be stable -* needs minified versions of complete toolkit and just readline.js -* needs code documentation and documentation site -* would like to add AMD support -* base shell UI should get some basic behaviors - * `more`-like handling for output that exceeds the shell viewport size - * resizing and close chrome -* Readline has not been tested with non-ascii. - -## Usage - -Until documentation is written, refer to `index.html` and `example.js` ([Annotated Source](http://sdether.github.com/josh.js/docs/example.html)) for a sample implementation of a shell with path completion. - -## Components -***josh*** is built from 5 components and can be used in part or in full. - -### readline.js - -`readline.js` has no dependencies on any outside libraries, although it requires either `history.js` and `killring.js` or objects implementing the same calls. - -It implements key trapping to bring [GNU Readline](http://cnswww.cns.cwru.edu/php/chet/readline/readline.html) like line editing to the browser. It can be used by itself to bring readline support to custom data entry fields or in conjunction with `shell.js` to create a full console. - -#### Line Editing -In the below `C-x` refers to the `Ctrl-x` keystroke, while `M-x` refers to the `Meta-x` keystroke which is mapped to `Alt`, `⌘` and `Left Windows`. - -
-
Movement
-
C-b or Left Arrow
-
Move back one character
-
M-b or Right Arrow
-
Move back one word
-
C-f
-
Move forward one character
-
M-f
-
Move forward one word
-
C-a or Home
-
Move to the beginning of the line
-
C-e or End
-
Move to the end of the line
- -
-
Edit/Kill
-
Backspace
-
Delete one character back
-
C-d or Delete
-
Delete character under cursor
-
C-k
-
Kill (i.e. put in kill ring) text to the end of the line
-
M-Backspace
-
Kill one word back
-
M-d
-
Kill word under cursor
-
C-y
-
Yank (i.e. pull from kill ring) the most recently killed text
-
M-y
-
Rotate to the next item in killring and yank it. Must be preceded by yank
- -
-
History
-
C-r
-
Reverse search through history
-
C-p or Up Arrow
-
Previous entry in history
-
C-n or Down Arrow
-
Next entry in history
-
Page Up
-
Top of history
-
Page Down
-
Bottom of history
- -
-
Misc
-
C-l
-
refresh line (clear screen in shell)
-
Tab
-
Invoke completion handler for text under cursor
-
Esc in reverse search
-
Cancel search
-
C-c
-
call onCancel handler
-
C-d on empty line
-
call onCancel handler
-
- -### shell.js -`shell.js` has external dependencies of [jQuery](http://jquery.com/), [Underscore](http://underscorejs.org/) and internal dependencies of `readline.js` and `history.js`. - -It provides a simple console UI, using a *panel* for the console viewport and an auto-scrolling *view* inside the panel. It uses Underscore templates for generating the view html, although any template generator can be substituted as long as it can be expressed in the form of a function that takes a JSON object of arguments and returns an html string. - -It also implements command handling so that new commands can be added by name with execution and completion handlers. Out of the box, `shell.js` provides the following commands: -* help - list all known commands (including user added) -* clear - clear the "screen" i.e. viewport -* history - show the command history captured by `readline.js` in `history.js` - -### pathhandler.js -`pathhandler.js` is a mix in to easily add the `cd`, `ls` and `pwd` commands as well as path completion. It has the same external dependencies of [jQuery](http://jquery.com/), [Underscore](http://underscorejs.org/) as `shell.js` and also uses Underscore templating. - -By implementing the functions `getNode` and `getChildNodes`, this library adds path traversal, discovery and completion just like a bash shell. - -### history.js -`history.js` implements a localStorage back command history storage that persists over page changes and reloads. It is used by the `shell.js` history command to list all executed commands, and by `readline.js` for up/down arrow and reverse search capabilities. - -### killring.js -`killring.js` implements the kill and yank behavior as well as state tracking, i.e. multiple consecutive kills are combined as a single kill and killring rotation tracks the previous yank, so that the `readline.js` can remove the previous yank and replace it with the rotated text. - -## Changelog - -**0.2.10** -- 2014/04/03 -* Added bower support (pr#19 - @bricef) -* Code clean-up for closure compiler issues (pr#20 - @aaronmars) - -**0.2.9** -- 2013/08/31 -* Added ability to bind ReadLine/Shell to an element. -* Added ability to bind/unbind keys (could be used to replace emacs bindings of readline, but primarily added to unbind some keys for using readline on input elements. -* Created input.js for easy binding of readline to either an input element or a span behaving like an input. - -**0.2.8** -- 2013/03/13 -* Added handling of . and .. in Josh.PathHandler.pathcompletionhandler, so that a trailing .. completes to ../ and . to ./ -* Removed the hardcoded **strong** in the input template, making it a span instead so it can be styled via css instead. -* The prompt value itself is now assumed to be html instead of plain text, allowing for richer formatting without changing the input template. - -**0.2.7** -- 2013/02/13 -* Removed all html used for Shell UI generation from config to `Shell.templates`, so that they can easily be customized (see: [Issue 11](https://github.com/sdether/josh.js/issues/11)) -* Removed `PathHandler.templates`. PathHandler now attches its templates to `Shell.templates` as well - -**0.2.6** -- 2013/01/21 -* Removed Activation/Deactivation keybindings from Readline, making it an outside concern (see: [Issue 2](https://github.com/sdether/josh.js/issues/2)) -* Fixed Backspace regression introduced by 0.2.5 -* Fixed `M-d` not deleting last character of line -* Example shell can now be resized (via jquery-ui.resizable) - -**0.2.5** -- 2013/01/14 -* Implemented missing Readline behavior (see: [Issue 1](https://github.com/sdether/josh.js/issues/1)) -* Added scrollbar to sample implemenation (also adds scrollwheel support) - -**0.2.4** -- 2013/01/14 -* fixed path completion handling for scenarios of two possible completions where one is the root of the other -* noted that spaces in paths are completely unsupported right now.. they will complete, but the exec handler will see the space as a separator between arguments - -**0.2.3** -- 2013/01/13 -* changed internal handling of the default command handler (i.e. when no named command is defined). -* removed the pathhandler commandhandlers from the public object, since they should be accessed via shell.getCommandHandler if required -* some readline.js property naming cleanup to make closure compiler happy - -**0.2.2** -- 2013/01/09 -* changed rendering of completion to be more bash-like, i.e. now renders completion and then re-renders prompt with completed text, rather than as a pop-under that disappears. - -**0.2.1** -- 2013/01/08 -* fixed activation/deactivation propagation through the shell, which caused issues with first time activation via activation key instead of method call to fail. - -**0.2** -- 2013/01/07 -* console wrapper to allow debug logging to be turned on and off -* refactored how pathhandler attaches to shell because it needs to keep a reference to the shell -* refactored how prompts are set. now uses dedicated callback rather than returning the prompt in the `onEnter` callback -* tested and made fixes to ensure compatibility with major modern browsers - -**0.1** -- 2013/01/04 -* Initial code-complete release diff --git a/bower.json b/bower.json deleted file mode 100644 index 9971f61..0000000 --- a/bower.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "josh.js", - "version": "0.2.10", - "homepage": "http://sdether.github.io/josh.js/", - "authors": [ - "Arne Claassen " - ], - "description": "Javascript Online SHell provides a toolkit for building bash-like command line consoles for web pages.", - "main": [ - "js/readline.js", - "js/history.js", - "js/killring.js", - "js/input.js", - "js/pathhandler.js", - "js/shell.js" - ], - "keywords": [ - "shell", - "josh" - ], - "license": "Apache 2.0", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "dependencies": { - "jquery": "~3.1.0", - "lodash": "~4.14.2" - } -} diff --git a/docs/docco.css b/docs/docco.css new file mode 100644 index 0000000..04cc7ec --- /dev/null +++ b/docs/docco.css @@ -0,0 +1,192 @@ +/*--------------------- Layout and Typography ----------------------------*/ +body { + font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; + font-size: 15px; + line-height: 22px; + color: #252519; + margin: 0; padding: 0; +} +a { + color: #261a3b; +} + a:visited { + color: #261a3b; + } +p { + margin: 0 0 15px 0; +} +h1, h2, h3, h4, h5, h6 { + margin: 0px 0 15px 0; +} + h1 { + margin-top: 40px; + } +hr { + border: 0 none; + border-top: 1px solid #e5e5ee; + height: 1px; + margin: 20px 0; +} +#container { + position: relative; +} +#background { + position: fixed; + top: 0; left: 525px; right: 0; bottom: 0; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + z-index: -1; +} +#jump_to, #jump_page { + background: white; + -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; + -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; + font: 10px Arial; + text-transform: uppercase; + cursor: pointer; + text-align: right; +} +#jump_to, #jump_wrapper { + position: fixed; + right: 0; top: 0; + padding: 5px 10px; +} + #jump_wrapper { + padding: 0; + display: none; + } + #jump_to:hover #jump_wrapper { + display: block; + } + #jump_page { + padding: 5px 0 3px; + margin: 0 0 25px 25px; + } + #jump_page .source { + display: block; + padding: 5px 10px; + text-decoration: none; + border-top: 1px solid #eee; + } + #jump_page .source:hover { + background: #f5f5ff; + } + #jump_page .source:first-child { + } +table td { + border: 0; + outline: 0; +} + td.docs, th.docs { + max-width: 450px; + min-width: 450px; + min-height: 5px; + padding: 10px 25px 1px 50px; + overflow-x: hidden; + vertical-align: top; + text-align: left; + } + .docs pre { + margin: 15px 0 15px; + padding-left: 15px; + } + .docs p tt, .docs p code { + background: #f8f8ff; + border: 1px solid #dedede; + font-size: 12px; + padding: 0 0.2em; + } + .pilwrap { + position: relative; + } + .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + top: 3px; left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + } + td.docs:hover .pilcrow { + opacity: 1; + } + td.code, th.code { + padding: 14px 15px 16px 25px; + width: 100%; + vertical-align: top; + background: #f5f5ff; + border-left: 1px solid #e5e5ee; + } + pre, tt, code { + font-size: 12px; line-height: 18px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; padding: 0; + } + + +/*---------------------- Syntax Highlighting -----------------------------*/ +td.linenos { background-color: #f0f0f0; padding-right: 10px; } +span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } +body .hll { background-color: #ffffcc } +body .c { color: #408080; font-style: italic } /* Comment */ +body .err { border: 1px solid #FF0000 } /* Error */ +body .k { color: #954121 } /* Keyword */ +body .o { color: #666666 } /* Operator */ +body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +body .cp { color: #BC7A00 } /* Comment.Preproc */ +body .c1 { color: #408080; font-style: italic } /* Comment.Single */ +body .cs { color: #408080; font-style: italic } /* Comment.Special */ +body .gd { color: #A00000 } /* Generic.Deleted */ +body .ge { font-style: italic } /* Generic.Emph */ +body .gr { color: #FF0000 } /* Generic.Error */ +body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +body .gi { color: #00A000 } /* Generic.Inserted */ +body .go { color: #808080 } /* Generic.Output */ +body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +body .gs { font-weight: bold } /* Generic.Strong */ +body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +body .gt { color: #0040D0 } /* Generic.Traceback */ +body .kc { color: #954121 } /* Keyword.Constant */ +body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ +body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ +body .kp { color: #954121 } /* Keyword.Pseudo */ +body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ +body .kt { color: #B00040 } /* Keyword.Type */ +body .m { color: #666666 } /* Literal.Number */ +body .s { color: #219161 } /* Literal.String */ +body .na { color: #7D9029 } /* Name.Attribute */ +body .nb { color: #954121 } /* Name.Builtin */ +body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +body .no { color: #880000 } /* Name.Constant */ +body .nd { color: #AA22FF } /* Name.Decorator */ +body .ni { color: #999999; font-weight: bold } /* Name.Entity */ +body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +body .nf { color: #0000FF } /* Name.Function */ +body .nl { color: #A0A000 } /* Name.Label */ +body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +body .nt { color: #954121; font-weight: bold } /* Name.Tag */ +body .nv { color: #19469D } /* Name.Variable */ +body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +body .w { color: #bbbbbb } /* Text.Whitespace */ +body .mf { color: #666666 } /* Literal.Number.Float */ +body .mh { color: #666666 } /* Literal.Number.Hex */ +body .mi { color: #666666 } /* Literal.Number.Integer */ +body .mo { color: #666666 } /* Literal.Number.Oct */ +body .sb { color: #219161 } /* Literal.String.Backtick */ +body .sc { color: #219161 } /* Literal.String.Char */ +body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ +body .s2 { color: #219161 } /* Literal.String.Double */ +body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +body .sh { color: #219161 } /* Literal.String.Heredoc */ +body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +body .sx { color: #954121 } /* Literal.String.Other */ +body .sr { color: #BB6688 } /* Literal.String.Regex */ +body .s1 { color: #219161 } /* Literal.String.Single */ +body .ss { color: #19469D } /* Literal.String.Symbol */ +body .bp { color: #954121 } /* Name.Builtin.Pseudo */ +body .vc { color: #19469D } /* Name.Variable.Class */ +body .vg { color: #19469D } /* Name.Variable.Global */ +body .vi { color: #19469D } /* Name.Variable.Instance */ +body .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/example.html b/docs/example.html new file mode 100644 index 0000000..e677fe8 --- /dev/null +++ b/docs/example.html @@ -0,0 +1,187 @@ + example.js

example.js

/*------------------------------------------------------------------------*
+ * Copyright 2013 Arne F. Claassen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------*/
+(function(root, $, _) {
+  Josh.Example = (function(root, $, _) {

Enable console debugging, when Josh.Debug is set and there is a console object on the document root.

    var _console = (Josh.Debug && root.console) ? root.console : {
+      log: function() {
+      }
+    };

Setup of Shell

build the fake directory structure used to illustrate path commands and completions.

    var treeroot = buildTree();

Create History and KillRing by hand since we will use the KillRing for an example command.

    var history = Josh.History();
+    var killring = new Josh.KillRing();

Create the ReadLine instance by hand so that we can provide it our KillRing. Since the shell needs to share +the History object with ReadLine and Shell isn't getting to create ReadLine automatically as it usually does +we need to pass in History into ReadLine as well.

    var readline = new Josh.ReadLine({history: history, killring: killring, console: _console });

Finally, create the Shell.

    var shell = Josh.Shell({readline: readline, history: history, console: _console});

Create killring command

Setup the Underscore template for displaying items in the KillRing.

    var killringItemTemplate = _.template("<div><% _.each(items, function(item, i) { %><div><%- i %>&nbsp;<%- item %></div><% }); %></div>")

Create a the command killring which will display all text currently in the KillRing, by attaching +a handler to the Shell.

    shell.setCommandHandler("killring", {

We don't implement any completion for the killring command, so we only provide an exec handler, and no completion handler.

      exec: function(cmd, args, callback) {

killring takes one optional argument -c which clears the killring (just like history -c).

        if(args[0] == "-c") {
+          killring.clear();

The callback of an exec handler expects the html to display as result of executing the command. Clearing the +killing has no output, so we just call the callback and exit the handler.

          callback();
+          return;
+        }

Return the output of feeding all items from the killring into our template.

        callback(killringItemTemplate({items: killring.items()}));
+      }
+    });

Setup PathHandler

PathHandler is a mix-in for Shell to provide provide the standard unix ls, pwd and cd commands, as well +as standard bash-style path tab-completion. It expects a Shell instance as its first argument so that it can +attach its command handlers to the shell as well as overrride the default handler to support completion of path's +starting with . or / without a leading command.

    var pathhandler = new Josh.PathHandler(shell, {console: _console});

PathHandler operates on path nodes which are expected to be objects with the minimum structure of

+ +
{
+  name: 'localname',
+  path: '/full/path/to/localname'
+}
+
+ +

where name is the name of the node and path is the absolute path to the node. PathHandler does not modify +these nodes, so any additional state your implementation requires can be attached to the nodes and be relied on +being part of the node when received by the handling methods you implement.

+ +

The pathhandler expects to be initialized with the current directory, i.e. a path node.

    pathhandler.current = treeroot;

PathHandler requires two method, getNode and getChildNodes, to be provided in order to operate.

+ +

getNode gets called with path string. This string is completely opaque to PathHandler, i.e. constructs such +as . and .. are an implementation detail. PathHandler does assume that the path separator is /. getNode +is called anytime the pathhandler has a path and need to determine what if any node exists at that path which happens +during path completion as well as cd and ls execution.

    pathhandler.getNode = function(path, callback) {
+      if(!path) {
+        return callback(pathhandler.current);
+      }
+      var parts = _.filter(path.split('/'), function(x) {
+        return x;
+      });
+      var start = ((path || '')[0] == '/') ? treeroot : pathhandler.current;
+      _console.log('start: ' + start.path + ', parts: ' + JSON.stringify(parts));
+      return findNode(start, parts, callback);
+    };

getChildNodes is used by path completion to determine the possible completion candidates. Path completion first +determines the node for the given path, looking for the nearest / in case if the given path does not return a +node via getNode. For our example, we've attached the child node objects directly to the node object, so we +can simply return it. Usually this would be used to call the server with the provided node's path or id so that +the appropriate children can be found.

    pathhandler.getChildNodes = function(node, callback) {
+      _console.log("children for " + node.name);
+      callback(node.childnodes);
+    };

findNode is called recursively from getNode with the current node and remaining path already split into +segments. It then simply resolves the node for the next segment in parts to a node, including relative +references like . and ... In implementations that let you explore an hierarchy on a server, this function +would live on the server side and be called remotely via getNode.

    function findNode(current, parts, callback) {
+      if(!parts || parts.length == 0) {
+        return callback(current);
+      }
+      if(parts[0] == ".") {
+
+      } else if(parts[0] == "..") {
+        current = current.parent;
+      } else {
+        current = _.first(_.filter(current.childnodes, function(node) {
+          return node.name == parts[0];
+        }));
+      }
+      if(!current) {
+        return callback();
+      }
+      return findNode(current, _.rest(parts), callback);
+    }

Setup Document Behavior

Activation and display behavior happens at document ready time.

    $(document).ready(function() {

The default name for the div the shell uses as its container is shell-panel, although that can be changed via +the shell config parameter shell-panel-id. The Shell display model relies on a 'panel' to contain a 'view'. +The 'panel' acts as the view-port, i.e. the visible portion of the shell content, while the 'view' is appended +to and scrolled up as new content is added.

      var $consolePanel = $('#shell-panel');

We use jquery-ui's resizable to let us drag the bottom edge of the console up and down.

      $consolePanel.resizable({ handles: "s"});

activate the shell

      shell.activate();
+    });

We attach the various objects we've created here to Josh.Instance purely so they can be inspected via a +javascript console. This is not required for the functionality of the example.

    Josh.Instance = {
+      Tree: treeroot,
+      Shell: shell,
+      PathHandler: pathhandler,
+      KillRing: killring
+    };

This code builds our fake directory structure. Since most real applications of Josh would not keep their +entire hierarchy in memory, but instead make callbacks to a server to find nodes and node children, the details +of this function are of little interest.

    function buildTree() {
+      var fs = {
+        bin: {},
+        boot: {},
+        dev: {},
+        etc: {
+          default: {},
+          'rc.d': {},
+          sysconfig: {},
+          x11: {}
+        },
+        home: {
+          bob: {
+            video: {
+              'firefly.m4v': {}
+            },
+            videos: {
+              'Arrested Development': {
+                's1e1.m4v': {}
+              },
+              'Better Off Ted': {
+                's1e1.m4v': {}
+              }
+            }
+          },
+          jane: {}
+        },
+        lib: {},
+        'lost+found': {},
+        misc: {},
+        mnt: {
+          cdrom: {},
+          sysimage: {}
+        },
+        net: {},
+        opt: {},
+        proc: {},
+        root: {},
+        sbin: {},
+        usr: {
+          x11: {},
+          bin: {},
+          include: {},
+          lib: {},
+          local: {},
+          man: {},
+          sbin: {},
+          share: {
+            doc: {}
+          },
+          src: {}
+        },
+        var: {
+          lib: {},
+          lock: {},
+          run: {},
+          log: {
+            httpd: {
+              access_log: {},
+              error_log: {}
+            },
+            'boot.log': {},
+            cron: {},
+            messages: {}
+          }
+        }
+      };
+
+      function build(parent, node) {
+        parent.childnodes = _.map(_.pairs(node), function(pair) {
+          var child = {
+            name: pair[0],
+            path: parent.path + "/" + pair[0],
+            parent: parent
+          };
+          build(child, pair[1]);
+          return child;
+        });
+        parent.children = _.keys(node);
+        return parent;
+      }
+      var tree = build({name: "", path: ""}, fs);
+      tree.path = '/';
+      return tree;
+    }
+  })(root, $, _);
+})(this, $, _);
+
+
\ No newline at end of file diff --git a/docs/githubconsole.html b/docs/githubconsole.html new file mode 100644 index 0000000..47ae469 --- /dev/null +++ b/docs/githubconsole.html @@ -0,0 +1,337 @@ + githubconsole.js

githubconsole.js

/*------------------------------------------------------------------------*
+ * Copyright 2013 Arne F. Claassen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------*/
+(function(root, $, _) {
+  Josh.GitHubConsole = (function(root, $, _) {

Enable console debugging, when Josh.Debug is set and there is a console object on the document root.

    var _console = (Josh.Debug && root.console) ? root.console : {
+      log: function() {
+      }
+    };

Console State

+ +

_self contains all state variables for the console's operation

    var _self = {
+      shell: Josh.Shell({console: _console}),
+      api: "http://josh.claassen.net/github/"
+    };

Josh.PathHandler is attached to Josh.Shell to provide basic file system navigation.

    _self.pathhandler = new Josh.PathHandler(_self.shell, {console: _console});

Custom Templates

+ +

Josh.Shell uses Underscore templates for rendering output to the shell. This console overrides some and adds a couple of new ones for its own commands.

templates.prompt

Override of the default prompt to provide a multi-line prompt of the current user, repo and path and branch.

    _self.shell.templates.prompt = _.template("<em>[<%= self.user.login %>/<%= self.repo.name %>]</em></br>(<%=self.branch%>) <strong><%= node.path %> $</strong>");

templates.ls

Override of the pathhandler ls template to create a multi-column listing.

    _self.shell.templates.ls = _.template("<ul class='widelist'><% _.each(nodes, function(node) { %><li><%- node.name %></li><% }); %></ul><div class='clear'/>");

templates.not_found

Override of the pathhandler not_found template, since we will throw not_found if you try to access a valid file. This is done for the simplicity of the tutorial.

    _self.shell.templates.not_found = _.template("<div><%=cmd%>: <%=path%>: No such directory</div>");

templates.rateLimitTemplate

Since GitHub rate limits un-authenticated use rather drastically, we render the current rate limit status in the shell so that it is clear that extended experimenting requires authentication.

    _self.shell.templates.rateLimitTemplate = _.template("<%=remaining%>/<%=limit%><% if(!authenticated) {%> <a href='http://josh.claassen.net/github/authenticate'>Authenticate with Github to increase your Rate Limit.</a><%}%>");

templates.user

Render basic information (including gravatar) whenever we switch users or enter user without an argument

    _self.shell.templates.user = _.template("<div class='userinfo'>" +
+      "<img src='<%=user.avatar_url%>' style='float:right;'/>" +
+      "<table>" +
+      "<tr><td><strong>Id:</strong></td><td><%=user.id %></td></tr>" +
+      "<tr><td><strong>Name:</strong></td><td><%=user.login %></td></tr>" +
+      "<tr><td><strong>Location:</strong></td><td><%=user.location %></td></tr>" +
+      "</table>" +
+      "</div>"
+    );

templates.user_error

Generic error in case setting the user fails.

    _self.shell.templates.user_error = _.template("Unable to set user '<%=name%>': <%=msg%>");

templates.repos

Just like ls, we render a wide list of repositories for repo -l.

    _self.shell.templates.repos = _.template("<ul class='widelist'><% _.each(repos, function(repo) { %><li><%- repo.name %></li><% }); %></ul><div class='clear'/>");

template.repo

Whenever we change repositories or repo is called without an argument, we show basic information about the repo.

    _self.shell.templates.repo = _.template("<div><div><strong>Name: </strong><%=repo.full_name%></div><div><strong>Description: </strong><%=repo.description %></div></div>");

template.reponotfound

Error message in case someone tries to switch to an invalid repo.

    _self.shell.templates.repo_not_found = _.template("<div>repo: <%=repo%>: No such repo for user '<%= user %>'</div>");

templates.repo_error

Generic error message in case setting the repo fails.

    _self.shell.templates.repo_error = _.template("Unable to switch to repository '<%=name%>': <%=msg%>");

templates.branches

Again, like ls, we render a wide like of branches for branch -l.

    _self.shell.templates.branches = _.template("webfont.woff<ul class='widelist'><% _.each(branches, function(branch) { %><li><%- branch.name %></li><% }); %></ul><div class='clear'/>");

templates.branch_error

Generic error message in case setting the current branch fails.

    _self.shell.templates.branch_error = _.template("Unable to switch to branch '<%=name%>': <%=msg%>");

templates.branches_error

Generic error in case fetching the list of branches fails.

    _self.shell.templates.branches_error = _.template("Unable to load branch list: <%=msg%>");

Adding Commands to the Console

user [ username ]

The user command is used to display information about the current user or switch between github users.

    _self.shell.setCommandHandler("user", {

exec handles the execution of the command.

      exec: function(cmd, args, callback) {

Given no arguments, it renders information about the current user, using the data fetched at user initialization.

        if(!args || args.length == 0) {
+          return callback(_self.shell.templates.user({user: _self.user}));
+        }
+        var username = args[0];

Given an argument (assumed to be a username), it calls setUser to fetch the specified user and repository +information.

        return setUser(username, null,
+          function(msg) {
+            return callback(_self.shell.templates.user_error({name: username, msg: msg}));
+          },
+          function(user) {
+            return callback(_self.shell.templates.user({user: user}));
+          }
+        );
+      }

user has no completion handler, since the userbase of github is quite large and creating a search based +completion handler is beyond the scope of this tutorial implementation.

    });

repo [ -l | reponame ]

The repo command is used to display information about the current repo or switch to. It

    _self.shell.setCommandHandler("repo", {

exec handles the execution of the command.

      exec: function(cmd, args, callback) {

Given no arguments, it renders information about the current repo.

        if(!args || args.length == 0) {
+          return callback(_self.shell.templates.repo({repo: _self.repo}));
+        }
+        var name = args[0];

Given the argument -l, it lists all repos for the current user. This information was fetched at user +initialization

        if(name === '-l') {
+          return callback(_self.shell.templates.repos({repos: _self.repos}));
+        }

Otherwise, the argument is assumed to a repo name, which getRepo uses to fetch the repository's information +from the data in _self.repos, if possible.

        var repo = getRepo(name, _self.repos);

If there is no matching repo, it renders an error.

        if(!repo) {
+          return callback(_self.shell.templates.repo_error({name: name, msg: 'no such repo'}));
+        }

Given a valid repo, setRepo initializes the repo (i.e. fetching the root directory) and renders the repo +information.

        return setRepo(repo,
+          function(msg) {
+            return callback(_self.shell.templates.repo_error({name: name, msg: msg}));
+          },
+          function(repo) {
+            if(!repo) {
+              return callback(_self.shell.templates.repo_not_found({repo: name, user: _self.user.login}));
+            }
+            return callback(_self.shell.templates.repo({repo: _self.repo}));
+          }
+        );
+      },

completion uses _self.repo and Josh.Shell.bestMatch to try and match the partial information to the possible +matching repositories.

      completion: function(cmd, arg, line, callback) {
+        callback(_self.shell.bestMatch(arg, _.map(_self.repos, function(repo) {
+          return repo.name;
+        })));
+      }
+    });

branch [ -l | branchname ]

The branch command is used to switch or list branches for the current repository.

    _self.shell.setCommandHandler("branch", {

exec handles the execution of the command.

      exec: function(cmd, args, callback) {

Given no arguments, it simply returns the current branch, which will be rendered by the shell.

        if(!args || args.length == 0) {
+          return callback(_self.branch);
+        }
+        var branch = args[0];

Given the argument -l, it lists all branches for the current repo. This information is lazily +initialized via ensureBranches.

        if(branch === '-l') {
+          return ensureBranches(
+            function(msg) {
+              callback(_self.shell.templates.branches_error({msg: msg}));
+            },
+            function() {
+              return callback(_self.shell.templates.branches({branches: _self.branches}));
+            }
+          );
+        }

Owherwise, the current branch is switched by fetching the root directory for the new branch, and on success, +setting _self.branch and setting the current pathandler node to the root directory fetched.

        return getDir(_self.repo.full_name, branch, "/", function(node) {
+          if(!node) {
+            callback(_self.shell.templates.branch_error({name: branch, msg: "unable to load root directory for branch"}));
+          }
+          _self.branch = branch;
+          _self.pathhandler.current = node;
+          _self.root = node;
+          callback();
+        });
+      },

completion handles TAB completion on a partial branch name. The list of possible branches is once again +lazily initialized via ensureBranches.

      completion: function(cmd, arg, line, callback) {
+        return ensureBranches(
+          function() {
+            callback();
+          },
+          function() {
+            callback(_self.shell.bestMatch(arg, _.map(_self.branches, function(branch) {
+              return branch.name;
+            })));
+          }
+        );
+      }
+    });

This attaches a custom prompt render to the shell.

    _self.shell.onNewPrompt(function(callback) {
+      callback(_self.shell.templates.prompt({self: _self, node: _self.pathhandler.current}));
+    });

Wiring up PathHandler

getNode

getNode is required by Josh.PathHandler to provide filesystem behavior. Given a path, it is expected to return +a pathnode or null;

    _self.pathhandler.getNode = function(path, callback) {
+      _console.log("looking for node at: " + path);
+      if(!path) {
+        return callback(_self.pathhandler.current);
+      }

buildAbsolutePath is called recursively to turn path into an absolute path that can be resolved against the +github API with getDir.

      buildAbsolutePath(path, _self.pathhandler.current, function(absPath) {
+        _console.log("path to fetch: " + absPath);
+        return getDir(_self.repo.full_name, _self.branch, absPath, callback);
+      });
+    };

getChildNodes

getChildNodes is the second function implementation required for Josh.PathHandler. Given a pathnode, it returns +a list of child pathnodes. This is used by Tab completion to resolve a partial path, after first resolving the +nearest parent node using `getNode

    _self.pathhandler.getChildNodes = function(node, callback) {

If the given node is a file node, no further work is required.

      if(node.isfile) {
+        _console.log("it's a file, no children");
+        return callback();
+      }

Otherwise, if the child nodes have already been initialized, which is done lazily, return them.

      if(node.children) {
+        _console.log("got children, let's turn them into nodes");
+        return callback(makeNodes(node.children));
+      }

Finally, use getDir to fetch and populate the child nodes.

      _console.log("no children, fetch them");
+      return getDir(_self.repo.full_name, _self.branch, node.path, function(detailNode) {
+        node.children = detailNode.children;
+        callback(makeNodes(node.children));
+      });
+    };

Supporting Functions

get

This function is responsible for all API requests, given a partial API path, resource, and an query argument object, +args.

    function get(resource, args, callback) {
+      var url = _self.api + resource;
+      if(args) {
+        url += "?" + _.map(args,function(v, k) {
+          return k + "=" + v;
+        }).join("&");
+      }
+      _console.log("fetching: " + url);
+      var request = {
+        url: url,
+        dataType: 'json',
+        xhrFields: {
+          withCredentials: true
+        }
+      };
+      $.ajax(request).done(function(response,status,xhr) {

Every response from the API includes rate limiting headers, as well as an indicator injected by the API proxy +whether the request was done with authentication. Both are used to display request rate information and a +link to authenticate, if required.

        var ratelimit = {
+          remaining: parseInt(xhr.getResponseHeader("X-RateLimit-Remaining")),
+          limit: parseInt(xhr.getResponseHeader("X-RateLimit-Limit")),
+          authenticated: xhr.getResponseHeader('Authenticated') === 'true'
+        };
+        $('#ratelimit').html(_self.shell.templates.rateLimitTemplate(ratelimit));
+        if(ratelimit.remaining == 0) {
+          alert("Whoops, you've hit the github rate limit. You'll need to authenticate to continue");
+          _self.shell.deactivate();
+          return null;
+        }

For simplicity, this tutorial trivially deals with request failures by just returning null from this function +via the callback.

        if(status !== 'success') {
+          return callback();
+        }
+        return callback(response);
+      })
+    }

ensureBranches

This function lazily fetches the branches for the current repo from the API.

    function ensureBranches(err, callback) {
+      get("repos/" + _self.repo.full_name + "/branches", null, function(branches) {
+        if(!branches) {
+          return err("api request failed to return branch list");
+        }
+        _self.branches = branches;
+        return callback();
+      });
+    }

setUser

This function fetches the specified user and initializes a repository to the provided value (which may be null). +one fetched by initialzeRepos.

    function setUser(user_name, repo_name, err, callback) {
+      if(_self.user && _self.user.login === user_name) {
+        return callback(_self.user);
+      }
+      return get("users/" + user_name, null, function(user) {
+        if(!user) {
+          return err("no such user");
+        }
+        return initializeRepos(user, repo_name, err, function(repo) {
+          _self.user = user;
+          return callback(_self.user);
+        });
+      });
+    }

initalizeRepos

This function first fetches all repos for the given user from the API and then sets the current repo to the provided +value (which may be null).

    function initializeRepos(user, repo_name, err, callback) {
+      return getRepos(user.login, function(repos) {
+        var repo = getRepo(repo_name, repos);
+        if(!repo) {
+          return err("user has no repositories");
+        }
+        return setRepo(repo, err, function(repo) {
+          _self.repos = repos;
+          return callback(repo);
+        });
+      });
+    }

getDir

This function function fetches the directory listing for a path on a given repo and branch.

    function getDir(repo_full_name, branch, path, callback) {

Although paths in the internal representation may have a trailing /, it has to be removed before using it +as the argument for an API request.

      if(path && path.length > 1 && path[path.length - 1] === '/') {
+        path = path.substr(0, path.length - 1);
+      }
+      get("repos/" + repo_full_name + "/contents" + path, {ref: branch}, function(data) {

The API call may return either an array, indicating that the path was a directory, or an object. Since only +are stored as pathnodes, retrieving anything but an array returns null via the callback.

        if(Object.prototype.toString.call(data) !== '[object Array]') {
+          _console.log("path '" + path + "' was a file");
+          return callback();
+        }

Given a directory listing, i.e. array, the current directory node is created and the API return value captured +as children so that they can later be transformed into child pathnodes, if required.

        var node = {
+          name: _.last(_.filter(path.split("/"), function(x) {
+            return x;
+          })) || "",
+          path: path,
+          children: data
+        };
+        _console.log("got node at: " + node.path);
+        return callback(node);
+      });
+    }

getRepos

This function fetches all repositories for a given user.

    function getRepos(userLogin, callback) {
+      return get("users/" + userLogin + "/repos", null, function(data) {
+        callback(data);
+      });
+    }

getRepo

This function tries to match a repository from the given list of known repositories. Should repo_name be null, +the first repository in repos is returned.

    function getRepo(repo_name, repos) {
+      if(!repos || repos.length == 0) {
+        return null;
+      }
+      var repo;
+      if(repo_name) {
+        repo = _.find(repos, function(repo) {
+          return repo.name === repo_name;
+        });
+        if(!repo) {
+          return callback();
+        }
+      } else {
+        repo = repos[0];
+      }
+      return repo;
+    }

setRepo

This function fetches the root directory for the specified repository and initializes the current repository +state.

    function setRepo(repo, err, callback) {
+      return getDir(repo.full_name, repo.default_branch, "/", function(node) {
+        if(!node) {
+          return err("could not initialize root directory of repository '" + repo.full_name + "'");
+        }
+        _console.log("setting repo to '" + repo.name + "'");
+        _self.repo = repo;
+        _self.branch = repo.default_branch;
+        _self.pathhandler.current = node;
+        _self.root = node;
+        return callback(repo);
+      });
+    }

buildAbsolutePath

This function resolves a path to an absolute path given a current node.

    function buildAbsolutePath(path, current, callback) {
+      _console.log("resolving path: "+path);
+      var parts = path.split("/");

If the first part of the path is .., current is used to determine the parent path and construct an absolute +path from the combination of the parent path and the remainder of path. Since this compoint path may still +contain . or .., path operators that the github API does not understand, the resulting value is fed back +into buildAbsolutePath.

      if(parts[0] === '..' ) {
+        var parentParts = _.filter(current.path.split("/"), function(x) {
+          return x;
+        });
+        path = "/" + parentParts.slice(0, parentParts.length - 1).join('/') + "/" + parts.slice(1).join("/");
+        return buildAbsolutePath(path, _self.root, callback);
+      }

If the first parht of the path is either a . or not empty (i.e. the path had started with a /, the path must +be relative and an absolute path can be constructed by combining the path and current. Once again, the value +isfed back into buildAbsolutePath for final resolution.

      if(parts[0] === '.' || parts[0] !== '') {
+        path = current.path+"/"+path;
+        return buildAbsolutePath(path, _self.root, callback);
+      }

At this point the path looks absolute, but all . and .. mentions need to removed and resolved before a truly +absolute path can be returned.

      var resolved = [];
+      _.each(parts, function(x) {
+        if(x === '.') {
+          return;
+        }
+        if(x === '..') {
+          resolved.pop();
+        } else {
+          resolved.push(x);
+        }
+      });
+      return callback(resolved.join('/'));
+    }

makeNodes

This method builds child pathnodes from the directory information returned by getDir.

    function makeNodes(children) {
+      return _.map(children, function(node) {
+        return {
+          name: node.name,
+          path: "/" + node.path,
+          isFile: node.type === 'file'
+        };
+      });
+    }

UI setup and initialization

initializationError

This function is a lazy way with giving up if some request failed during intialization, forcing the user +to reload to retry.

    function initializationError(context, msg) {
+      _console.log("[" + context + "] failed to initialize: " + msg);
+      alert("unable to initialize shell. Encountered a problem talking to github api. Try reloading the page");
+    }

intializeUI

After a current user and repo have been set, this function initializes the UI state to allow the shell to be +shown and hidden.

    function initializeUI() {
+      _console.log("activating");
+      var $consolePanel = $('#shell-container');
+      $consolePanel.resizable({ handles: "s"});
+      $(document).keypress(function(event) {
+        if(_self.shell.isActive()) {
+          return;
+        }
+        if(event.keyCode == 126) {
+          event.preventDefault();
+          activateAndShow();
+        }
+      });
+      function activateAndShow() {
+        _self.shell.activate();
+        $consolePanel.slideDown();
+        $consolePanel.focus();
+      }
+
+      function hideAndDeactivate() {
+        _self.shell.deactivate();
+        $consolePanel.slideUp();
+        $consolePanel.blur();
+      }
+
+      _self.shell.onEOT(hideAndDeactivate);
+      _self.shell.onCancel(hideAndDeactivate);
+    }

On document ready, the default user and repo are loaded from the API before the UI can complete initialization.

    $(document).ready(function() {
+      setUser("sdether", "josh.js",
+        function(msg) {
+          initializationError("default", msg);
+        },
+        initializeUI
+      );
+    });
+  })(root, $, _);
+})(this, $, _);
+
+
\ No newline at end of file diff --git a/fonts/OpenSans-Bold-webfont.eot b/fonts/OpenSans-Bold-webfont.eot new file mode 100644 index 0000000..e1c7674 Binary files /dev/null and b/fonts/OpenSans-Bold-webfont.eot differ diff --git a/fonts/OpenSans-Bold-webfont.svg b/fonts/OpenSans-Bold-webfont.svg new file mode 100644 index 0000000..364b368 --- /dev/null +++ b/fonts/OpenSans-Bold-webfont.svg @@ -0,0 +1,146 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Digitized data copyright 20102011 Google Corporation +Foundry : Ascender Corporation +Foundry URL : httpwwwascendercorpcom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/OpenSans-Bold-webfont.ttf b/fonts/OpenSans-Bold-webfont.ttf new file mode 100644 index 0000000..2d94f06 Binary files /dev/null and b/fonts/OpenSans-Bold-webfont.ttf differ diff --git a/fonts/OpenSans-Bold-webfont.woff b/fonts/OpenSans-Bold-webfont.woff new file mode 100644 index 0000000..cd86852 Binary files /dev/null and b/fonts/OpenSans-Bold-webfont.woff differ diff --git a/fonts/OpenSans-BoldItalic-webfont.eot b/fonts/OpenSans-BoldItalic-webfont.eot new file mode 100644 index 0000000..f44ac9a Binary files /dev/null and b/fonts/OpenSans-BoldItalic-webfont.eot differ diff --git a/fonts/OpenSans-BoldItalic-webfont.svg b/fonts/OpenSans-BoldItalic-webfont.svg new file mode 100644 index 0000000..8392240 --- /dev/null +++ b/fonts/OpenSans-BoldItalic-webfont.svg @@ -0,0 +1,146 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Digitized data copyright 20102011 Google Corporation +Foundry : Ascender Corporation +Foundry URL : httpwwwascendercorpcom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/OpenSans-BoldItalic-webfont.ttf b/fonts/OpenSans-BoldItalic-webfont.ttf new file mode 100644 index 0000000..f74e0e3 Binary files /dev/null and b/fonts/OpenSans-BoldItalic-webfont.ttf differ diff --git a/fonts/OpenSans-BoldItalic-webfont.woff b/fonts/OpenSans-BoldItalic-webfont.woff new file mode 100644 index 0000000..f3248c1 Binary files /dev/null and b/fonts/OpenSans-BoldItalic-webfont.woff differ diff --git a/fonts/OpenSans-Italic-webfont.eot b/fonts/OpenSans-Italic-webfont.eot new file mode 100644 index 0000000..277c189 Binary files /dev/null and b/fonts/OpenSans-Italic-webfont.eot differ diff --git a/fonts/OpenSans-Italic-webfont.svg b/fonts/OpenSans-Italic-webfont.svg new file mode 100644 index 0000000..29c7497 --- /dev/null +++ b/fonts/OpenSans-Italic-webfont.svg @@ -0,0 +1,146 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Digitized data copyright 20102011 Google Corporation +Foundry : Ascender Corporation +Foundry URL : httpwwwascendercorpcom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/OpenSans-Italic-webfont.ttf b/fonts/OpenSans-Italic-webfont.ttf new file mode 100644 index 0000000..63f187e Binary files /dev/null and b/fonts/OpenSans-Italic-webfont.ttf differ diff --git a/fonts/OpenSans-Italic-webfont.woff b/fonts/OpenSans-Italic-webfont.woff new file mode 100644 index 0000000..469a29b Binary files /dev/null and b/fonts/OpenSans-Italic-webfont.woff differ diff --git a/fonts/OpenSans-Light-webfont.eot b/fonts/OpenSans-Light-webfont.eot new file mode 100644 index 0000000..837daab Binary files /dev/null and b/fonts/OpenSans-Light-webfont.eot differ diff --git a/fonts/OpenSans-Light-webfont.svg b/fonts/OpenSans-Light-webfont.svg new file mode 100644 index 0000000..bdb6726 --- /dev/null +++ b/fonts/OpenSans-Light-webfont.svg @@ -0,0 +1,146 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Digitized data copyright 20102011 Google Corporation +Foundry : Ascender Corporation +Foundry URL : httpwwwascendercorpcom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/OpenSans-Light-webfont.ttf b/fonts/OpenSans-Light-webfont.ttf new file mode 100644 index 0000000..b50ef9d Binary files /dev/null and b/fonts/OpenSans-Light-webfont.ttf differ diff --git a/fonts/OpenSans-Light-webfont.woff b/fonts/OpenSans-Light-webfont.woff new file mode 100644 index 0000000..99514d1 Binary files /dev/null and b/fonts/OpenSans-Light-webfont.woff differ diff --git a/fonts/OpenSans-LightItalic-webfont.eot b/fonts/OpenSans-LightItalic-webfont.eot new file mode 100644 index 0000000..f0ebf2c Binary files /dev/null and b/fonts/OpenSans-LightItalic-webfont.eot differ diff --git a/fonts/OpenSans-LightItalic-webfont.svg b/fonts/OpenSans-LightItalic-webfont.svg new file mode 100644 index 0000000..60765da --- /dev/null +++ b/fonts/OpenSans-LightItalic-webfont.svg @@ -0,0 +1,146 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Digitized data copyright 20102011 Google Corporation +Foundry : Ascender Corporation +Foundry URL : httpwwwascendercorpcom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/OpenSans-LightItalic-webfont.ttf b/fonts/OpenSans-LightItalic-webfont.ttf new file mode 100644 index 0000000..5898c8c Binary files /dev/null and b/fonts/OpenSans-LightItalic-webfont.ttf differ diff --git a/fonts/OpenSans-LightItalic-webfont.woff b/fonts/OpenSans-LightItalic-webfont.woff new file mode 100644 index 0000000..9c978dc Binary files /dev/null and b/fonts/OpenSans-LightItalic-webfont.woff differ diff --git a/fonts/OpenSans-Regular-webfont.eot b/fonts/OpenSans-Regular-webfont.eot new file mode 100644 index 0000000..dd6fd2c Binary files /dev/null and b/fonts/OpenSans-Regular-webfont.eot differ diff --git a/fonts/OpenSans-Regular-webfont.svg b/fonts/OpenSans-Regular-webfont.svg new file mode 100644 index 0000000..01038bb --- /dev/null +++ b/fonts/OpenSans-Regular-webfont.svg @@ -0,0 +1,146 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Digitized data copyright 20102011 Google Corporation +Foundry : Ascender Corporation +Foundry URL : httpwwwascendercorpcom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/OpenSans-Regular-webfont.ttf b/fonts/OpenSans-Regular-webfont.ttf new file mode 100644 index 0000000..05951e7 Binary files /dev/null and b/fonts/OpenSans-Regular-webfont.ttf differ diff --git a/fonts/OpenSans-Regular-webfont.woff b/fonts/OpenSans-Regular-webfont.woff new file mode 100644 index 0000000..274664b Binary files /dev/null and b/fonts/OpenSans-Regular-webfont.woff differ diff --git a/fonts/OpenSans-Semibold-webfont.eot b/fonts/OpenSans-Semibold-webfont.eot new file mode 100644 index 0000000..289aade Binary files /dev/null and b/fonts/OpenSans-Semibold-webfont.eot differ diff --git a/fonts/OpenSans-Semibold-webfont.svg b/fonts/OpenSans-Semibold-webfont.svg new file mode 100644 index 0000000..cc2ca42 --- /dev/null +++ b/fonts/OpenSans-Semibold-webfont.svg @@ -0,0 +1,146 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Digitized data copyright 2011 Google Corporation +Foundry : Ascender Corporation +Foundry URL : httpwwwascendercorpcom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/OpenSans-Semibold-webfont.ttf b/fonts/OpenSans-Semibold-webfont.ttf new file mode 100644 index 0000000..6f15073 Binary files /dev/null and b/fonts/OpenSans-Semibold-webfont.ttf differ diff --git a/fonts/OpenSans-Semibold-webfont.woff b/fonts/OpenSans-Semibold-webfont.woff new file mode 100644 index 0000000..4e47cb1 Binary files /dev/null and b/fonts/OpenSans-Semibold-webfont.woff differ diff --git a/fonts/OpenSans-SemiboldItalic-webfont.eot b/fonts/OpenSans-SemiboldItalic-webfont.eot new file mode 100644 index 0000000..50a8a6f Binary files /dev/null and b/fonts/OpenSans-SemiboldItalic-webfont.eot differ diff --git a/fonts/OpenSans-SemiboldItalic-webfont.svg b/fonts/OpenSans-SemiboldItalic-webfont.svg new file mode 100644 index 0000000..65b50e2 --- /dev/null +++ b/fonts/OpenSans-SemiboldItalic-webfont.svg @@ -0,0 +1,146 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Digitized data copyright 20102011 Google Corporation +Foundry : Ascender Corporation +Foundry URL : httpwwwascendercorpcom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/OpenSans-SemiboldItalic-webfont.ttf b/fonts/OpenSans-SemiboldItalic-webfont.ttf new file mode 100644 index 0000000..55ba312 Binary files /dev/null and b/fonts/OpenSans-SemiboldItalic-webfont.ttf differ diff --git a/fonts/OpenSans-SemiboldItalic-webfont.woff b/fonts/OpenSans-SemiboldItalic-webfont.woff new file mode 100644 index 0000000..0adc6df Binary files /dev/null and b/fonts/OpenSans-SemiboldItalic-webfont.woff differ diff --git a/githubconsole.html b/githubconsole.html new file mode 100644 index 0000000..fe81243 --- /dev/null +++ b/githubconsole.html @@ -0,0 +1,308 @@ + + + + + + Josh.js by sdether + + + + + + + + + + + + + + + + + + + + + + + + +
+
Github rate limit:
+
+
Type help or hit TAB for a list of commands. Press + Ctrl-C to hide the console. +
+
+ +
+
+ +
+ +
+

Github Console

+ +

This tutorial expands on the + Quake Console tutorial by using the GitHub REST API instead of a faked filesystem. The purpose of the tutorial is to show how to wire up Josh.PathHandler and custom commands to a remote API. +

+ +

Try out the Console

+ +

Type ~ to activate the shell we will be discussed belowl.

+ +

You can explore the current repository's file system via the standard ls and + cd commands as well as take advantage of path TAB completion. Additional commands are: +

+
    +
  • user - show the current user's info
  • +
  • user username - change the user to explore
  • +
  • repo -l - list the current user's repositories
  • +
  • repo repository_name - change the repository to explore (supports + TAB completion) +
  • +
  • branch -l - list the current repository's branches
  • +
  • branch branch_name - change the branch to explore (supports TAB completion) +
  • +
+ +

You will be limited to 60 requests/hour by the API (where each console command may use multiple requests). If you + authenticate via GitHub, you will have a more flexible 5000 requests/hour to play with. +

+ +

Annotated Source

+ +

The approach of this tutorial is to walk through the pieces required to wire up + Josh.Shell to a remote REST API via asynchronous calls to produce an interactive command line interface by explaining the flow. While some code will be shown inline, the primary code reference is the + annotated source code with links to specific, mentioned functions throughout the tutorial. +

+ +

Application State

+ +

The console is designed to always be in the context of a repository, so that there is no state in which commands like + ls are not available. It initializes with the + sdether/josh.js repository and after this the user can switch users, repositories and branches, while always staying in the context of some repository. That means that at any point in time, we will have a current user object, a list of all that user's repositories, and a current directory on the current branch as state. Changing users picks a default repository and branch to keep that state populated. Branches and current directory information are loaded on demand. +

+

The available state looks like this:

+
{
+  api: "http://josh.claassen.net/github/", // the proxy we use for the GitHub API
+  shell: $instance_of_Josh.Shell,
+  pathhandler: $instance_of_Josh.PathHandler,
+  user: $current_user_object,
+  repo: $current_repository,
+  repos: $list_of_user_repos,
+  branch: $current_branch,
+  branches: $lazy_initialized_list_of_branches_for_current_repo
+}
+ +

The GitHub API

+ +

GitHub provides a + REST API, giving access to most of its data and functionality. We're just going to worry about read capability around repositories. Each repo is basically a file system which plays nicely into + Josh.PathHandler's area of applicability.

+ +

The user object comes from:

+
GET /users/:user
+

We never fetch an individual repository, instead opting to fetch all at user initialization via:

+
GET /users/:user/repos
+

We do the same for branches, fetching all, lazily, once we need to auto-complete or list them:

+
GET /repos/:owner/:repo/branches
+

Finally, we fetch the current directory via

+
GET /repos/:owner/:repo/contents/:path
+

This is done for the root during repo initialization, and for path completion, cd, ls, etc. on demand.

+ +

The API returns + json, which is perfect for us as well, but it is rather drastically rate limited without authentication. I.e. without authentication you will be limited to 60 requests per hour, while with authentication the limit is 5000 per hour. For this reason, we proxy all github calls through a simple node.js application that can handle oauth to optionally authenticate the use of this console. This application is outside the scope of the tutorial, but the code can be found on the josh.js + github-authentication-backend branch. +

+ +

Initializing the Console

+ +

In order for us to show the console, we have to have initialized a user, a repository and retrieved it's root directory. This is done after + document.ready

+
$(document).ready(function() {
+      setUser("sdether", "josh.js",
+        function(msg) {
+          initializationError("default", msg);
+        },
+        initializeUI
+      );
+    });
+ +

We call + setUser(user_name, repo_name, err, callback) for + sdether and + josh.js, before setting the authenticated user as the current user and initializing the UI of the shell. +

+
function setUser(user_name, repo_name, err, callback) {
+  if(_self.user && _self.user.login === user_name) {
+    return callback(_self.user);
+  }
+  return get("users/" + user_name, null, function(user) {
+    if(!user) {
+      return err("no such user");
+    }
+    return initializeRepos(user, repo_name, err, function(repo) {
+      _self.user = user;
+      return callback(_self.user);
+    });
+  });
+}
+

This function follows the pattern of providing both an + error and + success callback, since once the shell is initialized we need to make sure that any action we take on its behalf does result in its callback being called with some value, lest the shell stop functioning. Unlike previous tutorials, we're now doing network requests and those will fail sooner or later. For this reason we need to make sure we always have a quick + err callback to stop the current operation and call the callback provided by + Josh.Shell on command execution. We also need to make sure that we do not mutate the application state until we are done with all operations that can fail, so that the worst case is us reporting to the shell that the operation failed while our current, known good state is preserved. +

+ +

All API access goes through a helper function + get(resource, args, callback), which is responsible for constructing the + json call and inspecting the response. For simplicity, all error conditions just result in + callback being called with null instead of a + json payload.

+
function get(resource, args, callback) {
+  var url = _self.api + resource;
+  if(args) {
+    url += "?" + _.map(args,function(v, k) { return k + "=" + v; }).join("&");
+  }
+  var request = {
+    url: url,
+    dataType: 'json',
+    xhrFields: {
+      withCredentials: true
+    }
+  };
+  $.ajax(request).done(function(response, status, xhr) {
+
+    // Every response from the API includes rate limiting headers, as well as an
+    // indicator injected by the API proxy whether the request was done with
+    // authentication. Both are used to display request rate information and a
+    // link to authenticate, if required.
+    var ratelimit = {
+      remaining: parseInt(xhr.getResponseHeader("X-RateLimit-Remaining")),
+      limit: parseInt(xhr.getResponseHeader("X-RateLimit-Limit")),
+      authenticated: xhr.getResponseHeader('Authenticated') === 'true'
+    };
+    $('#ratelimit').html(_self.shell.templates.rateLimitTemplate(ratelimit));
+    if(ratelimit.remaining == 0) {
+      alert("Whoops, you've hit the github rate limit. You'll need to authenticate to continue");
+      _self.shell.deactivate();
+      return null;
+    }
+
+    // For simplicity, this tutorial trivially deals with request failures by
+    //just returning null from this function via the callback.
+    if(status !== 'success') {
+      return callback();
+    }
+    return callback(response);
+  })
+}
+

Most of this function is actually devoted to CORS and rate limiting handling, which is unique to us calling the GitHub API via a proxy located on another server. When dealing with your own API, calls will likely just be $.getJSON() +

+

+ Also for simplicity, any initialization failures, just bail out via + initializationError(). Once we have a user, we can call + initializeRepos(user, repo_name, err, callback). +

+ +

Adding Commands

+ +

Commands are added via Josh.Shell.SetCommandHandler(cmd,handler) where + handler is an object with two properties, exec and + completion.

+
Josh.Shell.SetCommandHandler($cmd, {
+  exec: function(cmd, args, callback) {
+    ...
+  },
+  completion: function(cmd, arg, line, callback) {
+    ...
+  });
+  }
+});
+

Unlike the callback pattern we used for + setUser, Josh functions do not have a separate error handler. Since Josh interacts with the UI, it has no concept of failure -- it has to execute the callback to continue executing. It is up to the caller to deal with errors and transform them into the appropriate UI response. But it still gives us the flexibility to undertake asynchronous actions, such as calling a remote API and complete the function execution upon asynchronous callback from the remote call. +

+ +

user [username]

+ +

The user command does not have + TAB completion, since doing efficient tab completion against the full set of GitHub users is beyond this tutorial. Instead it expects a valid username for + setUser(user_name, repo_name, err, callback) and renders the user template with the new current user. +

+ +

If called without a username, we simply render the user template with the current user.

+ +

repo [-l | reponame]

+ +

The + repo command can show information about the current repository, change the current repository or list all repositories belonging to the user. It also provides + TAB completion of partial repository names against the repositories of the current user.

+ +

Given no argument, we simply render the repository template with the current repository.

+ +

If the argument is + -l, we render the repository list template with the repositories we fetched on user initialization.

+ +

Finally, the argument is used to try and look up the repository from the known repositories list. If that succeeds, we call + setRepo(repo, err, callback), which fetches the root directory to initialize the current node of + Josh.PathHandler before changing the current repository to the one specified. Upon switching we again render the repository template with the now current repository. +

+ +

The completion handler for the command simply calls + Josh.Shell.bestMatch with the partial argument and a list of all repository names. + bestMatch takes care of creating the completion object with the appropriate argument completion and list of possible choices. +

+ +

branch [-l | branchname]

+ +

The branch command either displays the current branch name, changes the current branch or list all branches for the current repository. It also provides + TAB completion of partial branch names against the lazy initialized list of all branches for the current repository.

+ +

Given no argument, the command simply prints the current branch name. The + -l argument renders a list of all known branches for the current repository, while an actual branchname as argument will cause the console to change its current branch. +

+ +

Showing the list of branches uses + ensureBranches(err, callback) to lazily initialize the list of branches from the API. +

+ +

The completion handler for the command calls Josh.Shell.bestMatch -- just like repo completion -- with the partial argument and the list of all branches. +

+ +

Wiring up Josh.PathHandler

+

PathHandler provides unix filepath handling. This works by abstracting the filesystem into two operations, getNode(path, callback) and getChildNodes(node, callback). The former returns a pathnode given a path string while the latter returns pathnodes for all children of a given node. With these two all tree operations including TAB completion can be accomplished by PathHandler.

+

A pathnode is an opaque object in which we can track any node state we want but has to have two properties:

+
{
+  name: 'localname',
+  path: '/full/path/to/localname'
+}
+

getNode is responsible for fetching the appropriate directory from the API either by relative or absolute path. A path is considered relative if it lacks a leading /. PathHandler tracks the current directory/file node in PathHandler.current which is used to convert a relative path to an absolutish path. Since we also support the standard file system . and .. symbols and the github API does not, we then need to take the absolutish path and resolve these symbols before passing the resulting absolute path to getDir(repo_full_name, branch, path, callback).

+

It is the job of getDir to fetch a directory node via GET /repos/:owner/:repo/contents/:path. This API call returns either an array of file objects for a directory or a file object in case the path points directly at a file. We only care about directories for completion and cd, ls, etc. so we ignore file results and build a pathnode for the directory like this:

+
var node = {
+  name: _.last(_.filter(path.split("/"), function(x) { return x; })) || "",
+  path: path,
+  children: data
+};
+

where name is set to the last segment in the path and children stores the actual API results (which are lazily converted to childNodes for completion by function makeNodes(children)

+ +

Easy remote integration

+ +

The use of the github console is fairly limited, but it illustrates that wiring commands and file system behavior to a remote API is fairly simple. While we chose to create a custom vocabulary, we could have just as easily proxied calls to mimic git itself. Either way, Josh.js is an easy way to add a Command Line interface for your existing API or for an API custom tailored to your CLI, allowing you to create powerful admin tools without providing access to the servers themselves.

+
+ +
+ + + + \ No newline at end of file diff --git a/helloworld.html b/helloworld.html new file mode 100644 index 0000000..6ed5197 --- /dev/null +++ b/helloworld.html @@ -0,0 +1,192 @@ + + + + + + Josh.js by sdether + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

Hello World Shell

+ +

This tutorial shows how easy it is to create the below shell window with a custom prompt and a new command + hello.

+ +

+ +

+
Type help or hit TAB for a list of commands.
+
+
+

Creating the Shell

+ +

The + Josh.Shell uses local storage to store a history of the commands that you have typed. By default this is keyed with + josh.history. That history is available to all instances of the shell on your site. For this tutorial, we want to make sure we have our own copy, so we don't get commands from other tutorials and examples, so we need to create a history object with its own key: +

+
var history = new Josh.History({ key: 'helloworld.history'});
+

Now we can create a Shell instance with that history:

+
var shell = Josh.Shell({history: history});
+

Now the shell exists but has not yet been activated.

+ +

Note on how the shell attaches to its UI elements: By default + Josh.Shell expects to find a div#shell-panel that contains a + div#shell-view. The former is the physical container providing the dimensions of the shell, while the latter is a div the shell will continue to append to and scroll to mimic a screen. If you want to use other div IDs (because you have multiple shells on one page), you can provide + shell-panel-id and shell-view-id in the constructor.

+ +

Rendering the prompt

+ +

The default prompt for Josh is + jsh$. Let's create a prompt instead that keeps track of how many times it has been shown: +

+
var promptCounter = 0;
+shell.onNewPrompt(function(callback) {
+    promptCounter++;
+    callback("[" + promptCounter + "] $ ");
+});
+

+ onNewPrompt is called every time Josh needs to re-render the prompt. This happens usually a command is executed, but can also happen on tab completion, when a list of possible completions is shown. + onNewPrompt expects a function that accepts a callback as its only argument. Josh will not continue until the callback has been called with an html string to display. This allows the prompt to rendered as part of an asynchronous action. For our example, we just increment the promptCounter and send back a simple string with the counter. +

+ +

Adding a new command

+ +

Josh implements just three commands out of the box:

+
    +
  • help - show a list of known commands
  • +
  • history - show the commands previously entered
  • +
  • clear - clear the console (i.e. remove all children from div#shell-view
  • +
+

Let's add a new command called hello with tab completion:

+
shell.setCommandHandler("hello", {
+    exec: function(cmd, args, callback) {
+        var arg = args[0] || '';
+        var response = "who is this " + arg + " you are talking to?";
+        if(arg === 'josh') {
+            response = 'pleased to meet you.';
+        } else if(arg === 'world') {
+            response = 'world says hi.'
+        } else if(!arg) {
+            response = 'who are you saying hello to?';
+        }
+        callback(response);
+    },
+    completion: function(cmd, arg, line, callback) {
+        callback(shell.bestMatch(arg, ['world', 'josh']))
+    }
+});
+

To add a command, simply call shell.setCommandHandler and provide it at least an + exec handler, and optionally a completion handler.

+ +

+ exec expects a function that takes the name of the called command, an array of whitespace separated arguments to the command and a callback that MUST be called with an html string to output to the console. For our toy command we implement a command named + hello which understands arguments josh and + world and has alternate outputs for no arguments and unknown arguments.

+ +

+ completion expects a function that takes the current command, the current argument being completed, the complete line (since the cursor may not be at the tail) and a callback that MUST be called either with a completion data structure. The format of this data structure is: +

+
{
+  completion: {string to append to current argument},
+  suggestions: [{array of possible completions},...]
+}
+

Here are some expected completions:

+
    +
  • hello <TAB> => {completion:null,suggestions:['world', 'josh']}
  • +
  • hello wo<TAB> => {completion:'rld',suggestions:['world']}
  • +
  • hello x<TAB> => {completion:'',suggestions:[]}
  • +
+

To simplify this process of finding the partial strings and possible completions, Shell offers a method + bestMatch which expects as input the partial to match (our arg to the completion handler) and a list of all possible completions and it will narrow down what to append to the partial and what suggestions to show. +

+ +

Turning it on

+ +

Now that we've added our custom behavior to + Josh.Shell, all we have to do is activate the shell to render the prompt and start capturing all keystrokes via readline (i.e. if you want the shell to only capture keys while the shell has focus, it is up to you to write focus and blur code to activate and deactivate the shell.) +

+
shell.activate();
+

And that's all there is to getting a custom Bash-like shell in your web page.

+
+
+ + + + \ No newline at end of file diff --git a/images/bullet.png b/images/bullet.png new file mode 100644 index 0000000..22ea543 Binary files /dev/null and b/images/bullet.png differ diff --git a/images/hr.gif b/images/hr.gif new file mode 100644 index 0000000..bdb4168 Binary files /dev/null and b/images/hr.gif differ diff --git a/images/nav-bg.gif b/images/nav-bg.gif new file mode 100644 index 0000000..4743965 Binary files /dev/null and b/images/nav-bg.gif differ diff --git a/index.html b/index.html old mode 100755 new mode 100644 index 06d1ce2..bd17aa2 --- a/index.html +++ b/index.html @@ -2,74 +2,314 @@ - - Shell testbed - + + Josh.js by sdether + + + + + + - - - - - - - + + + + + + + #shell-cli .prompt { + font-weight: bold; + } -
-
+ -
-

Press ~ to activate console.

+ + +
+ +
+
+

Josh.js

+ +

Toolkit for building a bash-like shell in the browser, including full readline support

+
+ Project maintained by sdether + Hosted on GitHub Pages — Theme by mattgraham
+

josh.js 0.2

+

- Cmd1: + Javascript Online SHell provides a toolkit for building bash-like command line consoles for web pages. It consists of the following components:

-

- Cmd2: +

    +
  • + readline.js - full readline support for ctrl sequences, tab, history, etc. +
  • +
  • + shell.js - visual presentation of the shell and command handling +
  • +
  • + pathhandler.js - provide cd, ls, pwd and path completion toolkit +
  • +
  • + history.js - localStorage backed command history +
  • +
  • + killring.js - killring for kill & yank handling in readline +
  • +
+ +

Live Demo

+Drag bottom of shell to change size. +
+
Type help or hit TAB for a list of commands. +
+
+
+ +

Annotated source of demo

+ +

Tutorials

+
    +
  • + Hello world - put a console on a web page and add a new custom command with completion +
  • +
  • Quake Console - Create a quake-style console with ls, + cd, pwd and bash filename tab-completion +
  • +
  • + GitHub Console - Extend the Quake Console to talk to GitHub's REST API to navigate repositories, their branches and file system +
  • +
+

License

+ +

josh.js is licensed under the Apache 2.0 License

+ +

+ Status

+ +
    +
  • code is ready for experimental use + +
      +
    • Tested under Chrome, Firefox, Safari and IE9
    • +
    • API may not yet be stable
    • +
    +
  • +
  • needs minified versions of complete toolkit and just readline.js
  • +
  • needs code documentation and documentation site
  • +
  • would like to add AMD support
  • +
  • implement NCurses for better UI control
  • +
  • Readline has not been tested with non-ascii.
  • +
+

+ Usage

+ +

Until documentation is written, refer to index.html and + the annotated version of + example.js for a sample implementation of a shell with path completion.

+ +

+ Components +

+ +

josh is built from 5 components and can be used in part or in full.

+ +

+ readline.js +

+ +

readline.js has no dependencies on any outside libraries, although it requires either + history.js and killring.js or objects implementing the same calls.

+ +

It implements key trapping to bring + GNU Readline like line editing to the browser. It can be used by itself to bring readline support to custom data entry fields or in conjunction with + shell.js to create a full console.

+ +

+ Line Editing +

+ +

In the below C-x refers to the Ctrl-x keystroke, while + M-x refers to the Meta-x keystroke which is mapped to Alt, and + Left Windows.

+ +
+
Movement
+
+ C-b or Left Arrow +
+
Move back one character
+
+ M-b or Right Arrow +
+
Move back one word
+
C-f
+
Move forward one character
+
M-f
+
Move forward one word
+
+ C-a or Home +
+
Move to the beginning of the line
+
+ C-e or End +
+
Move to the end of the line
+ +
+
Edit/Kill
+
Backspace
+
Delete one character back
+
+ C-d or Delete +
+
Delete character under cursor
+
C-k
+
+ Kill (i.e. put in kill ring) text to the end of the line +
+
M-Backspace
+
+ Kill one word back +
+
M-d
+
+ Kill word under cursor +
+
C-y
+
+ Yank (i.e. pull from kill ring) the most recently killed text +
+
M-y
+
Rotate to the next item in killring and yank it. Must be preceded by yank +
+ +
+
History
+
C-r
+
Reverse search through history
+
+ C-p or Up Arrow +
+
Previous entry in history
+
+ C-n or Down Arrow +
+
Next entry in history
+
Page Up
+
Top of history
+
Page Down
+
Bottom of history
+ +
+
Misc
+
C-l
+
refresh line (clear screen in shell)
+
Tab
+
Invoke completion handler for text under cursor
+
+ Esc in reverse search +
+
Cancel search
+
C-c
+
call onCancel handler
+
+ C-d on empty line +
+
call onCancel handler
+
+

More readline.js docs

+

+ shell.js +

+ +

shell.js has external dependencies of jQuery, + Underscore and internal dependencies of readline.js and + history.js.

+ +

It provides a simple console UI, using a panel for the console viewport and an auto-scrolling + view inside the panel. It uses Underscore templates for generating the view html, although any template generator can be substituted as long as it can be expressed in the form of a function that takes a JSON object of arguments and returns an html string.

- \ No newline at end of file diff --git a/js/example.js b/javascripts/example.js similarity index 86% rename from js/example.js rename to javascripts/example.js index f8d2cc8..e0293f3 100644 --- a/js/example.js +++ b/javascripts/example.js @@ -1,5 +1,5 @@ /*------------------------------------------------------------------------* - * Copyright 2013-2014 Arne F. Claassen + * Copyright 2013 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ // ------------------------- // Setup the `Underscore` template for displaying items in the `KillRing`. - var killringItemTemplate = _.template("
<% _.each(items, function(item, i) { %>
<%- i %> <%- item %>
<% }); %>
"); + var killringItemTemplate = _.template("
<% _.each(items, function(item, i) { %>
<%- i %> <%- item %>
<% }); %>
") // Create a the command `killring` which will display all text currently in the `KillRing`, by attaching // a handler to the `Shell`. @@ -125,7 +125,7 @@ // references like `.` and `..`. In implementations that let you explore an hierarchy on a server, this function // would live on the server side and be called remotely via `getNode`. function findNode(current, parts, callback) { - if(!parts || parts.length === 0) { + if(!parts || parts.length == 0) { return callback(current); } if(parts[0] == ".") { @@ -147,7 +147,7 @@ // ----------------------- // Activation and display behavior happens at document ready time. - $(root).ready(function() { + $(document).ready(function() { // The default name for the div the shell uses as its container is `shell-panel`, although that can be changed via // the shell config parameter `shell-panel-id`. The `Shell` display model relies on a 'panel' to contain a 'view'. @@ -158,36 +158,8 @@ // We use **jquery-ui**'s `resizable` to let us drag the bottom edge of the console up and down. $consolePanel.resizable({ handles: "s"}); - // Wire up a the keypress handler. This will be used only for shell activation. - $(document).keypress(function(event) { - - // If the shell is already active drop out of the keyhandler, since all keyhandling happens in `Readline`. - if(shell.isActive()) { - return; - } - - // Mimicking *Quake*-style dropdown consoles, we activate and show on `~`. - if(event.keyCode == 126) { - _console.log("activating shell"); - event.preventDefault(); - shell.activate(); - $consolePanel.slideDown(); - $consolePanel.focus(); - } - }); - - // Whenever we get either a `EOT` (`Ctrl-D` on empty line) or a `Cancel` (`Ctrl-C`) signal from the shell, - // we deactivate the shell and hide the console. - function hideAndDeactivate() { - _console.log("deactivating shell"); - shell.deactivate(); - $consolePanel.slideUp(); - $consolePanel.blur(); - } - - // Attach our hide function to the EOT and Cancel events. - shell.onEOT(hideAndDeactivate); - shell.onCancel(hideAndDeactivate); + // activate the shell + shell.activate(); }); // We attach the various objects we've created here to `Josh.Instance` purely so they can be inspected via a @@ -208,7 +180,7 @@ boot: {}, dev: {}, etc: { - 'default': {}, + default: {}, 'rc.d': {}, sysconfig: {}, x11: {} @@ -254,7 +226,7 @@ }, src: {} }, - 'var': { + var: { lib: {}, lock: {}, run: {}, diff --git a/javascripts/githubconsole.js b/javascripts/githubconsole.js new file mode 100644 index 0000000..45ffae9 --- /dev/null +++ b/javascripts/githubconsole.js @@ -0,0 +1,642 @@ +/*------------------------------------------------------------------------* + * Copyright 2013 Arne F. Claassen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *-------------------------------------------------------------------------*/ +(function(root, $, _) { + Josh.GitHubConsole = (function(root, $, _) { + + // Enable console debugging, when Josh.Debug is set and there is a console object on the document root. + var _console = (Josh.Debug && root.console) ? root.console : { + log: function() { + } + }; + + // Console State + // ============= + // + // `_self` contains all state variables for the console's operation + var _self = { + shell: Josh.Shell({console: _console}), + api: "http://josh.claassen.net/github/" + }; + + // `Josh.PathHandler` is attached to `Josh.Shell` to provide basic file system navigation. + _self.pathhandler = new Josh.PathHandler(_self.shell, {console: _console}); + + // Custom Templates + // ================ + // `Josh.Shell` uses *Underscore* templates for rendering output to the shell. This console overrides some and adds a couple of new ones for its own commands. + + // **templates.prompt** + + // Override of the default prompt to provide a multi-line prompt of the current user, repo and path and branch. + _self.shell.templates.prompt = _.template("[<%= self.user.login %>/<%= self.repo.name %>]
(<%=self.branch%>) <%= node.path %> $"); + + // **templates.ls** + + // Override of the pathhandler ls template to create a multi-column listing. + _self.shell.templates.ls = _.template("
    <% _.each(nodes, function(node) { %>
  • <%- node.name %>
  • <% }); %>
"); + + // **templates.not_found** + + // Override of the pathhandler *not_found* template, since we will throw *not_found* if you try to access a valid file. This is done for the simplicity of the tutorial. + _self.shell.templates.not_found = _.template("
<%=cmd%>: <%=path%>: No such directory
"); + + //**templates.rateLimitTemplate** + + // Since GitHub rate limits un-authenticated use rather drastically, we render the current rate limit status in the shell so that it is clear that extended experimenting requires authentication. + _self.shell.templates.rateLimitTemplate = _.template("<%=remaining%>/<%=limit%><% if(!authenticated) {%> Authenticate with Github to increase your Rate Limit.<%}%>"); + + //**templates.user** + + // Render basic information (including gravatar) whenever we switch users or enter `user` without an argument + _self.shell.templates.user = _.template("
" + + "" + + "" + + "" + + "" + + "" + + "
Id:<%=user.id %>
Name:<%=user.login %>
Location:<%=user.location %>
" + + "
" + ); + + // **templates.user_error** + + // Generic error in case setting the user fails. + _self.shell.templates.user_error = _.template("Unable to set user '<%=name%>': <%=msg%>"); + + // **templates.repos** + + // Just like `ls`, we render a wide list of repositories for `repo -l`. + _self.shell.templates.repos = _.template("
    <% _.each(repos, function(repo) { %>
  • <%- repo.name %>
  • <% }); %>
"); + + // **template.repo** + + // Whenever we change repositories or `repo` is called without an argument, we show basic information about the repo. + _self.shell.templates.repo = _.template("
Name: <%=repo.full_name%>
Description: <%=repo.description %>
"); + + // **template.repo_not_found** + + // Error message in case someone tries to switch to an invalid repo. + _self.shell.templates.repo_not_found = _.template("
repo: <%=repo%>: No such repo for user '<%= user %>'
"); + + // **templates.repo_error** + + // Generic error message in case setting the repo fails. + _self.shell.templates.repo_error = _.template("Unable to switch to repository '<%=name%>': <%=msg%>"); + + // **templates.branches** + + // Again, like `ls`, we render a wide like of branches for `branch -l`. + _self.shell.templates.branches = _.template("webfont.woff
    <% _.each(branches, function(branch) { %>
  • <%- branch.name %>
  • <% }); %>
"); + + // **templates.branch_error** + + // Generic error message in case setting the current branch fails. + _self.shell.templates.branch_error = _.template("Unable to switch to branch '<%=name%>': <%=msg%>"); + + // **templates.branches_error** + + // Generic error in case fetching the list of branches fails. + _self.shell.templates.branches_error = _.template("Unable to load branch list: <%=msg%>"); + + // Adding Commands to the Console + // ============================== + + //
+ + // user [ username ] + // ----------------- + + // The `user` command is used to display information about the current user or switch between github users. + _self.shell.setCommandHandler("user", { + + // `exec` handles the execution of the command. + exec: function(cmd, args, callback) { + + // Given no arguments, it renders information about the current user, using the data fetched at user initialization. + if(!args || args.length == 0) { + return callback(_self.shell.templates.user({user: _self.user})); + } + var username = args[0]; + + // Given an argument (assumed to be a username), it calls `setUser` to fetch the specified user and repository + // information. + return setUser(username, null, + function(msg) { + return callback(_self.shell.templates.user_error({name: username, msg: msg})); + }, + function(user) { + return callback(_self.shell.templates.user({user: user})); + } + ); + } + + // `user` has no completion handler, since the userbase of github is quite large and creating a search based + // completion handler is beyond the scope of this tutorial implementation. + }); + + //
+ + // repo [ -l | reponame ] + // ---------------------- + + // The `repo` command is used to display information about the current repo or switch to. It + _self.shell.setCommandHandler("repo", { + + // `exec` handles the execution of the command. + exec: function(cmd, args, callback) { + + // Given no arguments, it renders information about the current repo. + if(!args || args.length == 0) { + return callback(_self.shell.templates.repo({repo: _self.repo})); + } + var name = args[0]; + + // Given the argument `-l`, it lists all repos for the current user. This information was fetched at user + // initialization + if(name === '-l') { + return callback(_self.shell.templates.repos({repos: _self.repos})); + } + + // Otherwise, the argument is assumed to a repo name, which `getRepo` uses to fetch the repository's information + // from the data in `_self.repos`, if possible. + var repo = getRepo(name, _self.repos); + + // If there is no matching repo, it renders an error. + if(!repo) { + return callback(_self.shell.templates.repo_error({name: name, msg: 'no such repo'})); + } + + // Given a valid repo, `setRepo` initializes the repo (i.e. fetching the root directory) and renders the repo + // information. + return setRepo(repo, + function(msg) { + return callback(_self.shell.templates.repo_error({name: name, msg: msg})); + }, + function(repo) { + if(!repo) { + return callback(_self.shell.templates.repo_not_found({repo: name, user: _self.user.login})); + } + return callback(_self.shell.templates.repo({repo: _self.repo})); + } + ); + }, + + // `completion` uses `_self.repo` and `Josh.Shell.bestMatch` to try and match the partial information to the possible + // matching repositories. + completion: function(cmd, arg, line, callback) { + callback(_self.shell.bestMatch(arg, _.map(_self.repos, function(repo) { + return repo.name; + }))); + } + }); + + //
+ + // branch [ -l | branchname ] + // -------------------------- + + // The `branch` command is used to switch or list branches for the current repository. + _self.shell.setCommandHandler("branch", { + + // `exec` handles the execution of the command. + exec: function(cmd, args, callback) { + + // Given no arguments, it simply returns the current branch, which will be rendered by the shell. + if(!args || args.length == 0) { + return callback(_self.branch); + } + var branch = args[0]; + + // Given the argument `-l`, it lists all branches for the current repo. This information is lazily + // initialized via `ensureBranches`. + if(branch === '-l') { + return ensureBranches( + function(msg) { + callback(_self.shell.templates.branches_error({msg: msg})); + }, + function() { + return callback(_self.shell.templates.branches({branches: _self.branches})); + } + ); + } + + // Owherwise, the current branch is switched by fetching the root directory for the new branch, and on success, + // setting `_self.branch` and setting the current pathandler node to the root directory fetched. + return getDir(_self.repo.full_name, branch, "/", function(node) { + if(!node) { + callback(_self.shell.templates.branch_error({name: branch, msg: "unable to load root directory for branch"})); + } + _self.branch = branch; + _self.pathhandler.current = node; + _self.root = node; + callback(); + }); + }, + + // `completion` handles `TAB` completion on a partial branch name. The list of possible branches is once again + // lazily initialized via `ensureBranches`. + completion: function(cmd, arg, line, callback) { + return ensureBranches( + function() { + callback(); + }, + function() { + callback(_self.shell.bestMatch(arg, _.map(_self.branches, function(branch) { + return branch.name; + }))); + } + ); + } + }); + + //
+ + // This attaches a custom prompt render to the shell. + _self.shell.onNewPrompt(function(callback) { + callback(_self.shell.templates.prompt({self: _self, node: _self.pathhandler.current})); + }); + + // Wiring up PathHandler + // ===================== + + //
+ + // getNode + // ------- + + // `getNode` is required by `Josh.PathHandler` to provide filesystem behavior. Given a path, it is expected to return + // a pathnode or null; + _self.pathhandler.getNode = function(path, callback) { + _console.log("looking for node at: " + path); + + // If the given path is empty, just return the current pathnode. + if(!path) { + return callback(_self.pathhandler.current); + } + var parts = getPathParts(path); + + // If the first part of path parts isn't empty, the path is a relative path, which can be turned into an + // *absolutish* path by pre-pending the parts of the current pathnode. + if(parts[0] !== '') { + parts = getPathParts(_self.pathhandler.current.path).concat(parts); + } + + // At this point the path is *absolutish*, i.e. looks absolute, but all `.` and `..` mentions need to removed and + // resolved before it is truly absolute. + var resolved = []; + _.each(parts, function(x) { + if(x === '.') { + return; + } + if(x === '..') { + resolved.pop(); + } else { + resolved.push(x); + } + }); + var absolute = resolved.join('/'); + _console.log("path to fetch: " + absolute); + return getDir(_self.repo.full_name, _self.branch, absolute, callback); + }; + + //
+ + // getChildNodes + // ------------- + + // `getChildNodes` is the second function implementation required for `Josh.PathHandler`. Given a pathnode, it returns + // a list of child pathnodes. This is used by `Tab` completion to resolve a partial path, after first resolving the + // nearest parent node using `getNode + _self.pathhandler.getChildNodes = function(node, callback) { + + // If the given node is a file node, no further work is required. + if(node.isfile) { + _console.log("it's a file, no children"); + return callback(); + } + + // Otherwise, if the child nodes have already been initialized, which is done lazily, return them. + if(node.children) { + _console.log("got children, let's turn them into nodes"); + return callback(makeNodes(node.children)); + } + + // Finally, use `getDir` to fetch and populate the child nodes. + _console.log("no children, fetch them"); + return getDir(_self.repo.full_name, _self.branch, node.path, function(detailNode) { + node.children = detailNode.children; + callback(makeNodes(node.children)); + }); + }; + + // Supporting Functions + // ==================== + + //
+ + // get + // --- + + // This function is responsible for all API requests, given a partial API path, `resource`, and an query argument object, + // `args`. + function get(resource, args, callback) { + var url = _self.api + resource; + if(args) { + url += "?" + _.map(args,function(v, k) { + return k + "=" + v; + }).join("&"); + } + _console.log("fetching: " + url); + var request = { + url: url, + dataType: 'json', + xhrFields: { + withCredentials: true + } + }; + $.ajax(request).done(function(response, status, xhr) { + + // Every response from the API includes rate limiting headers, as well as an indicator injected by the API proxy + // whether the request was done with authentication. Both are used to display request rate information and a + // link to authenticate, if required. + var ratelimit = { + remaining: parseInt(xhr.getResponseHeader("X-RateLimit-Remaining")), + limit: parseInt(xhr.getResponseHeader("X-RateLimit-Limit")), + authenticated: xhr.getResponseHeader('Authenticated') === 'true' + }; + $('#ratelimit').html(_self.shell.templates.rateLimitTemplate(ratelimit)); + if(ratelimit.remaining == 0) { + alert("Whoops, you've hit the github rate limit. You'll need to authenticate to continue"); + _self.shell.deactivate(); + return null; + } + + // For simplicity, this tutorial trivially deals with request failures by just returning null from this function + // via the callback. + if(status !== 'success') { + return callback(); + } + return callback(response); + }) + } + + //
+ + // ensureBranches + // -------------- + + // This function lazily fetches the branches for the current repo from the API. + function ensureBranches(err, callback) { + get("repos/" + _self.repo.full_name + "/branches", null, function(branches) { + if(!branches) { + return err("api request failed to return branch list"); + } + _self.branches = branches; + return callback(); + }); + } + + //
+ + // setUser + // ------- + + // This function fetches the specified user and initializes a repository to the provided value (which may be null). + // one fetched by `initialzeRepos`. + function setUser(user_name, repo_name, err, callback) { + if(_self.user && _self.user.login === user_name) { + return callback(_self.user); + } + return get("users/" + user_name, null, function(user) { + if(!user) { + return err("no such user"); + } + return initializeRepos(user, repo_name, err, function(repo) { + _self.user = user; + return callback(_self.user); + }); + }); + } + + //
+ + // initalizeRepos + // -------------- + + // This function first fetches all repos for the given user from the API and then sets the current repo to the provided + // value (which may be null). + function initializeRepos(user, repo_name, err, callback) { + return getRepos(user.login, function(repos) { + var repo = getRepo(repo_name, repos); + if(!repo) { + return err("user has no repositories"); + } + return setRepo(repo, err, function(repo) { + _self.repos = repos; + return callback(repo); + }); + }); + } + + //
+ + // getDir + // ------ + + // This function function fetches the directory listing for a path on a given repo and branch. + function getDir(repo_full_name, branch, path, callback) { + + // Although paths in the internal representation may have a trailing `/`, it has to be removed before using it + // as the argument for an API request. + if(path && path.length > 1 && path[path.length - 1] === '/') { + path = path.substr(0, path.length - 1); + } + get("repos/" + repo_full_name + "/contents" + path, {ref: branch}, function(data) { + + // The API call may return either an array, indicating that the path was a directory, or an object. Since only + // are stored as pathnodes, retrieving anything but an array returns null via the callback. + if(Object.prototype.toString.call(data) !== '[object Array]') { + _console.log("path '" + path + "' was a file"); + return callback(); + } + + // Given a directory listing, i.e. array, the current directory node is created and the API return value captured + // as children so that they can later be transformed into child pathnodes, if required. + var node = { + name: _.last(_.filter(path.split("/"), function(x) { + return x; + })) || "", + path: path, + children: data + }; + _console.log("got node at: " + node.path); + return callback(node); + }); + } + + //
+ + // getRepos + // -------- + + // This function fetches all repositories for a given user. + function getRepos(userLogin, callback) { + return get("users/" + userLogin + "/repos", null, function(data) { + callback(data); + }); + } + + //
+ + // getRepo + // ------- + + // This function tries to match a repository from the given list of known repositories. Should `repo_name` be null, + // the first repository in `repos` is returned. + function getRepo(repo_name, repos) { + if(!repos || repos.length == 0) { + return null; + } + var repo; + if(repo_name) { + repo = _.find(repos, function(repo) { + return repo.name === repo_name; + }); + if(!repo) { + return callback(); + } + } else { + repo = repos[0]; + } + return repo; + } + + //
+ + // setRepo + // ------- + + // This function fetches the root directory for the specified repository and initializes the current repository + // state. + function setRepo(repo, err, callback) { + return getDir(repo.full_name, repo.default_branch, "/", function(node) { + if(!node) { + return err("could not initialize root directory of repository '" + repo.full_name + "'"); + } + _console.log("setting repo to '" + repo.name + "'"); + _self.repo = repo; + _self.branch = repo.default_branch; + _self.pathhandler.current = node; + _self.root = node; + return callback(repo); + }); + } + + //
+ + // getPathParts + // ------------ + + // This function splits a path on `/` and removes any empty trailing element. + function getPathParts(path) { + var parts = path.split("/"); + if(parts[parts.length - 1] === '') { + return parts.slice(0, parts.length - 1); + } + return parts; + } + + //
+ + // makeNodes + // --------- + + // This function builds child pathnodes from the directory information returned by getDir. + function makeNodes(children) { + return _.map(children, function(node) { + return { + name: node.name, + path: "/" + node.path, + isFile: node.type === 'file' + }; + }); + } + + // UI setup and initialization + // =========================== + + //
+ + // initializationError + // ------------------- + + // This function is a lazy way with giving up if some request failed during intialization, forcing the user + // to reload to retry. + function initializationError(context, msg) { + _console.log("[" + context + "] failed to initialize: " + msg); + alert("unable to initialize shell. Encountered a problem talking to github api. Try reloading the page"); + } + + //
+ + // intializeUI + // ----------- + + // After a current user and repo have been set, this function initializes the UI state to allow the shell to be + // shown and hidden. + function initializeUI() { + _console.log("activating"); + var $consolePanel = $('#shell-container'); + $consolePanel.resizable({ handles: "s"}); + $(document).keypress(function(event) { + if(_self.shell.isActive()) { + return; + } + if(event.keyCode == 126) { + event.preventDefault(); + activateAndShow(); + } + }); + function activateAndShow() { + _self.shell.activate(); + $consolePanel.slideDown(); + $consolePanel.focus(); + } + + function hideAndDeactivate() { + _self.shell.deactivate(); + $consolePanel.slideUp(); + $consolePanel.blur(); + } + + _self.shell.onEOT(hideAndDeactivate); + _self.shell.onCancel(hideAndDeactivate); + } + + //
+ + // On document ready, the default user and repo are loaded from the API before the UI can complete initialization. + $(document).ready(function() { + setUser("sdether", "josh.js", + function(msg) { + initializationError("default", msg); + }, + initializeUI + ); + }); + })(root, $, _); +}) + (this, $, _); \ No newline at end of file diff --git a/javascripts/highlight.min.js b/javascripts/highlight.min.js new file mode 100644 index 0000000..545fa1d --- /dev/null +++ b/javascripts/highlight.min.js @@ -0,0 +1 @@ +var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(//gm,">")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("")}while(o!=u.node);r.splice(q,1);while(q'+L[0]+""}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return''+r.value+""}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+=""}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"
")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.javascript=function(a){return{k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const",literal:"true false null undefined NaN Infinity"},c:[a.ASM,a.QSM,a.CLCM,a.CBLCLM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBLCLM,{cN:"regexp",b:"/",e:"/[gim]*",i:"\\n",c:[{b:"\\\\/"}]},{b:"<",e:">;",sL:"xml"}],r:0},{cN:"function",bWK:true,e:"{",k:"function",c:[{cN:"title",b:"[A-Za-z$_][0-9A-Za-z$_]*"},{cN:"params",b:"\\(",e:"\\)",c:[a.CLCM,a.CBLCLM],i:"[\"'\\(]"}],i:"\\[|%"}]}}(hljs);hljs.LANGUAGES.css=function(a){var b={cN:"function",b:a.IR+"\\(",e:"\\)",c:[a.NM,a.ASM,a.QSM]};return{cI:true,i:"[=/|']",c:[a.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",eE:true,k:"import page media charset",c:[b,a.ASM,a.QSM,a.NM]},{cN:"tag",b:a.IR,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[b,a.NM,a.QSM,a.ASM,a.CBLCLM,{cN:"hexcolor",b:"\\#[0-9A-F]+"},{cN:"important",b:"!important"}]}}]}]}]}}(hljs);hljs.LANGUAGES.xml=function(a){var c="[A-Za-z0-9\\._:-]+";var b={eW:true,c:[{cN:"attribute",b:c,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},b]}]}}(hljs);hljs.LANGUAGES.json=function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}}(hljs); \ No newline at end of file diff --git a/js/history.js b/javascripts/history.js similarity index 92% rename from js/history.js rename to javascripts/history.js index 7e4e38c..49fb27a 100644 --- a/js/history.js +++ b/javascripts/history.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013-2014 Arne F. Claassen + * Copyright 2013 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,7 @@ var Josh = Josh || {}; var _key = config.key || 'josh.history'; if (_storage) { - try { - var data = _storage.getItem(_key); - } catch(e) { - _console.log("Error accessing storage"); - } + var data = _storage.getItem(_key); if (data) { _history = JSON.parse(data); _searchCursor = _cursor = _history.length - 1; @@ -42,11 +38,7 @@ var Josh = Josh || {}; } function save() { if (_storage) { - try { - _storage.setItem(_key, JSON.stringify(_history)); - } catch(e) { - _console.log("Error accessing storage"); - } + _storage.setItem(_key, JSON.stringify(_history)); } } diff --git a/js/killring.js b/javascripts/killring.js similarity index 98% rename from js/killring.js rename to javascripts/killring.js index 822af2b..dc22874 100644 --- a/js/killring.js +++ b/javascripts/killring.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013-2014 Arne F. Claassen + * Copyright 2013 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/js/pathhandler.js b/javascripts/pathhandler.js similarity index 96% rename from js/pathhandler.js rename to javascripts/pathhandler.js index bf1148f..d15247a 100644 --- a/js/pathhandler.js +++ b/javascripts/pathhandler.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013-2014 Arne F. Claassen + * Copyright 2013 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/javascripts/quakeconsole.js b/javascripts/quakeconsole.js new file mode 100644 index 0000000..c047bd8 --- /dev/null +++ b/javascripts/quakeconsole.js @@ -0,0 +1,161 @@ +/*------------------------------------------------------------------------* + * Copyright 2013 Arne F. Claassen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *-------------------------------------------------------------------------*/ +(function(root, $, _) { + Josh.QuakeConsole = (function(root, $, _) { + var treeroot = buildTree(); + var shell = Josh.Shell(); + var pathhandler = new Josh.PathHandler(shell); + pathhandler.current = treeroot; + pathhandler.getNode = function(path, callback) { + if(!path) { + return callback(pathhandler.current); + } + var parts = _.filter(path.split('/'), function(x) { + return x; + }); + var start = ((path || '')[0] == '/') ? treeroot : pathhandler.current; + return findNode(start, parts, callback); + }; + pathhandler.getChildNodes = function(node, callback) { + callback(node.childnodes); + }; + function findNode(current, parts, callback) { + if(!parts || parts.length == 0) { + return callback(current); + } + if(parts[0] == ".") { + + } else if(parts[0] == "..") { + current = current.parent; + } else { + current = _.first(_.filter(current.childnodes, function(node) { + return node.name == parts[0]; + })); + } + if(!current) { + return callback(); + } + return findNode(current, _.rest(parts), callback); + } + $(document).ready(function() { + var $consolePanel = $('#shell-panel'); + $consolePanel.resizable({ handles: "s"}); + $(document).keypress(function(event) { + if(shell.isActive()) { + return; + } + if(event.keyCode == 126) { + event.preventDefault(); + shell.activate(); + $consolePanel.slideDown(); + $consolePanel.focus(); + } + }); + function hideAndDeactivate() { + shell.deactivate(); + $consolePanel.slideUp(); + $consolePanel.blur(); + } + shell.onEOT(hideAndDeactivate); + shell.onCancel(hideAndDeactivate); + }); + function buildTree() { + var fs = { + bin: {}, + boot: {}, + dev: {}, + etc: { + default: {}, + 'rc.d': {}, + sysconfig: {}, + x11: {} + }, + home: { + bob: { + video: { + 'firefly.m4v': {} + }, + videos: { + 'Arrested Development': { + 's1e1.m4v': {} + }, + 'Better Off Ted': { + 's1e1.m4v': {} + } + } + }, + jane: {} + }, + lib: {}, + 'lost+found': {}, + misc: {}, + mnt: { + cdrom: {}, + sysimage: {} + }, + net: {}, + opt: {}, + proc: {}, + root: {}, + sbin: {}, + usr: { + x11: {}, + bin: {}, + include: {}, + lib: {}, + local: {}, + man: {}, + sbin: {}, + share: { + doc: {} + }, + src: {} + }, + var: { + lib: {}, + lock: {}, + run: {}, + log: { + httpd: { + access_log: {}, + error_log: {} + }, + 'boot.log': {}, + cron: {}, + messages: {} + } + } + }; + + function build(parent, node) { + parent.childnodes = _.map(_.pairs(node), function(pair) { + var child = { + name: pair[0], + path: parent.path + "/" + pair[0], + parent: parent + }; + build(child, pair[1]); + return child; + }); + parent.children = _.keys(node); + return parent; + } + var tree = build({name: "", path: ""}, fs); + tree.path = '/'; + return tree; + } + })(root, $, _); +})(this, $, _); \ No newline at end of file diff --git a/js/readline.js b/javascripts/readline.js similarity index 66% rename from js/readline.js rename to javascripts/readline.js index a710f23..739b713 100644 --- a/js/readline.js +++ b/javascripts/readline.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013-2014 Arne F. Claassen + * Copyright 2013 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,28 +15,26 @@ *-------------------------------------------------------------------------*/ var Josh = Josh || {}; -Josh.Version = "0.2.10"; +Josh.Version = "0.2.7"; (function(root) { - Josh.Keys = { - Special: { - Backspace: 8, - Tab: 9, - Enter: 13, - Pause: 19, - CapsLock: 20, - Escape: 27, - Space: 32, - PageUp: 33, - PageDown: 34, - End: 35, - Home: 36, - Left: 37, - Up: 38, - Right: 39, - Down: 40, - Insert: 45, - Delete: 46 - } + var SPECIAL = { + 8: 'BACKSPACE', + 9: 'TAB', + 13: 'ENTER', + 19: 'PAUSE', + 20: 'CAPS_LOCK', + 27: 'ESCAPE', + 32: 'SPACE', + 33: 'PAGE_UP', + 34: 'PAGE_DOWN', + 35: 'END', + 36: 'HOME', + 37: 'LEFT', + 38: 'UP', + 39: 'RIGHT', + 40: 'DOWN', + 45: 'INSERT', + 46: 'DELETE' }; Josh.ReadLine = function(config) { @@ -48,9 +46,8 @@ Josh.Version = "0.2.10"; } }); var _history = config.history || new Josh.History(); + var _deactivationKey = config.deactivationKey || { keyCode: 27 }; // Esc var _killring = config.killring || new Josh.KillRing(); - var _boundToElement = config.element ? true : false; - var _element = config.element || root; var _active = false; var _onActivate; var _onDeactivate; @@ -72,72 +69,6 @@ Josh.Version = "0.2.10"; var _completionActive; var _cmdQueue = []; var _suspended = false; - var _cmdMap = { - complete: cmdComplete, - done: cmdDone, - noop: cmdNoOp, - history_top: cmdHistoryTop, - history_end: cmdHistoryEnd, - history_next: cmdHistoryNext, - history_previous: cmdHistoryPrev, - end: cmdEnd, - home: cmdHome, - left: cmdLeft, - right: cmdRight, - cancel: cmdCancel, - 'delete': cmdDeleteChar, - backspace: cmdBackspace, - kill_eof: cmdKillToEOF, - kill_wordback: cmdKillWordBackward, - kill_wordforward: cmdKillWordForward, - yank: cmdYank, - clear: cmdClear, - search: cmdReverseSearch, - wordback: cmdBackwardWord, - wordforward: cmdForwardWord, - yank_rotate: cmdRotate - }; - var _keyMap = { - 'default': { - 8: cmdBackspace, // Backspace - 9: cmdComplete, // Tab - 13: cmdDone, // Enter - 27: cmdEsc, // Esc - 33: cmdHistoryTop, // Page Up - 34: cmdHistoryEnd, // Page Down - 35: cmdEnd, // End - 36: cmdHome, // Home - 37: cmdLeft, // Left - 38: cmdHistoryPrev, // Up - 39: cmdRight, // Right - 40: cmdHistoryNext, // Down - 46: cmdDeleteChar, // Delete - 10: cmdNoOp, // Pause - 19: cmdNoOp, // Caps Lock - 45: cmdNoOp // Insert - }, - control: { - 65: cmdHome, // A - 66: cmdLeft, // B - 67: cmdCancel, // C - 68: cmdDeleteChar, // D - 69: cmdEnd, // E - 70: cmdRight, // F - 80: cmdHistoryPrev, // P - 78: cmdHistoryNext, // N - 75: cmdKillToEOF, // K - 89: cmdYank, // Y - 76: cmdClear, // L - 82: cmdReverseSearch // R - }, - meta: { - 8: cmdKillWordBackward, // Backspace - 66: cmdBackwardWord, // B - 68: cmdKillWordForward, // D - 70: cmdForwardWord, // F - 89: cmdRotate // Y - } - }; // public methods var self = { @@ -156,36 +87,6 @@ Josh.Version = "0.2.10"; _onDeactivate(); } }, - bind: function(key, action) { - var k = getKey(key); - var cmd = _cmdMap[action]; - if(!cmd) { - return; - } - _keyMap[k.modifier][k.code]; - }, - unbind: function(key) { - var k = getKey(key); - delete _keyMap[k.modifier][k.code]; - }, - attach: function(el) { - if(_element) { - self.detach(); - } - _console.log("attaching"); - _console.log(el); - _element = el; - _boundToElement = true; - addEvent(_element, "focus", self.activate); - addEvent(_element, "blur", self.deactivate); - subscribeToKeys(); - }, - detach: function() { - removeEvent(_element, "focus", self.activate); - removeEvent(_element, "blur", self.deactivate); - _element = null; - _boundToElement = false; - }, onActivate: function(completionHandler) { _onActivate = completionHandler; }, @@ -224,31 +125,10 @@ Josh.Version = "0.2.10"; text: _text, cursor: _cursor }; - }, - setLine: function(line) { - _text = line.text; - _cursor = line.cursor; - refresh(); } }; // private methods - function addEvent(element, name, callback) { - if(element.addEventListener) { - element.addEventListener(name, callback, false); - } else if(element.attachEvent) { - element.attachEvent('on' + name, callback); - } - } - - function removeEvent(element, name, callback) { - if(element.removeEventListener) { - element.removeEventListener(name, callback, false); - } else if(element.detachEvent) { - element.detachEvent('on' + name, callback); - } - } - function getKeyInfo(e) { var code = e.keyCode || e.charCode; var c = String.fromCharCode(code); @@ -262,22 +142,6 @@ Josh.Version = "0.2.10"; }; } - function getKey(key) { - var k = { - modifier: 'default', - code: key.keyCode - }; - if(key.metaKey || key.altKey) { - k.modifier = 'meta'; - } else if(key.ctrlKey) { - k.modifier = 'control'; - } - if(key['char']) { - k.code = key['char'].charCodeAt(0); - } - return k; - } - function queue(cmd) { if(_suspended) { _cmdQueue.push(cmd); @@ -661,69 +525,164 @@ Josh.Version = "0.2.10"; return left + ins + right; } - function subscribeToKeys() { - // set up key capture - _element.onkeydown = function(e) { - e = e || window.event; - - // return as unhandled if we're not active or the key is just a modifier key - if(!_active || e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 || e.keyCode == 91) { - return true; + // set up key capture + root.onkeydown = function(e) { + e = e || window.event; + + // return as unhandled if we're not active or the key is just a modifier key + if(!_active || e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 || e.keyCode == 91) { + return true; + } + + var cmd = null; + + // check for some special first keys, regardless of modifiers + switch(e.keyCode) { + case 8: // Backspace + cmd = cmdBackspace; + break; + case 9: // Tab + cmd = cmdComplete; + break; + case 13: // Enter + cmd = cmdDone; + break; + case 27: // Esc + cmd = cmdEsc; + break; + case 33: // Page Up + cmd = cmdHistoryTop; + break; + case 34: // Page Down + cmd = cmdHistoryEnd; + break; + case 35: // End + cmd = cmdEnd; + break; + case 36: // Home + cmd = cmdHome; + break; + case 37: // Left + cmd = cmdLeft; + break; + case 38: // Up + cmd = cmdHistoryPrev; + break; + case 39: // Right + cmd = cmdRight; + break; + case 40: // Down + cmd = cmdHistoryNext; + break; + case 46: // Delete + cmd = cmdDeleteChar; + break; + + // these we catch and have no commands for + case 10: // Pause + case 19: // Caps Lock + case 45: // Insert + cmd = cmdNoOp; + break; + + // all others we don't handle at this level + default: + break; + } + + // intercept ctrl- and meta- sequences (may override the non-modifier cmd captured above + if(e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { + switch(e.keyCode) { + case 65: // A + cmd = cmdHome; + break; + case 66: // B + cmd = cmdLeft; + break; + case 67: // C + cmd = cmdCancel; + break; + case 68: // D + cmd = cmdDeleteChar; + break; + case 69: // E + cmd = cmdEnd; + break; + case 70: // F + cmd = cmdRight; + break; + case 80: // P + cmd = cmdHistoryPrev; + break; + case 78: // N + cmd = cmdHistoryNext; + break; + case 75: // K + cmd = cmdKillToEOF; + break; + case 89: // Y + cmd = cmdYank; + break; + case 76: // L + cmd = cmdClear; + break; + case 82: // R + cmd = cmdReverseSearch; + break; } - - // check for some special first keys, regardless of modifiers - _console.log("key: " + e.keyCode); - var cmd = _keyMap['default'][e.keyCode]; - // intercept ctrl- and meta- sequences (may override the non-modifier cmd captured above - var mod; - if(e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { - mod = _keyMap.control[e.keyCode]; - if(mod) { - cmd = mod; - } - } else if((e.altKey || e.metaKey) && !e.ctrlKey && !e.shiftKey) { - mod = _keyMap.meta[e.keyCode]; - if(mod) { - cmd = mod; - } + } else if((e.altKey || e.metaKey) && !e.ctrlKey && !e.shiftKey) { + switch(e.keyCode) { + case 8: // Backspace + cmd = cmdKillWordBackward; + break; + case 66: // B + cmd = cmdBackwardWord; + break; + case 68: // D + cmd = cmdKillWordForward; + break; + case 70: // F + cmd = cmdForwardWord; + break; + case 89: // Y + cmd = cmdRotate; + break; } - if(!cmd) { - return true; - } - queue(cmd); - e.preventDefault(); - e.stopPropagation(); - e.cancelBubble = true; - return false; - }; + } + if(!cmd) { + return true; + } + queue(cmd); + e.preventDefault(); + e.stopPropagation(); + e.cancelBubble = true; + return false; + }; - _element.onkeypress = function(e) { - if(!_active) { - return true; - } - var key = getKeyInfo(e); - if(key.code == 0 || e.defaultPrevented || e.metaKey || e.altKey || e.ctrlKey) { - return false; - } - queue(function cmdKeyPress() { - if(_inSearch) { - addSearchText(key.character); - } else { - addText(key.character); - } - }); - e.preventDefault(); - e.stopPropagation(); - e.cancelBubble = true; + root.onkeypress = function(e) { + if(!_active) { + return true; + } + var key = getKeyInfo(e); + if(key.code == 0 || e.defaultPrevented) { return false; - }; - } - if(_boundToElement) { - self.attach(_element); - } else { - subscribeToKeys(); - } + } + queue(function cmdKeyPress() { + if(_inSearch) { + addSearchText(key.character); + } else { + addText(key.character); + } + }); + e.preventDefault(); + e.stopPropagation(); + e.cancelBubble = true; + return false; + }; + return self; }; })(this); + + diff --git a/javascripts/respond.js b/javascripts/respond.js new file mode 100644 index 0000000..76bc260 --- /dev/null +++ b/javascripts/respond.js @@ -0,0 +1,779 @@ +if(typeof Object.create!=="function"){ +Object.create=function(o){ +function F(){ +}; +F.prototype=o; +return new F(); +}; +} +var ua={toString:function(){ +return navigator.userAgent; +},test:function(s){ +return this.toString().toLowerCase().indexOf(s.toLowerCase())>-1; +}}; +ua.version=(ua.toString().toLowerCase().match(/[\s\S]+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1]; +ua.webkit=ua.test("webkit"); +ua.gecko=ua.test("gecko")&&!ua.webkit; +ua.opera=ua.test("opera"); +ua.ie=ua.test("msie")&&!ua.opera; +ua.ie6=ua.ie&&document.compatMode&&typeof document.documentElement.style.maxHeight==="undefined"; +ua.ie7=ua.ie&&document.documentElement&&typeof document.documentElement.style.maxHeight!=="undefined"&&typeof XDomainRequest==="undefined"; +ua.ie8=ua.ie&&typeof XDomainRequest!=="undefined"; +var domReady=function(){ +var _1=[]; +var _2=function(){ +if(!arguments.callee.done){ +arguments.callee.done=true; +for(var i=0;i<_1.length;i++){ +_1[i](); +} +} +}; +if(document.addEventListener){ +document.addEventListener("DOMContentLoaded",_2,false); +} +if(ua.ie){ +(function(){ +try{ +document.documentElement.doScroll("left"); +} +catch(e){ +setTimeout(arguments.callee,50); +return; +} +_2(); +})(); +document.onreadystatechange=function(){ +if(document.readyState==="complete"){ +document.onreadystatechange=null; +_2(); +} +}; +} +if(ua.webkit&&document.readyState){ +(function(){ +if(document.readyState!=="loading"){ +_2(); +}else{ +setTimeout(arguments.callee,10); +} +})(); +} +window.onload=_2; +return function(fn){ +if(typeof fn==="function"){ +_1[_1.length]=fn; +} +return fn; +}; +}(); +var cssHelper=function(){ +var _3={BLOCKS:/[^\s{][^{]*\{(?:[^{}]*\{[^{}]*\}[^{}]*|[^{}]*)*\}/g,BLOCKS_INSIDE:/[^\s{][^{]*\{[^{}]*\}/g,DECLARATIONS:/[a-zA-Z\-]+[^;]*:[^;]+;/g,RELATIVE_URLS:/url\(['"]?([^\/\)'"][^:\)'"]+)['"]?\)/g,REDUNDANT_COMPONENTS:/(?:\/\*([^*\\\\]|\*(?!\/))+\*\/|@import[^;]+;)/g,REDUNDANT_WHITESPACE:/\s*(,|:|;|\{|\})\s*/g,MORE_WHITESPACE:/\s{2,}/g,FINAL_SEMICOLONS:/;\}/g,NOT_WHITESPACE:/\S+/g}; +var _4,_5=false; +var _6=[]; +var _7=function(fn){ +if(typeof fn==="function"){ +_6[_6.length]=fn; +} +}; +var _8=function(){ +for(var i=0;i<_6.length;i++){ +_6[i](_4); +} +}; +var _9={}; +var _a=function(n,v){ +if(_9[n]){ +var _b=_9[n].listeners; +if(_b){ +for(var i=0;i<_b.length;i++){ +_b[i](v); +} +} +} +}; +var _c=function(_d,_e,_f){ +if(ua.ie&&!window.XMLHttpRequest){ +window.XMLHttpRequest=function(){ +return new ActiveXObject("Microsoft.XMLHTTP"); +}; +} +if(!XMLHttpRequest){ +return ""; +} +var r=new XMLHttpRequest(); +try{ +r.open("get",_d,true); +r.setRequestHeader("X_REQUESTED_WITH","XMLHttpRequest"); +} +catch(e){ +_f(); +return; +} +var _10=false; +setTimeout(function(){ +_10=true; +},5000); +document.documentElement.style.cursor="progress"; +r.onreadystatechange=function(){ +if(r.readyState===4&&!_10){ +if(!r.status&&location.protocol==="file:"||(r.status>=200&&r.status<300)||r.status===304||navigator.userAgent.indexOf("Safari")>-1&&typeof r.status==="undefined"){ +_e(r.responseText); +}else{ +_f(); +} +document.documentElement.style.cursor=""; +r=null; +} +}; +r.send(""); +}; +var _11=function(_12){ +_12=_12.replace(_3.REDUNDANT_COMPONENTS,""); +_12=_12.replace(_3.REDUNDANT_WHITESPACE,"$1"); +_12=_12.replace(_3.MORE_WHITESPACE," "); +_12=_12.replace(_3.FINAL_SEMICOLONS,"}"); +return _12; +}; +var _13={mediaQueryList:function(s){ +var o={}; +var idx=s.indexOf("{"); +var lt=s.substring(0,idx); +s=s.substring(idx+1,s.length-1); +var mqs=[],rs=[]; +var qts=lt.toLowerCase().substring(7).split(","); +for(var i=0;i-1&&_23.href&&_23.href.length!==0&&!_23.disabled){ +_1f[_1f.length]=_23; +} +} +if(_1f.length>0){ +var c=0; +var _24=function(){ +c++; +if(c===_1f.length){ +_20(); +} +}; +var _25=function(_26){ +var _27=_26.href; +_c(_27,function(_28){ +_28=_11(_28).replace(_3.RELATIVE_URLS,"url("http://github.com/+_27.substring(0,_27.lastIndexOf(%22/"))+"/$1)"); +_26.cssHelperText=_28; +_24(); +},_24); +}; +for(i=0;i<_1f.length;i++){ +_25(_1f[i]); +} +}else{ +_20(); +} +}; +var _29={mediaQueryLists:"array",rules:"array",selectors:"object",declarations:"array",properties:"object"}; +var _2a={mediaQueryLists:null,rules:null,selectors:null,declarations:null,properties:null}; +var _2b=function(_2c,v){ +if(_2a[_2c]!==null){ +if(_29[_2c]==="array"){ +return (_2a[_2c]=_2a[_2c].concat(v)); +}else{ +var c=_2a[_2c]; +for(var n in v){ +if(v.hasOwnProperty(n)){ +if(!c[n]){ +c[n]=v[n]; +}else{ +c[n]=c[n].concat(v[n]); +} +} +} +return c; +} +} +}; +var _2d=function(_2e){ +_2a[_2e]=(_29[_2e]==="array")?[]:{}; +for(var i=0;i<_4.length;i++){ +_2b(_2e,_4[i].cssHelperParsed[_2e]); +} +return _2a[_2e]; +}; +domReady(function(){ +var els=document.body.getElementsByTagName("*"); +for(var i=0;i=_44)||(max&&_46<_44)||(!min&&!max&&_46===_44)); +}else{ +return false; +} +}else{ +return _46>0; +} +}else{ +if("device-height"===_41.substring(l-13,l)){ +_47=screen.height; +if(_42!==null){ +if(_43==="length"){ +return ((min&&_47>=_44)||(max&&_47<_44)||(!min&&!max&&_47===_44)); +}else{ +return false; +} +}else{ +return _47>0; +} +}else{ +if("width"===_41.substring(l-5,l)){ +_46=document.documentElement.clientWidth||document.body.clientWidth; +if(_42!==null){ +if(_43==="length"){ +return ((min&&_46>=_44)||(max&&_46<_44)||(!min&&!max&&_46===_44)); +}else{ +return false; +} +}else{ +return _46>0; +} +}else{ +if("height"===_41.substring(l-6,l)){ +_47=document.documentElement.clientHeight||document.body.clientHeight; +if(_42!==null){ +if(_43==="length"){ +return ((min&&_47>=_44)||(max&&_47<_44)||(!min&&!max&&_47===_44)); +}else{ +return false; +} +}else{ +return _47>0; +} +}else{ +if("device-aspect-ratio"===_41.substring(l-19,l)){ +return _43==="aspect-ratio"&&screen.width*_44[1]===screen.height*_44[0]; +}else{ +if("color-index"===_41.substring(l-11,l)){ +var _48=Math.pow(2,screen.colorDepth); +if(_42!==null){ +if(_43==="absolute"){ +return ((min&&_48>=_44)||(max&&_48<_44)||(!min&&!max&&_48===_44)); +}else{ +return false; +} +}else{ +return _48>0; +} +}else{ +if("color"===_41.substring(l-5,l)){ +var _49=screen.colorDepth; +if(_42!==null){ +if(_43==="absolute"){ +return ((min&&_49>=_44)||(max&&_49<_44)||(!min&&!max&&_49===_44)); +}else{ +return false; +} +}else{ +return _49>0; +} +}else{ +if("resolution"===_41.substring(l-10,l)){ +var res; +if(_45==="dpcm"){ +res=_3d("1cm"); +}else{ +res=_3d("1in"); +} +if(_42!==null){ +if(_43==="resolution"){ +return ((min&&res>=_44)||(max&&res<_44)||(!min&&!max&&res===_44)); +}else{ +return false; +} +}else{ +return res>0; +} +}else{ +return false; +} +} +} +} +} +} +} +} +}; +var _4a=function(mq){ +var _4b=mq.getValid(); +var _4c=mq.getExpressions(); +var l=_4c.length; +if(l>0){ +for(var i=0;i0){ +s[c++]=","; +} +s[c++]=n; +} +} +if(s.length>0){ +_39[_39.length]=cssHelper.addStyle("@media "+s.join("")+"{"+mql.getCssText()+"}",false); +} +}; +var _4e=function(_4f){ +for(var i=0;i<_4f.length;i++){ +_4d(_4f[i]); +} +if(ua.ie){ +document.documentElement.style.display="block"; +setTimeout(function(){ +document.documentElement.style.display=""; +},0); +setTimeout(function(){ +cssHelper.broadcast("cssMediaQueriesTested"); +},100); +}else{ +cssHelper.broadcast("cssMediaQueriesTested"); +} +}; +var _50=function(){ +for(var i=0;i<_39.length;i++){ +cssHelper.removeStyle(_39[i]); +} +_39=[]; +cssHelper.mediaQueryLists(_4e); +}; +var _51=0; +var _52=function(){ +var _53=cssHelper.getViewportWidth(); +var _54=cssHelper.getViewportHeight(); +if(ua.ie){ +var el=document.createElement("div"); +el.style.position="absolute"; +el.style.top="-9999em"; +el.style.overflow="scroll"; +document.body.appendChild(el); +_51=el.offsetWidth-el.clientWidth; +document.body.removeChild(el); +} +var _55; +var _56=function(){ +var vpw=cssHelper.getViewportWidth(); +var vph=cssHelper.getViewportHeight(); +if(Math.abs(vpw-_53)>_51||Math.abs(vph-_54)>_51){ +_53=vpw; +_54=vph; +clearTimeout(_55); +_55=setTimeout(function(){ +if(!_3a()){ +_50(); +}else{ +cssHelper.broadcast("cssMediaQueriesTested"); +} +},500); +} +}; +window.onresize=function(){ +var x=window.onresize||function(){ +}; +return function(){ +x(); +_56(); +}; +}(); +}; +var _57=document.documentElement; +_57.style.marginLeft="-32767px"; +setTimeout(function(){ +_57.style.marginTop=""; +},20000); +return function(){ +if(!_3a()){ +cssHelper.addListener("newStyleParsed",function(el){ +_4e(el.cssHelperParsed.mediaQueryLists); +}); +cssHelper.addListener("cssMediaQueriesTested",function(){ +if(ua.ie){ +_57.style.width="1px"; +} +setTimeout(function(){ +_57.style.width=""; +_57.style.marginLeft=""; +},0); +cssHelper.removeListener("cssMediaQueriesTested",arguments.callee); +}); +_3c(); +_50(); +}else{ +_57.style.marginLeft=""; +} +_52(); +}; +}()); +try{ +document.execCommand("BackgroundImageCache",false,true); +} +catch(e){ +} + diff --git a/js/shell.js b/javascripts/shell.js similarity index 99% rename from js/shell.js rename to javascripts/shell.js index ba27216..21a54cf 100644 --- a/js/shell.js +++ b/javascripts/shell.js @@ -1,5 +1,5 @@ /* ------------------------------------------------------------------------* - * Copyright 2013-2014 Arne F. Claassen + * Copyright 2013 Arne F. Claassen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/js/input.js b/js/input.js deleted file mode 100644 index 3085db2..0000000 --- a/js/input.js +++ /dev/null @@ -1,172 +0,0 @@ -/* ------------------------------------------------------------------------* - * Copyright 2013-2014 Arne F. Claassen - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *-------------------------------------------------------------------------*/ - -var Josh = Josh || {}; -(function (root, $, _) { - $.fn.josh_caretTo = function (index) { - return this.queue(function (next) { - if (this.createTextRange) { - var range = this.createTextRange(); - range.move("character", index); - range.select(); - } else if (this.selectionStart !== null) { - this.setSelectionRange(index, index); - } - next(); - }); - }; - $.fn.josh_caretPosition = function () { - var el = this.get(0); - if (el.createTextRange) { - var range = el.createTextRange(); - range.moveStart('character', -el.value.length); - return range.text.length; - } else if (el.selectionStart !== null) { - return el.selectionStart; - } - return 0; - }; - - var history = Josh.History(); - var killring = new Josh.KillRing(); - - Josh.Input = function (config) { - config = config || {}; - - // instance fields - var _console = config.console || (Josh.Debug && root.console ? root.console : { - log: function () { - } - }); - - var _id = "#" + config.id; - var _blinktime = config.blinktime || 500; - var _active = false; - var _cursor_visible = false; - var _isInput = false; - var _history = config.history || history; - var _killring = config.killring || killring; - var _text; - var self = { - templates: { - span: _.template('') - }, - history: _history, - killring: _killring - }; - - $(document).ready(function () { - var $input = $(_id); - var el = $input.get(0); - var readline = new Josh.ReadLine({ - history: _history, - killring: _killring, - console: _console - }); - self.readline = readline; - readline.attach(el); - var activate = null; - _isInput = $input.is('input'); - if (_isInput) { - - _console.log(_id + ' is an input'); - - function renderInput(line) { - var text = line ? line.text : ''; - _text = text; - $input.val(text); - $input.josh_caretTo(line.cursor); - } - readline.onChange(renderInput); - $input.click(function() { - var line = readline.getLine(); - line.cursor = $input.josh_caretPosition(); - readline.setLine(line); - }); - - activate = function() { - // Note: have to re-render with a setTimeout, because on focus, but after the onfocus event is processed, - // the input will select all, invalidating our render - setTimeout(function() { - renderInput(readline.getLine()); - }, 0); - }; - } else { - _console.log(_id + ' is a non-input element'); - $input.html(self.templates.span()); - if(typeof $input.attr('tabindex') === 'undefined') { - $input.attr('tabindex',0); - } - var $left = $input.find('.left'); - var $right = $input.find('.right'); - var $cursor = $input.find('.cursor'); - - function renderSpan(line) { - var text = line.text || ''; - _text = text; - var cursorIdx = line.cursor || 0; - var left = _.escape(text.substr(0, cursorIdx)).replace(/ /g, ' '); - var cursor = text.substr(cursorIdx, 1); - var right = _.escape(text.substr(cursorIdx + 1)).replace(/ /g, ' '); - $left.html(left); - if (!cursor) { - $cursor.html(' ').css('textDecoration', 'underline'); - } else { - $cursor.text(cursor).css('textDecoration', 'underline'); - } - $right.html(right); - } - - function blinkCursor() { - if (!_active) { - return; - } - root.setTimeout(function () { - if (!_active) { - return; - } - _cursor_visible = !_cursor_visible; - if (_cursor_visible) { - $cursor.css('textDecoration', 'underline'); - } else { - $cursor.css('textDecoration', ''); - } - blinkCursor(); - }, _blinktime); - } - - activate = function () { - blinkCursor(); - } - readline.onChange(renderSpan); - } - readline.unbind({keyCode: Josh.Keys.Special.Tab}); - readline.unbind({'char': 'R', ctrlKey: true}); - readline.onActivate(function () { - _active = true; - activate(); - }); - readline.onDeactivate(function () { - _active = false; - if (_text) { - _history.accept(_text); - } - }); - - }); - return self; - } -})(this, $, _); diff --git a/params.json b/params.json new file mode 100644 index 0000000..c07f8ae --- /dev/null +++ b/params.json @@ -0,0 +1 @@ +{"note":"Don't delete this file! It's used internally to help with page regeneration.","body":"josh.js 0.2\r\n===========\r\n\r\n***Javascript Online SHell*** provides a toolkit for building bash-like command line consoles for web pages. It consists of the following components:\r\n\r\n* `readline.js` - full readline support for ctrl sequences, tab, history, etc.\r\n* `shell.js` - visual presentation of the shell and command handling\r\n* `pathhandler.js` - provide cd, ls, pwd and path completion toolikit\r\n* `history.js` - localStorage backed command history\r\n\r\n## License\r\njosh.js is licensed under the Apache 2.0 License\r\n\r\n## Status\r\n\r\n* code is ready for experimental use\r\n * Tested under Chrome, Firefox, Safari and IE9\r\n * API may not yet be stable\r\n* needs minified versions of complete toolkit and just readline.js\r\n* needs code documentation and documenation site\r\n* would like to add AMD support\r\n\r\n## Usage\r\n\r\nUntil documentation is written, refer to `index.html` and `example.js` for a sample implementation of a shell with path completion.\r\n\r\n## Components\r\n***josh*** is built from 4 components and can be used in part or in full.\r\n\r\n### readline.js\r\n\r\n`readline.js` has no dependencies on any outside libraries, although it requires either `history.js` or an object implementing the same calls.\r\n\r\nIt implements key trapping to bring [GNU Readline](http://cnswww.cns.cwru.edu/php/chet/readline/readline.html) like line editing to the browser. It can be used by itself to bring readline support to custom data entry fields or in conjunction with `shell.js` to create a full console.\r\n\r\n### shell.js\r\n`shell.js` has external dependencies of [jQuery](http://jquery.com/), [Underscore](http://underscorejs.org/) and internal dependencies of `readline.js` and `history.js`.\r\n\r\nIt provides a simple console UI, using a *panel* for the console viewport and an auto-scrolling *view* inside the panel. It uses Underscore templates for generating the view html, although any template generator can be substituted as long as it can be expressed in the form of a function that takes a JSON object of arguments and returns an html string.\r\n\r\nIt also implements command handling so that new commands can be added by name with execution and completion handlers. Out of the box, `shell.js` provides the following commands:\r\n* help - list all known commands (including user added)\r\n* clear - clear the \"screen\" i.e. viewport\r\n* history - show the command history captured by `readline.js` in `history.js`\r\n\r\n### pathhandler.js\r\n`pathhandler.js` is a mix in to easily add the `cd`, `ls` and `pwd` commands as well as path completion. It has the same external dependencies of [jQuery](http://jquery.com/), [Underscore](http://underscorejs.org/) as `shell.js` and also uses Underscore templating.\r\n\r\nBy implementing the functions `getNode` and `getChildNodes`, this library adds path traversal, discovery and completion just like a bash shell.\r\n\r\n### history.js\r\n`history.js` implements a localStorage back command history storage that persists over page changes and reloads. It is used by the `shell.js` history command to list all executed commands, and by `readline.js` for up/down arrow and reverse search capabilities.\r\n\r\n## Changelog\r\n\r\n**0.2** -- 2013/01/07\r\n* console wrapper to allow debug logging to be turned on and off\r\n* refactored how pathhandler attaches to shell because it needs to keep a reference to the shell\r\n* refactored how prompts are set. now uses dedicated callback rather than returning the prompt in the `onEnter` callback\r\n* tested and made fixes to ensure compatibility with major modern browsers\r\n\r\n**0.1** -- 2013/01/04\r\n* Initial code-complete release\r\n","name":"Josh.js","google":"","tagline":"Toolkit for building bash-like shell in the browser, including full readline support"} \ No newline at end of file diff --git a/pathhandler.html b/pathhandler.html new file mode 100644 index 0000000..0bd5020 --- /dev/null +++ b/pathhandler.html @@ -0,0 +1,157 @@ + + + + + + Josh.js by sdether + + + + + + + + + + + + + + +
+ +
+

Josh.PathHandler

+ +

pathhandler.js is a mix in to easily add the cd, ls and + pwd commands as well as path completion. It has + Underscore as its dependency for templating, however since all + templates it uses are exposed, they could easily be replaced with a different function that accepts an argument object returns html. +

+ +

By implementing the functions getNode and + getChildNodes, this library adds path traversal, discovery and completion just like a bash shell. +

+ +

The Path Node

+ +

PathHandler deals with files/directories a path node with at least the following two fields:

+
{
+    name: 'localname',
+    path: '/full/path/to/localname'
+}
+

where name is the name of the node and + path is the absolute path to the node. PathHandler gets path nodes as the callback argument for + getNode and does not modify it itself, so any additional state required can be attached to the node and be relied on as being part of the node when it is provided to + getChildNodes or a template.

+ +

The Commands

+ +

PathHandler is used to add standard unix directory handling commands and path completion to Josh.Shell.

+ +

pwd

+ +

Prints the current directory, as defined by pathhandler.current.

+ +

ls [path]

+ +

Prints out the listing of all childNodes of the node represented by path. If path is not specified, pathhandler.current is used instead.

+ +

cd [path]

+ +

Changes pathhandler.current to the node found at path, if one could be found. If path is not specified, pathhandler.current is used instead, resulting in a no-op.

+ +

Templates

+ +

Each template is expected to a function that takes a data object and returns html. The default templates are built with + _.template but any templating system can be used as long as follows the same behavior.

+ +

not_found

+ +

Called when getNode returns null as its callback argument.

+ +

Data:

+
    +
  • cmd - command that resulted in a miss on path lookup
  • +
  • path - the path that did not match a node
  • +
+

ls

+ +

Called to generate output for a successful ls cmd.

+ +

Data:

+
    +
  • nodes - array of path nodes
  • +
+

pwd

+ +

Called to generate output for pwd cmd.

+ +

Data:

+
    +
  • node - the current node
  • +
+

prompt

+ +

Called to generate the new prompt after any cmd attempt (including after completions).

+ +

Data:

+
    +
  • node - the current node
  • +
+

Properties

+ +

current

+

Contains the current directory. It has to be initialized before activating the shell, since on activation, getPrompt will be called by the shell and requires current to be set to display the prompt. Could be changed manually, but generally is changed as a result of calling cd.

+ +

pathCompletionHandler

+

Contains the path completion handler used by PathHandler. Exposed so that other commands added that work on paths, can use it as their completion handler. It is used for the ls and cd commands. The only assumption it makes about paths is that they are separated by /.

+

Path completion is done by first finding the nearest node, i.e. if a trailing slash is found, it calls getNode with the path, otherwise it will call getNode and upon receiving null, will take the subpath to the nearest slash and call getNode again. If a node is found in either scenario, it then calls getChildnodes to get all all children, i.e. the possible completions and in turn call shell.bestMatch with any partial path after the slash and the list of possible child node names.

+ +

commandAndPathCompletionHandler

+ +

Contains a wrapper around pathCompletionHandler that replaces the default completion handler of the shell, so that a completion event without a known command can determine whether to complete as a command name or a path.

+ +

templates

+

Contains the templates described above.

+ +

Functions

+ +

new Josh.PathHandler(shell, config)

+

Create a new path handler and attach it to a shell instance.

+ +

getNode(path, callback)

+

This method is called with a path and a callback expecting a path node returned if the path is valid. The default implementation always calls callback with null. This is where custom path resolution logic goes and where path nodes are constructed.

+ +

getChildNodes(node, callback)

+ +

This method is called by the pathCompletionHandler after resolving a path to a node via getNode and expects its callback to be called with an array of pathnodes.

+ +

getPrompt

+ +

This method is called each time the prompt needs to be re-rendered, which in turn calls templates.prompt with pathhandler,current. Could be replaced for custom prompt behavior beyond altering the template.

+ +

+ +

Internal Dependencies

+ +

Josh.Shell

+ +

External Dependencies

+ +

Underscore

+
+
+ + + + \ No newline at end of file diff --git a/quakeconsole.html b/quakeconsole.html new file mode 100644 index 0000000..5872154 --- /dev/null +++ b/quakeconsole.html @@ -0,0 +1,220 @@ + + + + + + Josh.js by sdether + + + + + + + + + + + + + + + + + + + + + + + + +
+
Type help or hit TAB for a list of commands. Press + Ctrl-C to hide the console. +
+
+
+ +
+ +
+

Quake-style console with unix-style path handling

+ +

This tutorial shows implements a quake-style shell that drops down over your current page. Inside the shell a fake filesystem illustrates how + Pathhandler.js can be used to provide standard unix commands ls, cd, pwd and filepath tab completion. +

+ +

Try out the Console

+

Type ~ to activate the shell we will be building in this tutorial.

+ +

Hooking up PathHandler.js

+ +

PathHandler is a mix-in for Josh.Shell to provide provide the standard unix + ls, pwd and cd commands, as well + as standard bash-style path tab-completion. It expects a + Josh.Shell instance as its first argument so that it can + attach its command handlers to the shell as well as override the default handler to support completion of paths + starting with . or / without a leading command.

+
var shell = Josh.Shell();
+var pathhandler = new Josh.PathHandler(shell);
+ +

PathHandler operates on path nodes which are expected to be objects with a minimum structure of:

+ +
{
+    name: 'localname',
+    path: '/full/path/to/localname'
+}
+

where name is the name of the node and + path is the absolute path to the node. It does not modify + these nodes, so any additional state your implementation requires can be attached to the nodes and be relied on + as being part of the node when PathHandler provides one to your handler as an argument.

+ +

The pathhandler expects to be initialized with the current directory in the form pf a path node. For this + example, we've build up a full tree of nodes providing a skeleton unix file system. Our nodes take the structure of:

+
{
+    name: 'localname',
+    path: '/full/path/to/localname',
+    parent: {parent node},
+    children: [{child nodes}]
+}
+

This allows the example to simply navigate up and down a tree from any node. The tree is built by the helper builtTree() + whose implementation is not of consequence to how PathHandler works and is therefore just assumed to exist, so that we can create + the tree and assign its root as pathhandler.current.

+
var treeroot = buildTree();
+pathhandler.current = treeroot;
+ +

Implementing PathHandler's required methods

+

PathHandler requires two method, getNode and getChildNodes, to be provided in order to operate.

+ +

getNode gets called with a path string. This string is completely opaque to PathHandler, + i.e. supporting constructs such as . and .. are up to the implementor. PathHandler + calls getNode anytime it has a path and needs to determine what if any node exists at that path. Thish happens + during path completion as well as cd and ls execution. It simply provides the path and + expects its callback to be called with a pathnode for that path or null. The only assumption about paths that it does + have is that the path separator is /.

+ +
pathhandler.getNode = function(path, callback) {
+  if(!path) {
+    return callback(pathhandler.current);
+  }
+  var parts = _.filter(path.split('/'), function(x) {
+    return x;
+  });
+  var start = ((path || '')[0] == '/') ? treeroot : pathhandler.current;
+  return findNode(start, parts, callback);
+};
+

For this example, no path always means the current node, otherwise we split the path into its components and walk the tree + via a helper findNode from either the root or the current node depending on whether the path started with a /. findNode is specific to this implementation, since it can work on an in memory tree. In a service bound implementation the findNode logic would likely reside at the server and the callback called in the completion closure of an ajax call.

+
function findNode(current, parts, callback) {
+  if(!parts || parts.length == 0) {
+    return callback(current);
+  }
+  if(parts[0] == ".") {
+
+  } else if(parts[0] == "..") {
+    current = current.parent;
+  } else {
+    current = _.first(_.filter(current.childnodes, function(node) {
+      return node.name == parts[0];
+    }));
+  }
+  if(!current) {
+    return callback();
+  }
+  return findNode(current, _.rest(parts), callback);
+}
+ +

The second required method is getChildNodes and is used by path completion to determine the possible + completion candidates. Path completion first determines the nearest resolvable node for the given path. It does this + by first calling getNode on the current path and failing to get a node, looking for the nearest tail + / to find a parent and use the trailing characters as the partial path to be completed against children + found via getChildNodes. For our example, we've attached the child node objects directly to the pathnode + object, so we can simply return it. Usually this would be used to call the server with the provided node's path or id so that + the appropriate children could be retrieved.

+ + pathhandler.getChildNodes = function(node, callback) { + callback(node.childnodes); +}; + +

Setup Quake-console Behavior

+ +

The default name for the div the shell uses as its container is shell-panel, although that can be changed via + the shell config parameter shell-panel-id. The Josh.Shell display model is based on a + panel that defines the viewport of the console screen while the content is appended to a + view that is contained inside the panel and continuously scrolled down. For the quake-style console, + we want the panel to take up the width of the browser and overlay the page's top portion. For this we add the panel + right after the body tag:

+
<div id="shell-panel">
+  <div>Type <code>help</code> or hit <code>TAB</code> for a list of commands.
+    Press <code>Ctrl-C</code> to hide the console.
+  </div>
+  <div id="shell-view"></div>
+</div>
+

With css, we make sure this is initially invisible, is fixed in top position and has a high z-index so that it will + always be on top.

+ +

We use jquery-ui's resizable so that the shell can be resized by dragging its bottom edge.

+
var $consolePanel = $('#shell-panel');
+$consolePanel.resizable({ handles: "s"});
+ +

Next, we wire up a the keypress handler for shell activation, since Josh.ReadLine does not listen to keys + until the shell is activated. This handler will only execute while the shell is inactive and trigger on ~, + using jquery animaton to slide it down and give it focus.

+
$(document).keypress(function(event) {
+  if(shell.isActive()) {
+    return;
+  }
+  if(event.keyCode == 126) {
+    event.preventDefault();
+    shell.activate();
+    $consolePanel.slideDown();
+    $consolePanel.focus();
+  }
+});
+ +

Finally, we wire create a function to deactivate the shell, slide it back up and hide it and attach it to the shell's EOT (Ctrl-D on empty line) or a Cancel (Ctrl-C) signals: + we deactivate the shell and hide the console.

+
function hideAndDeactivate() {
+  shell.deactivate();
+  $consolePanel.slideUp();
+  $consolePanel.blur();
+}
+shell.onEOT(hideAndDeactivate);
+shell.onCancel(hideAndDeactivate);
+

Now the Shell is ready to be activated and it's faked unix file system browsed, all with minimal custom code.

+
+
+ + + + \ No newline at end of file diff --git a/readline.html b/readline.html new file mode 100644 index 0000000..8f0e115 --- /dev/null +++ b/readline.html @@ -0,0 +1,74 @@ + + + + + + Josh.js by sdether + + + + + + + + + + + + + + +
+ +
+

Josh.Readline

+ +

Josh.Readline is the underlying plumbing responsible for all keystroke handling, bringing + GNU Readline like line editing to the browser. It is used by + Josh.Shell to implement + bash-like command prompt behavior, but could easily be wired up to any text box requiring advanced key handling. It has no dependencies on any outside libraries, although it does require either + Josh.History and Josh.Killring or objects implementing the same calls.

+ +

Functions

+

When using Josh.Shell Readline is proxied for those calls that need to be exposed and otherwise invisible. The full API for readline is really only of interest for usage away from Josh.Shell.

+ +

new Josh.Readline(config)

+

isActive()

+

activate()

+

deactivate()

+

getLine

+

onActivate(eventHandler)

+

onDeactivate(eventHandler)

+

onChange(eventHandler)

+

onClear(eventHandler)

+

onEnter(eventHandler)

+

onCompletion(eventHandler)

+

onCancel(eventHandler)

+

onEOT(eventHandler)

+

onSearchStart(eventHandler)

+

onSearchEnd(eventHandler)

+

onSearchChange(eventHandler)

+

+

+ +

Internal Dependencies

+

Josh.History

+

Josh.Killring

+ +

External Dependencies

+

None

+
+
+ + + + \ No newline at end of file diff --git a/shell.html b/shell.html new file mode 100644 index 0000000..bfe18ef --- /dev/null +++ b/shell.html @@ -0,0 +1,112 @@ + + + + + + Josh.js by sdether + + + + + + + + + + + + + + +
+ +
+

Josh.Shell

+ +

It provides a simple console UI, using a panel for the console viewport and an auto-scrolling + view inside the panel. It uses Underscore templates for generating the view html, although any + template generator can be substituted as long as it can be expressed in the form of a function that takes a + JSON object of arguments and returns an html string. +

+ +

It also implements command handling so that new commands can be added by name with execution and completion + handlers. Out of the box, + shell.js provides the following commands:

+ +
    +
  • help - list all known commands (including user added)
  • +
  • clear - clear the "screen" i.e. viewport
  • +
  • history - show the command history captured by readline.js in + history.js +
  • +
+ +

Functions

+ +

new Josh.Shell(config)

+ +

Config keys:

+
    +
  • +
+ +

bestMatch(partial, possible)

+ +

commands()

+ +

isActive()

+ +

activate()

+ +

deactivate()

+ +

render()

+ +

refresh()

+ +

scrollToBottom()

+ +

getCommandHandler(commandHandler)

+ +

setCommandHandler(cmdName,cmdHandler)

+ +

setPrompt(prompt)

+ +

onEOT(eventHandler)

+ +

onCancel(eventHandler)

+ +

onInitialize(eventHandler)

+ +

onActivate(eventHandler)

+ +

onDeactivate(eventHandler)

+ +

onNewPrompt(eventHandler)

+ +

Internal Dependencies

+ +

Josh.Readline

+ +

Josh.History

+ +

External Dependencies

+ +

jQuery

+ +

Underscore

+
+
+ + + + \ No newline at end of file diff --git a/stylesheets/githubconsole.css b/stylesheets/githubconsole.css new file mode 100644 index 0000000..e8e2619 --- /dev/null +++ b/stylesheets/githubconsole.css @@ -0,0 +1,41 @@ +#shell-container { + display: none; + height: 400px; + width: 100%; + opacity: 0.9; + background-color: #002f05; + color: #00fe00; + position: fixed; + padding: 0px 20px 20px 20px; + top: 0; + left: 0; + z-index: 1000; + font-family: 'Source Code Pro', sans-serif; + font-size: 0.9em; +} +#shell-panel { + overflow: scroll; + overflow-x: hidden; + overflow-y: scroll; + height: 100%; + width: 98%; +} +#shell-status { + position: absolute; + left: 0; + bottom: 0; +} +ul.widelist li { + float: left; + list-style: none; + width: 33%; +} +.clear { + clear: both; +} +.userinfo img { + float: right; +} +.userinfo table { + width: 75%; +} \ No newline at end of file diff --git a/stylesheets/ie.css b/stylesheets/ie.css new file mode 100644 index 0000000..43882f2 --- /dev/null +++ b/stylesheets/ie.css @@ -0,0 +1,3 @@ +nav { + display: none; +} diff --git a/stylesheets/normalize.css b/stylesheets/normalize.css new file mode 100644 index 0000000..bc2ba93 --- /dev/null +++ b/stylesheets/normalize.css @@ -0,0 +1,459 @@ +/* normalize.css 2012-02-07T12:37 UTC - http://github.com/necolas/normalize.css */ +/* ============================================================================= + HTML5 display definitions + ========================================================================== */ +/* + * Corrects block display not defined in IE6/7/8/9 & FF3 + */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section, +summary { + display: block; +} + +/* + * Corrects inline-block display not defined in IE6/7/8/9 & FF3 + */ +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +/* + * Prevents modern browsers from displaying 'audio' without controls + */ +audio:not([controls]) { + display: none; +} + +/* + * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4 + * Known issue: no IE6 support + */ +[hidden] { + display: none; +} + +/* ============================================================================= + Base + ========================================================================== */ +/* + * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units + * http://clagnut.com/blog/348/#c790 + * 2. Prevents iOS text size adjust after orientation change, without disabling user zoom + * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ + */ +html { + font-size: 100%; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -ms-text-size-adjust: 100%; + /* 2 */ +} + +/* + * Addresses font-family inconsistency between 'textarea' and other form elements. + */ +html, +button, +input, +select, +textarea { + font-family: sans-serif; +} + +/* + * Addresses margins handled incorrectly in IE6/7 + */ +body { + margin: 0; +} + +/* ============================================================================= + Links + ========================================================================== */ +/* + * Addresses outline displayed oddly in Chrome + */ +a:focus { + outline: thin dotted; +} + +/* + * Improves readability when focused and also mouse hovered in all browsers + * people.opera.com/patrickl/experiments/keyboard/test + */ +a:hover, +a:active { + outline: 0; +} + +/* ============================================================================= + Typography + ========================================================================== */ +/* + * Addresses font sizes and margins set differently in IE6/7 + * Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5 + */ +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +h2 { + font-size: 1.5em; + margin: 0.83em 0; +} + +h3 { + font-size: 1.17em; + margin: 1em 0; +} + +h4 { + font-size: 1em; + margin: 1.33em 0; +} + +h5 { + font-size: 0.83em; + margin: 1.67em 0; +} + +h6 { + font-size: 0.75em; + margin: 2.33em 0; +} + +/* + * Addresses styling not present in IE7/8/9, S5, Chrome + */ +abbr[title] { + border-bottom: 1px dotted; +} + +/* + * Addresses style set to 'bolder' in FF3+, S4/5, Chrome +*/ +b, +strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +/* + * Addresses styling not present in S5, Chrome + */ +dfn { + font-style: italic; +} + +/* + * Addresses styling not present in IE6/7/8/9 + */ +mark { + background: #ff0; + color: #000; +} + +/* + * Addresses margins set differently in IE6/7 + */ +p, +pre { + margin: 1em 0; +} + +/* + * Corrects font family set oddly in IE6, S4/5, Chrome + * en.wikipedia.org/wiki/User:Davidgothberg/Test59 + */ +pre, +code, +kbd, +samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; +} + +/* + * 1. Addresses CSS quotes not supported in IE6/7 + * 2. Addresses quote property not supported in S4 + */ +/* 1 */ +q { + quotes: none; +} + +/* 2 */ +q:before, +q:after { + content: ''; + content: none; +} + +small { + font-size: 75%; +} + +/* + * Prevents sub and sup affecting line-height in all browsers + * gist.github.com/413930 + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ============================================================================= + Lists + ========================================================================== */ +/* + * Addresses margins set differently in IE6/7 + */ +dl, +menu, +ol, +ul { + margin: 1em 0; +} + +dd { + margin: 0 0 0 40px; +} + +/* + * Addresses paddings set differently in IE6/7 + */ +menu, +ol, +ul { + padding: 0 0 0 40px; +} + +/* + * Corrects list images handled incorrectly in IE7 + */ +nav ul, +nav ol { + list-style: none; + list-style-image: none; +} + +/* ============================================================================= + Embedded content + ========================================================================== */ +/* + * 1. Removes border when inside 'a' element in IE6/7/8/9, FF3 + * 2. Improves image quality when scaled in IE7 + * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ + */ +img { + border: 0; + /* 1 */ + -ms-interpolation-mode: bicubic; + /* 2 */ +} + +/* + * Corrects overflow displayed oddly in IE9 + */ +svg:not(:root) { + overflow: hidden; +} + +/* ============================================================================= + Figures + ========================================================================== */ +/* + * Addresses margin not present in IE6/7/8/9, S5, O11 + */ +figure { + margin: 0; +} + +/* ============================================================================= + Forms + ========================================================================== */ +/* + * Corrects margin displayed oddly in IE6/7 + */ +form { + margin: 0; +} + +/* + * Define consistent border, margin, and padding + */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/* + * 1. Corrects color not being inherited in IE6/7/8/9 + * 2. Corrects text not wrapping in FF3 + * 3. Corrects alignment displayed oddly in IE6/7 + */ +legend { + border: 0; + /* 1 */ + padding: 0; + white-space: normal; + /* 2 */ + *margin-left: -7px; + /* 3 */ +} + +/* + * 1. Corrects font size not being inherited in all browsers + * 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome + * 3. Improves appearance and consistency in all browsers + */ +button, +input, +select, +textarea { + font-size: 100%; + /* 1 */ + margin: 0; + /* 2 */ + vertical-align: baseline; + /* 3 */ + *vertical-align: middle; + /* 3 */ +} + +/* + * Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet + */ +button, +input { + line-height: normal; + /* 1 */ +} + +/* + * 1. Improves usability and consistency of cursor style between image-type 'input' and others + * 2. Corrects inability to style clickable 'input' types in iOS + * 3. Removes inner spacing in IE7 without affecting normal text inputs + * Known issue: inner spacing remains in IE6 + */ +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + /* 1 */ + -webkit-appearance: button; + /* 2 */ + *overflow: visible; + /* 3 */ +} + +/* + * Re-set default cursor for disabled elements + */ +button[disabled], +input[disabled] { + cursor: default; +} + +/* + * 1. Addresses box sizing set to content-box in IE8/9 + * 2. Removes excess padding in IE8/9 + * 3. Removes excess padding in IE7 + Known issue: excess padding remains in IE6 + */ +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ + *height: 13px; + /* 3 */ + *width: 13px; + /* 3 */ +} + +/* + * 1. Addresses appearance set to searchfield in S5, Chrome + * 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof) + */ +input[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + /* 2 */ + box-sizing: content-box; +} + +/* + * Removes inner padding and search cancel button in S5, Chrome on OS X + */ +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} + +/* + * Removes inner padding and border in FF3+ + * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ + */ +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* + * 1. Removes default vertical scrollbar in IE6/7/8/9 + * 2. Improves readability and alignment in all browsers + */ +textarea { + overflow: auto; + /* 1 */ + vertical-align: top; + /* 2 */ +} + +/* ============================================================================= + Tables + ========================================================================== */ +/* + * Remove most spacing between table cells + */ +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/stylesheets/pygment_trac.css b/stylesheets/pygment_trac.css new file mode 100644 index 0000000..c79bef4 --- /dev/null +++ b/stylesheets/pygment_trac.css @@ -0,0 +1,70 @@ +.highlight .hll { background-color: #404040 } +.highlight { color: #d0d0d0 } +.highlight .c { color: #999999; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .g { color: #d0d0d0 } /* Generic */ +.highlight .k { color: #6ab825; font-weight: normal } /* Keyword */ +.highlight .l { color: #d0d0d0 } /* Literal */ +.highlight .n { color: #d0d0d0 } /* Name */ +.highlight .o { color: #d0d0d0 } /* Operator */ +.highlight .x { color: #d0d0d0 } /* Other */ +.highlight .p { color: #d0d0d0 } /* Punctuation */ +.highlight .cm { color: #999999; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #cd2828; font-weight: normal } /* Comment.Preproc */ +.highlight .c1 { color: #999999; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #e50808; font-weight: normal; background-color: #520000 } /* Comment.Special */ +.highlight .gd { color: #d22323 } /* Generic.Deleted */ +.highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #d22323 } /* Generic.Error */ +.highlight .gh { color: #ffffff; font-weight: normal } /* Generic.Heading */ +.highlight .gi { color: #589819 } /* Generic.Inserted */ +.highlight .go { color: #cccccc } /* Generic.Output */ +.highlight .gp { color: #aaaaaa } /* Generic.Prompt */ +.highlight .gs { color: #d0d0d0; font-weight: normal } /* Generic.Strong */ +.highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ +.highlight .gt { color: #d22323 } /* Generic.Traceback */ +.highlight .kc { color: #6ab825; font-weight: normal } /* Keyword.Constant */ +.highlight .kd { color: #6ab825; font-weight: normal } /* Keyword.Declaration */ +.highlight .kn { color: #6ab825; font-weight: normal } /* Keyword.Namespace */ +.highlight .kp { color: #6ab825 } /* Keyword.Pseudo */ +.highlight .kr { color: #6ab825; font-weight: normal } /* Keyword.Reserved */ +.highlight .kt { color: #6ab825; font-weight: normal } /* Keyword.Type */ +.highlight .ld { color: #d0d0d0 } /* Literal.Date */ +.highlight .m { color: #3677a9 } /* Literal.Number */ +.highlight .s { color: #9dd5f1 } /* Literal.String */ +.highlight .na { color: #bbbbbb } /* Name.Attribute */ +.highlight .nb { color: #24909d } /* Name.Builtin */ +.highlight .nc { color: #447fcf; text-decoration: underline } /* Name.Class */ +.highlight .no { color: #40ffff } /* Name.Constant */ +.highlight .nd { color: #ffa500 } /* Name.Decorator */ +.highlight .ni { color: #d0d0d0 } /* Name.Entity */ +.highlight .ne { color: #bbbbbb } /* Name.Exception */ +.highlight .nf { color: #447fcf } /* Name.Function */ +.highlight .nl { color: #d0d0d0 } /* Name.Label */ +.highlight .nn { color: #447fcf; text-decoration: underline } /* Name.Namespace */ +.highlight .nx { color: #d0d0d0 } /* Name.Other */ +.highlight .py { color: #d0d0d0 } /* Name.Property */ +.highlight .nt { color: #6ab825;} /* Name.Tag */ +.highlight .nv { color: #40ffff } /* Name.Variable */ +.highlight .ow { color: #6ab825; font-weight: normal } /* Operator.Word */ +.highlight .w { color: #666666 } /* Text.Whitespace */ +.highlight .mf { color: #3677a9 } /* Literal.Number.Float */ +.highlight .mh { color: #3677a9 } /* Literal.Number.Hex */ +.highlight .mi { color: #3677a9 } /* Literal.Number.Integer */ +.highlight .mo { color: #3677a9 } /* Literal.Number.Oct */ +.highlight .sb { color: #9dd5f1 } /* Literal.String.Backtick */ +.highlight .sc { color: #9dd5f1 } /* Literal.String.Char */ +.highlight .sd { color: #9dd5f1 } /* Literal.String.Doc */ +.highlight .s2 { color: #9dd5f1 } /* Literal.String.Double */ +.highlight .se { color: #9dd5f1 } /* Literal.String.Escape */ +.highlight .sh { color: #9dd5f1 } /* Literal.String.Heredoc */ +.highlight .si { color: #9dd5f1 } /* Literal.String.Interpol */ +.highlight .sx { color: #ffa500 } /* Literal.String.Other */ +.highlight .sr { color: #9dd5f1 } /* Literal.String.Regex */ +.highlight .s1 { color: #9dd5f1 } /* Literal.String.Single */ +.highlight .ss { color: #9dd5f1 } /* Literal.String.Symbol */ +.highlight .bp { color: #24909d } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #40ffff } /* Name.Variable.Class */ +.highlight .vg { color: #40ffff } /* Name.Variable.Global */ +.highlight .vi { color: #40ffff } /* Name.Variable.Instance */ +.highlight .il { color: #3677a9 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/stylesheets/styles.css b/stylesheets/styles.css new file mode 100644 index 0000000..785bf05 --- /dev/null +++ b/stylesheets/styles.css @@ -0,0 +1,797 @@ +/* normalize.css 2012-02-07T12:37 UTC - http://github.com/necolas/normalize.css */ +/* ============================================================================= + HTML5 display definitions + ========================================================================== */ +/* + * Corrects block display not defined in IE6/7/8/9 & FF3 + */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section, +summary { + display: block; +} + +/* + * Corrects inline-block display not defined in IE6/7/8/9 & FF3 + */ +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +/* + * Prevents modern browsers from displaying 'audio' without controls + */ +audio:not([controls]) { + display: none; +} + +/* + * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4 + * Known issue: no IE6 support + */ +[hidden] { + display: none; +} + +/* ============================================================================= + Base + ========================================================================== */ +/* + * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units + * http://clagnut.com/blog/348/#c790 + * 2. Prevents iOS text size adjust after orientation change, without disabling user zoom + * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ + */ +html { + font-size: 100%; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -ms-text-size-adjust: 100%; + /* 2 */ +} + +/* + * Addresses font-family inconsistency between 'textarea' and other form elements. + */ +html, +button, +input, +select, +textarea { + font-family: sans-serif; +} + +/* + * Addresses margins handled incorrectly in IE6/7 + */ +body { + margin: 0; +} + +/* ============================================================================= + Links + ========================================================================== */ +/* + * Addresses outline displayed oddly in Chrome + */ +a:focus { + outline: thin dotted; +} + +/* + * Improves readability when focused and also mouse hovered in all browsers + * people.opera.com/patrickl/experiments/keyboard/test + */ +a:hover, +a:active { + outline: 0; +} + +/* ============================================================================= + Typography + ========================================================================== */ +/* + * Addresses font sizes and margins set differently in IE6/7 + * Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5 + */ +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +h2 { + font-size: 1.5em; + margin: 0.83em 0; +} + +h3 { + font-size: 1.17em; + margin: 1em 0; +} + +h4 { + font-size: 1em; + margin: 1.33em 0; +} + +h5 { + font-size: 0.83em; + margin: 1.67em 0; +} + +h6 { + font-size: 0.75em; + margin: 2.33em 0; +} + +/* + * Addresses styling not present in IE7/8/9, S5, Chrome + */ +abbr[title] { + border-bottom: 1px dotted; +} + +/* + * Addresses style set to 'bolder' in FF3+, S4/5, Chrome +*/ +b, +strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +/* + * Addresses styling not present in S5, Chrome + */ +dfn { + font-style: italic; +} + +/* + * Addresses styling not present in IE6/7/8/9 + */ +mark { + background: #ff0; + color: #000; +} + +/* + * Addresses margins set differently in IE6/7 + */ +p, +pre { + margin: 1em 0; +} + +/* + * Corrects font family set oddly in IE6, S4/5, Chrome + * en.wikipedia.org/wiki/User:Davidgothberg/Test59 + */ +pre, +code, +kbd, +samp { + font-family: monospace, serif; + _font-family: 'courier new', monospace; + font-size: 1em; +} + +/* + * 1. Addresses CSS quotes not supported in IE6/7 + * 2. Addresses quote property not supported in S4 + */ +/* 1 */ +q { + quotes: none; +} + +/* 2 */ +q:before, +q:after { + content: ''; + content: none; +} + +small { + font-size: 75%; +} + +/* + * Prevents sub and sup affecting line-height in all browsers + * gist.github.com/413930 + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* ============================================================================= + Lists + ========================================================================== */ +/* + * Addresses margins set differently in IE6/7 + */ +dl, +menu, +ol, +ul { + margin: 1em 0; +} + +dd { + margin: 0 0 0 40px; +} + +/* + * Addresses paddings set differently in IE6/7 + */ +menu, +ol, +ul { + padding: 0 0 0 40px; +} + +/* + * Corrects list images handled incorrectly in IE7 + */ +nav ul, +nav ol { + list-style: none; + list-style-image: none; +} + +/* ============================================================================= + Embedded content + ========================================================================== */ +/* + * 1. Removes border when inside 'a' element in IE6/7/8/9, FF3 + * 2. Improves image quality when scaled in IE7 + * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ + */ +img { + border: 0; + /* 1 */ + -ms-interpolation-mode: bicubic; + /* 2 */ +} + +/* + * Corrects overflow displayed oddly in IE9 + */ +svg:not(:root) { + overflow: hidden; +} + +/* ============================================================================= + Figures + ========================================================================== */ +/* + * Addresses margin not present in IE6/7/8/9, S5, O11 + */ +figure { + margin: 0; +} + +/* ============================================================================= + Forms + ========================================================================== */ +/* + * Corrects margin displayed oddly in IE6/7 + */ +form { + margin: 0; +} + +/* + * Define consistent border, margin, and padding + */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/* + * 1. Corrects color not being inherited in IE6/7/8/9 + * 2. Corrects text not wrapping in FF3 + * 3. Corrects alignment displayed oddly in IE6/7 + */ +legend { + border: 0; + /* 1 */ + padding: 0; + white-space: normal; + /* 2 */ + *margin-left: -7px; + /* 3 */ +} + +/* + * 1. Corrects font size not being inherited in all browsers + * 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome + * 3. Improves appearance and consistency in all browsers + */ +button, +input, +select, +textarea { + font-size: 100%; + /* 1 */ + margin: 0; + /* 2 */ + vertical-align: baseline; + /* 3 */ + *vertical-align: middle; + /* 3 */ +} + +/* + * Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet + */ +button, +input { + line-height: normal; + /* 1 */ +} + +/* + * 1. Improves usability and consistency of cursor style between image-type 'input' and others + * 2. Corrects inability to style clickable 'input' types in iOS + * 3. Removes inner spacing in IE7 without affecting normal text inputs + * Known issue: inner spacing remains in IE6 + */ +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + /* 1 */ + -webkit-appearance: button; + /* 2 */ + *overflow: visible; + /* 3 */ +} + +/* + * Re-set default cursor for disabled elements + */ +button[disabled], +input[disabled] { + cursor: default; +} + +/* + * 1. Addresses box sizing set to content-box in IE8/9 + * 2. Removes excess padding in IE8/9 + * 3. Removes excess padding in IE7 + Known issue: excess padding remains in IE6 + */ +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ + *height: 13px; + /* 3 */ + *width: 13px; + /* 3 */ +} + +/* + * 1. Addresses appearance set to searchfield in S5, Chrome + * 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof) + */ +input[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + /* 2 */ + box-sizing: content-box; +} + +/* + * Removes inner padding and search cancel button in S5, Chrome on OS X + */ +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} + +/* + * Removes inner padding and border in FF3+ + * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ + */ +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/* + * 1. Removes default vertical scrollbar in IE6/7/8/9 + * 2. Improves readability and alignment in all browsers + */ +textarea { + overflow: auto; + /* 1 */ + vertical-align: top; + /* 2 */ +} + +/* ============================================================================= + Tables + ========================================================================== */ +/* + * Remove most spacing between table cells + */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +body { + padding: 0px 0 20px 0px; + margin: 0px; + font: 14px/1.5 "OpenSansRegular", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #f0e7d5; + font-weight: normal; + background: #252525; + background-attachment: fixed !important; + background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #2a2a29), color-stop(100%, #1c1c1c)); + background: -webkit-linear-gradient(#2a2a29, #1c1c1c); + background: -moz-linear-gradient(#2a2a29, #1c1c1c); + background: -o-linear-gradient(#2a2a29, #1c1c1c); + background: -ms-linear-gradient(#2a2a29, #1c1c1c); + background: linear-gradient(#2a2a29, #1c1c1c); +} + +h1, h2, h3, h4, h5, h6 { + color: #e8e8e8; + margin: 0 0 10px; + font-family: 'OpenSansRegular', "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: normal; +} + +p, ul, ol, table, pre, dl { + margin: 0 0 20px; +} + +h1, h2, h3 { + line-height: 1.1; +} + +h1 { + font-size: 28px; +} + +h2 { + font-size: 24px; +} + +h4, h5, h6 { + color: #e8e8e8; +} + +h3 { + font-size: 18px; + line-height: 24px; + font-family: 'OpenSansRegular', "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-weight: normal; + color: #b6b6b6; +} + +a { + color: #ffcc00; + font-weight: 400; + text-decoration: none; +} + +a:hover { + color: #ffeb9b; +} + +a small { + font-size: 11px; + color: #666; + margin-top: -0.6em; + display: block; +} + +ul { + list-style-image: url("http://github.com/images/bullet.png"); +} + +strong { + font-family: 'OpenSansBold', "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-weight: normal; +} + +.wrapper { + max-width: 650px; + margin: 0 auto; + position: relative; + padding: 0 20px; +} + +section img { + max-width: 100%; +} + +blockquote { + border-left: 3px solid #ffcc00; + margin: 0; + padding: 0 0 0 20px; + font-style: italic; +} + +code { + font-family: 'Source Code Pro', "Lucida Sans", Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; + color: #efefef; + border: 1px solid #3a3a39; + background-color: #2a2a29; + border-radius: 5px; + -moz-border-radius: 4px; + padding: 0px 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} + +a code { + text-decoration: underline; + color: #efefef; +} + +a:hover code { + background-color: #4a4a49; + border: 1px dotted #6a6a69; +} + +pre code { + color: #efefef; + text-shadow: 0px 1px 0px #000; + margin: 0; + padding: 0; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th { + text-align: left; + padding: 5px 10px; + border-bottom: 1px solid #434343; + color: #b6b6b6; + font-family: 'OpenSansSemibold', "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-weight: normal; +} + +td { + text-align: left; + padding: 5px 10px; + border-bottom: 1px solid #434343; +} + +hr { + border: 0; + outline: none; + height: 3px; + background: transparent url("http://github.com/images/hr.gif") center center repeat-x; + margin: 0 0 20px; +} + +dt { + color: #F0E7D5; + font-family: 'OpenSansSemibold', "Helvetica Neue", Helvetica, Arial, sans-serif !important; + font-weight: normal; +} + +#header { + z-index: 100; + left: 0; + top: 0px; + height: 60px; + width: 100%; + position: fixed; + background: url(http://github.com/images/nav-bg.gif) #353535; + border-bottom: 4px solid #434343; + -moz-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); + -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); + -o-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); +} + +#header nav { + max-width: 650px; + margin: 0 auto; + padding: 0 10px; + margin: 6px auto; +} + +#header nav li { + font-family: 'OpenSansLight', "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: normal; + list-style: none; + display: inline; + color: white; + line-height: 50px; + text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.2); + font-size: 14px; +} + +#header nav li a { + color: white; + border: 1px solid #5d910b; + background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #93bd20), color-stop(100%, #659e10)); + background: -webkit-linear-gradient(#93bd20, #659e10); + background: -moz-linear-gradient(#93bd20, #659e10); + background: -o-linear-gradient(#93bd20, #659e10); + background: -ms-linear-gradient(#93bd20, #659e10); + background: linear-gradient(#93bd20, #659e10); + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; + -moz-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0px 3px 7px rgba(0, 0, 0, 0.7); + -webkit-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0px 3px 7px rgba(0, 0, 0, 0.7); + -o-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0px 3px 7px rgba(0, 0, 0, 0.7); + box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.3), 0px 3px 7px rgba(0, 0, 0, 0.7); + background-color: #93bd20; + padding: 10px 12px; + margin-top: 6px; + line-height: 14px; + font-size: 14px; + display: inline-block; + text-align: center; +} + +#header nav li a:hover { + background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #749619), color-stop(100%, #527f0e)); + background: -webkit-linear-gradient(#749619, #527f0e); + background: -moz-linear-gradient(#749619, #527f0e); + background: -o-linear-gradient(#749619, #527f0e); + background: -ms-linear-gradient(#749619, #527f0e); + background: linear-gradient(#749619, #527f0e); + background-color: #659e10; + border: 1px solid #527f0e; + -moz-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 0px rgba(0, 0, 0, 0); + -webkit-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 0px rgba(0, 0, 0, 0); + -o-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 0px rgba(0, 0, 0, 0); + box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 0px rgba(0, 0, 0, 0); +} + +#header nav li.fork { + float: left; + margin-left: 0px; +} + +#header nav li.downloads { + float: right; + margin-left: 6px; +} + +#header nav li.title { + float: right; + margin-right: 10px; + font-size: 11px; +} + +section { + max-width: 650px; + padding: 30px 0px 50px 0px; + margin: 20px 0; + margin-top: 70px; +} + +section #title { + border: 0; + outline: none; + margin: 0 0 50px 0; + padding: 0 0 5px 0; +} + +section #title h1 { + font-family: 'OpenSansLight', "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: normal; + font-size: 40px; + text-align: center; + line-height: 36px; +} + +section #title p { + color: #d7cfbe; + font-family: 'OpenSansLight', "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: normal; + font-size: 18px; + text-align: center; +} + +section #title .credits { + font-size: 11px; + font-family: 'OpenSansRegular', "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: normal; + color: #696969; + margin-top: -10px; +} + +section #title .credits.left { + float: left; +} + +section #title .credits.right { + float: right; +} + +@media print, screen and (max-width: 720px) { + #title .credits { + display: block; + width: 100%; + line-height: 30px; + text-align: center; + } + + #title .credits .left { + float: none; + display: block; + } + + #title .credits .right { + float: none; + display: block; + } +} + +@media print, screen and (max-width: 480px) { + #header { + margin-top: -20px; + } + + section { + margin-top: 40px; + } + + nav { + display: none; + } +} + +#demo { + border: 1px dashed #E6EBE0; + background: #454545; + padding: 2px; +} diff --git a/stylesheets/tomorrow-night.css b/stylesheets/tomorrow-night.css new file mode 100644 index 0000000..cf2c44d --- /dev/null +++ b/stylesheets/tomorrow-night.css @@ -0,0 +1,52 @@ +/* Tomorrow Night Theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ +.tomorrow-comment, pre .comment, pre .title { + color: #969896; +} + +.tomorrow-red, pre .variable, pre .attribute, pre .tag, pre .regexp, pre .ruby .constant, pre .xml .tag .title, pre .xml .pi, pre .xml .doctype, pre .html .doctype, pre .css .id, pre .css .class, pre .css .pseudo { + color: #cc6666; +} + +.tomorrow-orange, pre .number, pre .preprocessor, pre .built_in, pre .literal, pre .params, pre .constant { + color: #de935f; +} + +.tomorrow-yellow, pre .class, pre .ruby .class .title, pre .css .rules .attribute { + color: #f0c674; +} + +.tomorrow-green, pre .string, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata { + color: #b5bd68; +} + +.tomorrow-aqua, pre .css .hexcolor { + color: #8abeb7; +} + +.tomorrow-blue, pre .function, pre .python .decorator, pre .python .title, pre .ruby .function .title, pre .ruby .title .keyword, pre .perl .sub, pre .javascript .title, pre .coffeescript .title { + color: #81a2be; +} + +.tomorrow-purple, pre .keyword, pre .javascript .function { + color: #b294bb; +} + +pre code { + display: block; + background: #1d1f21; + color: #c5c8c6; + padding: 0.5em; +} + +pre .coffeescript .javascript, +pre .javascript .xml, +pre .tex .formula, +pre .xml .javascript, +pre .xml .vbscript, +pre .xml .css, +pre .xml .cdata { + opacity: 0.5; +}