diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..14cc7c6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ +### 1.2 - 20100721 + +* Auto-proofread is now disabled by default +* The auto-proofread option now warns users about bad things that could happen (the feature is pretty safe though). +* AtD now refuses to attach to Google spreadsheets +* Widget positioning now works with editors that have a z-index set. +* Disabled AtD in WYSIWYG editor for http://drafts.blogger.com (couldn't get it to work right) +* Added an option to disable "no writing errors were found" message box. +* Added a load delay to GMail (should fix widget not showing up sometimes on GMail). + +### 1.1 - 20100524 + +* Fixed a typo on preferences page. +* AtD toolbar button is now in the address bar, added an option to hide it +* Removed auto-proofread feature from Chrome/Stable channel. Upgrade if you want it. A JavaScript + function AtD relies on does not work as expected in Stable channel. This fixes the Facebook comment + submit issue many of you reported. +* AtD now loads CSS after page load and only when AtD is enabled on the current site. +* Added a hack to prevent AtD from loading on Acid3 test page. Chrome w/ AtD loaded now passes. +* Auto-proofread no longer activated on Like, Unlike, and Share Facebook events. +* Updated auto-proofread dialog to get explicit about OK/Cancel buttons. +* AtD does a better job of removing/restoring its widget when the editor becomes hidden/visible. This fixes an issue that affected blogger users losing the AtD widget after switching editors. +* Added a check to prevent WYSIWYG editor proofreader from inheriting properties that could mess up the experience. This means you can edit while proofreading on Blogger and should fix the menu cut-off issue in some CMSs +* AtD widget in proofread mode now lines up with widget in non-proofread mode on Google Docs. +* _To protect your data_, text sent to AtD server for grammar/spell check is now sent over SSL. + +### 1.0 - 20100514 + +* initial release \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e13fa6 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +atd-chrome +========== + +### Chrome extensions for After the Deadline spelling, style and grammar checker + +An extension for [Chrome](https://www.google.com/intl/en/chrome/browser/) that uses the After the Deadline spelling, style, and grammar checker. + +Automattic no longer supports this plugin. We're putting it on Github so that you can feel free to fork it, hack it, and release your own version. + +### License + +GPLv2 + +### Commercial use and running your own server + +This extension requires a running instance of an [After the Deadline Server](https://github.com/automattic/atd-server) in order to work. Automattic operates an that you can use for personal use as long as you don't send too much traffic. The extension is configured to use this instance by default. + +For high volume and commercial uses of AtD, you must run your own server. The code is available on Github: [After the Deadline Server](https://github.com/automattic/atd-server). See the [After the Deadline Developer's page](http://open.afterthedeadline.com/) for more information, and check out the [AtD Developers Google Group](http://groups.google.com/group/atd-developers) for discussion and community support. + +When you run your own server, replace `service.afterthedeadline.com` with your server's hostname. + +### Contact + +We (Automattic) are no longer supporting this extension. This code has always been open source. We're putting it on Github so that you can feel free to fork it, hack it, and release your own version. + +Join the [atd-developers](http://groups.google.com/group/atd-developers) list for support. diff --git a/action/actions.js b/action/actions.js new file mode 100644 index 0000000..38b2031 --- /dev/null +++ b/action/actions.js @@ -0,0 +1,61 @@ +function goTo(url) { + chrome.tabs.getAllInWindow(undefined, function(tabs) { + for (var i = 0, tab; tab = tabs[i]; i++) { + if (tab.url && tab.url == url) { + chrome.tabs.update(tab.id, {selected: true}); + return; + } + } + chrome.tabs.create({ url: url }); + }); +} + +function currentSite(callback) { + chrome.tabs.getSelected(null, function(t) { + callback(t.url); + window.close(); + }); +} + +function toHost(url) { + var host = new RegExp('(https{0,1}://.*?)/.*').exec(url); + if (host == null) + host = url; + else + host = host[1]; + return host; +} + +function __enable() { + currentSite(function(url) { + var host = toHost(url); + var sites = localStorage['sites'].split(/,\s+/); + + var newsites = []; + for (var x = 0; x < sites.length; x++) { + var site = sites[x]; + if (host != site) + newsites.push(site); + } + localStorage['sites'] = newsites.join(', '); + chrome.extension.sendRequest({ command: 'refreshTabs' }); + }); +} + +function __disable() { + currentSite(function(url) { + var sites = localStorage['sites'].split(/,\s+/); + var host = toHost(url); + sites.push(host); + localStorage['sites'] = sites.join(', '); + chrome.extension.sendRequest({ command: 'refreshTabs' }); + }); +} + +function home() { + goTo('http://chrome.afterthedeadline.com/proofreading-for-google-chrome-help/'); +} + +function options() { + goTo('chrome-extension://' + location.hostname + '/options/options.html'); +} diff --git a/action/disable.html b/action/disable.html new file mode 100644 index 0000000..98caaff --- /dev/null +++ b/action/disable.html @@ -0,0 +1,17 @@ + + + + + + +

AtD Actions:

+ + + + diff --git a/action/enable.html b/action/enable.html new file mode 100644 index 0000000..30c8540 --- /dev/null +++ b/action/enable.html @@ -0,0 +1,17 @@ + + + + + + +

AtD Actions:

+ +
    +
  1. Enable on this site
  2. +
  3. View Options
  4. +
  5. Documentation
  6. +
+ + diff --git a/action/style.css b/action/style.css new file mode 100644 index 0000000..4672908 --- /dev/null +++ b/action/style.css @@ -0,0 +1,15 @@ +body { + width: 200px; + background: #fff; + font: 14px "Lucida Grande", Arial, Helvetica, sans-serif; +} +a { + color: #1390ec; +} + +a:visited { + color: #006; +} +a:hover { + color: #000; +} diff --git a/background.html b/background.html new file mode 100644 index 0000000..2ffc955 --- /dev/null +++ b/background.html @@ -0,0 +1,186 @@ + + + + + + + + diff --git a/css/atd.css b/css/atd.css new file mode 100644 index 0000000..120c15a --- /dev/null +++ b/css/atd.css @@ -0,0 +1,154 @@ +/* images converted to base64 courtesy of: http://webcodertools.com/imagetobase64converter/Create + *props* to Chrome for not providing a way to reference the current extensions resources from CSS. + Hard coding the ID does not work because the ID is different when packaged. Props Chrome, Props!!! */ + +div.afterthedeadline-button +{ + position: absolute !important; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANFJREFUeNrklMENgzAMRQPqCBxy4JARMkVOUQZkDOQDY2SIHLIFxVGpjKkDVJxaSwh9Bz9/yxHNPM/qzmj+HBhCwJdfHnilfN/3KqUEpGaXG8fxfdiyBgVmrfVrAguJLueswSYeVGDnrus8zSEsxigCcs50op1DRYuxAerVIWoO01qD6HBxhyNuNDpem9Dxp2lSHCYt5TC4s9pSLsN4tHfCJKAXvj2ESffwE9SfnYIDoQKHb4A7qHPuNKy2lAJAmDHm0i2obRkQNgwD/Nb/8CnAAEn1bI8kYlTuAAAAAElFTkSuQmCC) transparent no-repeat top left !important; + width: 20px !important; + height: 20px !important; + padding: 0px !important; + margin: 0px !important; + top: 0px; + left: 0px; +} + +div.afterthedeadline-button.afterthedeadline-focus, div.afterthedeadline-button.afterthedeadline-hover, div.afterthedeadline-button:hover +{ + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAABCklEQVQ4EdWSMQ7CMAxFE+jAgBBiQLDToRNbxcY9QLCzM7HD1FtwEraKM3AIRoaKwkdyZKcOqFUXsrj/235x0tjBOjNtrk6bMLD+E1iyayjtbME1UprnWvwjo9lG6cZBylvO9ScPzxG8DwF8T2M4DLXQxfUcBAyfJ7c56gUQBm/GBtC0CTRfgN07B7FZxAu6o9gUJncWtEljNyE/fj9PjA9Do23ysLXJaIrKkSkRit9g6KkF/AVTgb3lXvw1mhS+dmeUpygmJBhFKvI1+VoUwMclc0+AIBR5TgORJ4AweSNgq/lWeNQYihUghwI2TcahXtVXgQQF7LibuGtQCZ7Z6GF7DCGDE4qqGuIF9ktf4BxMAREAAAAASUVORK5CYII=) !important; +} + +div.afterthedeadline-button.afterthedeadline-done +{ + background-image: url(data:image/gif;base64,R0lGODlhDgAOAMIGAGwAAJoAANQAAPwADP8pKf+AZ////////yH5BAEKAAcALAAAAAAOAA4AAAMxeHEq3G8NdeZ6Rww76QPOFGZfZW7PBmBs2bLri8XYFbVBHtm1rsu5AEEWIRSIj6MsAQA7) !important; +} + +/* AtD error styles */ + +.hiddenSpellError +{ + border-bottom: 2px solid red !important; + cursor: pointer; +} + +.hiddenGrammarError +{ + border-bottom: 2px solid green !important; + cursor: pointer; +} + +.hiddenSuggestion +{ + border-bottom: 2px solid blue !important; + cursor: pointer; +} + +/* Menu styles derived from: + * jquery.spellchecker.js - a simple jQuery Spell Checker + * Copyright (c) 2008 Richard Willis + * MIT license : http://www.opensource.org/licenses/mit-license.php + * Project : http://jquery-spellchecker.googlecode.com + */ + +div.suggestmenu +{ + background: #fff; + position: absolute; + display: none; + z-index: 9999; + overflow: none; + margin-top: 1px; + text-align: left; + font-size: 12px; + font-family: Tahoma, Verdana, Arial, Helvetica; + cursor: default; +} + +div.suggestmenu strong +{ + background: #ddd; + font-weight: bold; + padding:3px 6px 3px 6px; + display:block; + border:1px solid #ccc; + color: black; + cursor: default; +} + +div.suggestmenu em +{ + text-align:center; + padding:3px 6px 3px 6px; + display:block; + border-top:1px solid #ccc; + border-left:1px solid #ccc; + cursor: default; +} + +div.suggestmenu div +{ + background: #fff; + border-left:1px solid #bbb; + border-right:1px solid #bbb; + padding:3px 6px 3px 6px; + display:block; + margin:0px; + text-decoration:none; + color:#333; + outline:none + cursor: default; +} + +div.suggestmenu div.first +{ + border-top:1px solid #ccc +} + +.spell_sep_bottom +{ + border-bottom: 1px solid #ccc; +} + +.spell_sep_top +{ + border-top: 1px solid #ccc; +} + +div.suggestmenu div:hover +{ + color:#000; + background: #dbecf3 +} + +div.suggestmenu .foot +{ + border-top:1px solid #ddd; + background:#fff +} + +div.suggestmenu .foot div +{ + outline:none +} + +/* make it so AtD can work with TinyMCE */ +div #AtD_Content * +{ + white-space: pre-wrap !important; + overflow: visible !important; +} + +/* make it so some font elements are inherited */ +div #AtD_Content div, div #AtD_Content p, div #AtD_Content span +{ + font-size: inherit !important; + font-family: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; +} + +span.mceItemHidden, span.hiddenSuggestion, span.hiddenSpellError, span.hiddenGrammarError +{ + float: none; +} \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..4ca4658 Binary files /dev/null and b/icon.png differ diff --git a/images/icon128.png b/images/icon128.png new file mode 100644 index 0000000..4b4bc89 Binary files /dev/null and b/images/icon128.png differ diff --git a/images/icon48.png b/images/icon48.png new file mode 100644 index 0000000..1fc1e87 Binary files /dev/null and b/images/icon48.png differ diff --git a/images/icon48bw.png b/images/icon48bw.png new file mode 100644 index 0000000..4487040 Binary files /dev/null and b/images/icon48bw.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..ad60788 --- /dev/null +++ b/manifest.json @@ -0,0 +1,46 @@ +{ + "name": "After the Deadline", + "version": "1.2", + "description": "Check spelling, style, and grammar in your browser", + + "icons" : { "48" : "images/icon48.png", + "128" : "images/icon128.png" }, + + "page_action": { + "default_icon" : "images/icon48.png", + "default_title" : "After the Deadline - Checks Spelling and Grammar", + "popup" : "action/disable.html" + }, + + "options_page": "options/options.html", + "background_page": "background.html", + + "permissions": [ + "tabs", + "https://service.afterthedeadline.com/*", + "https://de.service.afterthedeadline.com/*", + "https://en.service.afterthedeadline.com/*", + "https://es.service.afterthedeadline.com/*", + "https://fr.service.afterthedeadline.com/*", + "https://pt.service.afterthedeadline.com/*" + ], + + + "content_scripts": [ + { + "matches": ["http://*/*", "https://*/*"], + + "js": ["scripts/jquery-1.4.2.js", + "scripts/atd.core.js", + "scripts/jquery.atd.js", + "scripts/jquery.atd.proofreader.js", + "scripts/jquery.atd.div.js", + "scripts/jquery.atd.iframe.js", + "scripts/widget.js", + "scripts/autoproof.js", + "scripts/atd-chrome.js"], + + "all_frames": true, + "run_at": "document_end" + }] +} diff --git a/options/automattic-small.png b/options/automattic-small.png new file mode 100644 index 0000000..ee7b392 Binary files /dev/null and b/options/automattic-small.png differ diff --git a/options/logo.png b/options/logo.png new file mode 100644 index 0000000..e758e7d Binary files /dev/null and b/options/logo.png differ diff --git a/options/options.html b/options/options.html new file mode 100644 index 0000000..875855e --- /dev/null +++ b/options/options.html @@ -0,0 +1,273 @@ + + + Options - After the Deadline + + + + +
+ + + +
+

Extension Options

+ +

Activate proofreading with the following key stroke:

+ +
    +
  • +
  • Check form contents before submission
  • +
  • Show AtD status button in address bar
  • +
  • Display a message when no writing errors are found
  • +
+ +

English Proofreading Options

+ +

Enable proofreading for the following grammar and style rules:

+ +
    +
  • Biased Language
  • +
  • Clichés
  • +
  • Complex Phrases
  • +
  • Diacritical Marks
  • +
  • Double Negatives
  • +
  • Hidden Verbs
  • +
  • Jargon
  • +
  • Passive Voice
  • +
  • Phrases to Avoid
  • +
  • Redundant Phrases
  • +
+ +

Language

+ +

After the Deadline supports English, French, German, Portuguese, and Spanish.

+ +
    +
  • + +
  • +
  • Detect language when proofreading
  • +
+ +

Ignored Phrases

+ +

Identify words and phrases to ignore while proofreading:

+ +

+ +
    +
+ +

Ignored Sites

+ +

After the Deadline will ignore the following sites:

+ +

+ +
    +
+ +
+

an extension

+
+
+ +
+ + diff --git a/options/style.css b/options/style.css new file mode 100644 index 0000000..7802579 --- /dev/null +++ b/options/style.css @@ -0,0 +1,420 @@ +* { + margin: 0; + padding: 0; +} +a { + color: #1390ec; +} + +a:visited { + color: #006; +} + +a:hover { + color: #000; +} + +body { + background: #fff; + font: 14px "Lucida Grande", Arial, Helvetica, sans-serif; + margin: 2em auto; + text-align: center; + line-height: 18px; +} +.left { + float: left; +} +.right { + float: right; +} +.centered { + text-align: center; +} +p { + margin: 0 0 10px 0; +} + + +#wrap { + width: 600px; + margin: 0 auto; + text-align: left; +} + + +#header { + float: left; + overflow: hidden; + margin: 0 0 20px 0; +} +#header h1 { + display:block; + height:47px; + width:246px; + float:left; + margin: 15px 0; + position: relative; +} +#header h1 a { + display:block; + width:100%; + height:100%; + outline:none; + text-decoration:none; + text-indent:-5000px; + background:url(../images/logo.gif) no-repeat 0 +} +#header ul { + width: 800px; + float: left; + display: block; + height: 30px; + background: url(../images/topnav.gif) no-repeat top left; + list-style: none; + overflow: hidden; + padding: 0; +} +#header ul li { + float: left; + display: block; +} +#header ul li a{ + float: left; + line-height: 30px; + height: 30px; + display: block; + padding: 0 10px; + border-right: 1px solid #d9d9d9; + color: #000; + text-decoration: none; +} +#header ul li a:hover, #header ul li a.selected { + background: url(../images/topnav.gif) no-repeat bottom; +} +#header ul .left a:hover, #header ul .left a.selected { + background: url(../images/topnav.gif) no-repeat bottom left; +} +#header ul li a.selected { + color: #1390ec; + font-weight: bold; + background: url(../images/topnav.gif) no-repeat bottom; /* used to have right here too */ +} +#header ul .mya { + float: right; +} +#header ul .mya a +{ + border-left: 1px solid #d9d9d9; + border-right: none; +} +#header ul .mya a:hover { + background: url(../images/topnav.gif) no-repeat bottom right; +} + +#header ul .mya a.selected +{ + background: url(../images/topnav.gif) no-repeat bottom right; +} + +#header ul .mya span { + padding-left: 20px; + background: url(../images/myaccount.gif) no-repeat 0 -1px; +} + +/** SPLASH **/ +.splash { + width: 780px; + height: 280px; + background: url(../images/splash.jpg) no-repeat 0 0; + display: block; + float: left; + padding: 10px; + font-size: 18px; + line-height: 22px; + margin: 0 0 20px 0; +} +.splash p { + width: 300px; + position: relative; + top: 85px; + left: 30px; + margin: 0; +} +.splash ul { + position: relative; + top: 95px; + left: 30px; + list-style: none; + padding: 0; +} +.splash ul li { + padding-left: 26px; + background: url(../images/check.gif) no-repeat 0 3px; + line-height: 24px; + margin-bottom: 8px; +} +.download { + position: relative; + float: right; + top: 20px; + height: 77px; + width: 228px; + background: url(../images/download.png) no-repeat; + text-indent: -5000px; +} + +/** Content **/ +#content { + width: 100%; + float: left; + overflow: hidden; + margin: 0 0 20px 0; +} + +.c0 { + width: 780px; + padding: 0 10px; +} + +.c1 { + width: 480px; + padding: 0 10px; +} +.c2 { + width: 280px; + padding: 0 10px; +} +.desc { + font-size: 17px; + line-height: 20px; +} +.desc strong { + color: #169a00; +} +.screenshot { + margin: 0 0 10px 50px; +} +h2 { + font-size: 22px; + line-height: 24px; + color: #169a00; + margin-bottom: 5px; + font-weight: normal; +} +h3 { + color: #0082dd; + font-weight: normal; + font-size: 20px; + line-height: 26px; + border-bottom: 5px solid #f4f4f4; + margin-bottom: 10px; +} +h4 { + color: #0082dd; + font-weight: normal; + font-size: 18px; + line-height: 22px; + margin-bottom: 10px; + margin-top: 10px; +} + + +.testimonials { + list-style: none; + margin: 10px 0; + float: left; + padding-left: 0; +} +.testimonials li { + float: left; + width: 100%; + margin-bottom: 10px; +} +.testimonials p { + margin: 0; +} +.testimonials span { + float: right; +} + + +.whitebox { + background: url(../images/whitebox.gif) no-repeat; + height: 10px; + width: 100%; + float: left; +} +.bottom { + background-position: bottom; +} +.top { + background-position: top; +} +.inline { + padding: 0 10px; + background: #fcfcfc; + border-right: 1px solid #e1e1e1; + border-left: 1px solid #e1e1e1; + float: left; + width: 258px; +} +.help ul { + list-style: none; + margin-bottom: 10px; +} + + +#footer { + background: url(../images/footer-bg.gif) no-repeat top left; + width: 780px; + height: 30px; + padding: 0 10px; + line-height: 30px; + float: left; + margin: 10px 0 20px; + color: #898989; +} +#footer ul { + list-style: none; + padding: 0; +} +#footer ul li { + float: left; + margin-right: 10px; +} + + +.checked { + list-style: none; + margin: 0 0 10px 0; +} +.checked li { + padding-left: 25px; + background: url(../images/smallcheck.gif) no-repeat top left; + line-height: 22px; + margin-bottom: 5px; +} + + +.t1 { + width: 100%; + margin-top: 5px; + border-top: 1px solid #d4e1c9; + border-bottom: 1px solid #d4e1c9; + margin-bottom: 10px; +} +.t1 th { + background: #f4fdec; + border-bottom: 5px solid #c7e5ac; + padding: 5px; + text-align: left; +} +.t1 td { + text-align: left; + padding: 2px; +} +.t1 .alt { + background: #efefef; +} + +img +{ + border: none; +} + +ol, ul +{ + padding-left: 2em; + padding-bottom: 0.5em; + padding-top: 0.5em; +} + +blockquote +{ + padding-left: 4em; + padding-bottom: 0.5em; + padding-top: 0.5em; +} + +/* error stuff */ + +div.error +{ + background: #ffdede; + border: solid 1px red; +} + +div.good +{ + background: #c9eaff; + border: solid 1px #1291e9; +} + +div.error, div.good +{ + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 10px; + margin-top: 5px; + padding-left: 10px; + padding-right: 10px; +} + +div.error h3, div.good h3 +{ + font-size: 125%; + color: black; + border-bottom: none; +} + +div.error p, div.error h3, div.good p, div.good h3 +{ + margin: 0; + padding: 0; +} + +.options +{ + list-style: none; +} + +.removeme +{ + background: url(xit.gif) no-repeat 0px 3px; + display: inline-block; + color: transparent; + width: 10px; + height: 15px; +} + +.removeme:hover +{ + background: url(xit.gif) no-repeat -10px 3px; +} + +#content ul li { + padding-bottom: 2px; +} + +input { + font: 14px "Lucida Grande", Arial, Helvetica, sans-serif; +} + +/* what's a little CSS amongst friends */ +#key { + border-radius: 10px; + border: 1px solid #ccc; + height: 24px; + text-align: center; + padding: 5px; + width: 160px; + margin-top: 1px; + margin-bottom: 4px; +} +#key:focus { + border-radius: 11px; + background-color: #ccc; + border: 1px solid #333; + outline: none; +} + diff --git a/options/xit.gif b/options/xit.gif new file mode 100644 index 0000000..6f1cc4f Binary files /dev/null and b/options/xit.gif differ diff --git a/scripts/atd-chrome.js b/scripts/atd-chrome.js new file mode 100644 index 0000000..31c2526 --- /dev/null +++ b/scripts/atd-chrome.js @@ -0,0 +1,144 @@ +var AtD_proofreaders = []; +var AtD_shortcut, AtD_auto; + +jQuery.fn.addProofreader = function(options) { + this.id = 0; + + var parent = this; + var opts = jQuery.extend({}, {}, options); + var ids = {}; + + return this.each(function() { + $this = jQuery(this); + + if ($this.data('AtD') == true || $this.css('display') == 'none') + return; + + var proofreader = undefined; + if ($this.context.tagName == 'DIV' && AtD_DIV_isExempt($this) == false) { + proofreader = AtD_DIV_Proofreader($this); + } + else if ($this.context.tagName == 'IFRAME' && AtD_IFRAME_isExempt($this) == false) { + proofreader = AtD_IFRAME_Proofreader($this); + } + else if ($this.context.tagName == 'TEXTAREA' && AtD_TEXTAREA_isExempt($this) == false) { + proofreader = new AtD_Proofreader($this); + } + + if (proofreader != undefined) { + proofreader.attach($this); + AtD_proofreaders.push(proofreader); + + /* attach a submit listener to the parent form */ + $this.parents('form').submit(function(event) { + proofreader.restore(); + }); + } + }); +}; + +var last = new Date().getTime(); +function doItLater(thefunction) { + window.setTimeout(function() { + var now = new Date().getTime(); + if ((now - last) >= 1000) { + thefunction(); + last = now; + } + }, 1000); +}; + +function AtD_handler(event) { + doItLater(function() { + jQuery('textarea').addProofreader(); + jQuery('div').addProofreader(); + jQuery('iframe').addProofreader(); + + for (var x = 0; x < AtD_proofreaders.length; x++) { + if (AtD_proofreaders[x].getWidget() != undefined) + AtD_proofreaders[x].getWidget().adjustWidget(); + } + }); +}; + +function toAtDHost(url) { + var host = new RegExp('(https{0,1}://.*?)/.*').exec(url); + if (host == null) + host = url; + else + host = host[1]; + return host; +} + +function AtD_start() { + chrome.extension.sendRequest({ command: 'options' }, function(o) { + var enabled = true; + + /* check if this is an ignored site... if it is, return */ + if (document.location) { + var current = toAtDHost(document.location.href); + var sites = o['sites'].split(/,\s+/); + for (var x = 0; x < sites.length; x++) { + if (sites[x] != '' && current == sites[x]) + enabled = false; + } + + if (current == 'http://acid3.acidtests.org' || current == 'https://chrome.google.com' || current == 'https://spreadsheets.google.com' || current == 'http://spreadsheets.google.com') + enabled = false; + } + + AtD_shortcut = o.shortcut.split(/,/); + AtD_autoproof = o.auto == 'true' ? true : false; + + if (enabled) + AtD_enable(); + else + AtD_disable(); + }); +} + +function AtD_disable() { + jQuery(document).unbind('DOMNodeInserted', AtD_handler); + jQuery(document).unbind('DOMSubtreeModified', AtD_handler); + jQuery('input[type="submit"], input[type="image"], button[type="submit"]').die('click', AtD_form_handler); + + /* make sure the AtD shortcut responds to nothing */ + AtD_shortcut = [false, false, false, false, false]; + AtD_autoproof = false; + + /* kill the proofreaders, eh :) */ + for (var x = 0; x < AtD_proofreaders.length; x++) { + AtD_proofreaders[x].detach(); + } + + AtD_proofreaders = []; +} + +function AtD_enable() { + jQuery('textarea').addProofreader(); + jQuery('div').addProofreader(); + jQuery('iframe').addProofreader(); + + jQuery('input[type="submit"], input[type="image"], button[type="submit"]').live('click', AtD_form_handler); + + jQuery(document).bind('DOMNodeInserted', AtD_handler); + jQuery(document).bind('DOMSubtreeModified', AtD_handler); + + jQuery('body').append(""); +} + +chrome.extension.onRequest.addListener(function(command) { + AtD_start(); +}); + +/* delay loading AtD on to a new page, some sites (e.g., gmail) mess up randomly if this happens too quickly */ +var AtD_load_wait; +if (window.document != null && window.document.location != null && window.document.location.host == 'mail.google.com') + AtD_load_wait = 2000; +else + AtD_load_wait = 250; + +window.setTimeout(function() { + AtD_start(); +}, AtD_load_wait); + diff --git a/scripts/atd.core.js b/scripts/atd.core.js new file mode 100644 index 0000000..8d18ab7 --- /dev/null +++ b/scripts/atd.core.js @@ -0,0 +1,535 @@ +/* + * atd.core.js - A building block to create a front-end for AtD (from http://github.com/Automattic/atd-core) + * Author : Raphael Mudge + * License : LGPL + * Project : http://open.afterthedeadline.com + * Discuss : https://groups.google.com/forum/#!forum/atd-developers + */ + +/* EXPORTED_SYMBOLS is set so this file can be a JavaScript Module */ +var EXPORTED_SYMBOLS = ['AtDCore']; + +function AtDCore() { + /* these are the categories of errors AtD should ignore */ + this.ignore_types = ['Bias Language', 'Cliches', 'Complex Expression', 'Diacritical Marks', 'Double Negatives', 'Hidden Verbs', 'Jargon Language', 'Passive voice', 'Phrases to Avoid', 'Redundant Expression']; + + /* these are the phrases AtD should ignore */ + this.ignore_strings = {}; + + /* Localized strings */ + this.i18n = {}; +}; + +/* + * Internationalization Functions + */ + +AtDCore.prototype.getLang = function(key, defaultk) { + if (this.i18n[key] == undefined) + return defaultk; + + return this.i18n[key]; +}; + +AtDCore.prototype.addI18n = function(localizations) { + this.i18n = localizations; +}; + +/* + * Setters + */ + +AtDCore.prototype.setIgnoreStrings = function(string) { + var parent = this; + + this.map(string.split(/,\s*/g), function(string) { + parent.ignore_strings[string] = 1; + }); +}; + +AtDCore.prototype.showTypes = function(string) { + var show_types = string.split(/,\s*/g); + var types = {}; + + /* set some default types that we want to make optional */ + + /* grammar checker options */ + types["Double Negatives"] = 1; + types["Hidden Verbs"] = 1; + types["Passive voice"] = 1; + types["Bias Language"] = 1; + + /* style checker options */ + types["Cliches"] = 1; + types["Complex Expression"] = 1; + types["Diacritical Marks"] = 1; + types["Jargon Language"] = 1; + types["Phrases to Avoid"] = 1; + types["Redundant Expression"] = 1; + + var ignore_types = []; + + this.map(show_types, function(string) { + types[string] = undefined; + }); + + this.map(this.ignore_types, function(string) { + if (types[string] != undefined) + ignore_types.push(string); + }); + + this.ignore_types = ignore_types; +}; + +/* + * Error Parsing Code + */ + +AtDCore.prototype.makeError = function(error_s, tokens, type, seps, pre) { + var struct = new Object(); + struct.type = type; + struct.string = error_s; + struct.tokens = tokens; + + if (new RegExp("\\b" + error_s + "\\b").test(error_s)) { + struct.regexp = new RegExp("(?!"+error_s+"<\\/span)\\b" + error_s.replace(/\s+/g, seps) + "\\b"); + } + else if (new RegExp(error_s + "\\b").test(error_s)) { + struct.regexp = new RegExp("(?!"+error_s+"<\\/span)" + error_s.replace(/\s+/g, seps) + "\\b"); + } + else if (new RegExp("\\b" + error_s).test(error_s)) { + struct.regexp = new RegExp("(?!"+error_s+"<\\/span)\\b" + error_s.replace(/\s+/g, seps)); + } + else { + struct.regexp = new RegExp("(?!"+error_s+"<\\/span)" + error_s.replace(/\s+/g, seps)); + } + + struct.used = false; /* flag whether we've used this rule or not */ + + return struct; +}; + +AtDCore.prototype.addToErrorStructure = function(errors, list, type, seps) { + var parent = this; + + this.map(list, function(error) { + var tokens = error["word"].split(/\s+/); + var pre = error["pre"]; + var first = tokens[0]; + + if (errors['__' + first] == undefined) { + errors['__' + first] = new Object(); + errors['__' + first].pretoks = {}; + errors['__' + first].defaults = new Array(); + } + + if (pre == "") { + errors['__' + first].defaults.push(parent.makeError(error["word"], tokens, type, seps, pre)); + } else { + if (errors['__' + first].pretoks['__' + pre] == undefined) + errors['__' + first].pretoks['__' + pre] = new Array(); + + errors['__' + first].pretoks['__' + pre].push(parent.makeError(error["word"], tokens, type, seps, pre)); + } + }); +}; + +AtDCore.prototype.buildErrorStructure = function(spellingList, enrichmentList, grammarList) { + var seps = this._getSeparators(); + var errors = {}; + + this.addToErrorStructure(errors, spellingList, "hiddenSpellError", seps); + this.addToErrorStructure(errors, grammarList, "hiddenGrammarError", seps); + this.addToErrorStructure(errors, enrichmentList, "hiddenSuggestion", seps); + return errors; +}; + +AtDCore.prototype._getSeparators = function() { + var re = '', i; + var str = '"s!#$%&()*+,./:;<=>?@[\]^_{|}'; + + // Build word separator regexp + for (i=0; i 0) + errorStruct = this.buildErrorStructure(spellingErrors, enrichment, grammarErrors); + else + errorStruct = undefined; + + /* save some state in this object, for retrieving suggestions later */ + return { errors: errorStruct, count: ecount, suggestions: this.suggestions }; +}; + +AtDCore.prototype.findSuggestion = function(element) { + var text = element.innerHTML; + var context = ( this.getAttrib(element, 'pre') + "" ).replace(/[\\,!\\?\\."\s]/g, ''); + if (this.getAttrib(element, 'pre') == undefined) + { + alert(element.innerHTML); + } + + var errorDescription = undefined; + var len = this.suggestions.length; + + for (var i = 0; i < len; i++) { + var key = this.suggestions[i]["string"]; + + if ((context == "" || context == this.suggestions[i]["context"]) && this.suggestions[i]["matcher"].test(text)) { + errorDescription = this.suggestions[i]; + break; + } + } + return errorDescription; +}; + +/* + * TokenIterator class + */ + +function TokenIterator(tokens) { + this.tokens = tokens; + this.index = 0; + this.count = 0; + this.last = 0; +}; + +TokenIterator.prototype.next = function() { + var current = this.tokens[this.index]; + this.count = this.last; + this.last += current.length + 1; + this.index++; + + /* strip single quotes from token, AtD does this when presenting errors */ + if (current != "") { + if (current[0] == "'") + current = current.substring(1, current.length); + + if (current[current.length - 1] == "'") + current = current.substring(0, current.length - 1); + } + + return current; +}; + +TokenIterator.prototype.hasNext = function() { + return this.index < this.tokens.length; +}; + +TokenIterator.prototype.hasNextN = function(n) { + return (this.index + n) < this.tokens.length; +}; + +TokenIterator.prototype.skip = function(m, n) { + this.index += m; + this.last += n; + + if (this.index < this.tokens.length) + this.count = this.last - this.tokens[this.index].length; +}; + +TokenIterator.prototype.getCount = function() { + return this.count; +}; + +TokenIterator.prototype.peek = function(n) { + var peepers = new Array(); + var end = this.index + n; + for (var x = this.index; x < end; x++) + peepers.push(this.tokens[x]); + return peepers; +}; + +/* + * code to manage highlighting of errors + */ +AtDCore.prototype.markMyWords = function(container_nodes, errors) { + var seps = new RegExp(this._getSeparators()); + var nl = new Array(); + var ecount = 0; /* track number of highlighted errors */ + var parent = this; + + /* Collect all text nodes */ + /* Our goal--ignore nodes that are already wrapped */ + + this._walk(container_nodes, function(n) { + if (n.nodeType == 3 && !parent.isMarkedNode(n)) + nl.push(n); + }); + + /* walk through the relevant nodes */ + + var iterator; + + this.map(nl, function(n) { + var v; + + if (n.nodeType == 3) { + v = n.nodeValue; /* we don't want to mangle the HTML so use the actual encoded string */ + var tokens = n.nodeValue.split(seps); /* split on the unencoded string so we get access to quotes as " */ + var previous = ""; + + var doReplaces = []; + + iterator = new TokenIterator(tokens); + + while (iterator.hasNext()) { + var token = iterator.next(); + var current = errors['__' + token]; + + var defaults; + + if (current != undefined && current.pretoks != undefined) { + defaults = current.defaults; + current = current.pretoks['__' + previous]; + + var done = false; + var prev, curr; + + prev = v.substr(0, iterator.getCount()); + curr = v.substr(prev.length, v.length); + + var checkErrors = function(error) { + if (error != undefined && !error.used && foundStrings['__' + error.string] == undefined && error.regexp.test(curr)) { + var oldlen = curr.length; + + foundStrings['__' + error.string] = 1; + doReplaces.push([error.regexp, '$&']); + + error.used = true; + done = true; + } + }; + + var foundStrings = {}; + + if (current != undefined) { + previous = previous + ' '; + parent.map(current, checkErrors); + } + + if (!done) { + previous = ''; + parent.map(defaults, checkErrors); + } + } + + previous = token; + } // end while + + /* do the actual replacements on this span */ + if (doReplaces.length > 0) { + newNode = n; + + for (var x = 0; x < doReplaces.length; x++) { + var regexp = doReplaces[x][0], result = doReplaces[x][1]; + + /* it's assumed that this function is only being called on text nodes (nodeType == 3), the iterating is necessary + because eventually the whole thing gets wrapped in an mceItemHidden span and from there it's necessary to + handle each node individually. */ + var bringTheHurt = function(node) { + if (node.nodeType == 3) { + ecount++; + + /* sometimes IE likes to ignore the space between two spans, solution is to insert a placeholder span with + a non-breaking space. The markup removal code substitutes this span for a space later */ + if (parent.isIE() && node.nodeValue.length > 0 && node.nodeValue.substr(0, 1) == ' ') + return parent.create(' ' + node.nodeValue.substr(1, node.nodeValue.length - 1).replace(regexp, result), false); + else + return parent.create(node.nodeValue.replace(regexp, result), false); + } + else { + var contents = parent.contents(node); + + for (var y = 0; y < contents.length; y++) { + if (contents[y].nodeType == 3 && regexp.test(contents[y].nodeValue)) { + var nnode; + + if (parent.isIE() && contents[y].nodeValue.length > 0 && contents[y].nodeValue.substr(0, 1) == ' ') + nnode = parent.create(' ' + contents[y].nodeValue.substr(1, contents[y].nodeValue.length - 1).replace(regexp, result), true); + else + nnode = parent.create(contents[y].nodeValue.replace(regexp, result), true); + + parent.replaceWith(contents[y], nnode); + parent.removeParent(nnode); + + ecount++; + + return node; /* we did a replacement so we can call it quits, errors only get used once */ + } + } + + return node; + } + }; + + newNode = bringTheHurt(newNode); + } + + parent.replaceWith(n, newNode); + } + } + }); + + return ecount; +}; + +AtDCore.prototype._walk = function(elements, f) { + var i; + for (i = 0; i < elements.length; i++) { + f.call(f, elements[i]); + this._walk(this.contents(elements[i]), f); + } +}; + +AtDCore.prototype.removeWords = function(node, w) { + var count = 0; + var parent = this; + + this.map(this.findSpans(node).reverse(), function(n) { + if (n && (parent.isMarkedNode(n) || parent.hasClass(n, 'mceItemHidden') || parent.isEmptySpan(n)) ) { + if (n.innerHTML == ' ') { + var nnode = document.createTextNode(' '); /* hax0r */ + parent.replaceWith(n, nnode); + } + else if (!w || n.innerHTML == w) { + parent.removeParent(n); + count++; + } + } + }); + + return count; +}; + +AtDCore.prototype.isEmptySpan = function(node) { + return (this.getAttrib(node, 'class') == "" && this.getAttrib(node, 'style') == "" && this.getAttrib(node, 'id') == "" && !this.hasClass(node, 'Apple-style-span') && this.getAttrib(node, 'mce_name') == ""); +}; + +AtDCore.prototype.isMarkedNode = function(node) { + return (this.hasClass(node, 'hiddenGrammarError') || this.hasClass(node, 'hiddenSpellError') || this.hasClass(node, 'hiddenSuggestion')); +}; + +/* + * Context Menu Helpers + */ +AtDCore.prototype.applySuggestion = function(element, suggestion) { + if (suggestion == '(omit)') { + this.remove(element); + } + else { + var node = this.create(suggestion); + this.replaceWith(element, node); + this.removeParent(node); + } +}; + +/* + * Check for an error + */ +AtDCore.prototype.hasErrorMessage = function(xmlr) { + return (xmlr != undefined && xmlr.getElementsByTagName('message').item(0) != null); +}; + +AtDCore.prototype.getErrorMessage = function(xmlr) { + return xmlr.getElementsByTagName('message').item(0); +}; + +/* this should always be an error, alas... not practical */ +AtDCore.prototype.isIE = function() { + return navigator.appName == 'Microsoft Internet Explorer'; +}; diff --git a/scripts/autoproof.js b/scripts/autoproof.js new file mode 100644 index 0000000..28f965d --- /dev/null +++ b/scripts/autoproof.js @@ -0,0 +1,63 @@ +function AtD_form_handler(e) { + if (!AtD_autoproof) + return; + + /* ignore Facebook's "view all N comments" link which is really a form submit, same for like, unlike, and friends */ + if (e.explicitOriginalTarget != undefined) { + var name = e.explicitOriginalTarget.name; + if (name == 'view_all' || name == 'like' || name == 'unlike' || name == 'share') + return; + } + + /* we've already warned the user or they've already checked their stuff */ + if (jQuery(e.target).data('AtD_Warned') == true) + return; + + jQuery(e.target).data('AtD_Warned', true); + + /* find all proofreaders that have this form as a descendant */ + var form = jQuery(e.target).closest('form'); + if (form == undefined) + return; + + var checkme = []; + for (var x = 0; x < AtD_proofreaders.length; x++) { + if (!AtD_proofreaders[x].hasBeenChecked() && AtD_proofreaders[x].getOriginal().closest('form')[0] == form[0]) + checkme.push(AtD_proofreaders[x]); + } + + var totalErrors = 0; + + /* this function continues the process of submitting the form (if necessary) */ + var finalize = function() { + if (totalErrors == 0 || confirm("After the Deadline detected spelling and grammar errors which you have not reviewed. Press OK to submit the form, or Cancel to review the errors.")) { + window.setTimeout(function() { + jQuery(e.target).click(); + }, 10); + } + else { + for (var x = 0; x < checkme.length; x++) { + if (checkme[x].hasErrors) + checkme[x].checkComponent(); + } + } + }; + + /* check them please */ + if (checkme.length > 0) { + var total = checkme.length; + var callback = function(count) { + total -= 1; + totalErrors += count; + if (total == 0) + finalize(); + }; + + e.preventDefault(); + e.stopPropagation(); + + for (var x = 0; x < checkme.length; x++) { + checkme[x].checkSilent(callback); + } + } +}; diff --git a/scripts/inherit-style.js b/scripts/inherit-style.js new file mode 100644 index 0000000..f25ddc6 --- /dev/null +++ b/scripts/inherit-style.js @@ -0,0 +1,135 @@ +/* This script inherits styles from an iframe onto the AtD content div. It has to execute + directly on the page because Chrome's dual-DOM implementation makes accessing this + information in an iframe impossible. + + 1. document.defaultView is null from a content script's perspective + 2. can't execute content scripts on dynamically generated iframes with an empty src="" tag +*/ + +function ATD_SCROLL_ADJUST__() { + /* find the iframe we want to inherit properties from */ + var findIframe = function() { + var frames = document.getElementsByTagName('iframe'); + for (var x = 0; x < frames.length; x++) { + if (frames[x].getAttribute('AtD_scroll') != '') { + return frames[x]; + } + } + return undefined; + }; + + var adjustScroll = function(frame) { + /* record the scroll value */ + var scrollValue = frame.getAttribute('AtD_scroll'); + + /* set it based on whatever mechanism it needs to be set through */ + if (frame.contentWindow && frame.contentWindow.scrollbars != undefined && frame.contentWindow.scrollbars.visible) { + frame.contentWindow.scroll(frame.contentWindow.scrollX, scrollValue); + } + else { + frame.contentDocument.documentElement.scrollTop = scrollValue; + } + }; + + var frame = findIframe(); + if (frame != undefined) + adjustScroll(frame); +}; + +function ATD_INHERIT__() { + /* find the iframe we want to inherit properties from */ + var findIframe = function() { + var frames = document.getElementsByTagName('iframe'); + for (var x = 0; x < frames.length; x++) { + if (frames[x].getAttribute('AtD_active') == 'true') { + return frames[x]; + } + } + return undefined; + }; + + /* copy properties to another element */ + var copyProperties = function(properties, target) { + for (var i = 0; i < properties.length; i++) { + var property = properties.item(i); + if (property != 'display' && property != '-webkit-user-modify' && property != 'overflow' && property != 'spellcheck') + target.style.setProperty(property, properties.getPropertyValue(property), properties.getPropertyPriority(property)); + } + }; + + /* this function sets everything up */ + var inheritProperties = function(frame) { + var _body = document.getElementById("AtD_Content"); + var _html = _body.parentNode; + var _top = _html.parentNode; + + /* do the document element */ + var css = frame.contentWindow.document.defaultView.getComputedStyle(frame.contentWindow.document.documentElement, null); + copyProperties(css, _html); + + /* do the body element */ + css = frame.contentWindow.document.defaultView.getComputedStyle(frame.contentWindow.document.body, null); + copyProperties(css, _body); + + /* make sure highlighted errors on last line always show (regardless of overflow: hidden) */ + if (css.getPropertyValue('padding-bottom') == '0px' || css.getPropertyValue('padding-bottom') == 0 || css.getPropertyValue('padding-bottom') == '') + _body.style.setProperty('padding-bottom', '2px', ''); + + /* margin-left and margin-right should default to auto, not 0 -- fixes Google Docs not centering content in fixed-width view */ + if (frame.contentWindow.document.body.style.getPropertyValue("margin-left") == "" || frame.contentWindow.document.body.style.getPropertyValue("margin-left") == "auto") + _body.style.setProperty("margin-left", "auto", ""); + + if (frame.contentWindow.document.body.style.getPropertyValue("margin-right") == "" || frame.contentWindow.document.body.style.getPropertyValue("margin-right") == "auto") + _body.style.setProperty("margin-right", "auto", ""); + + /* ok, now let's inherit properties from the IFRAME itself */ + css = frame.ownerDocument.defaultView.getComputedStyle(frame, null); + copyProperties(css, _top); + + var display = css.getPropertyValue('display'); + + // make sure scrollbars show up only in the fake iframe + _top.style.setProperty('overflow-y', 'auto', ''); + _top.style.setProperty('overflow-x', 'hidden', ''); + + _body.style.setProperty('overflow-x', 'hidden', ''); + _body.style.setProperty('overflow-y', 'hidden', ''); + + _html.style.setProperty('overflow-x', 'hidden', ''); + _html.style.setProperty('overflow-y', 'hidden', ''); + + // do some scrollbar adjustments + _body.style.removeProperty('height'); + _html.style.removeProperty('height'); + + /* some other miscellaneous properties */ + _top.style.setProperty('overflow', 'auto', ""); + _top.style.setProperty('white-space', 'pre-wrap', ""); + _top.style.setProperty('margin-left', 'auto', ""); + _top.style.setProperty('margin-right', 'auto', ""); + + _body.setAttribute('contenteditable', 'true'); + _body.setAttribute('spellcheck', false); /* ours is better */ + + /* record the scroll value */ + var scrollValue; + if (frame.contentWindow && frame.contentWindow.scrollbars != undefined && frame.contentWindow.scrollbars.visible) { + scrollValue = frame.contentWindow.scrollY + 0; + } + else { + scrollValue = frame.contentDocument.documentElement.scrollTop + 0; + } + + /* do the actual swapping now */ + frame.style.setProperty('display', 'none', ""); + _top.style.setProperty('display', display == 'inline' ? 'inline-block' : display,''); + + /* match the proofreader scroll value to the frame value */ + _top.scrollTop = scrollValue; + }; + + var frame = findIframe(); + if (frame != undefined) + inheritProperties(frame); +}; +ATD_INHERIT__(); diff --git a/scripts/jquery-1.4.2.js b/scripts/jquery-1.4.2.js new file mode 100644 index 0000000..fff6776 --- /dev/null +++ b/scripts/jquery-1.4.2.js @@ -0,0 +1,6240 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function( window, undefined ) { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwnProperty = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + indexOf = Array.prototype.indexOf; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + if ( elem ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && /^\w+$/.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging object literal values or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { + var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src + : jQuery.isArray(copy) ? [] : {}; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 13 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, i = 0; + while ( (fn = readyList[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Reset the list of functions + readyList = null; + } + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + return jQuery.ready(); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor + && !hasOwnProperty.call(obj, "constructor") + && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwnProperty.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") + .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + trim: function( text ) { + return (text || "").replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + if ( !inv !== !callback( elems[ i ], i ) ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch( error ) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + +// Mutifunctional method to get and set values to a collection +// The value/s can be optionally by executed if its a function +function access( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; +} + +function now() { + return (new Date).getTime(); +} +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + now(); + + div.style.display = "none"; + div.innerHTML = "
a"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, + + parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null, + + // Will be defined later + deleteExpando: true, + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null + }; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = ""; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + document.body.removeChild( div ).style.display = 'none'; + + div = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; +var expando = "jQuery" + now(), uuid = 0, windowData = {}; + +jQuery.extend({ + cache: {}, + + expando:expando, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + "object": true, + "applet": true + }, + + data: function( elem, name, data ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache; + + if ( !id && typeof name === "string" && data === undefined ) { + return null; + } + + // Compute a unique ID for the element + if ( !id ) { + id = ++uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + elem[ expando ] = id; + thisCache = cache[ id ] = jQuery.extend(true, {}, name); + + } else if ( !cache[ id ] ) { + elem[ expando ] = id; + cache[ id ] = {}; + } + + thisCache = cache[ id ]; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + // Completely remove the data cache + delete cache[ id ]; + } + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + if ( typeof key === "undefined" && this.length ) { + return jQuery.data( this[0] ); + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + } + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else { + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { + jQuery.data( this, key, value ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i, elem ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); +var rclass = /[\n\t]/g, + rspace = /\s+/, + rreturn = /\r/g, + rspecialurl = /href|src|style/, + rtype = /(button|input)/i, + rfocusable = /(button|input|object|select|textarea)/i, + rclickable = /^(a|area)$/i, + rradiocheck = /radio|checkbox/; + +jQuery.fn.extend({ + attr: function( name, value ) { + return access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", setClass = elem.className; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split(rspace); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, i = 0, self = jQuery(this), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Typecast each time if the value is a Function and the appended + // value is therefore different each time. + if ( typeof val === "number" ) { + val += ""; + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + if ( name in elem && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + elem[ name ] = value; + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + + // elem is actually elem.style ... set the style + // Using attr for specific style information is now deprecated. Use style instead. + return jQuery.style( elem, name, value ); + } +}); +var rnamespaces = /\.(.*)$/, + fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); + }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events = elemData.events || {}, + eventHandle = elemData.handle, eventHandle; + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + handleObj.guid = handler.guid; + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)") + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( var j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( var j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[expando] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data( elem, "handle" ); + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var target = event.target, old, + isClick = jQuery.nodeName(target, "a") && type === "click", + special = jQuery.event.special[ type ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ type ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + type ]; + + if ( old ) { + target[ "on" + type ] = null; + } + + jQuery.event.triggered = true; + target[ type ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( old ) { + target[ "on" + type ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace, events; + + event = arguments[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + var events = jQuery.data(this, "events"), handlers = events[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, arguments ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { + event.which = event.charCode || event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); + }, + + remove: function( handleObj ) { + var remove = true, + type = handleObj.origType.replace(rnamespaces, ""); + + jQuery.each( jQuery.data(this, "events").live || [], function() { + if ( type === this.origType.replace(rnamespaces, "") ) { + remove = false; + return false; + } + }); + + if ( remove ) { + jQuery.event.remove( this, handleObj.origType, liveHandler ); + } + } + + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( this.setInterval ) { + this.onbeforeunload = eventHandle; + } + + return false; + }, + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +var removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + elem.removeEventListener( type, handle, false ); + } : + function( elem, type, handle ) { + elem.detachEvent( "on" + type, handle ); + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); + + // Mark it as fixed + this[ expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + } + // otherwise set the returnValue property of the original event to false (IE) + e.returnValue = false; + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var formElems = /textarea|input|select/i, + + changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return formElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return formElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + context.each(function(){ + jQuery.event.add( this, liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + }); + + } else { + // unbind live handler + context.unbind( liveConvert( type, selector ), fn ); + } + } + + return this; + } +}); + +function liveHandler( event ) { + var stop, elems = [], selectors = [], args = arguments, + related, match, handleObj, elem, j, i, l, data, + events = jQuery.data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( match[i].selector === handleObj.selector ) { + elem = match[i].elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) { + stop = false; + break; + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + window.attachEvent("onunload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ + return "\\" + (num - 0 + 1); + })); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = getText; +jQuery.isXMLDoc = isXML; +jQuery.contains = contains; + +return; + +window.Sizzle = Sizzle; + +})(); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + slice = Array.prototype.slice; + +// Implement the identical functionality for filter and not +var winnow = function( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + if ( jQuery.isArray( selectors ) ) { + var ret = [], cur = this[0], match, matches = {}, selector; + + if ( cur && selectors.length ) { + for ( var i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur }); + delete matches[selector]; + } + } + cur = cur.parentNode; + } + } + + return ret; + } + + var pos = jQuery.expr.match.POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + return this.map(function( i, cur ) { + while ( cur && cur.ownerDocument && cur !== context ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { + return cur; + } + cur = cur.parentNode; + } + return null; + }); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], cur = elem[dir]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, + rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, + rtagName = /<([\w:]+)/, + rtbody = /"; + }, + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and "); + } + }; + + AtD.adjustWidget = function(offset) { + if (AtD.isProofreading()) { + offset.top += 4; + offset.left += 3; + } + + offset.top -= 1; + offset.left -= 1; + + /* check if there is a scrollbar, if there is, adjust accordingly */ + if (AtD.getOriginal().attr('id') == 'wys_frame') { + offset.left -= 15; /* this is a google docs hack, there is always a scrollbar */ + } + else if (AtD.isProofreading()) { + var target = AtD.getActiveComponent(); + target = target.context == undefined ? target[0] : target.context; + + if (target != undefined && target.scrollHeight > target.clientHeight) + offset.left -= 15; + } + else { + var target = AtD.getOriginal().context; + if (target.contentDocument && target.contentDocument.documentElement && (target.contentDocument.documentElement.clientHeight > target.clientHeight)) + offset.left -= 15; + } + }; + + AtD.showProofreader = function(component, proofreader) { + component.attr('AtD_active', 'true'); + AtD.displayValue = component.css('display'); + + proofreader.css('display', 'none'); + component.after(proofreader); + + jQuery('body').append(""); + }; + + AtD.createProofreader = function(contents) { + return jQuery('
' + contents + '
'); + }; + + AtD.inheritLookAndFeel = function(component, proofreader) { + /* load a script to go in like special forces and update some styles */ + return proofreader; + } + + return AtD; +}; + diff --git a/scripts/jquery.atd.js b/scripts/jquery.atd.js new file mode 100644 index 0000000..07eb93d --- /dev/null +++ b/scripts/jquery.atd.js @@ -0,0 +1,364 @@ +/* + * atd.core.js - A building block to create a front-end for AtD (from http://github.com/Automattic/atd-core, incorporated here). + * Author : Raphael Mudge + * License : LGPL + * Project : http://open.afterthedeadline.com + * Discuss : https://groups.google.com/forum/#!forum/atd-developers + * + * Derived from: + * + * jquery.spellchecker.js - a simple jQuery Spell Checker + * Copyright (c) 2008 Richard Willis + * MIT license : http://www.opensource.org/licenses/mit-license.php + * Project : http://jquery-spellchecker.googlecode.com + * Contact : willis.rh@gmail.com + */ + +function AtD_Basic() { + this.rpc = ''; /* see the proxy.php that came with the AtD/TinyMCE plugin */ + this.api_key = ''; + this.i18n = {}; + this.listener = {}; + + /* callbacks */ + + var parent = this; + this.clickListener = function(event) { + if (parent.core.isMarkedNode(event.target)) + parent.suggest(event.target); + }; + + this.ignoreSuggestion = function(e) { + parent.core.removeParent(parent.errorElement); + + parent.counter --; + if (parent.counter == 0 && parent.callback_f != undefined && parent.callback_f.success != undefined) + parent.callback_f.success(parent.count); + }; + + this.explainError = function(e) { + if (parent.callback_f != undefined && parent.callback_f.explain != undefined) + parent.callback_f.explain(parent.explainURL); + }; + + + + this.core = (function() { + var core = new AtDCore(); + + core.hasClass = function(node, className) { + return jQuery(node).hasClass(className); + }; + + core.map = jQuery.map; + + core.contents = function(node) { + return jQuery(node).contents(); + }; + + core.replaceWith = function(old_node, new_node) { + return jQuery(old_node).replaceWith(new_node); + }; + + core.findSpans = function(parent) { + return jQuery.makeArray(parent.find('span')); + }; + + /* taken from AtD/Firefox, thanks Mitcho */ + core.create = function(string) { + // replace out all tags with &-equivalents so that we preserve tag text. + string = string.replace(/\&/g,'&'); + string = string.replace(/\/g,'>'); + + // find all instances of AtD-created spans + var matches = string.match(/\<\;span class=\"hidden\w+?\" pre="[^"]*"\>\;.*?\<\;\/span\>\;/g); + + // ... and fix the tags in those substrings. + if (matches) { + matches.forEach(function(match) { + string = string.replace(match,match.replace(/\<\;/g, '<').replace(/\>\;/g, '>')); + },this); + } + + var node = jQuery(''); + node.html(string); + return node; + }; + + core.remove = function(node) { + return jQuery(node).remove(); + }; + + core.removeParent = function(node) { + /* unwrap exists in jQuery 1.4+ only. Thankfully because replaceWith as-used here won't work in 1.4 */ + if (jQuery(node).unwrap) + return jQuery(node).contents().unwrap(); + else + return jQuery(node).replaceWith(jQuery(node).html()); + }; + + core.getAttrib = function(node, name) { + return jQuery(node).attr(name); + }; + + return core; + })(); + + this.check = function(container, source, callback_f) { + chrome.extension.sendRequest({ command: 'options' }, function(o) { + parent.setIgnoreStrings(o.phrases); + parent.showTypes(o.options); + parent._check(container, source, callback_f); + }); + }; + + /* redefine the communication channel */ + this._check = function(container, source, callback_f) { + parent.callback_f = callback_f; /* remember the callback for later */ + parent.remove(container); + + var text = jQuery.trim(source); + + chrome.extension.sendRequest({ data: text, url: '/checkDocument' }, function(response) { + var xml = (new DOMParser()).parseFromString(response, 'text/xml'); + + /* check for and display error messages from the server */ + if (parent.core.hasErrorMessage(xml)) { + if (parent.callback_f != undefined && parent.callback_f.error != undefined) + parent.callback_f.error(parent.core.getErrorMessage(xml)); + + return; + } + + /* highlight the errors */ + + parent.container = container; + + var count = parent.processXML(container, xml); + + if (parent.callback_f != undefined && parent.callback_f.ready != undefined) + parent.callback_f.ready(count); + + if (count == 0 && parent.callback_f != undefined && parent.callback_f.success != undefined) + parent.callback_f.success(count); + + parent.counter = count; + parent.count = count; + }); + }; +} + +AtD_Basic.prototype.getLang = function(key, defaultk) { + if (this.i18n[key] == undefined) + return defaultk; + + return this.i18n[key]; +}; + +AtD_Basic.prototype.addI18n = function(localizations) { + this.i18n = localizations; + this.core.addI18n(localizations); +}; + +AtD_Basic.prototype.setIgnoreStrings = function(string) { + this.core.setIgnoreStrings(string); +}; + +AtD_Basic.prototype.showTypes = function(string) { + this.core.showTypes(string); +}; + +AtD_Basic.prototype.useSuggestion = function(word) { + this.core.applySuggestion(this.errorElement, word); + this.counter --; + if (this.counter == 0 && this.callback_f != undefined && this.callback_f.success != undefined) + this.callback_f.success(this.count); + else + this.sync(); +}; + +AtD_Basic.prototype.remove = function(container) { + /* destroy the menu when we remove the HTML */ + if (this.lastSuggest) + this.lastSuggest.remove(); + this.lastSuggest = undefined; + + this._removeWords(container, null); +}; + +AtD_Basic.prototype.processXML = function(container, responseXML) { + + var results = this.core.processXML(responseXML); + + if (results.count > 0) + results.count = this.core.markMyWords(container.contents(), results.errors); + + container.unbind('click', this.clickListener); + container.click(this.clickListener); + + return results.count; +}; + +AtD_Basic.prototype.editSelection = function() { + var parent = this.errorElement.parent(); + + if (this.callback_f != undefined && this.callback_f.editSelection != undefined) + this.callback_f.editSelection(this.errorElement); + + if (this.errorElement.parent() != parent) { + this.counter --; + if (this.counter == 0 && this.callback_f != undefined && this.callback_f.success != undefined) + this.callback_f.success(this.count); + } +}; + +AtD_Basic.prototype.ignoreAll = function(container) { + var target = this.errorElement.text(); + var removed = this._removeWords(container, target); + + this.counter -= removed; + + if (this.counter == 0 && this.callback_f != undefined && this.callback_f.success != undefined) + this.callback_f.success(this.count); + + if (this.callback_f != undefined && this.callback_f.ignore != undefined) { + this.callback_f.ignore(target); + this.core.setIgnoreStrings(target); + } +}; + +AtD_Basic.prototype.suggest = function(element) { + var parent = this; + + /* construct the menu if it doesn't already exist */ + + var suggest = jQuery('
'); + suggest.prependTo('body'); + + /* make sure there is only one menu at a time */ + + if (parent.lastSuggest) + parent.lastSuggest.remove(); + + parent.lastSuggest = suggest; + + /* find the correct suggestions object */ + + errorDescription = this.core.findSuggestion(element); + + /* build up the menu y0 */ + + this.errorElement = jQuery(element); + + suggest.empty(); + + if (errorDescription == undefined) { + suggest.append('' + this.getLang('menu_title_no_suggestions', 'No suggestions') + ''); + } + else if (errorDescription["suggestions"].length == 0) { + suggest.append('' + errorDescription['description'] + ''); + } + else { + suggest.append('' + errorDescription['description'] + ''); + + var parent = this; + for (var i = 0; i < errorDescription["suggestions"].length; i++) { + (function(sugg) { + var node = jQuery('
' + sugg + '
'); + node.click(function(e) { + parent.useSuggestion(sugg); + suggest.remove(); + e.preventDefault(); + e.stopPropagation(); + }); + suggest.append(node); + })(errorDescription["suggestions"][i]); + } + } + + /* do the explain menu if configured */ + + if (this.callback_f != undefined && this.callback_f.explain != undefined && errorDescription['moreinfo'] != undefined) { + var node = jQuery('
' + this.getLang('menu_option_explain', 'Explain...') + '
'); + node.click(function(e) { + parent.explainError(); + suggest.remove(); + e.preventDefault(); + e.stopPropagation(); + }); + suggest.append(node); + this.explainURL = errorDescription['moreinfo']; + } + + /* do the ignore option */ + + var node = jQuery('
' + this.getLang('menu_option_ignore_once', 'Ignore suggestion') + '
'); + node.click(function(e) { + parent.ignoreSuggestion(); + suggest.remove(); + e.preventDefault(); + e.stopPropagation(); + }); + suggest.append(node); + + /* add the edit in place and ignore always option */ + + if (this.callback_f != undefined && this.callback_f.editSelection != undefined) { + + if (this.callback_f != undefined && this.callback_f.ignore != undefined) + node = jQuery('
' + this.getLang('menu_option_ignore_always', 'Ignore always') + '
'); + else + node = jQuery('
' + this.getLang('menu_option_ignore_all', 'Ignore all') + '
'); + + suggest.append(node); + + var node2 = jQuery('
' + this.getLang('menu_option_edit_selection', 'Edit Selection...') + '
'); + node2.click(function(e) { + parent.editSelection(parent.container); + suggest.remove(); + e.preventDefault(); + e.stopPropagation(); + }); + suggest.append(node2); + } + else { + if (this.callback_f != undefined && this.callback_f.ignore != undefined) + node = jQuery('
' + this.getLang('menu_option_ignore_always', 'Ignore always') + '
'); + else + node = jQuery('
' + this.getLang('menu_option_ignore_all', 'Ignore all') + '
'); + suggest.append(node); + } + + node.click(function(e) { + parent.ignoreAll(parent.container); + suggest.remove(); + e.preventDefault(); + e.stopPropagation(); + }); + + /* show the menu */ + + var pos = jQuery(element).offset(); + var width = jQuery(element).width(); + jQuery(suggest).css({ left: (pos.left + width) + 'px', top: pos.top + 'px' }); + + jQuery(suggest).show(); + + /* bind events to make the menu disappear when the user clicks outside of it */ + + this.suggestShow = true; + setTimeout(function() { + jQuery("body").bind("click", function() { + if (!parent.suggestShow) + suggest.remove(); + }); + }, 1); + + setTimeout(function() { + parent.suggestShow = false; + }, 10); +}; + +AtD_Basic.prototype._removeWords = function(container, w) { + return this.core.removeWords(container, w); +}; diff --git a/scripts/jquery.atd.proofreader.js b/scripts/jquery.atd.proofreader.js new file mode 100644 index 0000000..56bbb67 --- /dev/null +++ b/scripts/jquery.atd.proofreader.js @@ -0,0 +1,398 @@ +function AtD_TEXTAREA_isExempt(node) { + if (node.width() == 0 || node.height() == 0) + return true; + + // exempt hotmail to/cc/bcc fields + if (/.*?\$[iI]nputBox/.test( node.attr('id') )) + return true; + + // exempt the facebook status textarea, only the status textarea + if (/UIComposer_TextArea\s/.test(node.attr('class')) && !/DOMControl_autogrow/.test(node.attr('class'))) { + return true; + } + + // exempt paypal checkout seller-notes area + if (node.attr('id') == 'seller-notes') + return true; + + /* exclude Yahoo Mail's fields */ + if (/compHeaderField/.test(node.attr('class'))) + return true; + + // exempt cPanel code editor + if (node.attr('id') == 'codewindow') + return true; + + if (node.css('display') == 'none') + return true; + + // exempt gmail's contact fields + if (node.context != null && node.context.ownerDocument != null && node.context.ownerDocument.location.host == 'mail.google.com' && node.context.parentNode.nodeName == 'TD') + return true; + + return false; +}; + +function AtD_Proofreader(container) { + var AtD = new AtD_Basic(); + var isProofreading = false; + var proofreader = undefined; + var original = undefined; + var widget = undefined; + AtD.displayValue = undefined; + AtD.transition = false; + AtD.position = 0; + var parent = this; + var hasBeenChecked = false; + + AtD.hasBeenChecked = function() { + return hasBeenChecked; + }; + + AtD.getOriginal = function() { + return original; + }; + + AtD.getWidget = function() { + return widget; + }; + + AtD.adjustWidget = function(offset) { + if (AtD.isProofreading()) { + offset.top += 4; + offset.left += 3; + } + + offset.top -= 6; + offset.left -= 6; + + /* detect scrollbars and adjust proofreader position based on it */ + var target = AtD.getActiveComponent(); + target = target.context == undefined ? target[0] : target.context; + + if (target != undefined && target.scrollHeight > target.clientHeight) + offset.left -= 15; + + /* hack for WordPress comment quick edit which has a bar over the bottom */ + if (original.attr('id') == 'replycontent') + offset.top -= 15; + }; + + AtD.resizeHandler = function() { + if (AtD.transition) + return; + + if (AtD.isProofreading()) + AtD.restore(); + else + AtD.getWidget().adjustWidget(); + }; + + /* returns the current active component */ + AtD.getActiveComponent = function() { + if (isProofreading) + return proofreader; + else + return original; + }; + + /* get/set scrollbar position */ + AtD.getScrollPosition = function() { + return AtD.getActiveComponent().scrollTop(); + }; + + AtD.setScrollPosition = function(value) { + AtD.getActiveComponent().scrollTop(value); + }; + + var lastSync = 0; + /* called to sync the original editor with the updated contents */ + AtD.sync = function() { + if (AtD.isProofreading) { + lastSync = new Date().getTime(); + window.setTimeout(function() { + var current = new Date().getTime() - lastSync; + if (current >= 1750 && AtD.isProofreading && !AtD.transition) { + lastSync += current; + var clone = jQuery(proofreader.parent().find('#AtD_Content').html()); + AtD._removeWords(clone, null); + var content = clone.html(); + if (content != null) + AtD.setValue(original, content); + lastSync = new Date().getTime(); + } + }, 1750); + } + }; + + /* convienence method to restore the text area from the preview div */ + AtD.restore = function() { + var options = AtD.properties; + + /* check if we're in the proofreading mode, if not... then return */ + if (!isProofreading) + return; + + AtD.transition = true; + + /* get the current position, do it before isProofreading changes. */ + AtD.position = AtD.getScrollPosition(); + + /* no longer in proofreading mode */ + isProofreading = false; + + /* swap the preview div for the textarea, notice how I have to restore the appropriate class/id/style attributes */ + + AtD.remove(proofreader); /* strip out the AtD markup please */ + var content = proofreader.parent().find('#AtD_Content').html(); + + AtD.setValue(original, content); + + /* clear the error HTML out of the preview div */ + proofreader.remove(); + + original.css('display', AtD.displayValue); + original.attr('AtD_active', null); + + /* update the scrollbar position based on the saved position */ + AtD.setScrollPosition(AtD.position); + + /* change the link text back to its original label */ + widget.mode("edit"); + + AtD.transition = false; + + widget.adjustWidget(); + }; + + /* other proofreader components can override these things */ + AtD.setValue = function(component, value) { + if (value == null) + component.val(""); + else + component.val( value.replace(/\<\;/g, '<').replace(/\>\;/g, '>').replace(/\&\;/g, '&') ); + }; + + AtD.getCheckValue = function(component) { + return component.val(); + } + + AtD.getValue = function(component) { + return component.val().replace(/\&/g, '&').replace(/\>/g, '>').replace(/\' + contents + '' ); + }; + + AtD.showProofreader = function(component, proofreader) { + AtD.displayValue = component.css('display'); + component.css('display', 'none'); + component.attr('AtD_active', 'true'); + component.after(proofreader); + + /* textareas that are inline should be replaced by an inline-block div */ + if (AtD.displayValue == 'inline') + proofreader.css('display', 'inline-block'); + else + proofreader.css('display', AtD.displayValue); + + + /* update the scrollbar position based on the saved position */ + AtD.setScrollPosition(AtD.position); + }; + + AtD.inheritLookAndFeel = function(component, proofreader) { + var css = component.context.ownerDocument.defaultView.getComputedStyle(component.context, null); + + for (var i = 0; i < css.length; i++) { + var property = css.item(i); + proofreader.css(property, css.getPropertyValue(property)); + } + + proofreader.css( { 'overflow' : 'auto', 'white-space' : 'pre-wrap' } ); + proofreader.attr('spellcheck', false); /* ours is better */ + proofreader.css('-webkit-user-modify', 'read-write-plaintext-only'); + + return proofreader; + } + + AtD.checkSilent = function(callback) { + /* create a proofreader */ + var div = jQuery('
' + AtD.getValue(container) + '
'); + div.data('AtD', true); + + /* check the writing in the textarea */ + AtD.check(div, AtD.getCheckValue(container), { + ready: function(errorCount) { + hasBeenChecked = true; + AtD.hasErrors = errorCount > 0; + callback(errorCount); + }, + + error: function(reason) { + AtD.hasErrors = false; + callback(0); + } + }); + }; + + AtD.checkComponent = function() { + /* If the text of the link says edit comment, then restore the textarea so the user can edit the text */ + if (isProofreading) { + AtD.restore(); + } + else { + AtD.position = AtD.getScrollPosition(); + + /* we're now proofreading */ + isProofreading = true; + + widget.mode("proofread"); + + /* disable the spell check link while an asynchronous call is in progress. if a user tries to make a request while one is in progress + they will lose their text. Not cool! */ + var disableClick = function() { return false; }; + widget.getWidget().click(disableClick); + + /* create a proofreader */ + var div = AtD.createProofreader(AtD.getValue(container)); + div.data('AtD', true); + proofreader = AtD.inheritLookAndFeel(container, div); + + /* hide the original container and insert the proofreader */ + original = container; + AtD.showProofreader(container, div); + + /* block the enter key in proofreading mode */ + div.keypress(function (event) { + return event.keyCode != 13; + }); + + /* tell the editor to sync as you type */ + div.keyup(function (event) { + AtD.sync(); + }); + + /* bind the restore shortcut */ + AtD.bindShortcut(div); + + /* tell the widget to adjust */ + widget.adjustWidget(); + + /* check the writing in the textarea */ + AtD.check(div.parent().find('#AtD_Content'), AtD.getCheckValue(container), { + ready: function(errorCount) { + /* this function is called when the AtD async service request has finished. + this is a good time to allow the user to click the spell check/edit text link again. */ + widget.getWidget().unbind('click', disableClick); + hasBeenChecked = true; + }, + + explain: function(url) { + chrome.extension.sendRequest({ command: "open", url: url }); + }, + + success: function(errorCount) { + if (errorCount == 0) + chrome.extension.sendRequest({ command: "alert", text: AtD.getLang('message_no_errors_found', "No writing errors were found") } ); + + /* once all errors are resolved, this function is called, it's an opportune time + to restore the textarea */ + AtD.restore(); + }, + + error: function(reason) { + widget.getWidget().unbind('click', disableClick); + + if (reason == undefined) + alert( AtD.getLang('message_server_error_short', "There was an error communicating with the spell checking service.") ); + else + alert( AtD.getLang('message_server_error_short', "There was an error communicating with the spell checking service.") + "\n\n" + reason ); + + /* restore the text area since there won't be any highlighted spelling errors */ + AtD.restore(); + }, + + ignore : function(element) { + chrome.extension.sendRequest({ command: 'ignore', word: element }); + }, + + editSelection : function(element) { + var text = prompt( AtD.getLang('dialog_replace_selection', "Replace selection with:"), element.text() ); + if (text != null) { + jQuery(element).html( text ); + AtD.core.removeParent(element); + } + } + }); + } + } + return AtD; +} + diff --git a/scripts/widget.js b/scripts/widget.js new file mode 100644 index 0000000..724acee --- /dev/null +++ b/scripts/widget.js @@ -0,0 +1,77 @@ +function AtD_Widget(component, parent) { + var widget = jQuery('
 
'); + var _mode = "edit"; + var my = this; + + /* attach the widget to the specified component */ + component.before(widget); + + /* attach listeners to make the widget pretty when it's supposed to be */ + var showButton = function(event) { + widget.attr('class', widget.attr('class') + ' afterthedeadline-hover'); + }; + + var hideButton = function(event) { + widget.attr('class', widget.attr('class').replace(/(\s+|\b)afterthedeadline-hover(\s+|\b)/, '$1$2')); + }; + + component.focusin(showButton); + component.focusout(hideButton); + component.mouseover(showButton); + component.mouseout(hideButton); + + this.mode = function(m) { + _mode = m; + + if (m == 'edit') + widget.attr('class', 'afterthedeadline-button'); + else + widget.attr('class', 'afterthedeadline-button afterthedeadline-done'); + } + + this.getWidget = function() { + return widget; + }; + + var lastAdjust = 0; + this.adjustWidget = function(event) { + if (!parent.isProofreading() && (component.css('display') == 'none' || component.css('visibility') == 'hidden') && !parent.transition) { + parent.detach(); + return; + }; + + var check = new Date().getTime() - lastAdjust; + if (check > 200) { + var target = parent.getActiveComponent(); + var targetj = jQuery(target); + + var offset = targetj.offset(); + offset.left += target.outerWidth(true) - widget.width(); + offset.top += target.outerHeight(true) - widget.height(); + + /* set the offset to deal with scrollbars properly */ + parent.adjustWidget(offset); + + if (targetj.css('z-index') != '0') + widget.css('z-index', parseInt(targetj.css('z-index'), 10) + 1); + else + widget.css('z-index', 0); + + widget.offset(offset); + lastAdjust += check; + } + }; + + /* when an attribute related to the textarea is modified... adjust the widget's positioning */ + jQuery(component).bind('DOMAttrModified', this.adjustWidget); + jQuery(component).mouseover(this.adjustWidget); + jQuery(component).focusin(this.adjustWidget); + jQuery(component).focusout(this.adjustWidget); + jQuery(component).resize(this.adjustWidget); + + /* check if the element is already focused, if it is--make button reflect it */ + if (document.activeElement == component[0] && document.activeElement == component.context) + showButton(); + + return this; +}