ADDED ABOUT Index: ABOUT ================================================================== --- ABOUT +++ ABOUT @@ -0,0 +1,6 @@ +web2py is an open source full-stack framework for agile development +of secure database-driven web-based applications, written and programmable in +Python. + +Created by Massimo Di Pierro + ADDED LICENSE Index: LICENSE ================================================================== --- LICENSE +++ LICENSE @@ -0,0 +1,137 @@ +## Web2py License + +Web2py is Licensed under the LGPL license version 3 +(http://www.gnu.org/licenses/lgpl.html) + +Copyrighted (c) by Massimo Di Pierro (2007-2011) + +### On Commercial Redistribution + +In accordance with LGPL you may: +- redistribute web2py with your apps (including official web2py binary versions) +- release your applications which use official web2py libraries under any license you wish +But you must: +- make clear in the documentation that your application uses web2py +- release any modification of the web2py libraries under the LGPLv3 license + +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. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT +HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. + +(Earlier versions of web2py, 1.0.*-1.90.*, were released under the GPL2 license plus a +commercial exception which, for practical purposes, was very similar to the current LPGLv3) + +### Licenses for third party contributed software + +web2py contains third party software under the gluon/contrib/ folder. +Each file/module in contrib is distributed with web2py under its original license. +Here we list some of them. + +#### gluon.contrib.simplejson LICENSE + +Copyright (c) 2006 Bob Ippolito - Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +#### gluon.contrib.rss2.py (originally PyRSS2Gen) LICENSE + +This is copyright (c) by Dalke Scientific Software, LLC and released under the +BSD license. See the file LICENSE in the distribution or + for details. + +#### gluon.contrib.markdown (markdown2) LICENSE + +MIT License from from + +#### gluon.contrib.feedparser LICENSE + +Copyright (c) 2002-2005, Mark Pilgrim + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#### gluon.wsgiserver.py LICENSE (borrowed from cherrypy) + +Copyright (c) 2004, CherryPy Team (team@cherrypy.org) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the CherryPy Team nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#### gluon.contrib.pam LICENSE + +Copyright (C) 2007-2009 Chris AtLee Licensed under the MIT license + +#### gluon.contrib.shell LICENSE + +Copyright (C) by Google inc. Apache 2.0 Lincense + +#### The javascript licenses are in the code itself + ADDED Makefile Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -0,0 +1,123 @@ +clean: + rm -f httpserver.log + rm -f parameters*.py + rm -f -r applications/*/compiled + find ./ -name '*~' -exec rm -f {} \; + find ./ -name '*.orig' -exec rm -f {} \; + find ./ -name '*.rej' -exec rm -f {} \; + find ./ -name '#*' -exec rm -f {} \; + find ./ -name 'Thumbs.db' -exec rm -f {} \; + find ./gluon/ -name '.*' -exec rm -f {} \; + find ./gluon/ -name '*class' -exec rm -f {} \; + find ./applications/admin/ -name '.*' -exec rm -f {} \; + find ./applications/examples/ -name '.*' -exec rm -f {} \; + find ./applications/welcome/ -name '.*' -exec rm -f {} \; + find ./ -name '*.pyc' -exec rm -f {} \; +all: + echo "The Makefile is used to build the distribution." + echo "In order to run web2py you do not need to make anything." + echo "just run web2py.py" +epydoc: + ### build epydoc + rm -f -r applications/examples/static/epydoc/ + epydoc --config epydoc.conf + cp applications/examples/static/title.png applications/examples/static/epydoc +tests: + cd gluon/tests; ./test.sh 1>tests.log 2>&1 +update: + wget -O gluon/contrib/feedparser.py http://feedparser.googlecode.com/svn/trunk/feedparser/feedparser.py + wget -O gluon/contrib/simplejsonrpc.py http://rad2py.googlecode.com/hg/ide2py/simplejsonrpc.py +src: + echo 'Version 1.99.2 ('`date +%Y-%m-%d\ %H:%M:%S`') stable' > VERSION + ### rm -f all junk files + make clean + ### clean up baisc apps + rm -f routes.py + rm -f applications/*/sessions/* + rm -f applications/*/errors/* | echo 'too many files' + rm -f applications/*/cache/* + rm -f applications/admin/databases/* + rm -f applications/welcome/databases/* + rm -f applications/examples/databases/* + rm -f applications/admin/uploads/* + rm -f applications/welcome/uploads/* + rm -f applications/examples/uploads/* + ### make admin layout and appadmin the default + cp applications/admin/views/appadmin.html applications/welcome/views + cp applications/admin/views/appadmin.html applications/examples/views + cp applications/admin/controllers/appadmin.py applications/welcome/controllers + cp applications/admin/controllers/appadmin.py applications/examples/controllers + ### update the license + cp ABOUT applications/admin/ + cp ABOUT applications/examples/ + cp LICENSE applications/admin/ + cp LICENSE applications/examples/ + ### build web2py_src.zip + echo '' > NEWINSTALL + mv web2py_src.zip web2py_src_old.zip | echo 'no old' + cd ..; zip -r web2py/web2py_src.zip web2py/gluon/*.py web2py/gluon/contrib/* web2py/splashlogo.gif web2py/*.py web2py/ABOUT web2py/LICENSE web2py/README web2py/NEWINSTALL web2py/VERSION web2py/Makefile web2py/epydoc.css web2py/epydoc.conf web2py/app.example.yaml web2py/logging.example.conf web2py_exe.conf web2py/queue.example.yaml MANIFEST.in w2p_apps w2p_clone w2p_run startweb2py web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py + +mdp: + make epydoc + make src + make app + make win +app: + echo 'did you uncomment import_all in gluon/main.py?' + python2.5 -c 'import compileall; compileall.compile_dir("gluon/")' + #python web2py.py -S welcome -R __exit__.py + find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \; + cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip * + mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.5 + cp ABOUT ../web2py_osx/web2py/web2py.app/Contents/Resources + cp NEWINSTALL ../web2py_osx/web2py/web2py.app/Contents/Resources + cp LICENSE ../web2py_osx/web2py/web2py.app/Contents/Resources + cp VERSION ../web2py_osx/web2py/web2py.app/Contents/Resources + cp README ../web2py_osx/web2py/web2py.app/Contents/Resources + cp splashlogo.gif ../web2py_osx/web2py/web2py.app/Contents/Resources + cp options_std.py ../web2py_osx/web2py/web2py.app/Contents/Resources + cp routes.example.py ../web2py_osx/web2py/web2py.app/Contents/Resources + cp router.example.py ../web2py_osx/web2py/web2py.app/Contents/Resources + cp app.example.yaml ../web2py_osx/web2py/web2py.app/Contents/Resources + cp queue.example.yaml ../web2py_osx/web2py/web2py.app/Contents/Resources + cp -r applications/admin ../web2py_osx/web2py/web2py.app/Contents/Resources/applications + cp -r applications/welcome ../web2py_osx/web2py/web2py.app/Contents/Resources/applications + cp -r applications/examples ../web2py_osx/web2py/web2py.app/Contents/Resources/applications + cp applications/__init__.py ../web2py_osx/web2py/web2py.app/Contents/Resources/applications + cd ../web2py_osx; zip -r web2py_osx.zip web2py + mv ../web2py_osx/web2py_osx.zip . +win: + echo 'did you uncomment import_all in gluon/main.py?' + python2.5 -c 'import compileall; compileall.compile_dir("gluon/")' + find gluon -path '*.pyc' -exec cp {} ../web2py_win/library/{} \; + cd ../web2py_win/library/; zip -r ../library.zip * + mv ../web2py_win/library.zip ../web2py_win/web2py + cp ABOUT ../web2py_win/web2py/ + cp NEWINSTALL ../web2py_win/web2py/ + cp LICENSE ../web2py_win/web2py/ + cp VERSION ../web2py_win/web2py/ + cp README ../web2py_win/web2py/ + cp splashlogo.gif ../web2py_win/web2py/ + cp options_std.py ../web2py_win/web2py/ + cp routes.example.py ../web2py_win/web2py/ + cp router.example.py ../web2py_win/web2py/ + cp app.example.yaml ../web2py_win/web2py/ + cp queue.example.yaml ../web2py_win/web2py/ + cp -r applications/admin ../web2py_win/web2py/applications + cp -r applications/welcome ../web2py_win/web2py/applications + cp -r applications/examples ../web2py_win/web2py/applications + cp applications/__init__.py ../web2py_win/web2py/applications + cd ../web2py_win; zip -r web2py_win.zip web2py + mv ../web2py_win/web2py_win.zip . +pip: + # create Web2py distribution for upload to Pypi + # after upload clean Web2py sources with rm -R ./dist + python setup.py sdist +run: + python2.5 web2py.py -a hello +push: + make src + echo '' > NEWINSTALL + hg push + bzr push bzr+ssh://mdipierro@bazaar.launchpad.net/~mdipierro/web2py/devel --use-existing-dir + ADDED README Index: README ================================================================== --- README +++ README @@ -0,0 +1,1178 @@ +## INSTALLATION INSTRUCTION - IMPORTANT + +To start web2py there is NO NEED to install it. Just unzip and do: + +> python web2py.py + +Thats is it!!! + +## web2py file structure + +start web2py with: + + python web2py.py + +`` + \project + README + LICENSE + TODO + Makefile ## make all and make app + web2py.py ## the startup script (*) + parameters.py ## created at startup + admin.tar ## the admin app (*) + examples.tar ## examples and documentation app (*) + welcome.tar ## the welcome app (entry point) (*) + \gluon ## the core libraries (*) + \deposit ## used for zip and install apps + setup_app.py ## used by py2app to make OSX executable + setup_exe.py ## used by py2app to make Winows executble + wsgihandler.py ## to use Gluon with mod_wsgi + \dist ## used by py2app, py2exe + \build ## used by py2app, py2exe + \tests ## under development stuff + \docs ## in progress documentation + \applications ## are the apps + \welcome ## application welcome, for example + \models + \views + \controllers + \sessions + \errors + \cache + \static + \uploads + \modules +`` + +## EWF v1.5 -> v1.6 +- load and save .py in ascii, avoids problem with LF+CR on windows +- added path.join in compileapp, fixed problem with Windows compileapp + +## EWF v1.6 -> v1.7 +- in paths replace '\' with '/' to fix problem with windows paths +- using limitby in database administration +- replaced mime/miltupart with multipart/form-data to fix a windows problem + +## EWF v1.7 -> Gluon v1.0 +- Name change +- Improved layout.html + +## Gluon v1.0 -> v1.1 +- bug in sqlhtml with JOINS queries + +## Gluon v1.1 -> v1.2 +- fixed some typos in examples +- IS_IN_SET now supports labels +- cleanup in sql.py does not cleanup, just checks valid field and table names + +## Gluon v1.3 +- added IS_IN_DB, IS_NOT_IN_DB and updated examples accordingly + +## Gluon v1.4 +- fixed problem with IS_INT_IN_RANGE and IS_FLOAT_IN_RANGE. Now an error in a validator is reported as a ticket. Good validators should not raise Exceptions. +- IS_IN_DB displays "label (id)" +- it can upload files without extension +- migration is now optional (define_table has migrate=False option) + +## Gluon v1.5 +-
-> in errors.html +- replace('//','////') in sub in template.py + +## Gluon v1.8 +- no more chdir (thread unsafe) +- no more sys.stdout (thread unsafe) +- response.body is StringIO() +- admin/default/site informs about upgrade +- response.locker (optional) + +## Gluon v1.9 +- allow "count(*)" in select +- db.execute() +- fixed problem with continue and return in template +- removed try: ... except in sql.py +- fixed '\t' + +## Gluon v1.10 +- fixed concurrency problems with SQLDB._instances and SQLDB._folders, now use lock +- now, by default, edit SQLFORMs retain uploaded files + +## Gluon v1.11 +- appadmin allows to keep or delete uploaded files + +## Gluon v1.12 +- in sql.py +- handles NULL values properly +- unicode support (data always stored in utf-8) +- 'date' -> datetime.date ,'time' -> datetime.time, 'datetime' -> datetime.datetime, 'boolean' -> True/False +- most types have default validators +- SQLField(...,required=True) option. +- SQLRows has __str__ that serializes in CSV and xml() that serializes in HTML +- SQLTable has import_from_csv_file(...) +- gluon.simplejson for AJAX +- in validators.py +- IS_IN_DB(db,..) - db can be an SQLSet or an SQLDB +- better error messages +- in admin +- new import/export in csv, update and delete interface. +- in appadmin +- edit form allows to keep stored encrypted password +- in main.py +- http://host not defaults to http://host/init/default/index +- New third party modules +- gluon.simplejson(.dumps, .loads) +- gluon.pyrtf(.dumps) +- gluon.rss2(.dumps) + +## Gluon v1.13 +- (this is one of the biggest revisions ever) +- Improved sql.py has support MySQL, cxOracle (experimental), extract, like and better testing +- SQLDB.tables and SQLTable.fields are now SQLCalableList objects +- Fixed bug with editing integer fields storing zero +- Admin interface now says "insert new [tablename]" and display insert, select or update properly in the title. +- Added a cache mechamism. Works for data, controllers, views and SQLRows. +- main.py now uses a request.folder absolute path when not os.name in ['nt','posix']. Seems to work on windowsce devices, except no file locking has consequences. +- Now you can put modules in applications/[anyapp]/modules and import them with +- import applications.[anyapp].modules.[module] as [module] +- Fixed problem with init +- New applications/examples/controller/global.py controller for docs. + +## Gluon v1.14 +- Fixed a bug fix in URLs + +## Gluon v1.15 +- New try:... except. in gluon/main.py for when sessions cannot be saved +- Now validator/formatter method allows IS_DATE('%d/%m/%Y') + +## web2py v1.16 +- yes we changed the name! Turns out Gluon was trademarked by somebody else. +- Although we are not infringing the trademark since this is a non-commercial +- product we could have run into some issues. So we have been professional +- and changed the name to web2py. +- Now SQLFORMs and FORM can have a formname and multiple forms are allowed +- per page. +- A new examples/default/index page. +- web2py.py instead of runme.py +- mysql sets utf8 encoding. +- input integer field values are automatically converted int(). + +## web2py v1.17 +- I posted v1.16 too soon. v1.17 was released after 1h to fix some bugs. + +## web2py v1.18 +- removed vulnerability in accept_languages and session_id +- Minor bug fixes. Typos and cleanup cache. Textarea now clears. +- Support for PyAMF. +- T returns a class, not a string +- new template parser (faster?) +- got rid of sintaxhighlighter in favor of server side CODE +- fix problem with cacheondisk locking +- fix 'None' instead of NULL in IS_NOT_IN_DB (I think) +- gluon.contrib.markdown +- notnull and unique in SQLField now supported (tested on sqlite mysql and postgresql) +- Storage now has __getstate__ and __setstate__ needed for pickling. +- session files are now locked to make it work better with asynchronous requests +- cxoracle should work, apart for limitby +- .../examples is now mapped to .../examples/default/index etc. +- .../init is now mapped to .../welcome if init is not present + +## web2py 1.19 +- minor typos + +## web2py 1.20 +- new IFRAME, LABEL, FIELDSET validators +- P(..cr2br=True) option +- FORM and SQLFORM have hidden=dict(...) option for REST +- testing framework. +- improved examples pages + +## web2py 1.21 +- replaced paste.httpserver with cherrypy.wsgi server +- temporary sessions are no longer saved +- widget has [stop] button and graph +- logging is done by main by appfactory +- fixed a bug in sql belongs + +## web2py 1.22-1.25 +- fixed minor bugs, added IS_NULL_OR + +## web2py 1.26 +- added shell.py (thanks Limodou!) +- added memcache support + +## web2py 1.27 +- IS_NULL_OR now works will all fields +- admin creates paths to static files +- wsgiserver options are passed to HttpServer +- faking limitby for oracle to make appadmin work +- all objects inherit from object +- fixed bug in app names with . +- fixed bug in created RestrictedError object on windows +- shell is now in gluon and accessible via web2py.py + +## web2py 1.28 +- fixed bug with belongs, faster sql.py +- included jquery.js +- minor aestetical fixes +- sortable.js is gone + +## web2py 1.29 +- Now selet mutliple works with get, so does is IS_LENGTH +- Added IS_LIST_OF +- fixed problem with admin from windows and localhost + +## web2py 1.30 +- added flv to contenttype +- added support for appengine + +## web2py 1.31-1.41 +- some bug fixes, mostly better appengine support +- mssql support +- firebird support +- widgets support +- connection pools + +## 1.42 +- fixed security issue by removing slash escape in mysql +- removed random everywhere +- use uuid for session and tickets +- use http_x_forward_for to figure out the client causing a ticket +- use longtext and longblob for mysql +- main now really catches all exceptions +- no more warnings on GAE + +## 1.43-1.48 +- html.py rewrite (better support for custom forms) (Bill Ferrett) +- new stickers in examples (thanks Mateusz) +- on windows can run in taskbar (Mark Larsen) +- in admin|edit page link to edit|controller (Nathan Freeze) +- better error codes and routes_onerror (Timothy Farrell) +- DAL support for groupy and having +- DAL support for expressions instead of values +- DAL has experimental Informix support +- fixed bug with non-printable chars in DAL +- 'text' fields limited to 2**16 (to avoid mysql problems) +- widget has -quiet and -debug (Attila Csipa) +- web2py_session uses BLOB instead of TEXT +- improved IS_URL +- Runs with python 2.6 (Tim) +- On GAE uses GAE for static files (Robin) + + +## 1.49 +- fixed a bug with taskbar widget, thanks Mark +- fixed a bug with form.latest +- made many DIV methods private (_) + + +## 1.50 +- Fixed some bugs introduced in 1.49 + +## 1.51 +- Fixed more bugs introduced in 1.49 (sql _extra and html select) +- support for sqlite:memory: + +## 1.52 +- Fixed a minor bug with _extra[key] and key not str. +- check for upgrade via ajax + +## 1.53 +- On GAE upload data goes automatically in datastore (blob created automatically) +- New appadmin runs on GAE (most of it, not all) +- Martin Hufsky patch allow slicing of fields in DAL expressions + +## 1.54 +- fixed minor bugs + +## 1.55? +- rowcount +- fixed bug when IS_IN_DB involved multiple fields on GAE +- T.set_current_languages +- better unittests +- response.custom_commit and response.custom_rollback +- you can next cache calls (like cache a controller that caches a select). Thanks Iceberg +- db(....id==None).select() no longer returns an error but an empty SQLRows on GAE +- db(...).delete(delete_uploads=True) and SQLFORM.accepts(....delete_uploads=True) will delete all referenced uploaded files +- DIV.element and DIV.update +- sqlrows.json() +- SQLFORM.widgets +- URL(r=request,args=0) +- IS_IN_DB(...,multiple=True) for Many2Many (sort of) +- In URL(...,f) f is url encoded +- In routes_in=[['a/b/c/','a/b/c/?var=value']] +- simplejson 2.0.7 + + +## 1.56 +- Consider the following table: + +- db.define_table('cirlce', +- db.Field('radius','double'), +- db.Field('area','double'), +- db.Field('modified_on','datetime')) + +- now you can do: + +## add a comment in the forms +- db.circle.area.comment="(this is a comment)" + +## do not show area in create/edit forms +- db.circle.area.writable=False + +- ## do not show now in display forms +- db.circle.modified_on.readable=False + +## automatically timestamp when record cretaed +- db.circle.modified_on.default=request.now + +## automatically timestamp when record is modified +- db.circle.modified_on.update=request.now + +## make the radius appear in bold in display and table +- db.circle.radius.represent=lambda value: B(value) + +## make a form that automatically computes area +- pi=3.1415 +- form=SQLFOM(db.circle) +- if form.accepts(request.vars, +- onvalidation=lambda form: form.vars.area=pi*form.vars.radius**2): ... + +## make a create form in two possible ways: +- form=SQLFORM(db.circle) +- form=SQLFORM(db.circle,0) + +## make an update form in two possible ways: +- form=SQLFORM(db.circle,record) +- form=SQLFORM(db.circle,record_id) + +## make a display form in two possible ways: +- form=SQLFORM(db.circle,record,readonly=True) +- form=SQLFORM(db.circle,record_id,readonly=True) + +## so now you can do... + +- form=SQLFORM(db.circle,request.args[-1]) + +- and you get a create form if the URL ends in /0, you get an update +- form if the URL ends in /[valid_record_id] + +## you can also define once for all + +- timestamp=SQLTable(None,'timestamp', +- SQLField('created_on','datetime', +- writable=False, +- default=request.now), +- SQLField('modified_on','datetime', +- writable=False, +- default=request.now,update=request.now)) + +## and use it in all your tables + +- db.define_table('mytable',db.Field('somefield'),timestamp) + +## ## ## + +- One more feature in trunk.... + +- db.define_table('image',SQLField('file','upload')) + +- db.image.file.authorize=lambda row: True or False + +- then controller +- def download(): return response.download(request,db) +- id' is now a hidden field sqlform +- gql references converted to long +- admin login has autofocus +- new notation proposed by Robin, db.table[id] +- new UploadWidget shows images +- new generic.html shows request, response, session +- new LEGEND helper (thanks Marcus) +- fixed doctests in sql (thanks Robin) +- new notation for DB + +- record=db.table[id] +- db.table[id]=dict(...) +- del db.table[id] + +- request.env.web2py_version +- new class gluon.storage.Settings has lock_keys, lock_values +- jquery 1.3.1 +- PEP8 compliance +- new examples application +- runs on jython (no database drivers yet, thanks Phyo) +- fixed bugs in tests +- passes all unittest but test_rewite (not sure it should pass that one) + +- Lots of patches from Fran Boone (about tools) and Dougla Soares de Andarde (Python 2.6 compliance, user use of hashlib instead of md5, new markdown2.py) + +## 1.56.1-1.56.4 +- fixing lots of small bugs with tool and languages +- jquery.1.3.2 + +## 1.57 +- New ajax edit with keepalive (no longer logged out when editing code) +- Fixed conflict resolution page. +- Removed /user/bin/python from models/controllers + +## 1.58 +- Fixed some CRON bugs +- Fixed a bug with new ajax edit +- Experimental DB2 support in DAL +- Customizable font size in admin edit page +- New welcome/models/db.py shows how to memcache sessions on GAE with MEMDB +- More expressive titles in admin +- DB2 support. Thanks Denes! + +## 1.59-1.60 +- fixed lots of small bugs +- routes_in can filter by http_host + +## 1.61 +- fixed some typos +- auth.add_permissions(0,....) 0 indicates group of current user +- crud.update has deletable=True or False +- fixed issue with GAE detection -> gluon.settings.web2py_runtime -> request + +## 1.62 +- SQLFORMS and crud now show readble fields +- Better WingIDE support +- Languages are automatically translated +- T.force and lazyT works better, optional T.lazy=False +- gluon.storage.Messages are now translated without T +- if routes.py then request.env.web2py_original_uri +- db.table.field.isattachment = True +- internationalizaiton of admin by Yair +- admin.py by Alvaro +- new MENU helper +- new w2p file format +- new welcome app with auth, service and crud turned on + +## 1.63-1.63.4 +- no more import gluon. +- support for generic.xxx +- simplejson can handle datetime date and time + +## 1.63.5 +- You can do jQuery.noConflict() without breaking web2py_ajax +- Wigets can have attributes (thanks Hans) +- Lots of internal cleanup and better code reusage (thanks Hans) + +## 1.64 +- Models 2-3 times faster (thanks Alexey) +- Better LDAP support +- Works with Jython (including sqlite and postgresql with zxJDBC): + +- download jython-2.5rc3.jar +- download qlite-jdbc-3.6.14.2.jar +- java -jar jython-xxx.jar +- export CLASSPATH=$CLASSPATH:/Users/mdipierro/jython2.5rc3/sqlite-jdbc-3.6.14.2.jar +- cd web2py +- ../jython2.5rc3/jython web2py.py + +## 1.64.2 +- New IS_COMPLEX validator, thank Mr. Freeze +- Experimental Informix support +- Autologin on registration + +## 1.64.3 +- Some bug fixes + +## 1.64.4 +- Som bug fixes +- Informix Support +- response.render(stream) +- SQLFORM.factory +- SQLFORM.widgets.radio and SQLFORM.widgets.checkboxes + +## 1.65 +- reST docstrings for Sphinx, thanks Hans +- gluon/conrtib/login_methods/gae_google_account.py for google CAS login on GAE, thanks Hans +- fixed problem with Auth and Firebird 'password' issue +- new auth.settings.create_user_groups +- tickets stored on datastore on GAE and also logged, thanks Hans +- imporved IS_LENGTH with max and min, thanks Mateusz +- improved IS_EMAIL with filters, thanks Mateusz +- new IS_IMAGE checks for format and size, thanks Mateusz +- new IS_IPV4, thanks Mateusz + +## 1.65.1 +- spreadsheet +- shell history, thanks sherdim +- crontab editor, thanks Angelo +- gluon/contrib/login_methods/cas_auth.py (thanks Hans) +- DAL(...) instead of SQLDB(...) +- DAL('gae') instead of GQLDB() +- Field instead of SQLField +- (the old syntax still works) + +## 1.65.2 +- Fixed some small auth bugs +- Field.store(...) + +## 1.65.3-10 +- Fixed some small bugs and typos in the docstrings +- Fixed AMF3 support + +## 1.65.11 +- Fixed a sqlhtml bug with image upload + +## 1.65.12 +- lables in auth auto-translate (thanks Alvaro) +- better ldap_auth (thanks Fran) +- auth chacks locally for blocked accounts even for alternate login methods (thanks Fran) + +## 1.65.13 +- request.url (thanks Jonathan) +- restored uploadfield_newfilename +- new examples layout nad logo (thanks Mateusz) + +## 1.66 +- new doctypes +- form.vars.newfilename +- new HTML and XHTML helpers +- better IS_LENGTH + +## 1.67.0 +- Python 2.4 support (again) +- New layout for welcome +- changed defauld field sizes to 512 +- Field(uploadfolder="...") +- appadmin works on GAE (again, somehting got broken at some point) +- new wsgiserver 3.2.0 should fix recurrent broken download problems + +## 1.67.1 +- Bux fixed + +## 1.67.2 +- Security fix in markdown + +## 1.68.1 +- New official markdown with security fix +- rows.first() +- rows.last() +- New cron +- New hindi and spanish translation +- cached uploads allow for progress bars (thanks AndCycle) +- ingres support (thanks Chris) +- legacy database support for db2, mssql with non-int primary keys (thanks Denes) +- default setting of content-type (this may cause strange behavior in old apps when downloading images) +- IS_UPPER and IS_LOWER works with unicode +- CLENUP not takes regex of allowed/now allowed chartares +- New rewrite.py allows dynamic routes +- Better error messages for IS_INT_* and IS_FLOAT_* + +## 1.68.2 +- Fixing bug with admin and missing crontab +- Fixing bug with rewrite.load on GAE (thanks Willian Wang) + +## 1.69.1 +- Fixed a bug introduced in 1.68 about inserting unicode in DAL +- Fixed other small bugs +- Better support for legacy databases (thank Denes) +- response.meta replaces response.author, response.keywords, response.description +- response.files stets dependes in plugins +- better admin for packing/unpacking plugins +- reference fiels nor evaluate to DALRef with lazy evaluation (cool, thanks Mr Freeze) +- can insert a record in place of a reference +- record[e] instead of record._extra[e] (tentatively!) +- record.update_record() with no args +- rows.find() (thanks Mr Freeze) +- rows.exclude() +- rows.sort() +- rows[:] + +## 1.70.1 +- Fixed bug with Rows.as_list and DALRef +- Added Rows.as_dict (thanks Mr Freeze and Thedeus) +- Added request.wsgi (thanks hcvst) allows running wsgi apps under web2py and applying wegi middleware to regular web2py actions that return strings. +- Experimental distributed transactions between postgresql, mysql and firebird +- Finally local_import is here! + +## 1.71.1 +- Complete rewrite of Rows +- renamed DALStorage->Rows, DALRef->Reference +- Experimental serializarion of Row and Rows (get serialized to dict and list of dict) +- DAL(...,folder) and template.render(content=, context=) make it more modular + +## 1.72.1 - 1.72.3 +- Better support for legacy databases + +## 1.73.1 +- Fixed problem with storage and comparison of Row objects +- Fixed problem with mail on GAE +- Fixed problem with T in IS_DATE(TIME) error_message and format +- Rows[i].delete_record() +- Even better support for legacy databases +- Experimantal support for non UTF8 encoding in DB +- Better IPV4 (thanks Thandeus) +- T.current_languages default to 'en' and new T.set_current_languages(...) (thanks Yarko) +- INPUT(...,hideerror=False) used to fix rare chechbox widget problem +- Admin allows change of admin password +- New gluon/contrib/populate.py +- Size of input/textarea set by CSS no more by jQuery (thanks Iceberg) +- Customizable CSV (thanks Thandeus) +- More bug fixed (thanks Thandeus) +- Better regex for template fixed Jython problem (thank Jonathan) + +## 1.74.1 +- Moved to mercurial +- Default validators use the new define_table(....,format='...') +- New get_vars and post_vars compatible in 2.5 and 2.6 (thanks Tim) +- Major rewrite of gql.py extends DAL syntax on GAE +- No more *.w2p, welcome.w2p is create automatically, base apps are always upgraded +- export_to_csv(delimiter = ',', quotechar = '"', quoting = csv.QUOTE_MINIMAL), thanks Thadeus + +## 1.74.2-4 +- Fix bugs including including unicode in emails and blobs on GAE + +## 1.74.5 +- bug fixes +- restored python 2.4 support,thanks ont.rif +- support for native types on Google App Engine +- cache.ram usage statictics, thanks Thadus +- no more auth manu in scaffolding +- no more spash screen with -Q +- fixed doctest in html.py, thanks Anand Vaidya +- export_to_csv_file has represent, thanks Thadeus + +## 1.74.6 +- bug fixes +- IS_IN_DB(...,_and=IS_NOT_IN_DB) +- Smaller populate, thanks Piotr +- better slicing of fields, thanks Michael Fig +- Cache stats, thanks Thadeus +- Better gql.py +- IS_IN_DB and IS_IN_SET default to zero='', no longer zero=None + +## 1.74.7 +- request_password_reset and password reset verification +- python web2py.py -S app -M -R script.py -A arg1 arg2 arg3 +- T("%(a)s") % dict(a="hello") + +## 1.74.8 +- IS_SLUG, thanks Gustavo and Jonathan +- web2py.py -nogui, thanks Jeff Bauer +- solved a problem with jython, thanks Tim Farrel +- login has "remember be option", thanks Piotr Banasziewicz +- fixed problem with keepvalue in update forms, thanks Miguel Lopez + +## 1.74.9 +- IS_IN_SET(((0,'label0'),(1,'label1'))), thanks Falko Krause +- SQLFORM(...).accpets stores True or False in boolean types no None, thanks Frederik Wagner +- SQLFORM.factory(...,table_name='no_table'), thanks Thedeus +- jQuery 1.4.1 +- Fixed major problem with internationalization of multiple languages. +- Fixed a serius security issue with login +- Possibly fixed some issues with cron + +## 1.75.1 +- better cron +- better fetch +- logging of email failures +- new web2py.fedora.sh +- new setup-web2py-ubuntu.sh +- experimental autocomplete +- menus work on IE6 + +## 1.75.2 +- fetch supports cache +- curd.update(....,onaccept=crud.archive) magic +- new UUID mechnism fixes session conflicts with cloned machine in cloud +- allow to upload app and overwrite existing old ones, thanks Jonathan +- print gluon.tools.prettydate(request.now,T), thanks Richard + +## 1.75.3 +- added support for PAM authentican for apps and for admin +- INTRODUCED MAJOR BUG IN BEAUTIFY (upgrade to 1.75.4) IMMEDIATELY + +## 1.75.4 +- customizable BEAUTIFY, thanks John + +## 1.75.5 +- fixed behaviour with languages.py, thanks Iceberg +- added chinese (thanks Iceberg) and Hungarian (thanks Gyuris) +- fixed problem with GAE deleted by id (thanks what_ho) +- fixed bug in LOAD with custom views, thanks vhang +- improved IS_IN_SET takes iterator, dict, list and list of tuples, thanks Iceberg +- Auth(...,controller='default') +- Fixed major bug in parsing repeated request.vars, thanks Ben +- IS_DATE and IS_DATETIME can now handle any 00).select(),headers='fieldname:capitalize') +- Oracle limitby improved (thanks Sergey) +- fixed migrations in Firebird, thanks Jose Jachuf +- gluon/contrib/login_methods/linkedin_account.py (to be tested) + +## 1.76.5 +- Fixed a typo in auth that created some registration problems + +## 1.77.1 +- Replaced CherryPy with Rocket web server, thanks Tim +- CacheOnDisk allows to specify a folder +- IS_DATE/DATETIME can handle any year since 0 +- SQLTABLE(...,headers='fieldname:capitalize') +- Field().with_alias, thanks Nathan and Mengu +- has_membership(group=...,role=...), thank Jonathan +- db.define_table(username=True), thanks Jonathan +- gluon.tools.prettydate +- can specify hostname in routes_out (same syntax as routes in), thanks Martin +- db.table.bulk_insert([...records...]) now works on GAE, thanks Jon +- IS_EMAIL validates on 'localhost', thanks Jonathan +- welcome/views/layout.html uses ez.css, thanks Yarko +- mail attachments support utf8, thanks szimszon +- works with PyPy, thanks Joe +- better Firebird support, thanks Jose +- better Oracle support, thanks Gabriele +- cron supports days of week +- SQLFORM(...,formstyle="table3cols") or "table2cols" or "divs" or "ul" +- crud.settings.formstyle +- web2py.py -f folder allows to specify locations of applications, thanks Iceberg +- better/faster regex in template works in Jython +- fixed lots of small bugs + +## 1.77.2 +- fixed x-index in calendar +## 1.77.3 +- some cleanup of code in compileapp + +## 1.78.1 +- new template system allows {{block name}}{{end}}, thanks Thadeus +- fixed mime headers in emails, included PGP in emails, thanks Gyuris +- automatic database retry connect when pooling and lost connections +- OPTGROUP helper, thanks Iceberg +- web2py_ajax_trap captures all form submissions, thank you Skiros +- multicolumn checkwidget and arbitrary chars in multiple is_in_set, thanks hy +- Québécois for welcome, thanks Chris +- crud.search(), thanks Mr Freeze +- DAL(...migrate,fake_migrate), thanks Thadeus + +## 1.78.3 +- reverted temporarily to old template system because of backward compatibility issues + +## 1.79.1 +- x509 emails, thanks Gyuris +- attachment and html in Mail on GAE, thanks PanosJee +- fixed docstring in SQLTABLE, thanks aabelyakov +- TAG(html) parese html into helpers (experimental, still some problems with unicode, , thanks RobertVa for unicode help) +- DIV.elements(find=re.compile(....)) +- DIV.flatten() +- DIV.elements('....') supports jQuery syntax in '....' +- better it-it.py and it.py, thanks Marcello Della Longa +- Many Bug fixes: +- improved support for DAL and joins in postgresql/oracle, thanks Nico de Groot +- bux fixex in html.py, thanks Ian +- fixed an issue with registration_key==None, thanks Jay Kelkar +- fixed bug in gql.py, thanks NoNoNo +- fixed problem with multiple and checkboxes, thanks MIchael Howden +- fixed bug in gae, thanks NoNoNo +- restored 2.4 compatibility, thanks Paolo Gasparello +- auth.update() when pictures in profile +- formstyle can be a function, thanks Howden +- escape in sanitizer, thanks Howes +- add missing settings, thanks Hamdy +- find and exclude return empty Rows instead of [], thanks iceberg +- simplejson 2.1.1 should fix compatibility problems +- added sms_utils and Authorize.net in contrib + +## 1.79.2 +- solved simplejson imcompatibility problem + +## 1.80.1 +- MARKMIN helper (no backward compatibility promise yet) +- self._last_reference, thanks Dave (no backward compatibility promise yet) +- IS_EQUAL_TO +- zh-tw and better internationalization page, thanks Daniel Lin and Iceberg +- better crud.search, thanks MrFreeze +- Rocket interfaces, thanks Nik Klever +- db.table.field.uploadseparate=True, thanks Gyuris +- SCOPE_IDENITY for MSSQL, thanks Jose +- fixed email attachment issue, thanks Bob_in_Comox +- fixed problem with groupby and IS_IN_DB +- other bug fixes +- new implementation for local_import +- ajax(..,...,null) +- fixed Chrome bug in calendar.js, thanks Iceberg +- experimental scrips/web2py-setup-fedora.sh +- generic.load, thanks Iceberg + +## 1.81.1 +- rpx (janrain) support out of the box, allows login with Facebook, MySpace, etc. Thanks Mr Freeze +- Increased security (escape single and double quotes, thanks Craig" +- Fixed a bug with db.table.field.uploadseparate=True and autodelete +- New welcome app with superfish and jQuery 1.4.2 +- Deleted openwysiwyg from admin +- In XML and xmlescape quote defaults to True. Both ' and " are escaped. Thanks Craig Younkins + +## 1.81.2 +- fixed bug in Auth + +## 1.81.3 +- fixed bug in label names in formstyle +- fixed id names in admin test.html page + +## 1.81.4 +- Fixed gluon.tools to work work with load and base.css to nowrap labels + +## 1.81.5 +- Fixed a few bugs. The most important bugs we fixed are in memcache (thanks Scott) and in a process starvation issue with Rocket (thanks Mike Ellis and Tim). + +## 1.82.1 +- request.ajax to detect if action is called via ajax, tahnks Jonathan and David Mako +- more captcha options, thanks Vidul +- openid and oauth2 thanks Michele and Keith +- better PluginManager and load components +- new template system, thanks Thadeus +- new db.table(id,[field=value]) and db.table(query) syntax +- URL('index') (no more r=request), thanks Thadeus +- mail.send(message='...', ....) +- DAL([....]) for load balancing +- @service.soap(...) with mysimplesoap, thanks Mariano +- hideerror + +## 1.83.1 +- New error reporting mechanism (thanks Mariano) +- New routing system with app level routing (thanks Jonathan) +- Integrated GAE appstat and GAE precompilation (thanks Scott) +- New Field types "list:string", "list:integer", "list:reference" +- request.cid, request.ajax, A(cid=request.cid), response.js + +## 1.83.2 +- mostly cleanup + +## 1.84.1-4 +- flash now stays put in the top right corner +- improved behavior for URL and T objects +- new app level logging with logging.conf (thanks Jonathan) +- improved OpenID (thanks Michele) +- web2py_ajax handles prepend, append, hide (thanks Juris) +- web2py_ajax also handels pre-validation of decimal fields +- ru-ru translation (thanks Michele) +- sk-sk translation (thanks Julius) +- migrations save .table only if changed and after each ALTER TABLE (no more mysql inconsistencies) +- fixed bugs in SQLCustomField, Field(default=...), IS_IMAGE, IS_DECIMAL_IN_RANGE and a few more. +- Better validators (IS_DECIMAL_IN_RANGE, IS_INT_IN_RANGE, etc) thanks Jonatham +- Polymmodel support on GAE +- Experimental ListWidget +- moved DAL and routes to thread.local (thanks Jonathan, again) +- scripts/extract_mysql_models.py, thanks Falko Krause and Ron McOuat +- scripts/dbsessions2trash.py, thanks Scott + +## 1.85.1-3 +- fixed some bugs +- added pyfpdf, thank Mariano + +## 1.86.1-1.86.3 +- markmin2latex +- markmin2pdf +- fixed some bugs +- Storage getfirst, getlast, getall by Kevin and Nathan +- db(db.table), db(db.table.id) both suported and equivalent to db(db.table.id>0) +- postresql ssl support +- less un-necessary logging and warnings on GAE +- IS_DECIMAL_IN_RANGE and IS_FLOAT_IN_RANGE support dot="," (dot="." is default) +- on_failed_authorization can be a function, thanks Niphold +- gluon/contrib/login_methods/cas_auth.py for integration between CAS and Auth. + +## 1.86.3 +- Error reporting on save, thanks Mariano +recalled + +## 1.87.1-2 +- new layout for examples, thanks Bruno and Martin +- admin allow ``DEMO_MODE=True`` and ``FILTER_APPS=['welcome']`` +- fixed a possible problem with CRON and paths + + +## 1.87.3 +- fixed a major bug introduced in 1.87.1 that prevents appadmin from working for new apps created with 1.87.1-2. +- upgraded to clockpick 1.28, thanks villas + +## 1.88.1-2 +- better list: string support, thanks Bob +- jquery 1.4.3 +- scripts/autoroutes.py +- new admin wizard +- added retrieve_username to navbar (if username) +- internal rewrite for arbitrary paths (abspath), thanks Jonathan +- populate support for list: and decimal, thanks Chirstian +- markmin2latex has extra attribute +- better mercual admin allows list of files, versions and retrieve +- new error report system, thanks Thadeus and Selecta +- SQLFORM.accepts(detect_record_change).record_changed +- fixed cron for bytecode compiled apps, thanks Álvaro J. Iradier Muro +- other bugs fixes and pep8 compliant fixes + +## 1.89.1-.5 +- new admin layout (thanks Branko Vukelic) +- new admin search +- new admin language selector (thanks Yair) +- new Welcome app (thanks Martin Mulone) +- better wizard +- admin support for DEMO_MODE=True +- admin exposes GAE deployment button (always) +- MENU support None links (thanks Michael Wolfe) +- web2py.py -J for running cron (thanks Jonathan Lundell) +- fixed ~db.table.id on GAE (thanks MicLee) +- service.jsonrpc supports service.JsonRpcException (thanks Matt) +- bug fixes + +## 1.90.1 +- new DAL (complete rewrite of the web2py DAL is more modular) +- rewrite has fail safe reload, thanks Jonathan +- better CAS with v2 support, thanks Olivier ROCH VILATO +- better markmin2latex +- session.connect(separate=True) to handle many session files, thanks huaiyu wang +- changed bahvior of impersonate (more secure, can generate form or used as API) +- new rocket, thanks Tim +- new pyfpdf +- no more old style classes +- experimental couchdb support in new dal (only insert, select, update by id) +- mysql support out of the box via pymysql +- SQLITABLE(...,headers='labels') thanks Bruno +- optional: digitally signed URLs, thanks Brian Meredyk +- minor bug fixes + +## 1.90.2-4 +- pymysql no longer requires ssl (if not used) +- fixed bug with virtualfields +- fixed bug in truncate (new dal) +- fixed bug in select with alternate primary key (new dal) +- fixed bug with IS_IN_DB and self refences (also new dal) + +## 1.90.5 +- set poll = False in rocket because of poll python thread bug often unfixed, thanks Jonathan +- fixes issue with crud and reCaptcha + +## 1.90.6 +- fix issue with pickling new dal Row and Rows. + +## 1.91.1 +- LICENSE CHANGE FROM GPLv2 to LGPLv3 +- URL(...,hash_vars=...) allows to specify which vars need to be signed +- fixed bug with aliasing in new DAL + +## 1.91.2-1.91.5 +- fixed a problem with deplyment on GAE +- other new dal bug fixes + +## 1.91.6 +- web2py comet via gluon/contrib/comet_messaging.py (html5 websockets) experimental +- fixed problem with services (broken in 1.91.5), thanks Vollrath +- customizable uploadwidget, thanks Fran +- fixed problem with mail unicode support, thanks Richard +- fixed problem with linkto=None and references fields in SQLTABLE, thanks villas +- no more upgrade button on windows since does not work +- better remember-me login, thanks Martin Weissenboeck +- support for recatcha options +- support for GAE namespaces via DAL('gae://namespace') +- new rocket (1.2.2), thanks Tim +- many other bug fixes and improvements (thanks Jonathan) + +## 1.92.1 +- much improved routing (thanks Jonathan) +- Expression.__mod__ (thanks Denes) +- admin has MULTI_USER_MODE (admin/models/0.py) +- support for count(distinct=...) +- has_permissions(...,group_id) +- IS_MATCH(...,strict=True) +- URL(...,scheme=,host=,port=), thanks Jonathan +- admin in Afrikaans, thanks Caleb +- auth.signature (experimental) +- many other bug fixes + +## 1.93.1-2 +- support for multiple interfaces, thanks Jonathan +- jquery 1.5.1 +- simplejson 2.1.3 +- customizable simplejson +- leaner app.yaml +- css3 buttons in welcome +- android support (experimental) +- Field(':hidden'), Field('.readonly'), Field('name=value') +- combined expressions print db.data.body.len().sum() +- wizard can download plugins +- better json serilization (object.custom_json) +- better xml serialization (object.custom_xml) +- better formstyle support +- better comet_messaging.py (needs more testing) +- many bug fixes + +## 1.94.1 +- moderniz 1.17 +- web2py no longer saves session if no change, this makes it up up to 10x faster for simple actions +- experimental REST API +- better support for MSSQL NOT NULL +- small bug fixes + +## 1.94.2 +- reverted wrong behavior of auth.requires(condition) in 1.94.1 + +## 1.94.3 +- fixed major bug in auth redirection + +## 1.94.4 +- removed debug print statement that caused problems on GAE and mod_wsgi + +## 1.94.5 +- fixed a major bug with session introdued in 1.94.1 + +## 1.94.6 +- fixed a number of minor bugs including adding some missing files +- better session handling on session._unlock(..), thanks Jonathan +- added experimental pip support, thanks Lifeeth +- added experimental SAP DB support + +## 1.95.1 +- Google MySQL support (experimental) +- pip support, thanks lifeeth +- better setup_exe.py, thanks meredyk +- importved pyfpdf +- domain check in email_auth.py, thanks Gyuris +- added change_password_onvalidation and change_password_onaccept +- DAL(...,migrate_enabled=True) +- login_methods/loginza.py, thanks Vladimir +- bpython shell support, thanks Arun +- request.uuid and response.uuid (for a future toolbar) +- db._timings contains database query timing info +- efficient db(...).isempty() +- setup-web2py-nginx-uwsgi-ubuntu.sh +- Many bug fixes, thanks Jonathan + +## 1.96.1 + +- "from gluon import *" imports in every python module a web2py environment (A, DIV,..SQLFORM, DAL, Field,...) including current.request, current.response, current.session, current.T, current.cache, thanks Jonathan. +- conditional models in + models//a.py and models///a.py +- from mymodule import *, looks for mymodule in applications/thisapp/modules first and then in sys.path. No more need for local_import. Thanks Pierre. +- usage of generic.* views is - by default - restricted to localhost for security. This can be changed in a granular way with: response.generic_patterns=['*']. This is a slight change of behavior for new app but a major security fix. + +- all applications have cas 2.0 provider at http://.../user/cas/login +- all applications can delegate to login to external provider Auth(...,cas_provider='http://.../other_app/default/user/cas') +- A(...,callback=URL(...),larget='id') does Ajax +- URL(...,user_signature=True), LOAD(...,user_signature=True) can sign urls and @auth.requires_signature() will check the signature for any decorated action. + +- DAL(...,migrate_enabled=False) to disable all migrations +- DAL(...,fake_migrate_all=True) to rebuild all corrupted metadata +- new DAL metadata format (databases/*.table) +- DAL(...,adapter_arg={}) allows support for alternate drivers +- DAL now allows circular table defintions +- DAL(..,auto_import=True) automatically imports tables from metadata without need to db.define_table(...)s. +- new alterante syntax for inner joins: db(...).select(join=...) +- experimental cubrid database support +- DAL 'request_tenant' fields are special, the altomatically filer all records based on their default value. +- db._common_fields.append(Field('owner')) allows to add fields to ALL tables +- DAL ignores repeated fields with same names + +- web2py_ajax.html is more modular, thanks Anthony +- request.is_local +- request.is_http +- new sessions2trash.py thanks Jim Karsten +- corrupted cache files are automatically deleted +- new simpler API gluon.contrib.AuthorizeNet.procss(...) +- fixed recaptcha (as they released new API) +- messages in validators have default internationalization +- No more Auth(globals(),db), just Auth(db). Same for Crud and Service. +- scripts/access.wsgi allows apache+mod_wsgi to delegate authentication of any URL to any web2py app +- json now supports T(...) +- scripts/setup-web2py-nginx-uwsgi-ubuntu.sh +- web2py HTTP responses now set: "X-Powered-By: web2py", thanks Bruno +- mostly fixed generic.pdf. You can view any page in PDF if you have pdflatex installed or if your html follows the pyfpdf convention. +- auth.settings.extra_fields['auth_user'].append(Field('country')) allows to extend auth_* tables without need of definiting a custom auth_* table. Must be placed before auth.define_tables() +- {{=response.toolbar()}} to help you debug applications +- web based shell now supports object modifications (but no redefinitions of non-serializable types) +- jQuery 1.6.1 +- Lots of bug fixes + +# 1.96.2-1.96.4 +- bug fixes + +# 1.97.1 +- validate_and_update, thanks Bruno +- fixed problem with new custom import, thanks Mart +- fixed pyamf 0.6, thanks Alexei and Nickd +- fixed "+ =" bug in wizard +- fixed problem with allowed_patterns +- fixed problems with LOAD and vars and ajax +- closed lots of google code tickets +- checkboxes should now work with list:string +- web2py works on Android, thanks Corne Dickens +- new cpdb.py, thanks Mart +- improved translation (frech in particuler), thanks Pierre +- improved cas_auth.py, thanks Sergio +- IS_DATE and IS_DATETIME validators now work with native types +- better description of --shell, thanks Anthony +- extra SQLTABLE columns, thanks Martin +- fixed toolbar conflics, thanks Simon +- GAE password shows with **** + +# 1.98.1-1.98.2 +- fixed some problems with LOAD(ajax=False), thanks Anthony +- jquery 1.6.2 +- gevent.pywsgi adds ssl support, thanks Vasile +- import/export of blobs are base64 encoded +- max number of login attemts in admin, thanks Ross +- fixed joins with alias tables +- new field.custom_delete attribute +- removed resctions on large 'text fields, thanks Martin +- field.represent = lambda value,record: .... (record is optional) +- FORM.validate() and FORM.process(), thanks Bruno +- faster visrtualfields, thanks Howsec +- mail has ssl support separate from tls, thanks Eric +- TAG objects are now pickable +- new CAT tag for no tags +- request.user_agent(), thanks Ross +- fixed fawps support +- SQLFORM(...,separator=': ') now customizable +- many small bug fixes + +## 1.99.1 +- gluon/contrib/simplejsonrpc.py +- gluon/contrib/redis_cache.py +- support for A(name,callback=url,target='id',delete='tr') +- support for A(name,component=url,target='id',delete='tr') +- new pip installer, thanks Chris Steel +- isapiwsgihandler.py +- dal expression.coalesce(*options) +- gluon/contrib/simplejsonrpc.py, thanks Mariano +- expire_sessions.py respects expiration time, thanks iceberg +- addressed this issue: http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/ +- x509 support (thanks Michele) +- form.process() and for.validate() +- rocket upgrade (1.2.4) +- jQuery upgrade (1.6.3) +- new syntax rows[i]('tablename.fieldname') +- new query syntax field.contains(list,all=True or False) +- new SQLFORM.grid and SQLFORM.smartgrid (should replace crud.search and crud.select) +- support for natural language queries (english only) in SQLFORM.grid +- support for computed columns and additional links in SQLFORM.grid +- new style virtual fields (experimental): db.table.field=Field.Lazy(...) +- request.utcnow +- cleaner/simpler welcome/models/db.py and welcome layout.html +- response.include_meta() and response.include_files(), thanks Denes +- dal auto-reconnect on time-out connections +- COL and COLGROUP helpers +- addresed OWASP #10, thanks Anthony and Eric +- auth.settings.login_after_registration=True +- detection of mobile devices and @mobilize helper (view.mobile.html), thanks Angelo +- experimental gluon/scheduler.py +- scripts/make_min_web2py.py +- crud.search has more options, thanks Denes +- many bug fixes (thanks Jonathan, Michele, Fran and others) ADDED VERSION Index: VERSION ================================================================== --- VERSION +++ VERSION @@ -0,0 +1,1 @@ +Version 1.99.2 (2011-09-26 06:55:33) stable ADDED __init__.py Index: __init__.py ================================================================== --- __init__.py +++ __init__.py @@ -0,0 +1,3 @@ + + + ADDED anyserver.py Index: anyserver.py ================================================================== --- anyserver.py +++ anyserver.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of the web2py Web Framework +Copyrighted by Massimo Di Pierro +License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) + +This file is based, althought a rewrite, on MIT code from the Bottle web framework. +""" + +import os, sys, optparse, urllib +path = os.path.dirname(os.path.abspath(__file__)) +os.chdir(path) +sys.path = [path]+[p for p in sys.path if not p==path] +import gluon.main +from gluon.fileutils import read_file, write_file + +class Servers: + @staticmethod + def cgi(app, address=None, **options): + from wsgiref.handlers import CGIHandler + CGIHandler().run(app) # Just ignore host and port here + + @staticmethod + def flup(app,address, **options): + import flup.server.fcgi + flup.server.fcgi.WSGIServer(app, bindAddress=address).run() + + @staticmethod + def wsgiref(app,address,**options): # pragma: no cover + from wsgiref.simple_server import make_server, WSGIRequestHandler + class QuietHandler(WSGIRequestHandler): + def log_request(*args, **kw): pass + options['handler_class'] = QuietHandler + srv = make_server(address[0],address[1],app,**options) + srv.serve_forever() + + @staticmethod + def cherrypy(app,address, **options): + from cherrypy import wsgiserver + server = wsgiserver.CherryPyWSGIServer(address, app) + server.start() + + @staticmethod + def rocket(app,address, **options): + from gluon.rocket import CherryPyWSGIServer + server = CherryPyWSGIServer(address, app) + server.start() + + @staticmethod + def rocket_with_repoze_profiler(app,address, **options): + from gluon.rocket import CherryPyWSGIServer + from repoze.profile.profiler import AccumulatingProfileMiddleware + from gluon.settings import global_settings + global_settings.web2py_crontype = 'none' + wrapped = AccumulatingProfileMiddleware( + app, + log_filename='wsgi.prof', + discard_first_request=True, + flush_at_shutdown=True, + path = '/__profile__' + ) + server = CherryPyWSGIServer(address, wrapped) + server.start() + + @staticmethod + def paste(app,address,**options): + from paste import httpserver + from paste.translogger import TransLogger + httpserver.serve(app, host=address[0], port=address[1], **options) + + @staticmethod + def fapws(app,address, **options): + import fapws._evwsgi as evwsgi + from fapws import base + evwsgi.start(address[0],str(address[1])) + evwsgi.set_base_module(base) + def app(environ, start_response): + environ['wsgi.multiprocess'] = False + return app(environ, start_response) + evwsgi.wsgi_cb(('',app)) + evwsgi.run() + + + @staticmethod + def gevent(app,address, **options): + from gevent import monkey; monkey.patch_all() + from gevent import pywsgi + from gevent.pool import Pool + pywsgi.WSGIServer(address, app, spawn = 'workers' in options and Pool(int(option.workers)) or 'default').serve_forever() + + @staticmethod + def bjoern(app,address, **options): + import bjoern + bjoern.run(app, *address) + + @staticmethod + def tornado(app,address, **options): + import tornado.wsgi + import tornado.httpserver + import tornado.ioloop + container = tornado.wsgi.WSGIContainer(app) + server = tornado.httpserver.HTTPServer(container) + server.listen(address=address[0], port=address[1]) + tornado.ioloop.IOLoop.instance().start() + + @staticmethod + def twisted(app,address, **options): + from twisted.web import server, wsgi + from twisted.python.threadpool import ThreadPool + from twisted.internet import reactor + thread_pool = ThreadPool() + thread_pool.start() + reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) + factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, app)) + reactor.listenTCP(address[1], factory, interface=address[0]) + reactor.run() + + @staticmethod + def diesel(app,address, **options): + from diesel.protocols.wsgi import WSGIApplication + app = WSGIApplication(app, port=address[1]) + app.run() + + @staticmethod + def gnuicorn(app,address, **options): + import gunicorn.arbiter + gunicorn.arbiter.Arbiter(address, 4, app).run() + + @staticmethod + def eventlet(app,address, **options): + from eventlet import wsgi, listen + wsgi.server(listen(address), app) + + @staticmethod + def mongrel2(app,address,**options): + import uuid + sys.path.append(os.path.abspath(os.path.dirname(__file__))) + from mongrel2 import handler + conn = handler.Connection(str(uuid.uuid4()), + "tcp://127.0.0.1:9997", + "tcp://127.0.0.1:9996") + mongrel2_handler(app,conn,debug=False) + + +def run(servername,ip,port,softcron=True,logging=False,profiler=None): + if logging: + application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, + logfilename='httpserver.log', + profilerfilename=profiler) + else: + application = gluon.main.wsgibase + if softcron: + from gluon.settings import global_settings + global_settings.web2py_crontype = 'soft' + getattr(Servers,servername)(application,(ip,int(port))) + +def mongrel2_handler(application,conn,debug=False): + """ + Based on : + https://github.com/berry/Mongrel2-WSGI-Handler/blob/master/wsgi-handler.py + + WSGI handler based on the Python wsgiref SimpleHandler. + A WSGI application should return a iterable op StringTypes. + Any encoding must be handled by the WSGI application itself. + """ + from wsgiref.handlers import SimpleHandler + try: + import cStringIO as StringIO + except: + import StringIO + + # TODO - this wsgi handler executes the application and renders a page + # in memory completely before returning it as a response to the client. + # Thus, it does not "stream" the result back to the client. It should be + # possible though. The SimpleHandler accepts file-like stream objects. So, + # it should be just a matter of connecting 0MQ requests/response streams to + # the SimpleHandler requests and response streams. However, the Python API + # for Mongrel2 doesn't seem to support file-like stream objects for requests + # and responses. Unless I have missed something. + + while True: + if debug: print "WAITING FOR REQUEST" + + # receive a request + req = conn.recv() + if debug: print "REQUEST BODY: %r\n" % req.body + + if req.is_disconnect(): + if debug: print "DISCONNECT" + continue #effectively ignore the disconnect from the client + + # Set a couple of environment attributes a.k.a. header attributes + # that are a must according to PEP 333 + environ = req.headers + environ['SERVER_PROTOCOL'] = 'HTTP/1.1' # SimpleHandler expects a server_protocol, lets assume it is HTTP 1.1 + environ['REQUEST_METHOD'] = environ['METHOD'] + if ':' in environ['Host']: + environ['SERVER_NAME'] = environ['Host'].split(':')[0] + environ['SERVER_PORT'] = environ['Host'].split(':')[1] + else: + environ['SERVER_NAME'] = environ['Host'] + environ['SERVER_PORT'] = '' + environ['SCRIPT_NAME'] = '' # empty for now + environ['PATH_INFO'] = urllib.unquote(environ['PATH']) + if '?' in environ['URI']: + environ['QUERY_STRING'] = environ['URI'].split('?')[1] + else: + environ['QUERY_STRING'] = '' + if environ.has_key('Content-Length'): + environ['CONTENT_LENGTH'] = environ['Content-Length'] # necessary for POST to work with Django + environ['wsgi.input'] = req.body + + if debug: print "ENVIRON: %r\n" % environ + + # SimpleHandler needs file-like stream objects for + # requests, errors and reponses + reqIO = StringIO.StringIO(req.body) + errIO = StringIO.StringIO() + respIO = StringIO.StringIO() + + # execute the application + handler = SimpleHandler(reqIO, respIO, errIO, environ, multithread = False, multiprocess = False) + handler.run(application) + + # Get the response and filter out the response (=data) itself, + # the response headers, + # the response status code and the response status description + response = respIO.getvalue() + response = response.split("\r\n") + data = response[-1] + headers = dict([r.split(": ") for r in response[1:-2]]) + code = response[0][9:12] + status = response[0][13:] + + # strip BOM's from response data + # Especially the WSGI handler from Django seems to generate them (2 actually, huh?) + # a BOM isn't really necessary and cause HTML parsing errors in Chrome and Safari + # See also: http://www.xs4all.nl/~mechiel/projects/bomstrip/ + # Although I still find this a ugly hack, it does work. + data = data.replace('\xef\xbb\xbf', '') + + # Get the generated errors + errors = errIO.getvalue() + + # return the response + if debug: print "RESPONSE: %r\n" % response + if errors: + if debug: print "ERRORS: %r" % errors + data = "%s\r\n\r\n%s" % (data, errors) + conn.reply_http(req, data, code = code, status = status, headers = headers) + +def main(): + usage = "python anyserver.py -s tornado -i 127.0.0.1 -p 8000 -l -P" + try: + version = read_file('VERSION') + except IOError: + version = '' + parser = optparse.OptionParser(usage, None, optparse.Option, version) + parser.add_option('-l', + '--logging', + action='store_true', + default=False, + dest='logging', + help='log into httpserver.log') + parser.add_option('-P', + '--profiler', + default=False, + dest='profiler', + help='profiler filename') + servers = ', '.join(x for x in dir(Servers) if not x[0]=='_') + parser.add_option('-s', + '--server', + default='rocket', + dest='server', + help='server name (%s)' % servers) + parser.add_option('-i', + '--ip', + default='127.0.0.1', + dest='ip', + help='ip address') + parser.add_option('-p', + '--port', + default='8000', + dest='port', + help='port number') + parser.add_option('-w', + '--workers', + default='', + dest='workers', + help='number of workers number') + (options, args) = parser.parse_args() + print 'starting %s on %s:%s...' % (options.server,options.ip,options.port) + run(options.server,options.ip,options.port,logging=options.logging,profiler=options.profiler) + + +if __name__=='__main__': + main() + ADDED app.example.yaml Index: app.example.yaml ================================================================== --- app.example.yaml +++ app.example.yaml @@ -0,0 +1,88 @@ +# For Google App Engine deployment, copy this file to app.yaml +# and edit as required +# See http://code.google.com/appengine/docs/python/config/appconfig.html +# and http://web2py.com/book/default/chapter/11?search=app.yaml + +application: web2py +version: 1 +api_version: 1 +runtime: python + +default_expiration: "24h" + +derived_file_type: +- python_precompiled + +handlers: + +- url: /_ah/stats.* + script: $PYTHON_LIB/google/appengine/ext/appstats/ui.py + login: admin + +- url: /(?P.+?)/static/(?P.+) + static_files: applications/\1/static/\2 + upload: applications/(.+?)/static/(.+) + secure: optional + +- url: /favicon.ico + static_files: applications/welcome/static/favicon.ico + upload: applications/welcome/static/favicon.ico + +- url: /robots.txt + static_files: applications/welcome/static/robots.txt + upload: applications/welcome/static/robots.txt + +- url: /_ah/admin/.* + script: $PYTHON_LIB/google/appengine/ext/admin + login: admin + +- url: /_ah/queue/default + script: gaehandler.py + login: admin + +- url: .* + script: gaehandler.py + secure: optional + +admin_console: + pages: + - name: Appstats + url: /_ah/stats + +skip_files: | + ^(.*/)?( + (app\.yaml)| + (app\.yml)| + (index\.yaml)| + (index\.yml)| + (#.*#)| + (.*~)| + (.*\.py[co])| + (.*/RCS/.*)| + (\..*)| + (applications/(admin|examples)/.*)| + ((admin|examples|welcome)\.(w2p|tar))| + (applications/.*?/(cron|databases|errors|cache|sessions)/.*)| + ((logs|scripts)/.*)| + (anyserver\.py)| + (web2py\.py)| + ((cgi|fcgi|modpython|wsgi)handler\.py)| + (epydoc\.(conf|css))| + (httpserver\.log)| + (logging\.example\.conf)| + (route[rs]\.example\.py)| + (setup_(app|exe)\.py)| + (splashlogo\.gif)| + (parameters_\d+\.py)| + (options_std.py)| + (gluon/tests/.*)| + (gluon/(rocket|winservice)\.py)| + (contrib/(gateways|markdown|memcache|pymysql)/.*)| + (contrib/(populate|taskbar_widget)\.py)| + (google_appengine/.*)| + (.*\.(bak|orig))| + )$ + +builtins: +- remote_api: on +- datastore_admin: on ADDED appengine_config.py Index: appengine_config.py ================================================================== --- appengine_config.py +++ appengine_config.py @@ -0,0 +1,5 @@ +def webapp_add_wsgi_middleware(app): + from google.appengine.ext.appstats import recording + app = recording.appstats_wsgi_middleware(app) + return app + ADDED applications/__init__.py Index: applications/__init__.py ================================================================== --- applications/__init__.py +++ applications/__init__.py ADDED applications/admin/ABOUT Index: applications/admin/ABOUT ================================================================== --- applications/admin/ABOUT +++ applications/admin/ABOUT @@ -0,0 +1,6 @@ +web2py is an open source full-stack framework for agile development +of secure database-driven web-based applications, written and programmable in +Python. + +Created by Massimo Di Pierro + ADDED applications/admin/LICENSE Index: applications/admin/LICENSE ================================================================== --- applications/admin/LICENSE +++ applications/admin/LICENSE @@ -0,0 +1,137 @@ +## Web2py License + +Web2py is Licensed under the LGPL license version 3 +(http://www.gnu.org/licenses/lgpl.html) + +Copyrighted (c) by Massimo Di Pierro (2007-2011) + +### On Commercial Redistribution + +In accordance with LGPL you may: +- redistribute web2py with your apps (including official web2py binary versions) +- release your applications which use official web2py libraries under any license you wish +But you must: +- make clear in the documentation that your application uses web2py +- release any modification of the web2py libraries under the LGPLv3 license + +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. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT +HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 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. + +(Earlier versions of web2py, 1.0.*-1.90.*, were released under the GPL2 license plus a +commercial exception which, for practical purposes, was very similar to the current LPGLv3) + +### Licenses for third party contributed software + +web2py contains third party software under the gluon/contrib/ folder. +Each file/module in contrib is distributed with web2py under its original license. +Here we list some of them. + +#### gluon.contrib.simplejson LICENSE + +Copyright (c) 2006 Bob Ippolito - Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +#### gluon.contrib.rss2.py (originally PyRSS2Gen) LICENSE + +This is copyright (c) by Dalke Scientific Software, LLC and released under the +BSD license. See the file LICENSE in the distribution or + for details. + +#### gluon.contrib.markdown (markdown2) LICENSE + +MIT License from from + +#### gluon.contrib.feedparser LICENSE + +Copyright (c) 2002-2005, Mark Pilgrim + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#### gluon.wsgiserver.py LICENSE (borrowed from cherrypy) + +Copyright (c) 2004, CherryPy Team (team@cherrypy.org) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the CherryPy Team nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#### gluon.contrib.pam LICENSE + +Copyright (C) 2007-2009 Chris AtLee Licensed under the MIT license + +#### gluon.contrib.shell LICENSE + +Copyright (C) by Google inc. Apache 2.0 Lincense + +#### The javascript licenses are in the code itself + ADDED applications/admin/__init__.py Index: applications/admin/__init__.py ================================================================== --- applications/admin/__init__.py +++ applications/admin/__init__.py ADDED applications/admin/controllers/appadmin.py Index: applications/admin/controllers/appadmin.py ================================================================== --- applications/admin/controllers/appadmin.py +++ applications/admin/controllers/appadmin.py @@ -0,0 +1,408 @@ +# -*- coding: utf-8 -*- + +# ########################################################## +# ## make sure administrator is on localhost +# ########################################################### + +import os +import socket +import datetime +import copy +import gluon.contenttype +import gluon.fileutils + +# ## critical --- make a copy of the environment + +global_env = copy.copy(globals()) +global_env['datetime'] = datetime + +http_host = request.env.http_host.split(':')[0] +remote_addr = request.env.remote_addr +try: + hosts = (http_host, socket.gethostname(), + socket.gethostbyname(http_host), + '::1','127.0.0.1','::ffff:127.0.0.1') +except: + hosts = (http_host, ) + +if request.env.http_x_forwarded_for or request.env.wsgi_url_scheme\ + in ['https', 'HTTPS']: + session.secure() +elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1"): + raise HTTP(200, T('appadmin is disabled because insecure channel')) + +if (request.application=='admin' and not session.authorized) or \ + (request.application!='admin' and not gluon.fileutils.check_credentials(request)): + redirect(URL('admin', 'default', 'index')) + +ignore_rw = True +response.view = 'appadmin.html' +response.menu = [[T('design'), False, URL('admin', 'default', 'design', + args=[request.application])], [T('db'), False, + URL('index')], [T('state'), False, + URL('state')], [T('cache'), False, + URL('ccache')]] + +# ########################################################## +# ## auxiliary functions +# ########################################################### + + +def get_databases(request): + dbs = {} + for (key, value) in global_env.items(): + cond = False + try: + cond = isinstance(value, GQLDB) + except: + cond = isinstance(value, SQLDB) + if cond: + dbs[key] = value + return dbs + + +databases = get_databases(None) + + +def eval_in_global_env(text): + exec ('_ret=%s' % text, {}, global_env) + return global_env['_ret'] + + +def get_database(request): + if request.args and request.args[0] in databases: + return eval_in_global_env(request.args[0]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_table(request): + db = get_database(request) + if len(request.args) > 1 and request.args[1] in db.tables: + return (db, request.args[1]) + else: + session.flash = T('invalid request') + redirect(URL('index')) + + +def get_query(request): + try: + return eval_in_global_env(request.vars.query) + except Exception: + return None + + +def query_by_table_type(tablename,db,request=request): + keyed = hasattr(db[tablename],'_primarykey') + if keyed: + firstkey = db[tablename][db[tablename]._primarykey[0]] + cond = '>0' + if firstkey.type in ['string', 'text']: + cond = '!=""' + qry = '%s.%s.%s%s' % (request.args[0], request.args[1], firstkey.name, cond) + else: + qry = '%s.%s.id>0' % tuple(request.args[:2]) + return qry + + + +# ########################################################## +# ## list all databases and tables +# ########################################################### + + +def index(): + return dict(databases=databases) + + +# ########################################################## +# ## insert a new record +# ########################################################### + + +def insert(): + (db, table) = get_table(request) + form = SQLFORM(db[table], ignore_rw=ignore_rw) + if form.accepts(request.vars, session): + response.flash = T('new record inserted') + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## list all records in table and insert new record +# ########################################################### + + +def download(): + import os + db = get_database(request) + return response.download(request,db) + +def csv(): + import gluon.contenttype + response.headers['Content-Type'] = \ + gluon.contenttype.contenttype('.csv') + db = get_database(request) + query = get_query(request) + if not query: + return None + response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\ + % tuple(request.vars.query.split('.')[:2]) + return str(db(query).select()) + + +def import_csv(table, file): + table.import_from_csv_file(file) + +def select(): + import re + db = get_database(request) + dbname = request.args[0] + regex = re.compile('(?P\w+)\.(?P\w+)=(?P\d+)') + if len(request.args)>1 and hasattr(db[request.args[1]],'_primarykey'): + regex = re.compile('(?P
\w+)\.(?P\w+)=(?P.+)') + if request.vars.query: + match = regex.match(request.vars.query) + if match: + request.vars.query = '%s.%s.%s==%s' % (request.args[0], + match.group('table'), match.group('field'), + match.group('value')) + else: + request.vars.query = session.last_query + query = get_query(request) + if request.vars.start: + start = int(request.vars.start) + else: + start = 0 + nrows = 0 + stop = start + 100 + table = None + rows = [] + orderby = request.vars.orderby + if orderby: + orderby = dbname + '.' + orderby + if orderby == session.last_orderby: + if orderby[0] == '~': + orderby = orderby[1:] + else: + orderby = '~' + orderby + session.last_orderby = orderby + session.last_query = request.vars.query + form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', + _name='query', _value=request.vars.query or '', + requires=IS_NOT_EMPTY(error_message=T("Cannot be empty")))), TR(T('Update:'), + INPUT(_name='update_check', _type='checkbox', + value=False), INPUT(_style='width:400px', + _name='update_fields', _value=request.vars.update_fields + or '')), TR(T('Delete:'), INPUT(_name='delete_check', + _class='delete', _type='checkbox', value=False), ''), + TR('', '', INPUT(_type='submit', _value='submit'))), + _action=URL(r=request,args=request.args)) + if request.vars.csvfile != None: + try: + import_csv(db[request.vars.table], + request.vars.csvfile.file) + response.flash = T('data uploaded') + except Exception, e: + response.flash = DIV(T('unable to parse csv file'),PRE(str(e))) + if form.accepts(request.vars, formname=None): +# regex = re.compile(request.args[0] + '\.(?P
\w+)\.id\>0') + regex = re.compile(request.args[0] + '\.(?P
\w+)\..+') + + match = regex.match(form.vars.query.strip()) + if match: + table = match.group('table') + try: + nrows = db(query).count() + if form.vars.update_check and form.vars.update_fields: + db(query).update(**eval_in_global_env('dict(%s)' + % form.vars.update_fields)) + response.flash = T('%s rows updated', nrows) + elif form.vars.delete_check: + db(query).delete() + response.flash = T('%s rows deleted', nrows) + nrows = db(query).count() + if orderby: + rows = db(query).select(limitby=(start, stop), + orderby=eval_in_global_env(orderby)) + else: + rows = db(query).select(limitby=(start, stop)) + except Exception, e: + (rows, nrows) = ([], 0) + response.flash = DIV(T('Invalid Query'),PRE(str(e))) + return dict( + form=form, + table=table, + start=start, + stop=stop, + nrows=nrows, + rows=rows, + query=request.vars.query, + ) + + +# ########################################################## +# ## edit delete one record +# ########################################################### + + +def update(): + (db, table) = get_table(request) + keyed = hasattr(db[table],'_primarykey') + record = None + if keyed: + key = [f for f in request.vars if f in db[table]._primarykey] + if key: + record = db(db[table][key[0]] == request.vars[key[0]]).select().first() + else: + record = db(db[table].id == request.args(2)).select().first() + + if not record: + qry = query_by_table_type(table, db) + session.flash = T('record does not exist') + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + + if keyed: + for k in db[table]._primarykey: + db[table][k].writable=False + + form = SQLFORM(db[table], record, deletable=True, delete_label=T('Check to delete'), + ignore_rw=ignore_rw and not keyed, + linkto=URL('select', + args=request.args[:1]), upload=URL(r=request, + f='download', args=request.args[:1])) + + if form.accepts(request.vars, session): + session.flash = T('done!') + qry = query_by_table_type(table, db) + redirect(URL('select', args=request.args[:1], + vars=dict(query=qry))) + return dict(form=form,table=db[table]) + + +# ########################################################## +# ## get global variables +# ########################################################### + + +def state(): + return dict() + +def ccache(): + form = FORM( + P(TAG.BUTTON("Clear CACHE?", _type="submit", _name="yes", _value="yes")), + P(TAG.BUTTON("Clear RAM", _type="submit", _name="ram", _value="ram")), + P(TAG.BUTTON("Clear DISK", _type="submit", _name="disk", _value="disk")), + ) + + if form.accepts(request.vars, session): + clear_ram = False + clear_disk = False + session.flash = "" + if request.vars.yes: + clear_ram = clear_disk = True + if request.vars.ram: + clear_ram = True + if request.vars.disk: + clear_disk = True + + if clear_ram: + cache.ram.clear() + session.flash += "Ram Cleared " + if clear_disk: + cache.disk.clear() + session.flash += "Disk Cleared" + + redirect(URL(r=request)) + + try: + from guppy import hpy; hp=hpy() + except ImportError: + hp = False + + import shelve, os, copy, time, math + from gluon import portalocker + + ram = { + 'bytes': 0, + 'objects': 0, + 'hits': 0, + 'misses': 0, + 'ratio': 0, + 'oldest': time.time() + } + disk = copy.copy(ram) + total = copy.copy(ram) + + for key, value in cache.ram.storage.items(): + if isinstance(value, dict): + ram['hits'] = value['hit_total'] - value['misses'] + ram['misses'] = value['misses'] + try: + ram['ratio'] = ram['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + ram['ratio'] = 0 + else: + if hp: + ram['bytes'] += hp.iso(value[1]).size + ram['objects'] += hp.iso(value[1]).count + + if value[0] < ram['oldest']: + ram['oldest'] = value[0] + + locker = open(os.path.join(request.folder, + 'cache/cache.lock'), 'a') + portalocker.lock(locker, portalocker.LOCK_EX) + disk_storage = shelve.open(os.path.join(request.folder, 'cache/cache.shelve')) + try: + for key, value in disk_storage.items(): + if isinstance(value, dict): + disk['hits'] = value['hit_total'] - value['misses'] + disk['misses'] = value['misses'] + try: + disk['ratio'] = disk['hits'] * 100 / value['hit_total'] + except (KeyError, ZeroDivisionError): + disk['ratio'] = 0 + else: + if hp: + disk['bytes'] += hp.iso(value[1]).size + disk['objects'] += hp.iso(value[1]).count + if value[0] < disk['oldest']: + disk['oldest'] = value[0] + finally: + portalocker.unlock(locker) + locker.close() + disk_storage.close() + + total['bytes'] = ram['bytes'] + disk['bytes'] + total['objects'] = ram['objects'] + disk['objects'] + total['hits'] = ram['hits'] + disk['hits'] + total['misses'] = ram['misses'] + disk['misses'] + try: + total['ratio'] = total['hits'] * 100 / (total['hits'] + total['misses']) + except (KeyError, ZeroDivisionError): + total['ratio'] = 0 + + if disk['oldest'] < ram['oldest']: + total['oldest'] = disk['oldest'] + else: + total['oldest'] = ram['oldest'] + + def GetInHMS(seconds): + hours = math.floor(seconds / 3600) + seconds -= hours * 3600 + minutes = math.floor(seconds / 60) + seconds -= minutes * 60 + seconds = math.floor(seconds) + + return (hours, minutes, seconds) + + ram['oldest'] = GetInHMS(time.time() - ram['oldest']) + disk['oldest'] = GetInHMS(time.time() - disk['oldest']) + total['oldest'] = GetInHMS(time.time() - total['oldest']) + + return dict(form=form, total=total, + ram=ram, disk=disk) + ADDED applications/admin/controllers/debug.py Index: applications/admin/controllers/debug.py ================================================================== --- applications/admin/controllers/debug.py +++ applications/admin/controllers/debug.py @@ -0,0 +1,33 @@ +import sys +import cStringIO +import gluon.contrib.shell +import code, thread +from gluon.debug import communicate + + +if DEMO_MODE or MULTI_USER_MODE: + session.flash = T('disabled in demo mode') + redirect(URL('default','site')) + +FE=10**9 + +def index(): + app = request.args(0) or 'admin' + reset() + # read buffer + data = communicate() + return dict(app=app,data=data) + +def callback(): + app = request.args[0] + command = request.vars.statement + session['debug_commands:'+app].append(command) + output = communicate(command) + k = len(session['debug_commands:'+app]) - 1 + return '[%i] %s%s\n' % (k + 1, command, output) + +def reset(): + app = request.args(0) or 'admin' + session['debug_commands:'+app] = [] + return 'done' + ADDED applications/admin/controllers/default.py Index: applications/admin/controllers/default.py ================================================================== --- applications/admin/controllers/default.py +++ applications/admin/controllers/default.py @@ -0,0 +1,1223 @@ +# coding: utf8 + +from gluon.admin import * +from gluon.fileutils import abspath, read_file, write_file +from glob import glob +import shutil +import platform + +if DEMO_MODE and request.function in ['change_password','pack','pack_plugin','upgrade_web2py','uninstall','cleanup','compile_app','remove_compiled_app','delete','delete_plugin','create_file','upload_file','update_languages','reload_routes']: + session.flash = T('disabled in demo mode') + redirect(URL('site')) + +if not is_manager() and request.function in ['change_password','upgrade_web2py']: + session.flash = T('disabled in multi user mode') + redirect(URL('site')) + +if FILTER_APPS and request.args(0) and not request.args(0) in FILTER_APPS: + session.flash = T('disabled in demo mode') + redirect(URL('site')) + +def safe_open(a,b): + if DEMO_MODE and 'w' in b: + class tmp: + def write(self,data): pass + return tmp() + return open(a,b) + +def safe_read(a, b='r'): + safe_file = safe_open(a, b) + try: + return safe_file.read() + finally: + safe_file.close() + +def safe_write(a, value, b='w'): + safe_file = safe_open(a, b) + try: + safe_file.write(value) + finally: + safe_file.close() + +def get_app(name=None): + app = name or request.args(0) + if app and (not MULTI_USER_MODE or db(db.app.name==app)(db.app.owner==auth.user.id).count()): + return app + session.flash = 'App does not exist or your are not authorized' + redirect(URL('site')) + +def index(): + """ Index handler """ + + send = request.vars.send + if DEMO_MODE: + session.authorized = True + session.last_time = t0 + if not send: + send = URL('site') + if session.authorized: + redirect(send) + elif request.vars.password: + if verify_password(request.vars.password): + session.authorized = True + login_record(True) + + if CHECK_VERSION: + session.check_version = True + else: + session.check_version = False + + session.last_time = t0 + if isinstance(send, list): # ## why does this happen? + send = str(send[0]) + + redirect(send) + else: + times_denied = login_record(False) + if times_denied >= allowed_number_of_attempts: + response.flash = \ + T('admin disabled because too many invalid login attempts') + elif times_denied == allowed_number_of_attempts - 1: + response.flash = \ + T('You have one more login attempt before you are locked out') + else: + response.flash = T('invalid password.') + return dict(send=send) + + +def check_version(): + """ Checks if web2py is up to date """ + + session.forget() + session._unlock(response) + + new_version, version_number = check_new_version(request.env.web2py_version, + WEB2PY_VERSION_URL) + + if new_version == -1: + return A(T('Unable to check for upgrades'), _href=WEB2PY_URL) + elif new_version != True: + return A(T('web2py is up to date'), _href=WEB2PY_URL) + elif platform.system().lower() in ('windows','win32','win64') and os.path.exists("web2py.exe"): + return SPAN('You should upgrade to version %s' % version_number) + else: + return sp_button(URL('upgrade_web2py'), T('upgrade now')) \ + + XML(' %s' % version_number) + + +def logout(): + """ Logout handler """ + session.authorized = None + if MULTI_USER_MODE: + redirect(URL('user/logout')) + redirect(URL('index')) + + +def change_password(): + + if session.pam_user: + session.flash = T('PAM authenticated user, cannot change password here') + redirect(URL('site')) + form=SQLFORM.factory(Field('current_admin_password','password'), + Field('new_admin_password','password',requires=IS_STRONG()), + Field('new_admin_password_again','password')) + if form.accepts(request.vars): + if not verify_password(request.vars.current_admin_password): + form.errors.current_admin_password = T('invalid password') + elif form.vars.new_admin_password != form.vars.new_admin_password_again: + form.errors.new_admin_password_again = T('no match') + else: + path = abspath('parameters_%s.py' % request.env.server_port) + safe_write(path, 'password="%s"' % CRYPT()(request.vars.new_admin_password)[0]) + session.flash = T('password changed') + redirect(URL('site')) + return dict(form=form) + +def site(): + """ Site handler """ + + myversion = request.env.web2py_version + + # Shortcut to make the elif statements more legible + file_or_appurl = 'file' in request.vars or 'appurl' in request.vars + + if DEMO_MODE: + pass + + elif request.vars.filename and not 'file' in request.vars: + # create a new application + appname = cleanpath(request.vars.filename).replace('.', '_') + if app_create(appname, request): + if MULTI_USER_MODE: + db.app.insert(name=appname,owner=auth.user.id) + session.flash = T('new application "%s" created', appname) + redirect(URL('design',args=appname)) + else: + session.flash = \ + T('unable to create application "%s" (it may exist already)', request.vars.filename) + redirect(URL(r=request)) + + elif file_or_appurl and not request.vars.filename: + # can't do anything without an app name + msg = 'you must specify a name for the uploaded application' + response.flash = T(msg) + + elif file_or_appurl and request.vars.filename: + # fetch an application via URL or file upload + f = None + if request.vars.appurl is not '': + try: + f = urllib.urlopen(request.vars.appurl) + except Exception, e: + session.flash = DIV(T('Unable to download app because:'),PRE(str(e))) + redirect(URL(r=request)) + fname = request.vars.appurl + elif request.vars.file is not '': + f = request.vars.file.file + fname = request.vars.file.filename + + if f: + appname = cleanpath(request.vars.filename).replace('.', '_') + installed = app_install(appname, f, request, fname, + overwrite=request.vars.overwrite_check) + if f and installed: + msg = 'application %(appname)s installed with md5sum: %(digest)s' + session.flash = T(msg, dict(appname=appname, + digest=md5_hash(installed))) + elif f and request.vars.overwrite_check: + msg = 'unable to install application "%(appname)s"' + session.flash = T(msg, dict(appname=request.vars.filename)) + + else: + msg = 'unable to install application "%(appname)s"' + session.flash = T(msg, dict(appname=request.vars.filename)) + + redirect(URL(r=request)) + + regex = re.compile('^\w+$') + + if is_manager(): + apps = [f for f in os.listdir(apath(r=request)) if regex.match(f)] + else: + apps = [f.name for f in db(db.app.owner==auth.user_id).select()] + + if FILTER_APPS: + apps = [f for f in apps if f in FILTER_APPS] + + apps = sorted(apps,lambda a,b:cmp(a.upper(),b.upper())) + + return dict(app=None, apps=apps, myversion=myversion) + + +def pack(): + app = get_app() + + if len(request.args) == 1: + fname = 'web2py.app.%s.w2p' % app + filename = app_pack(app, request) + else: + fname = 'web2py.app.%s.compiled.w2p' % app + filename = app_pack_compiled(app, request) + + if filename: + response.headers['Content-Type'] = 'application/w2p' + disposition = 'attachment; filename=%s' % fname + response.headers['Content-Disposition'] = disposition + return safe_read(filename, 'rb') + else: + session.flash = T('internal error') + redirect(URL('site')) + +def pack_plugin(): + app = get_app() + if len(request.args) == 2: + fname = 'web2py.plugin.%s.w2p' % request.args[1] + filename = plugin_pack(app, request.args[1], request) + if filename: + response.headers['Content-Type'] = 'application/w2p' + disposition = 'attachment; filename=%s' % fname + response.headers['Content-Disposition'] = disposition + return safe_read(filename, 'rb') + else: + session.flash = T('internal error') + redirect(URL('plugin',args=request.args)) + +def upgrade_web2py(): + if 'upgrade' in request.vars: + (success, error) = upgrade(request) + if success: + session.flash = T('web2py upgraded; please restart it') + else: + session.flash = T('unable to upgrade because "%s"', error) + redirect(URL('site')) + elif 'noupgrade' in request.vars: + redirect(URL('site')) + return dict() + +def uninstall(): + app = get_app() + if 'delete' in request.vars: + if MULTI_USER_MODE: + if is_manager() and db(db.app.name==app).delete(): + pass + elif db(db.app.name==app)(db.app.owner==auth.user.id).delete(): + pass + else: + session.flash = T('no permission to uninstall "%s"', app) + redirect(URL('site')) + if app_uninstall(app, request): + session.flash = T('application "%s" uninstalled', app) + else: + session.flash = T('unable to uninstall "%s"', app) + redirect(URL('site')) + elif 'nodelete' in request.vars: + redirect(URL('site')) + return dict(app=app) + + +def cleanup(): + app = get_app() + clean = app_cleanup(app, request) + if not clean: + session.flash = T("some files could not be removed") + else: + session.flash = T('cache, errors and sessions cleaned') + + redirect(URL('site')) + + +def compile_app(): + app = get_app() + c = app_compile(app, request) + if not c: + session.flash = T('application compiled') + else: + session.flash = DIV(T('Cannot compile: there are errors in your app:'), + CODE(c)) + redirect(URL('site')) + + +def remove_compiled_app(): + """ Remove the compiled application """ + app = get_app() + remove_compiled_application(apath(app, r=request)) + session.flash = T('compiled application removed') + redirect(URL('site')) + +def delete(): + """ Object delete handler """ + app = get_app() + filename = '/'.join(request.args) + sender = request.vars.sender + + if isinstance(sender, list): # ## fix a problem with Vista + sender = sender[0] + + if 'nodelete' in request.vars: + redirect(URL(sender)) + elif 'delete' in request.vars: + try: + os.unlink(apath(filename, r=request)) + session.flash = T('file "%(filename)s" deleted', + dict(filename=filename)) + except Exception: + session.flash = T('unable to delete file "%(filename)s"', + dict(filename=filename)) + redirect(URL(sender)) + return dict(filename=filename, sender=sender) + +def peek(): + """ Visualize object code """ + app = get_app() + filename = '/'.join(request.args) + try: + data = safe_read(apath(filename, r=request)).replace('\r','') + except IOError: + session.flash = T('file does not exist') + redirect(URL('site')) + + extension = filename[filename.rfind('.') + 1:].lower() + + return dict(app=request.args[0], + filename=filename, + data=data, + extension=extension) + + +def test(): + """ Execute controller tests """ + app = get_app() + if len(request.args) > 1: + file = request.args[1] + else: + file = '.*\.py' + + controllers = listdir(apath('%s/controllers/' % app, r=request), file + '$') + + return dict(app=app, controllers=controllers) + +def keepalive(): + return '' + +def search(): + keywords=request.vars.keywords or '' + app = get_app() + def match(filename,keywords): + filename=os.path.join(apath(app, r=request),filename) + if keywords in read_file(filename,'rb'): + return True + return False + path = apath(request.args[0], r=request) + files1 = glob(os.path.join(path,'*/*.py')) + files2 = glob(os.path.join(path,'*/*.html')) + files3 = glob(os.path.join(path,'*/*/*.html')) + files=[x[len(path)+1:].replace('\\','/') for x in files1+files2+files3 if match(x,keywords)] + return response.json({'files':files}) + +def edit(): + """ File edit handler """ + # Load json only if it is ajax edited... + app = get_app() + filename = '/'.join(request.args) + # Try to discover the file type + if filename[-3:] == '.py': + filetype = 'python' + elif filename[-5:] == '.html': + filetype = 'html' + elif filename[-5:] == '.load': + filetype = 'html' + elif filename[-4:] == '.css': + filetype = 'css' + elif filename[-3:] == '.js': + filetype = 'js' + else: + filetype = 'html' + + # ## check if file is not there + + path = apath(filename, r=request) + + if request.vars.revert and os.path.exists(path + '.bak'): + try: + data = safe_read(path + '.bak') + data1 = safe_read(path) + except IOError: + session.flash = T('Invalid action') + if 'from_ajax' in request.vars: + return response.json({'error': str(T('Invalid action'))}) + else: + redirect(URL('site')) + + safe_write(path, data) + file_hash = md5_hash(data) + saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) + safe_write(path + '.bak', data1) + response.flash = T('file "%s" of %s restored', (filename, saved_on)) + else: + try: + data = safe_read(path) + except IOError: + session.flash = T('Invalid action') + if 'from_ajax' in request.vars: + return response.json({'error': str(T('Invalid action'))}) + else: + redirect(URL('site')) + + file_hash = md5_hash(data) + saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) + + if request.vars.file_hash and request.vars.file_hash != file_hash: + session.flash = T('file changed on disk') + data = request.vars.data.replace('\r\n', '\n').strip() + '\n' + safe_write(path + '.1', data) + if 'from_ajax' in request.vars: + return response.json({'error': str(T('file changed on disk')), + 'redirect': URL('resolve', + args=request.args)}) + else: + redirect(URL('resolve', args=request.args)) + elif request.vars.data: + safe_write(path + '.bak', data) + data = request.vars.data.replace('\r\n', '\n').strip() + '\n' + safe_write(path, data) + file_hash = md5_hash(data) + saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) + response.flash = T('file saved on %s', saved_on) + + data_or_revert = (request.vars.data or request.vars.revert) + + # Check compile errors + highlight = None + if filetype == 'python' and request.vars.data: + import _ast + try: + code = request.vars.data.rstrip().replace('\r\n','\n')+'\n' + compile(code, path, "exec", _ast.PyCF_ONLY_AST) + except Exception, e: + start = sum([len(line)+1 for l, line + in enumerate(request.vars.data.split("\n")) + if l < e.lineno-1]) + if e.text and e.offset: + offset = e.offset - (len(e.text) - len(e.text.splitlines()[-1])) + else: + offset = 0 + highlight = {'start': start, 'end': start + offset + 1} + try: + ex_name = e.__class__.__name__ + except: + ex_name = 'unknown exception!' + response.flash = DIV(T('failed to compile file because:'), BR(), + B(ex_name), T(' at line %s') % e.lineno, + offset and T(' at char %s') % offset or '', + PRE(str(e))) + + if data_or_revert and request.args[1] == 'modules': + # Lets try to reload the modules + try: + mopath = '.'.join(request.args[2:])[:-3] + exec 'import applications.%s.modules.%s' % (request.args[0], mopath) + reload(sys.modules['applications.%s.modules.%s' + % (request.args[0], mopath)]) + except Exception, e: + response.flash = DIV(T('failed to reload module because:'),PRE(str(e))) + + edit_controller = None + editviewlinks = None + view_link = None + if filetype == 'html' and len(request.args) >= 3: + cfilename = os.path.join(request.args[0], 'controllers', + request.args[2] + '.py') + if os.path.exists(apath(cfilename, r=request)): + edit_controller = URL('edit', args=[cfilename]) + view = request.args[3].replace('.html','') + view_link = URL(request.args[0],request.args[2],view) + elif filetype == 'python' and request.args[1] == 'controllers': + ## it's a controller file. + ## Create links to all of the associated view files. + app = get_app() + viewname = os.path.splitext(request.args[2])[0] + viewpath = os.path.join(app,'views',viewname) + aviewpath = apath(viewpath, r=request) + viewlist = [] + if os.path.exists(aviewpath): + if os.path.isdir(aviewpath): + viewlist = glob(os.path.join(aviewpath,'*.html')) + elif os.path.exists(aviewpath+'.html'): + viewlist.append(aviewpath+'.html') + if len(viewlist): + editviewlinks = [] + for v in viewlist: + vf = os.path.split(v)[-1] + vargs = "/".join([viewpath.replace(os.sep,"/"),vf]) + editviewlinks.append(A(T(vf.split(".")[0]),\ + _href=URL('edit',args=[vargs]))) + + if len(request.args) > 2 and request.args[1] == 'controllers': + controller = (request.args[2])[:-3] + functions = regex_expose.findall(data) + else: + (controller, functions) = (None, None) + + if 'from_ajax' in request.vars: + return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions':functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight }) + else: + + editarea_preferences = {} + editarea_preferences['FONT_SIZE'] = '10' + editarea_preferences['FULL_SCREEN'] = 'false' + editarea_preferences['ALLOW_TOGGLE'] = 'true' + editarea_preferences['REPLACE_TAB_BY_SPACES'] = '4' + editarea_preferences['DISPLAY'] = 'onload' + for key in editarea_preferences: + if globals().has_key(key): + editarea_preferences[key]=globals()[key] + return dict(app=request.args[0], + filename=filename, + filetype=filetype, + data=data, + edit_controller=edit_controller, + file_hash=file_hash, + saved_on=saved_on, + controller=controller, + functions=functions, + view_link=view_link, + editarea_preferences=editarea_preferences, + editviewlinks=editviewlinks) + +def resolve(): + """ + """ + + filename = '/'.join(request.args) + # ## check if file is not there + path = apath(filename, r=request) + a = safe_read(path).split('\n') + try: + b = safe_read(path + '.1').split('\n') + except IOError: + session.flash = 'Other file, no longer there' + redirect(URL('edit', args=request.args)) + + d = difflib.ndiff(a, b) + + def leading(line): + """ """ + + # TODO: we really need to comment this + z = '' + for (k, c) in enumerate(line): + if c == ' ': + z += ' ' + elif c == ' \t': + z += ' ' + elif k == 0 and c == '?': + pass + else: + break + + return XML(z) + + def getclass(item): + """ Determine item class """ + + if item[0] == ' ': + return 'normal' + if item[0] == '+': + return 'plus' + if item[0] == '-': + return 'minus' + + if request.vars: + c = '\n'.join([item[2:].rstrip() for (i, item) in enumerate(d) if item[0] \ + == ' ' or 'line%i' % i in request.vars]) + safe_write(path, c) + session.flash = 'files merged' + redirect(URL('edit', args=request.args)) + else: + # Making the short circuit compatible with <= python2.4 + gen_data = lambda index,item: not item[:1] in ['+','-'] and "" \ + or INPUT(_type='checkbox', + _name='line%i' % index, + value=item[0] == '+') + + diff = TABLE(*[TR(TD(gen_data(i,item)), + TD(item[0]), + TD(leading(item[2:]), + TT(item[2:].rstrip())), _class=getclass(item)) + for (i, item) in enumerate(d) if item[0] != '?']) + + return dict(diff=diff, filename=filename) + + +def edit_language(): + """ Edit language file """ + app = get_app() + filename = '/'.join(request.args) + from gluon.languages import read_dict, write_dict + strings = read_dict(apath(filename, r=request)) + keys = sorted(strings.keys(),lambda x,y: cmp(x.lower(), y.lower())) + rows = [] + rows.append(H2(T('Original/Translation'))) + + for key in keys: + name = md5_hash(key) + if key==strings[key]: + _class='untranslated' + else: + _class='translated' + if len(key) <= 40: + elem = INPUT(_type='text', _name=name,value=strings[key], + _size=70,_class=_class) + else: + elem = TEXTAREA(_name=name, value=strings[key], _cols=70, + _rows=5, _class=_class) + + # Making the short circuit compatible with <= python2.4 + k = (strings[key] != key) and key or B(key) + + rows.append(P(k, BR(), elem, TAG.BUTTON(T('delete'), + _onclick='return delkey("%s")' % name), _id=name)) + + rows.append(INPUT(_type='submit', _value=T('update'))) + form = FORM(*rows) + if form.accepts(request.vars, keepvalues=True): + strs = dict() + for key in keys: + name = md5_hash(key) + if form.vars[name]==chr(127): continue + strs[key] = form.vars[name] + write_dict(apath(filename, r=request), strs) + session.flash = T('file saved on %(time)s', dict(time=time.ctime())) + redirect(URL(r=request,args=request.args)) + return dict(app=request.args[0], filename=filename, form=form) + + +def about(): + """ Read about info """ + app = get_app() + # ## check if file is not there + about = safe_read(apath('%s/ABOUT' % app, r=request)) + license = safe_read(apath('%s/LICENSE' % app, r=request)) + return dict(app=app, about=MARKMIN(about), license=MARKMIN(license)) + + +def design(): + """ Application design handler """ + app = get_app() + + if not response.flash and app == request.application: + msg = T('ATTENTION: you cannot edit the running application!') + response.flash = msg + + if request.vars.pluginfile!=None and not isinstance(request.vars.pluginfile,str): + filename=os.path.basename(request.vars.pluginfile.filename) + if plugin_install(app, request.vars.pluginfile.file, + request, filename): + session.flash = T('new plugin installed') + redirect(URL('design',args=app)) + else: + session.flash = \ + T('unable to create application "%s"', request.vars.filename) + redirect(URL(r=request)) + elif isinstance(request.vars.pluginfile,str): + session.flash = T('plugin not specified') + redirect(URL(r=request)) + + + # If we have only pyc files it means that + # we cannot design + if os.path.exists(apath('%s/compiled' % app, r=request)): + session.flash = \ + T('application is compiled and cannot be designed') + redirect(URL('site')) + + # Get all models + models = listdir(apath('%s/models/' % app, r=request), '.*\.py$') + models=[x.replace('\\','/') for x in models] + defines = {} + for m in models: + data = safe_read(apath('%s/models/%s' % (app, m), r=request)) + defines[m] = regex_tables.findall(data) + defines[m].sort() + + # Get all controllers + controllers = sorted(listdir(apath('%s/controllers/' % app, r=request), '.*\.py$')) + controllers = [x.replace('\\','/') for x in controllers] + functions = {} + for c in controllers: + data = safe_read(apath('%s/controllers/%s' % (app, c), r=request)) + items = regex_expose.findall(data) + functions[c] = items + + # Get all views + views = sorted(listdir(apath('%s/views/' % app, r=request), '[\w/\-]+(\.\w+)+$')) + views = [x.replace('\\','/') for x in views if not x.endswith('.bak')] + extend = {} + include = {} + for c in views: + data = safe_read(apath('%s/views/%s' % (app, c), r=request)) + items = regex_extend.findall(data) + + if items: + extend[c] = items[0][1] + + items = regex_include.findall(data) + include[c] = [i[1] for i in items] + + # Get all modules + modules = listdir(apath('%s/modules/' % app, r=request), '.*\.py$') + modules = modules=[x.replace('\\','/') for x in modules] + modules.sort() + + # Get all static files + statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*') + statics = [x.replace('\\','/') for x in statics] + statics.sort() + + # Get all languages + languages = listdir(apath('%s/languages/' % app, r=request), '[\w-]*\.py') + + #Get crontab + cronfolder = apath('%s/cron' % app, r=request) + if not os.path.exists(cronfolder): os.mkdir(cronfolder) + crontab = apath('%s/cron/crontab' % app, r=request) + if not os.path.exists(crontab): + safe_write(crontab, '#crontab') + + plugins=[] + def filter_plugins(items,plugins): + plugins+=[item[7:].split('/')[0].split('.')[0] for item in items if item.startswith('plugin_')] + plugins[:]=list(set(plugins)) + plugins.sort() + return [item for item in items if not item.startswith('plugin_')] + + return dict(app=app, + models=filter_plugins(models,plugins), + defines=defines, + controllers=filter_plugins(controllers,plugins), + functions=functions, + views=filter_plugins(views,plugins), + modules=filter_plugins(modules,plugins), + extend=extend, + include=include, + statics=filter_plugins(statics,plugins), + languages=languages, + crontab=crontab, + plugins=plugins) + +def delete_plugin(): + """ Object delete handler """ + app=request.args(0) + plugin = request.args(1) + plugin_name='plugin_'+plugin + if 'nodelete' in request.vars: + redirect(URL('design',args=app)) + elif 'delete' in request.vars: + try: + for folder in ['models','views','controllers','static','modules']: + path=os.path.join(apath(app,r=request),folder) + for item in os.listdir(path): + if item.startswith(plugin_name): + filename=os.path.join(path,item) + if os.path.isdir(filename): + shutil.rmtree(filename) + else: + os.unlink(filename) + session.flash = T('plugin "%(plugin)s" deleted', + dict(plugin=plugin)) + except Exception: + session.flash = T('unable to delete file plugin "%(plugin)s"', + dict(plugin=plugin)) + redirect(URL('design',args=request.args(0))) + return dict(plugin=plugin) + +def plugin(): + """ Application design handler """ + app = get_app() + plugin = request.args(1) + + if not response.flash and app == request.application: + msg = T('ATTENTION: you cannot edit the running application!') + response.flash = msg + + # If we have only pyc files it means that + # we cannot design + if os.path.exists(apath('%s/compiled' % app, r=request)): + session.flash = \ + T('application is compiled and cannot be designed') + redirect(URL('site')) + + # Get all models + models = listdir(apath('%s/models/' % app, r=request), '.*\.py$') + models=[x.replace('\\','/') for x in models] + defines = {} + for m in models: + data = safe_read(apath('%s/models/%s' % (app, m), r=request)) + defines[m] = regex_tables.findall(data) + defines[m].sort() + + # Get all controllers + controllers = sorted(listdir(apath('%s/controllers/' % app, r=request), '.*\.py$')) + controllers = [x.replace('\\','/') for x in controllers] + functions = {} + for c in controllers: + data = safe_read(apath('%s/controllers/%s' % (app, c), r=request)) + items = regex_expose.findall(data) + functions[c] = items + + # Get all views + views = sorted(listdir(apath('%s/views/' % app, r=request), '[\w/\-]+\.\w+$')) + views = [x.replace('\\','/') for x in views] + extend = {} + include = {} + for c in views: + data = safe_read(apath('%s/views/%s' % (app, c), r=request)) + items = regex_extend.findall(data) + if items: + extend[c] = items[0][1] + + items = regex_include.findall(data) + include[c] = [i[1] for i in items] + + # Get all modules + modules = listdir(apath('%s/modules/' % app, r=request), '.*\.py$') + modules = modules=[x.replace('\\','/') for x in modules] + modules.sort() + + # Get all static files + statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*') + statics = [x.replace('\\','/') for x in statics] + statics.sort() + + # Get all languages + languages = listdir(apath('%s/languages/' % app, r=request), '[\w-]*\.py') + + #Get crontab + crontab = apath('%s/cron/crontab' % app, r=request) + if not os.path.exists(crontab): + safe_write(crontab, '#crontab') + + def filter_plugins(items): + regex=re.compile('^plugin_'+plugin+'(/.*|\..*)?$') + return [item for item in items if regex.match(item)] + + return dict(app=app, + models=filter_plugins(models), + defines=defines, + controllers=filter_plugins(controllers), + functions=functions, + views=filter_plugins(views), + modules=filter_plugins(modules), + extend=extend, + include=include, + statics=filter_plugins(statics), + languages=languages, + crontab=crontab) + + +def create_file(): + """ Create files handler """ + try: + app = get_app(name=request.vars.location.split('/')[0]) + path = apath(request.vars.location, r=request) + filename = re.sub('[^\w./-]+', '_', request.vars.filename) + + if path[-11:] == '/languages/': + # Handle language files + if len(filename) == 0: + raise SyntaxError + if not filename[-3:] == '.py': + filename += '.py' + app = path.split('/')[-3] + path=os.path.join(apath(app, r=request),'languages',filename) + if not os.path.exists(path): + safe_write(path, '') + findT(apath(app, r=request), filename[:-3]) + session.flash = T('language file "%(filename)s" created/updated', + dict(filename=filename)) + redirect(request.vars.sender) + + elif path[-8:] == '/models/': + # Handle python models + if not filename[-3:] == '.py': + filename += '.py' + + if len(filename) == 3: + raise SyntaxError + + text = '# coding: utf8\n' + + elif path[-13:] == '/controllers/': + # Handle python controllers + if not filename[-3:] == '.py': + filename += '.py' + + if len(filename) == 3: + raise SyntaxError + + text = '# coding: utf8\n# %s\ndef index(): return dict(message="hello from %s")' + text = text % (T('try something like'), filename) + + elif path[-7:] == '/views/': + if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin): + filename = 'plugin_%s/%s' % (request.vars.plugin, filename) + # Handle template (html) views + if filename.find('.')<0: + filename += '.html' + extension = filename.split('.')[-1].lower() + + if len(filename) == 5: + raise SyntaxError + + msg = T('This is the %(filename)s template', + dict(filename=filename)) + if extension == 'html': + text = dedent(""" + {{extend 'layout.html'}} +

%s

+ {{=BEAUTIFY(response._vars)}}""" % msg) + else: + generic = os.path.join(path,'generic.'+extension) + if os.path.exists(generic): + text = read_file(generic) + else: + text = '' + + elif path[-9:] == '/modules/': + if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin): + filename = 'plugin_%s/%s' % (request.vars.plugin, filename) + # Handle python module files + if not filename[-3:] == '.py': + filename += '.py' + + if len(filename) == 3: + raise SyntaxError + + text = dedent(""" + #!/usr/bin/env python + # coding: utf8 + from gluon import *\n""") + + elif path[-8:] == '/static/': + if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin): + filename = 'plugin_%s/%s' % (request.vars.plugin, filename) + text = '' + else: + redirect(request.vars.sender) + + full_filename = os.path.join(path, filename) + dirpath = os.path.dirname(full_filename) + + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + if os.path.exists(full_filename): + raise SyntaxError + + safe_write(full_filename, text) + session.flash = T('file "%(filename)s" created', + dict(filename=full_filename[len(path):])) + redirect(URL('edit', + args=[os.path.join(request.vars.location, filename)])) + except Exception, e: + if not isinstance(e,HTTP): + session.flash = T('cannot create file') + + redirect(request.vars.sender) + + +def upload_file(): + """ File uploading handler """ + + try: + filename = None + app = get_app(name=request.vars.location.split('/')[0]) + path = apath(request.vars.location, r=request) + + if request.vars.filename: + filename = re.sub('[^\w\./]+', '_', request.vars.filename) + else: + filename = os.path.split(request.vars.file.filename)[-1] + + if path[-8:] == '/models/' and not filename[-3:] == '.py': + filename += '.py' + + if path[-9:] == '/modules/' and not filename[-3:] == '.py': + filename += '.py' + + if path[-13:] == '/controllers/' and not filename[-3:] == '.py': + filename += '.py' + + if path[-7:] == '/views/' and not filename[-5:] == '.html': + filename += '.html' + + if path[-11:] == '/languages/' and not filename[-3:] == '.py': + filename += '.py' + + filename = os.path.join(path, filename) + dirpath = os.path.dirname(filename) + + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + safe_write(filename, request.vars.file.file.read(), 'wb') + session.flash = T('file "%(filename)s" uploaded', + dict(filename=filename[len(path):])) + except Exception: + if filename: + d = dict(filename = filename[len(path):]) + else: + d = dict(filename = 'unkown') + session.flash = T('cannot upload file "%(filename)s"', d) + + redirect(request.vars.sender) + + +def errors(): + """ Error handler """ + import operator + import os + import pickle + import hashlib + + app = get_app() + + method = request.args(1) or 'new' + + + if method == 'new': + errors_path = apath('%s/errors' % app, r=request) + + delete_hashes = [] + for item in request.vars: + if item[:7] == 'delete_': + delete_hashes.append(item[7:]) + + hash2error = dict() + + for fn in listdir(errors_path, '^\w.*'): + fullpath = os.path.join(errors_path, fn) + if not os.path.isfile(fullpath): continue + try: + fullpath_file = open(fullpath, 'r') + try: + error = pickle.load(fullpath_file) + finally: + fullpath_file.close() + except IOError: + continue + + hash = hashlib.md5(error['traceback']).hexdigest() + + if hash in delete_hashes: + os.unlink(fullpath) + else: + try: + hash2error[hash]['count'] += 1 + except KeyError: + error_lines = error['traceback'].split("\n") + last_line = error_lines[-2] + error_causer = os.path.split(error['layer'])[1] + hash2error[hash] = dict(count=1, pickel=error, + causer=error_causer, + last_line=last_line, + hash=hash,ticket=fn) + + decorated = [(x['count'], x) for x in hash2error.values()] + decorated.sort(key=operator.itemgetter(0), reverse=True) + + return dict(errors = [x[1] for x in decorated], app=app, method=method) + else: + for item in request.vars: + if item[:7] == 'delete_': + os.unlink(apath('%s/errors/%s' % (app, item[7:]), r=request)) + func = lambda p: os.stat(apath('%s/errors/%s' % \ + (app, p), r=request)).st_mtime + tickets = sorted(listdir(apath('%s/errors/' % app, r=request), '^\w.*'), + key=func, + reverse=True) + + return dict(app=app, tickets=tickets, method=method) + + +def make_link(path): + """ Create a link from a path """ + tryFile = path.replace('\\', '/') + + if os.path.isabs(tryFile) and os.path.isfile(tryFile): + (folder, filename) = os.path.split(tryFile) + (base, ext) = os.path.splitext(filename) + app = get_app() + + editable = {'controllers': '.py', 'models': '.py', 'views': '.html'} + for key in editable.keys(): + check_extension = folder.endswith("%s/%s" % (app,key)) + if ext.lower() == editable[key] and check_extension: + return A('"' + tryFile + '"', + _href=URL(r=request, + f='edit/%s/%s/%s' % (app, key, filename))).xml() + return '' + + +def make_links(traceback): + """ Make links using the given traceback """ + + lwords = traceback.split('"') + + # Making the short circuit compatible with <= python2.4 + result = (len(lwords) != 0) and lwords[0] or '' + + i = 1 + + while i < len(lwords): + link = make_link(lwords[i]) + + if link == '': + result += '"' + lwords[i] + else: + result += link + + if i + 1 < len(lwords): + result += lwords[i + 1] + i = i + 1 + + i = i + 1 + + return result + + +class TRACEBACK(object): + """ Generate the traceback """ + + def __init__(self, text): + """ TRACEBACK constructor """ + + self.s = make_links(CODE(text).xml()) + + def xml(self): + """ Returns the xml """ + + return self.s + + +def ticket(): + """ Ticket handler """ + + if len(request.args) != 2: + session.flash = T('invalid ticket') + redirect(URL('site')) + + app = get_app() + myversion = request.env.web2py_version + ticket = request.args[1] + e = RestrictedError() + e.load(request, app, ticket) + + return dict(app=app, + ticket=ticket, + output=e.output, + traceback=(e.traceback and TRACEBACK(e.traceback)), + snapshot=e.snapshot, + code=e.code, + layer=e.layer, + myversion=myversion) + +def error(): + """ Generate a ticket (for testing) """ + raise RuntimeError('admin ticket generator at your service') + +def update_languages(): + """ Update available languages """ + + app = get_app() + update_all_languages(apath(app, r=request)) + session.flash = T('Language files (static strings) updated') + redirect(URL('design',args=app,anchor='languages')) + +def twitter(): + session.forget() + session._unlock(response) + import gluon.tools + import gluon.contrib.simplejson as sj + try: + if TWITTER_HASH: + page = gluon.tools.fetch('http://twitter.com/%s?format=json'%TWITTER_HASH) + return sj.loads(page)['#timeline'] + else: + return 'disabled' + except Exception, e: + return DIV(T('Unable to download because:'),BR(),str(e)) + +def user(): + if MULTI_USER_MODE: + if not db(db.auth_user).count(): + auth.settings.registration_requires_approval = False + return dict(form=auth()) + else: + return dict(form=T("Disabled")) + +def reload_routes(): + """ Reload routes.py """ + import gluon.rewrite + gluon.rewrite.load() + redirect(URL('site')) ADDED applications/admin/controllers/gae.py Index: applications/admin/controllers/gae.py ================================================================== --- applications/admin/controllers/gae.py +++ applications/admin/controllers/gae.py @@ -0,0 +1,87 @@ +### this works on linux only + +import re +try: + import fcntl + import subprocess + import signal + import os + import shutil + from gluon.fileutils import read_file, write_file +except: + session.flash='sorry, only on Unix systems' + redirect(URL(request.application,'default','site')) + +forever=10**8 + +def kill(): + p = cache.ram('gae_upload',lambda:None,forever) + if not p or p.poll()!=None: + return 'oops' + os.kill(p.pid, signal.SIGKILL) + cache.ram('gae_upload',lambda:None,-1) + +class EXISTS(object): + def __init__(self, error_message='file not found'): + self.error_message = error_message + def __call__(self, value): + if os.path.exists(value): + return (value,None) + return (value,self.error_message) + +def deploy(): + regex = re.compile('^\w+$') + apps = sorted(file for file in os.listdir(apath(r=request)) if regex.match(file)) + form = SQLFORM.factory( + Field('appcfg',default=GAE_APPCFG,label='Path to appcfg.py', + requires=EXISTS(error_message=T('file not found'))), + Field('google_application_id',requires=IS_ALPHANUMERIC()), + Field('applications','list:string', + requires=IS_IN_SET(apps,multiple=True), + label=T('web2py apps to deploy')), + Field('email',requires=IS_EMAIL(),label=T('GAE Email')), + Field('password','password',requires=IS_NOT_EMPTY(),label=T('GAE Password'))) + cmd = output = errors= "" + if form.accepts(request,session): + try: + kill() + except: + pass + ignore_apps = [item for item in apps \ + if not item in form.vars.applications] + regex = re.compile('\(applications/\(.*') + yaml = apath('../app.yaml', r=request) + if not os.path.exists(yaml): + example = apath('../app.example.yaml', r=request) + shutil.copyfile(example,yaml) + data = read_file(yaml) + data = re.sub('application:.*','application: %s' % form.vars.google_application_id,data) + data = regex.sub('(applications/(%s)/.*)|' % '|'.join(ignore_apps),data) + write_file(yaml, data) + + path = request.env.applications_parent + cmd = '%s --email=%s --passin update %s' % \ + (form.vars.appcfg, form.vars.email, path) + p = cache.ram('gae_upload', + lambda s=subprocess,c=cmd:s.Popen(c, shell=True, + stdin=s.PIPE, + stdout=s.PIPE, + stderr=s.PIPE, close_fds=True),-1) + p.stdin.write(form.vars.password+'\n') + fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) + fcntl.fcntl(p.stderr.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) + return dict(form=form,command=cmd) + +def callback(): + p = cache.ram('gae_upload',lambda:None,forever) + if not p or p.poll()!=None: + return '' + try: + output = p.stdout.read() + except: + output='' + try: + errors = p.stderr.read() + except: + errors='' + return (output+errors).replace('\n','
') ADDED applications/admin/controllers/mercurial.py Index: applications/admin/controllers/mercurial.py ================================================================== --- applications/admin/controllers/mercurial.py +++ applications/admin/controllers/mercurial.py @@ -0,0 +1,82 @@ +from gluon.fileutils import read_file, write_file + +if DEMO_MODE or MULTI_USER_MODE: + session.flash = T('disabled in demo mode') + redirect(URL('default','site')) +if not have_mercurial: + session.flash=T("Sorry, could not find mercurial installed") + redirect(URL('default','design',args=request.args(0))) + +_hgignore_content = """\ +syntax: glob +*~ +*.pyc +*.pyo +*.bak +*.bak2 +cache/* +private/* +uploads/* +databases/* +sessions/* +errors/* +""" + +def hg_repo(path): + import os + uio = ui.ui() + uio.quiet = True + if not os.environ.get('HGUSER') and not uio.config("ui", "username"): + os.environ['HGUSER'] = 'web2py@localhost' + try: + repo = hg.repository(ui=uio, path=path) + except: + repo = hg.repository(ui=uio, path=path, create=True) + hgignore = os.path.join(path, '.hgignore') + if not os.path.exists(hgignore): + write_file(hgignore, _hgignore_content) + return repo + +def commit(): + app = request.args(0) + path = apath(app, r=request) + repo = hg_repo(path) + form = FORM('Comment:',INPUT(_name='comment',requires=IS_NOT_EMPTY()), + INPUT(_type='submit',_value='Commit')) + if form.accepts(request.vars,session): + oldid = repo[repo.lookup('.')] + cmdutil.addremove(repo) + repo.commit(text=form.vars.comment) + if repo[repo.lookup('.')] == oldid: + response.flash = 'no changes' + try: + files = TABLE(*[TR(file) for file in repo[repo.lookup('.')].files()]) + changes = TABLE(TR(TH('revision'),TH('description'))) + for change in repo.changelog: + ctx=repo.changectx(change) + revision, description = ctx.rev(), ctx.description() + changes.append(TR(A(revision,_href=URL('revision', + args=(app,revision))), + description)) + except: + files = [] + changes = [] + return dict(form=form,files=files,changes=changes,repo=repo) + +def revision(): + app = request.args(0) + path = apath(app, r=request) + repo = hg_repo(path) + revision = request.args(1) + ctx=repo.changectx(revision) + form=FORM(INPUT(_type='submit',_value='revert')) + if form.accepts(request.vars): + hg.update(repo, revision) + session.flash = "reverted to revision %s" % ctx.rev() + redirect(URL('default','design',args=app)) + return dict( + files=ctx.files(), + rev=str(ctx.rev()), + desc=ctx.description(), + form=form + ) ADDED applications/admin/controllers/shell.py Index: applications/admin/controllers/shell.py ================================================================== --- applications/admin/controllers/shell.py +++ applications/admin/controllers/shell.py @@ -0,0 +1,45 @@ +import sys +import cStringIO +import gluon.contrib.shell +import code, thread +from gluon.shell import env + +if DEMO_MODE or MULTI_USER_MODE: + session.flash = T('disabled in demo mode') + redirect(URL('default','site')) + +FE=10**9 + +def index(): + app = request.args(0) or 'admin' + reset() + return dict(app=app) + +def callback(): + app = request.args[0] + command = request.vars.statement + escape = command[:1]!='!' + history = session['history:'+app] = session.get('history:'+app,gluon.contrib.shell.History()) + if not escape: + command = command[1:] + if command == '%reset': + reset() + return '*** reset ***' + elif command[0] == '%': + try: + command=session['commands:'+app][int(command[1:])] + except ValueError: + return '' + session['commands:'+app].append(command) + environ=env(app,True) + output = gluon.contrib.shell.run(history,command,environ) + k = len(session['commands:'+app]) - 1 + #output = PRE(output) + #return TABLE(TR('In[%i]:'%k,PRE(command)),TR('Out[%i]:'%k,output)) + return 'In [%i] : %s%s\n' % (k + 1, command, output) + +def reset(): + app = request.args(0) or 'admin' + session['commands:'+app] = [] + session['history:'+app] = gluon.contrib.shell.History() + return 'done' ADDED applications/admin/controllers/toolbar.py Index: applications/admin/controllers/toolbar.py ================================================================== --- applications/admin/controllers/toolbar.py +++ applications/admin/controllers/toolbar.py @@ -0,0 +1,29 @@ +import os +from gluon.settings import global_settings, read_file +# + +def index(): + app = request.args(0) + return dict(app=app) + +def profiler(): + """ + to use the profiler start web2py with -F profiler.log + """ + KEY = 'web2py_profiler_size' + filename = global_settings.cmd_options.profiler_filename + data = 'profiler disabled' + if filename: + if KEY in request.cookies: + size = int(request.cookies[KEY].value) + else: + size = 0 + if os.path.exists(filename): + data = read_file('profiler.log','rb') + if size=m: redirect(URL('step2')) + table=session.app['tables'][n] + form=SQLFORM.factory(Field('field_names','list:string', + default=session.app.get('table_'+table,[]))) + if form.accepts(request.vars) and form.vars.field_names: + fields=listify(form.vars.field_names) + if table=='auth_user': + for field in ['first_name','last_name','username','email','password']: + if not field in fields: + fields.append(field) + session.app['table_'+table]=[t.strip().lower() + for t in listify(form.vars.field_names) + if t.strip()] + try: + tables=sort_tables(session.app['tables']) + except RuntimeError: + response.flash=T('invalid circual reference') + else: + if n=m: redirect(URL('step4')) + page=session.app['pages'][n] + markmin_url='http://web2py.com/examples/static/markmin.html' + form=SQLFORM.factory(Field('content','text', + default=session.app.get('page_'+page,[]), + comment=A('use markmin', + _href=markmin_url,_target='_blank')), + formstyle='table2cols') + if form.accepts(request.vars): + session.app['page_'+page]=form.vars.content + if n 0 +FONT_SIZE = 10 + +# Displays the editor in full screen mode. The value must be 'true' or 'false' +FULL_SCREEN = 'false' + +# Display a check box under the editor to allow the user to switch +# between the editor and a simple +# HTML text area. The value must be 'true' or 'false' +ALLOW_TOGGLE = 'true' + +# Replaces tab characters with space characters. +# The value can be 'false' (meaning that tabs are not replaced), +# or an integer > 0 that specifies the number of spaces to replace a tab with. +REPLACE_TAB_BY_SPACES = 4 + +# Toggle on/off the code editor instead of textarea on startup +DISPLAY = "onload" or "later" + +# if demo mode is True then admin works readonly and does not require login +DEMO_MODE = False + +# if visible_apps is not empty only listed apps will be accessible +FILTER_APPS = [] + +# To upload on google app engine this has to point to the proper appengine +# config file +import os +# extract google_appengine_x.x.x.zip to web2py root directory +#GAE_APPCFG = os.path.abspath(os.path.join('appcfg.py')) +# extract google_appengine_x.x.x.zip to applications/admin/private/ +GAE_APPCFG = os.path.abspath(os.path.join('/usr/local/bin/appcfg.py')) + +# To use web2py as a teaching tool, set MULTI_USER_MODE to True +MULTI_USER_MODE = False + +# configurable twitterbox, set to None/False to suppress +TWITTER_HASH = "web2py" + +# parameter for downloading LAYOUTS +LAYOUTS_APP = 'http://web2py.com/layouts' +#LAYOUTS_APP = 'http://127.0.0.1:8000/layouts' + + +# parameter for downloading PLUGINS +PLUGINS_APP = 'http://web2py.com/plugins' +#PLUGINS_APP = 'http://127.0.0.1:8000/plugins' + +# set the language +if 'adminLanguage' in request.cookies and not (request.cookies['adminLanguage'] is None): + T.force(request.cookies['adminLanguage'].value) ADDED applications/admin/models/0_imports.py Index: applications/admin/models/0_imports.py ================================================================== --- applications/admin/models/0_imports.py +++ applications/admin/models/0_imports.py @@ -0,0 +1,26 @@ +import time +import os +import sys +import re +import urllib +import cgi +import difflib +import shutil +import stat +import socket + +from textwrap import dedent + +try: + from mercurial import ui, hg, cmdutil + have_mercurial = True +except ImportError: + have_mercurial = False + +from gluon.utils import md5_hash +from gluon.fileutils import listdir, cleanpath, up +from gluon.fileutils import tar, tar_compiled, untar, fix_newlines +from gluon.languages import findT, update_all_languages +from gluon.myregex import * +from gluon.restricted import * +from gluon.compileapp import compile_application, remove_compiled_application ADDED applications/admin/models/access.py Index: applications/admin/models/access.py ================================================================== --- applications/admin/models/access.py +++ applications/admin/models/access.py @@ -0,0 +1,146 @@ +import os, time +from gluon import portalocker +from gluon.admin import apath +from gluon.fileutils import read_file +# ########################################################### +# ## make sure administrator is on localhost or https +# ########################################################### + +http_host = request.env.http_host.split(':')[0] + +if request.env.web2py_runtime_gae: + session_db = DAL('gae') + session.connect(request, response, db=session_db) + hosts = (http_host, ) + +if request.env.http_x_forwarded_for or request.is_https: + session.secure() +elif not request.is_local and not DEMO_MODE: + raise HTTP(200, T('Admin is disabled because insecure channel')) + +try: + _config = {} + port = int(request.env.server_port or 0) + restricted(read_file(apath('../parameters_%i.py' % port, request)), _config) + + if not 'password' in _config or not _config['password']: + raise HTTP(200, T('admin disabled because no admin password')) +except IOError: + import gluon.fileutils + if request.env.web2py_runtime_gae: + if gluon.fileutils.check_credentials(request): + session.authorized = True + session.last_time = time.time() + else: + raise HTTP(200, + T('admin disabled because not supported on google app engine')) + else: + raise HTTP(200, T('admin disabled because unable to access password file')) + + +def verify_password(password): + session.pam_user = None + if DEMO_MODE: + return True + elif not 'password' in _config: + return False + elif _config['password'].startswith('pam_user:'): + session.pam_user = _config['password'][9:].strip() + import gluon.contrib.pam + return gluon.contrib.pam.authenticate(session.pam_user,password) + else: + return _config['password'] == CRYPT()(password)[0] + + +# ########################################################### +# ## handle brute-force login attacks +# ########################################################### + +deny_file = os.path.join(request.folder, 'private', 'hosts.deny') +allowed_number_of_attempts = 5 +expiration_failed_logins = 3600 + +def read_hosts_deny(): + import datetime + hosts = {} + if os.path.exists(deny_file): + hosts = {} + f = open(deny_file, 'r') + portalocker.lock(f, portalocker.LOCK_SH) + for line in f.readlines(): + if not line.strip() or line.startswith('#'): + continue + fields = line.strip().split() + if len(fields) > 2: + hosts[fields[0].strip()] = ( # ip + int(fields[1].strip()), # n attemps + int(fields[2].strip()) # last attempts + ) + portalocker.unlock(f) + f.close() + return hosts + +def write_hosts_deny(denied_hosts): + f = open(deny_file, 'w') + portalocker.lock(f, portalocker.LOCK_EX) + for key, val in denied_hosts.items(): + if time.time()-val[1] < expiration_failed_logins: + line = '%s %s %s\n' % (key, val[0], val[1]) + f.write(line) + portalocker.unlock(f) + f.close() + +def login_record(success=True): + denied_hosts = read_hosts_deny() + val = (0,0) + if success and request.client in denied_hosts: + del denied_hosts[request.client] + elif not success and not request.is_local: + val = denied_hosts.get(request.client,(0,0)) + if time.time()-val[1]= allowed_number_of_attempts: + return val[0] # locked out + time.sleep(2**val[0]) + val = (val[0]+1,int(time.time())) + denied_hosts[request.client] = val + write_hosts_deny(denied_hosts) + return val[0] + + +# ########################################################### +# ## session expiration +# ########################################################### + +t0 = time.time() +if session.authorized: + + if session.last_time and session.last_time < t0 - EXPIRATION: + session.flash = T('session expired') + session.authorized = False + else: + session.last_time = t0 + +if not session.authorized and not \ + (request.controller == 'default' and \ + request.function in ('index','user')): + + if request.env.query_string: + query_string = '?' + request.env.query_string + else: + query_string = '' + + if request.env.web2py_original_uri: + url = request.env.web2py_original_uri + else: + url = request.env.path_info + query_string + redirect(URL(request.application, 'default', 'index', vars=dict(send=url))) +elif session.authorized and \ + request.controller == 'default' and \ + request.function == 'index': + redirect(URL(request.application, 'default', 'site')) + + +if request.controller=='appadmin' and DEMO_MODE: + session.flash = 'Appadmin disabled in demo mode' + redirect(URL('default','sites')) + ADDED applications/admin/models/buttons.py Index: applications/admin/models/buttons.py ================================================================== --- applications/admin/models/buttons.py +++ applications/admin/models/buttons.py @@ -0,0 +1,15 @@ +# Template helpers + +import os + +def button(href, label): + return A(SPAN(label),_class='button',_href=href) + +def sp_button(href, label): + return A(SPAN(label),_class='button special',_href=href) + +def helpicon(): + return IMG(_src=URL('static', 'images/help.png'), _alt='help') + +def searchbox(elementid): + return TAG[''](LABEL(IMG(_src=URL('static', 'images/search.png'), _alt=T('filter')), _class='icon', _for=elementid), ' ', INPUT(_id=elementid, _type='text', _size=12)) ADDED applications/admin/models/db.py Index: applications/admin/models/db.py ================================================================== --- applications/admin/models/db.py +++ applications/admin/models/db.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# this file is released under public domain and you can use without limitations + +if MULTI_USER_MODE: + db = DAL('sqlite://storage.sqlite') # if not, use SQLite or other DB + from gluon.tools import * + mail = Mail() # mailer + auth = Auth(globals(),db) # authentication/authorization + crud = Crud(globals(),db) # for CRUD helpers using auth + service = Service(globals()) # for json, xml, jsonrpc, xmlrpc, amfrpc + plugins = PluginManager() + + mail.settings.server = 'logging' or 'smtp.gmail.com:587' # your SMTP server + mail.settings.sender = 'you@gmail.com' # your email + mail.settings.login = 'username:password' # your credentials or None + + auth.settings.hmac_key = '' # before define_tables() + auth.define_tables() # creates all needed tables + auth.settings.mailer = mail # for user email verification + auth.settings.registration_requires_verification = False + auth.settings.registration_requires_approval = True + auth.messages.verify_email = 'Click on the link http://'+request.env.http_host+URL('default','user',args=['verify_email'])+'/%(key)s to verify your email' + auth.settings.reset_password_requires_verification = True + auth.messages.reset_password = 'Click on the link http://'+request.env.http_host+URL('default','user',args=['reset_password'])+'/%(key)s to reset your password' + + db.define_table('app',Field('name'),Field('owner',db.auth_user)) + +if not session.authorized and MULTI_USER_MODE: + if auth.user and not request.function=='user': + session.authorized = True + elif not request.function=='user': + redirect(URL('default','user/login')) + +def is_manager(): + if not MULTI_USER_MODE: + return True + elif auth.user and auth.user.id==1: + return True + else: + return False ADDED applications/admin/models/menu.py Index: applications/admin/models/menu.py ================================================================== --- applications/admin/models/menu.py +++ applications/admin/models/menu.py @@ -0,0 +1,33 @@ +# ########################################################### +# ## generate menu +# ########################################################### + +_a = request.application +_c = request.controller +_f = request.function +response.title = '%s %s' % (_f, '/'.join(request.args)) +response.subtitle = 'admin' +response.menu = [(T('Site'), _f == 'site', URL(_a,'default','site'))] + +if request.args: + _t = request.args[0] + response.menu.append((T('Edit'), _c == 'default' and _f == 'design', + URL(_a,'default','design',args=_t))) + response.menu.append((T('About'), _c == 'default' and _f == 'about', + URL(_a,'default','about',args=_t))) + response.menu.append((T('Errors'), _c == 'default' and _f == 'errors', + URL(_a,'default','errors',args=_t))) + response.menu.append((T('Versioning'), + _c == 'mercurial' and _f == 'commit', + URL(_a,'mercurial','commit',args=_t))) + +if not session.authorized: + response.menu = [(T('Login'), True, '')] +else: + response.menu.append((T('Logout'), False, + URL(_a,'default',f='logout'))) + +if os.path.exists('applications/examples'): + response.menu.append((T('Help'), False, URL('examples','default','index'))) +else: + response.menu.append((T('Help'), False, 'http://web2py.com/examples')) ADDED applications/admin/models/plugin_multiselect.py Index: applications/admin/models/plugin_multiselect.py ================================================================== --- applications/admin/models/plugin_multiselect.py +++ applications/admin/models/plugin_multiselect.py @@ -0,0 +1,4 @@ +response.files.append(URL('static','plugin_multiselect/jquery.dimensions.js')) +response.files.append(URL('static','plugin_multiselect/jquery.multiselect.js')) +response.files.append(URL('static','plugin_multiselect/jquery.multiselect.css')) +response.files.append(URL('static','plugin_multiselect/start.js')) ADDED applications/admin/modules/__init__.py Index: applications/admin/modules/__init__.py ================================================================== --- applications/admin/modules/__init__.py +++ applications/admin/modules/__init__.py ADDED applications/admin/static/css/calendar.css Index: applications/admin/static/css/calendar.css ================================================================== --- applications/admin/static/css/calendar.css +++ applications/admin/static/css/calendar.css @@ -0,0 +1,4 @@ +.calendar{z-index:99;position:relative;display:none;border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar table{border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;font-size:11px;color:#000;cursor:default;background:#d4d0c8;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar .nav{background:transparent}.calendar thead .title{font-weight:bold;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar thead .name{border-bottom:1px solid #000;padding:2px;text-align:center;background:#f4f0e8;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite{border-top:2px solid #fff;border-right:2px solid #000;border-bottom:2px solid #000;border-left:2px solid #fff;padding:0;background-color:#e4e0d8;}.calendar thead .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{font-size:80%;color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;border-right:1px solid #000;background:#f4f0e8;}.calendar tbody .rowhilite td{background:#e4e0d8;}.calendar tbody .rowhilite td.wn{background:#d4d0c8;}.calendar tbody td.hilite{padding:1px 3px 1px 1px;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;}.calendar tbody td.active{padding:2px 2px 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar tbody td.selected{font-weight:bold;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;padding:2px 2px 0 2px;background:#e4e0d8;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#f4f0e8;padding:1px;border:1px solid #000;background:#848078;color:#fff;text-align:center;}.calendar tfoot .hilite{border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;padding:1px;background:#e4e0d8;}.calendar tfoot .active{padding:2px 0 0 2px;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;border-top:1px solid #fff;border-right:1px solid #000;border-bottom:1px solid #000;border-left:1px solid #fff;background:#e4e0d8;font-size:90%;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;padding:0;border-top:1px solid #000;border-right:1px solid #fff;border-bottom:1px solid #fff;border-left:1px solid #000;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{border-top:1px solid #000;padding:1px 0;text-align:center;background-color:#f4f0e8;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;border:1px solid #889;font-weight:bold;background-color:#fff;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{border-color:#000;background-color:#766;color:#fff;}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;} + +#CP_hourcont {z-index:99;padding: 0px;position:absolute;border:1px dashed #bbbbbb;background-color:#dddddd;display:none;} #CP_minutecont {z-index:99;background-color:#dddddd;padding: 0px;position:absolute;width:45px;border: 1px dashed #cccccc;display:none;} .floatleft {float:left;} .CP_hour {z-index:99;padding:1px;font-family: Arial, Helvetica, sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;margin:1px;background-color:#eeeeee;} .CP_minute {z-index:99;padding:1px;background-color:#eeeeee;font-family: Arial, Helvetica, sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;margin:1px;} .CP_over {z-index:99;background-color:#ffffff;} + ADDED applications/admin/static/css/jqueryMultiSelect.css Index: applications/admin/static/css/jqueryMultiSelect.css ================================================================== --- applications/admin/static/css/jqueryMultiSelect.css +++ applications/admin/static/css/jqueryMultiSelect.css @@ -0,0 +1,47 @@ +.multiSelect { + width: 200px; + border: solid 1px #BBB; + background: #FFF right center no-repeat; + padding: 2px 4px; + padding-right: 20px; + display: inline; +} + +.multiSelect.hover { + background: right center no-repeat; +} + +.multiSelect.active, +.multiSelect.focus { + border: inset 1px #000; +} + +.multiSelect.active { + background: right center no-repeat; +} + +.multiSelectOptions { + width: 500px; + max-height: 150px; + margin-top: -1px; + overflow: auto; + border: solid 1px #B2B2B2; + background: #FFF; +} + +.multiSelectOptions LABEL { + padding: 2px 5px; + display: block; +} + +.multiSelectOptions LABEL.checked { + background: #E6E6E6; +} + +.multiSelectOptions LABEL.selectAll { + border-bottom: dotted 1px #CCC; +} + +.multiSelectOptions LABEL.hover { + background: #CFCFCF; +} ADDED applications/admin/static/css/styles.css Index: applications/admin/static/css/styles.css ================================================================== --- applications/admin/static/css/styles.css +++ applications/admin/static/css/styles.css @@ -0,0 +1,1065 @@ +/** +* cSans v0.6.3 +* 2009 Copyright A navalla suíza http://anavallasuiza.com +* cSans is released under the GNU Affero GPL version 3 - more information at http://www.fsf.org/licensing/licenses/agpl-3.0.html +*/ + +/* 1. RESET */ +html,body,div,span,object,iframe, +h1,h2,h3,h4,h5,h6,p,blockquote,pre, +a,abbr,acronym,address,code, +del,dfn,em,img,q,dl,dt,dd,ol,ul,li, +fieldset,form,label,legend,textarea, +table,caption,tbody,tfoot,thead,tr,th,td { + margin:0; + padding:0; + border:0; +} +textarea,select,input { font-size:1em; } +html,body { height:100%; } +body { + font-family:Arial,Helvetica,"Liberation Sans",Sans,sans-serif; + font-size: 12pt; + line-height:1.25em; + text-align:center; + background:#fff; + color:#000; +} +.page,.expanded-page { text-align:left; } + +/* 2. DEBUG: Show borders to stress an element */ +.omg-red,.omg-yellow,.omg-blue,.omg-green,.omg-black,.omg-white { + border-top:dotted 3px; + border-bottom:dotted 3px; +} +.omg-red { border-color:red; } +.omg-yellow { border-color:yellow; } +.omg-blue { border-color:blue; } +.omg-green { border-color:green; } +.omg-black { border-color:black; } +.omg-white { border-color:white; } + +/* 3. BASIC STYLES */ +/* 3.1. Titles */ +h1,h2,h3,h4,h5,h6 { + font-weight:bold; + line-height:1em; + margin:1em 0 0.5em 0; +} +h1 { font-size:2em; } +h2 { font-size:1.75em; } +h3 { font-size:1.5em; } +h4 { font-size:1.125em; } +h5 { font-size:1em; } +h6 { font-size:1em; font-weight:normal; } + +/* 3.2. Lists */ +ul,dd { margin-left:1em; } +ol { list-style-type:decimal; margin-left:1.5em; } +dl dt { font-weight:bold; } + +/* 3.3. Tables */ +table { border-collapse:collapse; border-spacing:0; } +caption,th { font-weight:bold; } +th,td { text-align:left; padding:0; border:1px solid #ccc; } +tfoot { font-style:italic; } + +/* 3.4. Images */ +a img { border:none; } +img.right { margin-left:1em; } +img.left { margin-right:1em; } + +/* 3.5. Forms */ +fieldset { border:1px solid #ccc; } +legend { font-weight:bold; font-size:1.2em; } +input { margin:0; } +input.text,input.password { overflow-y:visible; } +textarea { width:400px; height:100px; border:1px solid #ccc; overflow-y: auto; } +select { margin:0; font-size:1em; } +input,textarea,select { font-family:Arial,sans-serif; font-size: 1em; } + +/* 3.6. Monospace elements */ +pre,code,tt { font-family:"Courier New", Courier, monospace; line-height:1.5; } +pre,code { white-space:pre; } +tt { display:block; line-height:1.5; } + +/* 3.7. Block elements */ +p,form,table,address,blockquote,pre,code,tt,ul,ol,dl { margin-bottom:1em; } + +/* 3.8. Inline elements */ +em,dfn { font-style:italic; } + +/* 3.9. HR element */ +hr { + background:none; + visibility:hidden; + clear:both; + float:none; + width:100%; + height:1px; + border:none; + margin:-1px 0; +} + +/* 3.10. Flash objects */ +object { outline:none; } + +/* 4. UTILS */ +/* 4.1. Images with link and without link must overwrite background, width and height properties in your css */ +.image a,.image li,.image { + background-repeat:no-repeat; + background-color:transparent; + margin:0; + padding:0; + outline:none; + font-size:0px !important; + line-height:0em !important; + letter-spacing:-20px; + text-indent: -2px; + display:block; + overflow:hidden; + text-align:left; + border: none; +} +.image a { + display:block; + width:100%; + height:100%; + height:inherit; +} +ol.image,ul.image { + background:none; + width:100%; + height:auto; + float:left; +} +.image li { + float:left; +} +span.image,strong.image,em.image,a.image { + display:inline-block; + vertical-align:bottom; +} +input.image { cursor:pointer; } + +/* 4.2. Simple tabs system. You can overwrite background and text styles */ +.tabs { + list-style:none; + padding:0; + margin:0; + float:left; + width:100%; +} +.tabs li { + float:left; + margin:0 1px 0 0; +} +.tabs li span, +.tabs li a { + float: left; + padding:2px 5px; + white-space:nowrap; + text-align:center; + cursor:pointer; + outline:0; + text-decoration:none; +} +.tabs li span { cursor:default; } +.tabs .select a,.tabs a:hover { + background:#ddd; +} + +/* 4.3. Convert a block element (like a fieldset) into a inline element */ +.inline { + border:none; + margin:0; + padding:0; + display:inline; +} +fieldset.inline { + display:block; + float:left; + width: 100%; +} + +/* 4.4. Inline-block element must content any element inside (div, p, etc) for the correct visualization in FF<3 */ +.inline-block-top,.inline-block-middle,.inline-block-bottom { + float:none !important; + display:inline-block; +} +.inline-block-top { vertical-align:top !important; } +.inline-block-middle { vertical-align:middle !important; } +.inline-block-bottom { vertical-align:bottom !important; } + +/* 4.5. Float elements */ +.left { float:left !important; } +.right,.right-right { float:right !important; } +.right-right { text-align:right !important; } +.right-full { width:100%; text-align:right !important; } + +/* 4.6. Clear */ +.clear,.content,.page,.expanded-page { display:block; } +.clear:after,.tabs:after,.content:after,.page:after,.expanded-page:after { + content:" "; + display:block; + height:0; + clear:both; + visibility:hidden; + font-size:0; +} + +/* 4.7. Destroy margin collapse */ +.no-collapse { padding-top:1px; } + +/* 4.8. Hide an element */ +.hide { display:none; } + +/* 5. LAYOUT */ +/* 5.1. Row */ +.row { + float:left; + width:100%; + margin:0; + padding:0; + border:none; +} + +/* 5.2. Last column in a row */ +.last { margin-right:0 !important; } + +/* 5.3. Content */ +.content { + padding:0; + margin:0; +} + +/* 6. HACKS */ +img { -ms-interpolation-mode:bicubic; } /* IE */ +.clear,.tabs,.content,.page,.expanded-page { + *overflow-y:auto; /* IE7 */ + *overflow-x:hidden; /* IE7 */ + _height:1%; /* IE6 */ + _overflow-y:visible; /* IE6 */ + _overflow-x:visible; /* IE6 */ +} +a.image,span.image,strong.image,em.image,.inline-block-top,.inline-block-middle,.inline-block-bottom { + display:-moz-inline-box; /* FF<3 */ + -moz-box-orient:vertical; /* FF<3 */ + *display:inline; /* IE */ +} + +/** +* cSans Button plugin v0.3.1 +* 2009 Copyright A navalla suíza http://anavallasuiza.com +* cSans is released under the GNU Affero GPL version 3 - more information at http://www.fsf.org/licensing/licenses/agpl-3.0.html +*/ + +/* 1. BASIC STYLES */ + +ul.button { + list-style: none; + padding: 0; + margin: 0; + display:block; + } +ul.button { + overflow-y: auto; + overflow-x: hidden; + } +ul.button li { + float: left; + margin: 0; + } +ul.button li a { + display: block; + z-index: 2; + } +a.button { + display: inline-block; + vertical-align: middle; + } + +/* 1.1. Normal status */ +a.button, +ul.button li a { + background-position: 100% 0; + background-repeat: no-repeat; + white-space: nowrap; + text-align: center; + cursor: pointer; + outline: 0; + } +a.button span, +ul.button li a span { + display: block; + background-position: 0 0; + background-repeat: no-repeat; + } + +/* 1.2. Hover/selected status */ +a.button:hover, +a.button.select, +ul.button li a:hover, +ul.button li.select a { + background-position: 100% -70px !important; + } +a.button:hover span, +a.button.select span, +ul.button li a:hover span, +ul.button li.select a span { + background-position: 0 -70px !important; + } + +/* 2. EDITABLE STYLES */ + +/* 2.1. Image background used */ +a.button, +.button a, +.button span { + background-image: url(../images/button.png); + } + +/* 2.2. Normal status (Example for padding 10px) */ +a.button, +.button a { + padding: 0 10px 0 0; /* Padding-right: 10px */ + margin: 0 1px 0 10px; /* Margin-left: 10px */ + text-decoration: none; + } +.button span { + padding: 4px 0 6px 10px; /*Padding-left: 10px */ + margin: 0 0 0 -10px; /* Margin-left: -10px */ + } + +/* 3. HACKS */ + +ul.button { + _height: 1%; /* IE6 */ + _overflow-y: visible; /* IE6 */ + _overflow-x: visible; /* IE6 */ + } +a.button span { + _float: left; /* Only IE6 */ + _position: relative; /* Only IE6 */ + } +a.button { + display: -moz-inline-box; /* FF<3 */ + display: inline-block; /* FF<3 */ + -moz-box-orient: vertical; /* FF<3 */ + *display: inline; /* IE */ + } + +/** +* cSans Tooltip pluging v0.1 +* 2009 Copyright A navalla suíza http://anavallasuiza.com +* cSans is released under the GNU Affero GPL version 3 - more information at http://www.fsf.org/licensing/licenses/agpl-3.0.html +*/ + +/* BASIC STYLES */ + +.tooltip:hover { + background:transparent; + text-decoration:none; + } +.tooltip span { + display:none; + padding:5px; + margin-left:10px; + width:150px; + } +.tooltip:hover span { + cursor: default; + display:inline; + position:absolute; + } + +/** +* cSans Flexible v0.1 +* 2009 Copyright A navalla suíza http://anavallasuiza.com +* cSans is released under the GNU Affero GPL version 3 - more information at http://www.fsf.org/licensing/licenses/agpl-3.0.html +*/ + +/* 1. BASIC STYLES */ +/* 1.1 Widths */ +.f10 { width:10%; } +.f20 { width:20%; } +.f25 { width:25%; } +.f30 { width:30%; } +.f33 { width:33.33%; } +.f40 { width:40%; } +.f50 { width:50%; } +.f60 { width:60%; } +.f66 { width:66.66%; } +.f70 { width:70%; } +.f75 { width:75%; } +.f80 { width:80%; } +.f90 { width:90%; } +.f100 { width:100%; } + +.f10,.f20,.f25,.f30,.f33,.f40,.f50,.f60,.f66,.f70,.f75,.f80,.f90,.f100 { + float:left; + overflow:hidden; +} + +/* 1.2 Margin-left */ +.fl10 { margin-left:10%; } +.fl20 { margin-left:20%; } +.fl25 { margin-left:25%; } +.fl30 { margin-left:30%; } +.fl33 { margin-left:33.33%; } +.fl40 { margin-left:40%; } +.fl50 { margin-left:50%; } +.fl60 { margin-left:60%; } +.fl66 { margin-left:66.66%; } +.fl70 { margin-left:70%; } +.fl75 { margin-left:75%; } +.fl80 { margin-left:80%; } +.fl90 { margin-left:90%; } +.fl100 { margin-left:100%; } + +/* 1.3 Margin-right */ +.fr10 { margin-right:10%; } +.fr20 { margin-right:20%; } +.fr25 { margin-right:25%; } +.fr30 { margin-right:30%; } +.fr33 { margin-right:33.33%; } +.fr40 { margin-right:40%; } +.fr50 { margin-right:50%; } +.fr60 { margin-right:60%; } +.fr66 { margin-right:66.66%; } +.fr70 { margin-right:70%; } +.fr75 { margin-right:75%; } +.fr80 { margin-right:80%; } +.fr90 { margin-right:90%; } +.fr100 { margin-right:100%; } + +/* 2. HACKS */ +.f10,.f20,.f25,.f30,.f33,.f40,.f50,.f60,.f66,.f70,.f75,.f80,.f90,.f100 { + _display:inline; /* IE5-6 */ + *margin-left:-1px; /* IE5-7 */ +} + +/** +* web2py Nuovo Theme +* ------------------ +**/ + +/* Basics */ + +html, body { + font-size: 13px; + text-align: left; + color: #333; + padding: 0; + margin: 0; + background: #fff url(../images/header_shadow.png) repeat-x left 33px; +} + +a { + color: #e8953c; + text-decoration: none; +} + +label { + color: #777; + font-weight: bold; + font-size: 100%; +} + +img { + vertical-align: baseline; +} + +td, th { + border: none; +} + +.center { + text-align: center; +} + +.centerblock { + margin: 0 auto; +} + +.clear { + clear: both; +} + +.att { + color: #d22; +} + +/* General */ + +h3 { + padding-left: 18px; + background: url(../images/sidebar_bullet.gif) no-repeat; + color: #555; + font-weight: normal; + font-size: 130%; +} + +.formfield { + padding: 0.7em 0.5em; + -moz-border-radius: 0.3em; + border-radius: 0.3em; + margin: 1em 0; + border: 1px solid #ddd; +} + +.buttongroup { + padding: 0.7em 0.5em; +} + +.formfield { + background: #eee; +} + +.flash { + position: fixed; + top: 2em; + right: 2em; + background: #e8953c; + color: #fff; + border: 2px solid #fff; + -moz-border-radius: 0.7em; + border-radius: 0.7em; + padding: 0.5em 1em; +} + +.tooltip span { + background: #9fb364; + color: #eef1d9; + border: 1px solid #eef1d9; + font-style: italic; + width: 20%; + padding: 0.3em; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + font-size: 13px; + text-transform: none; +} + +.help { + width: 60%; + font-size: 1em; + padding: 0.3em; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + background: #eef1d9; + color: #9fb364; + font-style: italic; + text-transform: none; +} + +.help h3 { + color: #9fb364; + font-size: 1.2em; + background: transparent; + font-weight: bold; +} + +.icon img { + vertical-align: middle; + cursor: pointer; +} + +.form td { + padding: 0.2em 1em 0.2em 0; +} + +/* Buttons */ + +.controls a.button, +.controls a.button span { + background-image: url(../images/small_button.png); +} + +.controls a.button span { + padding-top: 2px; +} + +.controls a.button { + color: #333; +} + +.controls a.special, +.controls a.special span { + background-image: url(../images/small_special_button.png); +} + +.controls a.special { + color: #ddd; +} + +/* Header */ + +#header { + background: #292929 url(../images/header_bg.png) repeat-x; + height: 33px; + overflow: hidden; +} + +/* Home button */ + +#start { + position: absolute; + top: 2px; + left: 13px; + margin: 0; +} + +#start a.button { + display: block; +} + +#start a.button, +#start a.button span { + background-image: url(../images/start.png); +} + +#start a { + text-indent: -999px; + overflow: hidden; + padding: 0; + margin: 0; +} + +#start a.button span { + width: 112px; + height: 36px; + padding: 0; + margin: 0; +} + +/* Menu */ + +#menu { + float: right; + margin: 3px 13px 0 0; +} + +#menu li { + float: left; + list-style: none; + margin-right: 0.4em; +} + +#menu a.button, +#menu a.button span { + background-image: url(../images/menu.png); +} + +#menu a.button { + padding-right: 1em; +} + +#menu a.button span { + padding-left: 1em; +} + +#menu a { + color: #333; +} + +/* Main area */ + +#main { + padding: 2em 1em 5em; + position: relative; +} + +#main h2 { + margin-top: 0; + font-weight: normal; + text-transform: uppercase; + border-bottom: 1px dotted #aaa; + padding-left: 18px; + background: transparent url(../images/section_bullet.png) no-repeat left 3px; + color: #aaa; +} + +/* Applist */ + +.applist h3 { + color: #aaa; + font-weight: normal; +} + +.applist ul { + margin: 0; +} + +.applist li { + list-style: none; + padding: 0; +} + +h3.editableapp, +h3.currentapp { + padding: 5px 0 5px 54px; +} + +h3.editableapp { + background: #fff url(../images/folder.png) no-repeat; +} + +h3.currentapp { + background: #fff url(../images/folder_locked.png) no-repeat; +} + +.applist .controls { + margin-left: 1.5em; +} + + +/* Site sidebar */ + +.sidebar_inner { + margin: 0 1em 0; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + border: 1px solid #ddd; + min-width:400px; +} + +.sidebar h4 { + color: #888; +} + +.pwdchange { + padding: 1em; + float: right !important; +} + +.sidebar .box { + clear: right; + margin-top: 2em; + border-top: 1px solid #eee; + padding: 0 1em; +} + +.sidebar .box { + background: url(../images/sidebar_background.jpg) no-repeat; +} + +.sidebar .upgrade_version { + color: #71c837; +} + +/* Tweets */ + +#tweets ol { + margin: 1em 0; +} + +#tweets ol li { + background: #ebe8d0; + list-style: none; + -moz-border-radius: 0.5em; + border-radius: 0.5em; + padding: 0.5em; + margin: 1em 0; + border: 1px solid #aaa; +} + +#tweets .entry-date { + font-weight: bold; + display: block; +} + +/* Design/Plugin page */ + +.component { + cursor: pointer; +} + +.component_contents { + padding-left: 20px; +} + +.component_contents li { + list-style: none; +} + +.component_contents div.comptools { + margin-bottom: 1em; + padding-bottom: 0.5em; +} + +.component_contents div.formfield form { + margin-bottom: 0.2em; + margin-top: 0.2em; +} + +.file { + font-weight: bold; +} + +.folder { + display: block; + padding: 4px 0 4px 40px; + background: url(../images/folder_sm.png) no-repeat; + margin: 0.5em 0; +} + +.folder .file { + font-weight: bold; +} + +.sublist { + margin-left: 0; + border-left: 1px dotted #aaa; + padding-left: 0.5em; + margin-top: 0.5em; + margin-bottom: 0.0em; +} + +/* About */ + +.legalese { + background: #eee url(../images/embossed.png) repeat-y; + padding: 1em 1em 1em 2em; +} + +/* Wizard */ + +.step li { + list-style: none; + margin-left: 1em; + margin-top: 0.5em; +} + +.step #wizard_nav .box { + border-bottom: 1px dotted #aaa; + padding: 0.5em; +} + +.step #wizard_form { + padding: 0.5em 0 2em 2em; +} + +/* Editor */ + +.edit #body { + height: auto; + width: 100%; +} + +.edit .help li { + list-style: none; + padding: 0.5em 0; +} + +.edit .help tt { + font-weight: bold; + font-style: normal; + display: inline; /* Rest cSans base style */ + border: 1px solid #999; + background: #333; + color: #ddd; + padding: 0.3em; + -moz-border-radius: 0.3em; + border-radius: 0.3em; +} + +/* Ticket */ + +ul#snapshot > li { + list-style: none; +} + +.inspect td, +.versions td, +.inspect th, +.versions th { + padding: 0.3em; + // border: 1px solid #aaa; +} + +.inspect th, +.versions th { + background: #ddd; + color: #777; +} + +.ticket h3 { + margin-top: 0.5em; + background: url(../images/ticket_section.png) no-repeat; + padding: 30px; + text-transform: uppercase; +} + + +.ticket .inspect li { + list-style: none; +} + +#frames ul { + margin: 0; +} + +#frames li { + margin: 0.5em 0; + padding: 0; + list-style: none; +} + +/* Errors */ + +.errors table.sortable th { + background: url(../images/header_bg.png) repeat-x; + color: #eee; + // border-right: 1px solid #eee; + padding-top: 0.5em; +} + +.errors table.sortable td { + border-bottom: 1px dotted #ddd; + padding: 0.4em 0.2em; +} + +/* Tests */ + +.test h3.failed { + background-image: url(../images/red_bullet.gif); +} + +.test h3.nodoctests { + background-image: url(../images/dim_bullet.gif); +} + +/* Footer */ + +#footer { + padding: 1em 0 0; + color: #eee; + text-align: center; + background: #292929 url(../images/header_bg.png) repeat-x; + height: 45px; + overflow: hidden; + clear: both; +} + +/* Shell */ + +.shell #wrapper { + margin: 0 auto; +} + +.shell #output { + width: 75%; + height:30em; +} + +.shell #output, +.shell #output pre { + color: #e8953c; + background: white; + border: 1px solid #333; +} + +.shell .prompt, +.shell #output, +.shell pre, +.shell #caret { + font-family: monospace; +} + +.shell .prompt, +.shell #output, +.shell #caret { + font-size: 10pt; + padding: 6px; + padding-right: 0em; +} + +.shell #shellwrapper { + background: white; + border: 1px solid #333; + color: #e8953c; + width: 75%; + // padding: 6px; + // -moz-border-radius: 1em; + // border-radius: 1em; + margin: 1em 0 +} + +.shell #caret { + border: 0; + float: left; +} + +.shell .prompt { + color: #e8953c; + width: 85%; + height: 4em; + border: 0; +} + +.shell .prompt, .shell #output { + overflow: auto; +} + +.shell table, tr, td { + text-align: left; + vertical-align: top; +} + +.shell pre { + border: 0; + padding: 0; + margin: 0; + color: #333333; +} + +.shell .message { + width: 100%; + color: #8AD; + font-weight: bold; + font-style: italic; +} + +.shell .error { + color: #F44; +} + +.shell .username { + font-weight: bold; +} + +.shell dd{ + color: #000033; +} + +.shell dt{ + color: #333333; +} + +.shell #ajax-status { + font-weight: bold; +} + +.shell .processing { + background-image: url('../images/spinner.gif'); +} + +.shell #caret { + width: 2.5em; + margin-right: 0px; + padding-right: 0px; + border-right: 0px; +} + + +/* ie7 hacks */ + +.sublist { + zoom: 1; +} + +.untranslated { background-color: #FFCC00; } +.translated { background-color: white; } +.ui-multiselect { border: 1px solid #ccc; width:400px;} +#editor_area textarea { height: 400px; width: 100% } ADDED applications/admin/static/eamy/bundle_markup.js Index: applications/admin/static/eamy/bundle_markup.js ================================================================== --- applications/admin/static/eamy/bundle_markup.js +++ applications/admin/static/eamy/bundle_markup.js @@ -0,0 +1,389 @@ +/* + * eAmy.Offline - Amy Editor embedded for offline use. + * http://www.april-child.com/amy + * + * Published under MIT License. + * Copyright (c) 2007-2008 Petr Krontorád, April-Child.com + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + * + * + * This file is auto-generated from original Fry Framework and Amy Editor sources.. + */ + + +// Generated from theme definition file. +$class('ac.chap.theme.EAmy < ac.chap.Theme'); + + ac.chap.theme.EAmy.prototype.initDefinition = function() + { +// $call(this, 'ac.chap.Theme.initDefinition');this.cssId = 'black'; +//this.background = '#072240'; +//this.textColor = '#DFEFFF'; +//this.caretColor = 'lime'; +//this.caretRowStyleActive = '#041629'; +//this.selectionStyle = '#86553b'; +//this.colorScheme[ac.chap.TOKEN_MULTIROW_COMMENT] = 'color:#0084FF;font-style:italic'; +//this.colorScheme[ac.chap.TOKEN_SINGLEROW_COMMENT] = 'color:#0084FF;font-style:italic'; +//this.colorScheme[ac.chap.TOKEN_SINGLE_QUOTED] = 'color:#00DF00'; +//this.colorScheme[ac.chap.TOKEN_DOUBLE_QUOTED] = 'color:#00DF00'; +//this.colorScheme[ac.chap.CHUNK_KEYWORD] = 'color:#FF9D00'; +//this.colorScheme[ac.chap.CHUNK_NUMBER] = 'color:#FF5B8C'; +//this.colorScheme[ac.chap.CHUNK_OPERATOR] = 'color:#FF9D00;'; +//this.colorScheme[ac.chap.CHUNK_PARENTHESIS] = 'color:#FFF177'; +//this.colorScheme[ac.chap.CHUNK_KEYWORD_CUSTOM] = 'color:#54FFB8'; +//this.colorScheme[ac.chap.CHUNK_FUNCTION_NAME] = 'color:#FFE000'; +//this.colorScheme[ac.chap.CHUNK_LIBRARY] = 'color:#71E5B6'; +//this.colorScheme[ac.chap.CHUNK_LIBRARY_CUSTOM] = 'color:#FF78E5'; + + $call(this, 'ac.chap.Theme.initDefinition');this.cssId = 'twilight'; +this.background = '#141414'; +this.textColor = '#F8F8F8'; +this.caretColor = '#A7A7A7'; +this.caretRowStyleActive = '#1B1B1B'; +this.selectionStyle = '#3C4043'; +this.colorScheme[ac.chap.TOKEN_MULTIROW_COMMENT] = 'color:#605A60;font-style:italic'; +this.colorScheme[ac.chap.TOKEN_SINGLEROW_COMMENT] = 'color:#605A60;font-style:italic'; +this.colorScheme[ac.chap.TOKEN_SINGLE_QUOTED] = 'color:#8B9F67'; +this.colorScheme[ac.chap.TOKEN_DOUBLE_QUOTED] = 'color:#D4F29E'; +this.colorScheme[ac.chap.CHUNK_KEYWORD] = 'color:#D2A964'; +this.colorScheme[ac.chap.CHUNK_NUMBER] = 'color:#DE6848'; +this.colorScheme[ac.chap.CHUNK_OPERATOR] = 'color:#EFC25A;'; +this.colorScheme[ac.chap.CHUNK_PARENTHESIS] = 'color:#ABC4DC'; +this.colorScheme[ac.chap.CHUNK_KEYWORD_CUSTOM] = 'color:#A0849E'; +this.colorScheme[ac.chap.CHUNK_FUNCTION_NAME] = 'color:#DAD280'; +this.colorScheme[ac.chap.CHUNK_LIBRARY] = 'color:#7286A8'; +this.colorScheme[ac.chap.CHUNK_LIBRARY_CUSTOM] = 'color:#A55C29'; +} + + +// Generated from bundle keymap definition file. +ac.chap.KeyMap.prototype.initDefinition = function() + { + var _ = '\n'; + this.compile + (""+_+ "KEY: 0" ++_+ " insert(character:true)" ++_+ "KEY: -37" ++_+ " caret(move:'left')" ++_+ "KEY: -37+shift" ++_+ " caret(move:'left')" ++_+ " selection(add:true)" ++_+ "KEY: -37+ctrl" ++_+ " caret(move:'prev_regexp', re:'[^|._A-Z ,|(|);]*$')" ++_+ "KEY: -37+alt" ++_+ " caret(move:'prev_word')" ++_+ "KEY: -37+ctrl+shift" ++_+ " caret(move:'prev_regexp', re:'[^|._A-Z ,|(|);]*$')" ++_+ " selection(add:true)" ++_+ "KEY: -37+alt+shift" ++_+ " caret(move:'prev_word')" ++_+ " selection(add:true)" ++_+ "KEY: -37+meta" ++_+ " caret(move:'row_start')" ++_+ "KEY: -37+meta+shift" ++_+ " caret(move:'row_start')" ++_+ " selection(add:true)" ++_+ "KEY: -39" ++_+ " caret(move:'right')" ++_+ "KEY: -39+shift" ++_+ " caret(move:'right')" ++_+ " selection(add:true)" ++_+ "KEY: -39+ctrl" ++_+ " caret(move:'next_regexp', re:'^[^|._A-Z ,|(|);]*')" ++_+ "KEY: -39+alt" ++_+ " caret(move:'next_word')" ++_+ "KEY: -39+ctrl+shift" ++_+ " caret(move:'next_regexp', re:'^[^|._A-Z ,|(|);]*')" ++_+ " selection(add:true)" ++_+ "KEY: -39+alt+shift" ++_+ " caret(move:'next_word')" ++_+ " selection(add:true)" ++_+ "KEY: -39+meta" ++_+ " caret(move:'row_end')" ++_+ "KEY: -39+meta+shift" ++_+ " caret(move:'row_end')" ++_+ " selection(add:true)" ++_+ "KEY: -38" ++_+ " caret(move:'up')" ++_+ "KEY: -38+shift" ++_+ " caret(move:'up')" ++_+ " selection(add:true)" ++_+ "KEY: -40" ++_+ " caret(move:'down')" ++_+ "KEY: -40+shift" ++_+ " caret(move:'down')" ++_+ " selection(add:true)" ++_+ "KEY: -13" ++_+ " insert(row:true)" ++_+ "KEY: -8" ++_+ " delete(character:true)" ++_+ "KEY: -46" ++_+ " delete(character:false)" ++_+ "KEY: 75+ctrl+shift" ++_+ " delete(row:true)" ++_+ "KEY: -27" ++_+ " custom(action:'WordComplete', direction:true)" ++_+ "KEY: -27+shift" ++_+ " custom(action:'WordComplete', direction:false)" ++_+ "KEY: -9" ++_+ " custom(action:'SnippetComplete')" ++_+ "KEY: 123" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'}')" ++_+ "KEY: 34" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'\"')" ++_+ "KEY: 91" ++_+ " custom(action:'AutoComplete', use_selection:true, text:']')" ++_+ "KEY: 40" ++_+ " custom(action:'AutoComplete', use_selection:true, text:')')" ++_+ "KEY: -36" ++_+ " caret(move:'doc_start')" ++_+ "KEY: -36+shift" ++_+ " caret(move:'doc_start')" ++_+ " selection(add:true)" ++_+ "KEY: -35" ++_+ " caret(move:'doc_end')" ++_+ "KEY: -35+shift" ++_+ " caret(move:'doc_end')" ++_+ " selection(add:true)" ++_+ "KEY: -34+meta" ++_+ " caret(move:'page_down')" ++_+ "KEY: -34+meta+shift" ++_+ " caret(move:'page_down')" ++_+ " selection(add:true)" ++_+ "KEY: -33+meta" ++_+ " caret(move:'page_up')" ++_+ "KEY: -33+meta+shift" ++_+ " caret(move:'page_down')" ++_+ " selection(add:true)" ++_+ "KEY: 99+meta" ++_+ " clipboard(copy:true)" ++_+ "KEY: 120+meta" ++_+ " clipboard(cut:true)" ++_+ "KEY: 122+meta" ++_+ " undo()" ++_+ "KEY: 90+meta+shift" ++_+ " redo()" ++_+ "KEY: 97+meta" ++_+ " selection(all:true)" ++_+ "KEY: 97+ctrl" ++_+ " selection(all:true)" ++_+ "KEY: -113" ++_+ " custom(action:'GoToBookmark', direction:1)" ++_+ "KEY: -113+shift" ++_+ " custom(action:'GoToBookmark', direction:-1)" ++_+ "KEY: -113+meta" ++_+ " custom(action:'ToggleBookmark')" ++_+ "KEY: 91+meta" ++_+ " custom(action:'Indent', direction:'left')" ++_+ "KEY: 93+meta" ++_+ " custom(action:'Indent', direction:'right')" ++_+ "KEY: 47+meta" ++_+ " custom(action:'Comment')" ++_+ "KEY: 43+meta" ++_+ " custom(action:'RuntimeOption', key:'font.size', value:'bigger')" ++_+ "KEY: 45+meta" ++_+ " custom(action:'RuntimeOption', key:'font.size', value:'smaller')" ++_+ "KEY: 101+meta" ++_+ " custom(action:'SetSearchKeyword')" ++_+ "KEY: 103+meta" ++_+ " custom(action:'SearchKeyword', direction:'down')" ++_+ "KEY: 71+shift+meta" ++_+ " custom(action:'SearchKeyword', direction:'up')" ++_+ "KEY: 102+ctrl" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: 83+ctrl+shift" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: 102+meta" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: -13" ++_+ " custom(action:'SmartIndent', split_line:true, indent_tab_when_starts:'class module def if else unless rescue ensure while do __class__')" ++_+ "KEY: -13+meta" ++_+ " custom(action:'SmartIndent', split_line:false, indent_tab_when_starts:'class module def if else unless rescue ensure while do __class__')" ++_+ "KEY: 39" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'\\'')" +)}; + +$class('ac.chap.lang.EAmy < ac.chap.Language'); + + ac.chap.lang.EAmy.prototype.initDefinition = function() + { + $call(this, 'ac.chap.Language.initDefinition'); +this.singleQuoteStringMarker = "'"; +this.singleQuoteStringMarkerException = "\\"; +this.doubleQuoteStringMarker = "\""; +this.doubleQuoteStringMarkerException = "\\" +this.wordDelimiter = /[\w\.\d]/; +this.indentIgnoreMarker = /[\.]/; +this.foldingStartMarkers = [/^\s*<(div)\b.*>/i, /^\s*<(ul)\b.*>/i]; +this.foldingParityMarkers = [/^\s*<(div)\b.*>/i, /^\s*<(ul)\b.*>/i]; +this.foldingStopMarkers = [/^\s*<\/(div)>/i, /^\s*<\/(ul)>/i]; +this.singleRowCommentStartMarkers = []; +this.multiRowCommentStartMarker = ""; +this.chunkRules.push([/(([^\w]|^)(\d{1,}[\d\.Ee]*)([^w]|$))/i, 3, ac.chap.CHUNK_NUMBER]) +this.chunkRules.push([/(\+|\-|\*|\/|\=|\!|\^|\%|\||\&|\<|\>)/i, 0, ac.chap.CHUNK_OPERATOR]) +this.chunkRules.push([/(\(|\)|\[|\]|\{|\})/i, 0, ac.chap.CHUNK_PARENTHESIS]) +this.chunkRules.push([/((<|<\/)([\w-_\:]*)([ >]))/i, 3, ac.chap.CHUNK_KEYWORD]) +this.chunkRules.push([/(([ \t])([\w-_\:]*)(=$))/i, 3, ac.chap.CHUNK_KEYWORD_CUSTOM]) +this.chunkRules.push([/(([^\w]|^)(!DOCTYPE)([^\w]|$))/i, 3, ac.chap.CHUNK_LIBRARY]) +this.chunkRules.push([/(([^\w]|^)(\d{1,}[\d\.Ee]*)([^w]|$))/i, 3, ac.chap.CHUNK_NUMBER]) +this.chunkRules.push([/(\+|\-|\*|\/|\=|\!|\^|\%|\||\&|\<|\>)/i, 0, ac.chap.CHUNK_OPERATOR]) +this.chunkRules.push([/(\(|\)|\[|\]|\{|\})/i, 0, ac.chap.CHUNK_PARENTHESIS]) +this.chunkRules.push([/((<|<\/)([\w-_\:]*)([ >]))/i, 3, ac.chap.CHUNK_KEYWORD]) +this.chunkRules.push([/(([ \t])([\w-_\:]*)(=$))/i, 3, ac.chap.CHUNK_KEYWORD_CUSTOM]) +this.chunkRules.push([/(([^\w]|^)(!DOCTYPE)([^\w]|$))/i, 3, ac.chap.CHUNK_LIBRARY]) +} +var snippet = {}; +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ie6', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'iegte7', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ie5', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ie', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ienot', code: '${1:${AMY_SELECTED_TEXT: IE Conditional Comment: NOT Internet Explorer }}$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ielte6', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ielt6', code: '$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: '${0:${AMY_SELECTED_TEXT/\A(.*)<\/em>\z|.*/(?1:$1:$0<\/em>)/m}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: '${0:${AMY_SELECTED_TEXT/\A(.*)<\/strong>\z|.*/(?1:$1:$0<\/strong>)/m}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'left', code: '←'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'backtab', code: '⇤'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'enter', code: '⌅'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'arrow', code: '→'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'option', code: '⌥'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'shift', code: '⇧'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ' '}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'delete', code: '⌦'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'backspace', code: '⌫'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'escape', code: '⎋'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'tab', code: '⇥'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'up', code: '↑'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'control', code: '⌃'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'return', code: '↩'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'down', code: '↓'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'command', code: '⌘'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctype', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctype', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctypexf', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctypext', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctypex', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'doctypexs', code: '\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 't', code: '<{${1:tag_name}}>$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'body', code: '\n $0\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'textarea', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'div', code: '\n ${0:$AMY_SELECTED_TEXT}\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: '
'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'title', code: '${1:${AMY_FILENAME/((.+)\..*)?/(?2:$2:Page Title)/}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'movie', code: '\n \n \n \n \n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'input', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'head', code: '\n \n ${1:${AMY_FILENAME/((.+)\..*)?/(?2:$2:Page Title)/}}\n $0\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'meta', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'h1', code: '

${1:$AMY_SELECTED_TEXT}

'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'form', code: '\n $0\n\n

\n'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'link', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'style', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'table', code: '
\n \n \n
${5:Header}
${0:Data}
'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'base', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'scriptsrc', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'mailto', code: '
${3:email me}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'script', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'c', code: 'class="$1"'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'i', code: 'id="$1"'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'p', code: '{{pass}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ex', code: '{{extend \'${1:layout.html}\'}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'for', code: '{{for ${1:bar} in ${2:foo}:}}\n $0\n{{pass}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'if', code: '{{if ${1:foo} ${2:==/!=/=>/=/<} ${3:bar}:}}\n $0\n{{pass}}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '=', code: '{{=$0}}'}; +eamy.snippets.push(snippet); + + ADDED applications/admin/static/eamy/bundle_python.js Index: applications/admin/static/eamy/bundle_python.js ================================================================== --- applications/admin/static/eamy/bundle_python.js +++ applications/admin/static/eamy/bundle_python.js @@ -0,0 +1,296 @@ +/* + * eAmy.Offline - Amy Editor embedded for offline use. + * http://www.april-child.com/amy + * + * Published under MIT License. + * Copyright (c) 2007-2008 Petr Krontorád, April-Child.com + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + * + * + * This file is auto-generated from original Fry Framework and Amy Editor sources.. + */ + + +// Generated from theme definition file. +$class('ac.chap.theme.EAmy < ac.chap.Theme'); + + ac.chap.theme.EAmy.prototype.initDefinition = function() + { +// $call(this, 'ac.chap.Theme.initDefinition');this.cssId = 'black'; +//this.background = '#072240'; +//this.textColor = '#DFEFFF'; +//this.caretColor = 'lime'; +//this.caretRowStyleActive = '#041629'; +//this.selectionStyle = '#86553b'; +//this.colorScheme[ac.chap.TOKEN_MULTIROW_COMMENT] = 'color:#0084FF;font-style:italic'; +//this.colorScheme[ac.chap.TOKEN_SINGLEROW_COMMENT] = 'color:#0084FF;font-style:italic'; +//this.colorScheme[ac.chap.TOKEN_SINGLE_QUOTED] = 'color:#00DF00'; +//this.colorScheme[ac.chap.TOKEN_DOUBLE_QUOTED] = 'color:#00DF00'; +//this.colorScheme[ac.chap.CHUNK_KEYWORD] = 'color:#FF9D00'; +//this.colorScheme[ac.chap.CHUNK_NUMBER] = 'color:#FF5B8C'; +//this.colorScheme[ac.chap.CHUNK_OPERATOR] = 'color:#FF9D00;'; +//this.colorScheme[ac.chap.CHUNK_PARENTHESIS] = 'color:#FFF177'; +//this.colorScheme[ac.chap.CHUNK_KEYWORD_CUSTOM] = 'color:#54FFB8'; +//this.colorScheme[ac.chap.CHUNK_FUNCTION_NAME] = 'color:#FFE000'; +//this.colorScheme[ac.chap.CHUNK_LIBRARY] = 'color:#71E5B6'; +//this.colorScheme[ac.chap.CHUNK_LIBRARY_CUSTOM] = 'color:#FF78E5'; +// + $call(this, 'ac.chap.Theme.initDefinition'); +this.cssId = 'twilight'; +this.background = '#141414'; +this.textColor = '#F8F8F8'; +this.caretColor = '#A7A7A7'; +this.caretRowStyleActive = '#1B1B1B'; +this.selectionStyle = '#3C4043'; +this.colorScheme[ac.chap.TOKEN_MULTIROW_COMMENT] = 'color:#605A60;font-style:italic'; +this.colorScheme[ac.chap.TOKEN_SINGLEROW_COMMENT] = 'color:#605A60;font-style:italic'; +this.colorScheme[ac.chap.TOKEN_SINGLE_QUOTED] = 'color:#8B9F67'; +this.colorScheme[ac.chap.TOKEN_DOUBLE_QUOTED] = 'color:#D4F29E'; +this.colorScheme[ac.chap.CHUNK_KEYWORD] = 'color:#D2A964'; +this.colorScheme[ac.chap.CHUNK_NUMBER] = 'color:#DE6848'; +this.colorScheme[ac.chap.CHUNK_OPERATOR] = 'color:#EFC25A;'; +this.colorScheme[ac.chap.CHUNK_PARENTHESIS] = 'color:#ABC4DC'; +this.colorScheme[ac.chap.CHUNK_KEYWORD_CUSTOM] = 'color:#A0849E'; +this.colorScheme[ac.chap.CHUNK_FUNCTION_NAME] = 'color:#DAD280'; +this.colorScheme[ac.chap.CHUNK_LIBRARY] = 'color:#7286A8'; +this.colorScheme[ac.chap.CHUNK_LIBRARY_CUSTOM] = 'color:#A55C29'; +} + + + +// Generated from bundle keymap definition file. +ac.chap.KeyMap.prototype.initDefinition = function() + { + var _ = '\n'; + this.compile + (""+_+ "KEY: 0" ++_+ " insert(character:true)" ++_+ "KEY: -37" ++_+ " caret(move:'left')" ++_+ "KEY: -37+shift" ++_+ " caret(move:'left')" ++_+ " selection(add:true)" ++_+ "KEY: -37+ctrl" ++_+ " caret(move:'prev_regexp', re:'[^|._A-Z ,|(|);]*$')" ++_+ "KEY: -37+alt" ++_+ " caret(move:'prev_word')" ++_+ "KEY: -37+ctrl+shift" ++_+ " caret(move:'prev_regexp', re:'[^|._A-Z ,|(|);]*$')" ++_+ " selection(add:true)" ++_+ "KEY: -37+alt+shift" ++_+ " caret(move:'prev_word')" ++_+ " selection(add:true)" ++_+ "KEY: -37+meta" ++_+ " caret(move:'row_start')" ++_+ "KEY: -37+meta+shift" ++_+ " caret(move:'row_start')" ++_+ " selection(add:true)" ++_+ "KEY: -39" ++_+ " caret(move:'right')" ++_+ "KEY: -39+shift" ++_+ " caret(move:'right')" ++_+ " selection(add:true)" ++_+ "KEY: -39+ctrl" ++_+ " caret(move:'next_regexp', re:'^[^|._A-Z ,|(|);]*')" ++_+ "KEY: -39+alt" ++_+ " caret(move:'next_word')" ++_+ "KEY: -39+ctrl+shift" ++_+ " caret(move:'next_regexp', re:'^[^|._A-Z ,|(|);]*')" ++_+ " selection(add:true)" ++_+ "KEY: -39+alt+shift" ++_+ " caret(move:'next_word')" ++_+ " selection(add:true)" ++_+ "KEY: -39+meta" ++_+ " caret(move:'row_end')" ++_+ "KEY: -39+meta+shift" ++_+ " caret(move:'row_end')" ++_+ " selection(add:true)" ++_+ "KEY: -38" ++_+ " caret(move:'up')" ++_+ "KEY: -38+shift" ++_+ " caret(move:'up')" ++_+ " selection(add:true)" ++_+ "KEY: -40" ++_+ " caret(move:'down')" ++_+ "KEY: -40+shift" ++_+ " caret(move:'down')" ++_+ " selection(add:true)" ++_+ "KEY: -13" ++_+ " insert(row:true)" ++_+ "KEY: -8" ++_+ " delete(character:true)" ++_+ "KEY: -46" ++_+ " delete(character:false)" ++_+ "KEY: 75+ctrl+shift" ++_+ " delete(row:true)" ++_+ "KEY: -27" ++_+ " custom(action:'WordComplete', direction:true)" ++_+ "KEY: -27+shift" ++_+ " custom(action:'WordComplete', direction:false)" ++_+ "KEY: -9" ++_+ " custom(action:'SnippetComplete')" ++_+ "KEY: 123" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'}')" ++_+ "KEY: 34" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'\"')" ++_+ "KEY: 91" ++_+ " custom(action:'AutoComplete', use_selection:true, text:']')" ++_+ "KEY: 40" ++_+ " custom(action:'AutoComplete', use_selection:true, text:')')" ++_+ "KEY: -36" ++_+ " caret(move:'doc_start')" ++_+ "KEY: -36+shift" ++_+ " caret(move:'doc_start')" ++_+ " selection(add:true)" ++_+ "KEY: -35" ++_+ " caret(move:'doc_end')" ++_+ "KEY: -35+shift" ++_+ " caret(move:'doc_end')" ++_+ " selection(add:true)" ++_+ "KEY: -34+meta" ++_+ " caret(move:'page_down')" ++_+ "KEY: -34+meta+shift" ++_+ " caret(move:'page_down')" ++_+ " selection(add:true)" ++_+ "KEY: -33+meta" ++_+ " caret(move:'page_up')" ++_+ "KEY: -33+meta+shift" ++_+ " caret(move:'page_down')" ++_+ " selection(add:true)" ++_+ "KEY: 99+meta" ++_+ " clipboard(copy:true)" ++_+ "KEY: 120+meta" ++_+ " clipboard(cut:true)" ++_+ "KEY: 122+meta" ++_+ " undo()" ++_+ "KEY: 90+meta+shift" ++_+ " redo()" ++_+ "KEY: 97+meta" ++_+ " selection(all:true)" ++_+ "KEY: 97+ctrl" ++_+ " selection(all:true)" ++_+ "KEY: -113" ++_+ " custom(action:'GoToBookmark', direction:1)" ++_+ "KEY: -113+shift" ++_+ " custom(action:'GoToBookmark', direction:-1)" ++_+ "KEY: -113+meta" ++_+ " custom(action:'ToggleBookmark')" ++_+ "KEY: 91+meta" ++_+ " custom(action:'Indent', direction:'left')" ++_+ "KEY: 93+meta" ++_+ " custom(action:'Indent', direction:'right')" ++_+ "KEY: 47+meta" ++_+ " custom(action:'Comment')" ++_+ "KEY: 43+meta" ++_+ " custom(action:'RuntimeOption', key:'font.size', value:'bigger')" ++_+ "KEY: 45+meta" ++_+ " custom(action:'RuntimeOption', key:'font.size', value:'smaller')" ++_+ "KEY: 101+meta" ++_+ " custom(action:'SetSearchKeyword')" ++_+ "KEY: 103+meta" ++_+ " custom(action:'SearchKeyword', direction:'down')" ++_+ "KEY: 71+shift+meta" ++_+ " custom(action:'SearchKeyword', direction:'up')" ++_+ "KEY: 102+ctrl" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: 83+ctrl+shift" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: 102+meta" ++_+ " custom(action:'SearchInteractive')" ++_+ "KEY: -13" ++_+ " custom(action:'SmartIndent', split_line:true, indent_tab_when_starts:'class module def if else unless rescue ensure while do __class__')" ++_+ "KEY: -13+meta" ++_+ " custom(action:'SmartIndent', split_line:false, indent_tab_when_starts:'class module def if else unless rescue ensure while do __class__')" ++_+ "KEY: 39" ++_+ " custom(action:'AutoComplete', use_selection:true, text:'\\'')" +)}; + +$class('ac.chap.lang.EAmy < ac.chap.Language'); + + ac.chap.lang.EAmy.prototype.initDefinition = function() + { + $call(this, 'ac.chap.Language.initDefinition'); +this.singleQuoteStringMarker = "'"; +this.singleQuoteStringMarkerException = "\\"; +this.doubleQuoteStringMarker = "\""; +this.doubleQuoteStringMarkerException = "\\"; +this.wordDelimiter = /[\w\d]/; +this.indentIgnoreMarker = /[\t \s]/; +this.foldingStartMarkers = [/^\s*def|class/i]; +this.foldingParityMarkers = [/do|(^\s*if)|(^\s*def)|(^\s*class)/i]; +this.foldingStopMarkers = [/^\s{0,1}$/i]; +this.singleRowCommentStartMarkers = ['#']; +this.multiRowCommentStartMarker = "\"\"\""; +this.multiRowCommentEndMarker = "\"\"\""; +this.stringInterpolation = ['(#\{[^\}]*\})', 1]; +this.chunkRules.push([/(([^\w]|^)(\d{1,}[\d\.Ee]*)([^w]|$))/i, 3, ac.chap.CHUNK_NUMBER]) +this.chunkRules.push([/(\+|\-|\*|\/|\=|\!|\^|\%|\||\&|\<|\>)/i, 0, ac.chap.CHUNK_OPERATOR]) +this.chunkRules.push([/(\(|\)|\[|\]|\{|\})/i, 0, ac.chap.CHUNK_PARENTHESIS]) +this.chunkRules.push([/(([^\w]|^)(elif|else|except|finally|for|if|try|while|with)([^\w]|$))/i, 3, ac.chap.CHUNK_KEYWORD]) +this.chunkRules.push([/(([^\w]|^)(@[\w]*|break|continue|pass|raise|return|yield|and|in|is|not|or|as|assert|del|exec|print)([^\w]|$))/i, 3, ac.chap.CHUNK_KEYWORD_CUSTOM]) +this.chunkRules.push([/((def[ ]{1,})([\w]{1,}))/i, 3, ac.chap.CHUNK_FUNCTION_NAME]) +this.chunkRules.push([/(([^\w]|^)(__import__|all|abs|any|apply|callable|chr|cmp|coerce|compile|delattr|dir|divmod|eval|execfile|filter|getattr|globals|hasattr|hash|hex|id|input|intern|isinstance|issubclass|iter|len|locals|map|max|min|oct|ord|pow|range|raw_input|reduce|reload|repr|round|setattr|sorted|sum|unichr|vars|zip|basestring|bool|buffer|classmethod|complex|dict|enumerate|file|float|frozenset|int|list|long|object|open|property|reversed|set|slice|staticmethod|str|super|tuple|type|unicode|xrange)([^\w]|$))/i, 3, ac.chap.CHUNK_LIBRARY]) +this.chunkRules.push([/(([^\w]|^)((__(all|bases|class|debug|dict|doc|file|members|metaclass|methods|name|slots|weakref)__)|(import|from| abs|add|and|call|cmp|coerce|complex|contains|del|delattr|delete|delitem|delslice|div|divmod|enter|eq|exit|float|floordiv|ge|get|getattr|getattribute|getitem|getslice|gt|hash|hex|iadd|iand|idiv|ifloordiv|ilshift|imod|imul|init|int|invert|ior|ipow|irshift|isub|iter|itruediv|ixor|le|len|long|lshift|lt|mod|mul|ne|neg|new|nonzero|oct|or|pos|pow|radd|rand|rdiv|rdivmod|repr|rfloordiv|rlshift|rmod|rmul|ror|rpow|rrshift|rshift|rsub|rtruediv|rxor|set|setattr|setitem|setslice|str|sub|truediv|unicode|xor))([^\w]|$))/i, 3, ac.chap.CHUNK_LIBRARY_CUSTOM]) +} +var snippet = {}; +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ifmain', code: 'if __name__ == '+"'"+'__main__'+"'"+':\n ${1:main()}$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'try', code: 'try:\n ${1:pass}\nexcept ${2:Exception}, ${3:e}:\n ${4:raise e}\nelse:\n ${5:pass}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'property', code: 'def ${1:foo}():\n doc = "${2:The $1 property.}"\n def fget(self):\n ${3:return self._$1}\n def fset(self, value):\n ${4:self._$1 = value}\n def fdel(self):\n ${5:del self._$1}\n return locals()\n$1 = property(**$1())$0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '__', code: '__${1:init}__'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '.', code: 'self.'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: '', code: ''}; +eamy.snippets.push(snippet); +//snippet = {tab_activation: 'def', code: 'def ${1:fname}(${2:`if [ "$TM_CURRENT_LINE" != "" ]\n # poor man'+"'"+'s way ... check if there is an indent or not\n # (cuz we would have lost the class scope by this point)\n then\n echo "self"\n fi`}):\n ${3/.+/"""/}${3:docstring for $1}${3/.+/"""\n/}${3/.+/\t/}${0:pass}'}; +//eamy.snippets.push(snippet); +snippet = {tab_activation: 'def', code: 'def ${1:fname}(${2:`if [ "$TM_CURRENT_LINE" != "" ]\n # poor man'+"'"+'s way ... check if there is an indent or not\n # (cuz we would have lost the class scope by this point)\n then\n echo "self"\n fi`}):\n ${3:}\n ${0:return dict()}'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'class', code: 'class ${1:ClassName}(${2:object}):\n ${3/.+/"""/}${3:docstring for $1}${3/.+/"""\n/}${3/.+/\t/}def __init__(self${4/([^,])?(.*)/(?1:, )/}${4:arg}):\n ${5:super($1, self).__init__()}\n${4/(\A\s*,\s*\Z)|,?\s*([A-Za-z_][a-zA-Z0-9_]*)\s*(=[^,]*)?(,\s*|$)/(?2:\t\tself.$2 = $2\n)/g} $0'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'aurm', code: '@auth.requires_membership(\'$0\'):'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'dbt', code: '${1:db_name}.define_table("${2:table_name}",\n SQLField("${3:field_name}", "${4:string/text/password/blob/upload/boolean/integer/double/time/date/datetime/db.reference_table}", ${5:length=$6}, ${7:default="$8"}),$9\n)'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'dbf', code: 'SQLField("${1:field_name}", "${2:string/text/password/blob/upload/boolean/integer/double/time/date/datetime/db.reference_table}", ${3:length=$4}, ${5:default="$6"}),$7'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'dbi', code: '${1:db_name}.${2:table_name}.insert(\n ${3:field_name}="$4" $5\n)'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 't', code: 'T("$0")'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'rev', code: 'response.view=\'$0\''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'ref', code: 'response.flash=\'$0\''}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 're', code: 'redirect(\'$0\')'}; +eamy.snippets.push(snippet); +snippet = {tab_activation: 'rej', code: 'response.json=\'$0\''}; +eamy.snippets.push(snippet); ADDED applications/admin/static/eamy/chap-bg-sidebar.gif Index: applications/admin/static/eamy/chap-bg-sidebar.gif ================================================================== --- applications/admin/static/eamy/chap-bg-sidebar.gif +++ applications/admin/static/eamy/chap-bg-sidebar.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-bookmark-default.gif Index: applications/admin/static/eamy/chap-bookmark-default.gif ================================================================== --- applications/admin/static/eamy/chap-bookmark-default.gif +++ applications/admin/static/eamy/chap-bookmark-default.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-folding-expand-inner.gif Index: applications/admin/static/eamy/chap-folding-expand-inner.gif ================================================================== --- applications/admin/static/eamy/chap-folding-expand-inner.gif +++ applications/admin/static/eamy/chap-folding-expand-inner.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-folding-expand.gif Index: applications/admin/static/eamy/chap-folding-expand.gif ================================================================== --- applications/admin/static/eamy/chap-folding-expand.gif +++ applications/admin/static/eamy/chap-folding-expand.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-folding-start.gif Index: applications/admin/static/eamy/chap-folding-start.gif ================================================================== --- applications/admin/static/eamy/chap-folding-start.gif +++ applications/admin/static/eamy/chap-folding-start.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-folding-stop.gif Index: applications/admin/static/eamy/chap-folding-stop.gif ================================================================== --- applications/admin/static/eamy/chap-folding-stop.gif +++ applications/admin/static/eamy/chap-folding-stop.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/chap-wrapped-row.gif Index: applications/admin/static/eamy/chap-wrapped-row.gif ================================================================== --- applications/admin/static/eamy/chap-wrapped-row.gif +++ applications/admin/static/eamy/chap-wrapped-row.gif cannot compute difference between binary files ADDED applications/admin/static/eamy/eamy.js Index: applications/admin/static/eamy/eamy.js ================================================================== --- applications/admin/static/eamy/eamy.js +++ applications/admin/static/eamy/eamy.js @@ -0,0 +1,8143 @@ +/* + * eAmy.Offline - Amy Editor embedded for offline use. + * http://www.april-child.com/amy + * + * Published under MIT License. + * Copyright (c) 2007-2008 Petr Krontorád, April-Child.com + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + * + * + * This file is auto-generated from original Fry Framework and Amy Editor sources.. + */ + + + + + +/* + * AC Fry - JavaScript Framework v1.0 + * (c)2006 Petr Krontorad, April-Child.com + * Portions of code based on WHOA Bender Framework, (c)2002-2005 Petr Krontorad, WHOA Group. + * http://www.april-child.com. All rights reserved. + * See the license/license.txt for additional details regarding the license. + * Special thanks to Matt Groening and David X. Cohen for all the robots. + */ + +/* Reserving global `fry` object */ +var fry = +{ + version:1.0, + __production_mode:false +}; + +// String prototype enhancements +String.prototype.camelize = function() +{ + return this.replace( /([-_].)/g, function(){ return arguments[0].substr(1).toUpperCase();} ); +} + +String.prototype.decamelize = function() +{ + return this.replace( /([A-Z])/g, function(){ return '-'+arguments[0].toLowerCase();} ); +} + +String.prototype.trim = function() +{ + return this.replace(/(^\s*)|(\s*$)/g, '' ); +} + +String.prototype.stripLines = function() +{ + return this.replace( /\n/g, '' ); +} + +String.prototype.stripMarkup = function() +{ + return this.replace( /<(.|\n)+?>/g, '' ); +} + +String.prototype.replaceMarkup = function( charRep ) +{ + return this.replace( /(<(.|\n)+?>)/g, function() + { + var t = ''; + for ( var i=0; i/g, '>' ).replace( /' ).replace( /&/g, '&' ); +} + +String.prototype.surround = function(t, side) +{ + side = side || 3; + return (1==1&side?t:'')+this+(2==2&side?t:''); +} + +String.prototype.surroundTag = function(t) +{ + return '<'+t+'>'+this+''; +} + +// example of use: var pattern = '? is ?; alert(pattern.embed('Decin', 'sunny')); pattern = '@city is @weather'; alert(pattern.embed({weather:'cloudy', city:'Decin'})) +String.prototype.embed = function() +{ + var t = this; + if ( 1 == arguments.length && 'object' == typeof arguments[0] ) + { + // named placeholders + for ( var i in arguments[0] ) + { + eval('var re=/@'+i+'/g;'); + t = t.replace(re, arguments[0][i]); + } + } + else + { + // anonymous placeholders `?` + for ( var i=0; i opacity ) + { + opacity = 0; + } + if ( 1 < opacity ) + { + opacity = 1; + } + if ( $__tune.isIE ) + { + node.style.filter = 'alpha(opacity='+(100*opacity)+')'; + } + else + { + node.style.opacity = opacity; + node.style.MozOpacity = opacity; + } + }, + getPageScrollPosition:function() + { + var d = document.documentElement; + if ( d && d.scrollTop) + { + return [d.scrollLeft, d.scrollTop]; + } + else if (document.body) + { + return [document.body.scrollLeft, document.body.scrollTop]; + } + else + { + return [0, 0]; + } + } + }, + event: + { + get:function(evt, type) + { + if ( $notset(evt.target) ) + { + evt.target = evt.srcElement; + evt.stopPropagation = function() + { + window.event.cancelBubble = true; + }; + } + evt.stop = function() + { + evt.stopPropagation(); + evt.stopped = true; + } + evt.$ = $(evt.target); + if ( $notset(evt.pageX) ) + { + evt.pageX = evt.clientX + document.body.scrollLeft; + evt.pageY = evt.clientY + document.body.scrollTop; + } + evt.getOffsetX = function() + { + if ( $notset(evt.offsetX) ) + { + var pos = evt.$.abspos(); + evt.offsetX = evt.pageX - pos.x; + evt.offsetY = evt.pageY - pos.y; + } + return evt.offsetX; + } + evt.getOffsetY = function() + { + if ( $notset(evt.offsetY) ) + { + var pos = evt.$.abspos(); + evt.offsetX = evt.pageX - pos.x; + evt.offsetY = evt.pageY - pos.y; + } + return evt.offsetY; + } + evt.isAnyControlKeyPressed = function() + { + return evt.metaKey||evt.ctrlKey||evt.altKey||evt.shiftKey; + } + evt.KEY_ESCAPE = 27; + evt.KEY_ENTER = 13; + evt.KEY_ARR_RIGHT = 39; + evt.KEY_ARR_LEFT = 37; + evt.KEY_ARR_UP = 38; + evt.KEY_ARR_DOWN = 40; + return evt; + }, + addListener:function(node, type, listener) + { + if ( $__tune.isIE && node.attachEvent ) + { + node.attachEvent('on'+type, listener); + } + else + { + node.addEventListener(type, listener, false); + } + }, + removeListener:function(node, type, listener) + { + if ( node.detachEvent ) + { + node.detachEvent('on'+type, listener); + node['on'+type] = null; + } + else if ( node.removeEventListener ) + { + node.removeEventListener(type, listener, false); + } + } + }, + behavior: + { + disablePageScroll:function() + { + if ( $notset($__tune.__prop.page_scroll) ) + { + $__tune.__prop.page_scroll = [$().s().overflow, $().ga('scroll')]; + } + $().s('overflow:hidden').sa('scroll', 'no'); + }, + enablePageScroll:function() + { + $().s('overflow:auto').sa('scroll', 'yes'); + }, + disableCombos:function() + { + $().g('select', function(node) + { + node.sa('__dis_combo', node.s().visibility); + $(node).v(false); + }); + }, + enableCombos:function() + { + $().g('select', function(node) + { + node.s({visibility:node.ga('__dis_combo') || 'visible'}); + }); + }, + clearSelection:function() + { + try + { + if ( window.getSelection ) + { + if ( $__tune.isSafari ) + { + window.getSelection().collapse(); + } + else + { + window.getSelection().removeAllRanges(); + } + } + else + { + if ( document.selection ) + { + if ( document.selection.empty ) + { + document.selection.empty(); + } + else + { + if ( document.selection.clear ) + { + document.selection.clear(); + } + } + } + } + } + catch (e) {} + }, + makeBodyUnscrollable:function() + { + $().s('position:fixed').w(fry.ui.info.page.width); + } + }, + ui: + { + scrollbarWidth:-1!=navigator.appVersion.indexOf('intosh')?15:17 + }, + selection: + { + setRange:function(el, selectionStart, selectionEnd) + { + if (el.setSelectionRange) + { + el.focus(); + el.setSelectionRange(selectionStart, selectionEnd); + } + else if (el.createTextRange) + { + var range = el.createTextRange(); + range.collapse(true); + range.moveEnd('character', selectionEnd); + range.moveStart('character', selectionStart); + range.select(); + } + } + } +} +// some browsers masks its presence having Gecko string somewhere inside its userAgent field... +$__tune.isGecko = !$__tune.isSafari&&!$__tune.isIE&&-1!=navigator.userAgent.indexOf('ecko'); +$__tune.isSafari2 = $__tune.isSafari && -1 != navigator.appVersion.indexOf('Kit/4'); +$__tune.isSafari3 = $__tune.isSafari && -1 != navigator.appVersion.indexOf('Kit/5'); + + +// Node manipulations + +function ACNode(node) +{ + this.$ = node; + if ( node ) + { + node.setAttribute('fryis', '1'); + } +} +// `$$` creates new node +ACNode.prototype.$$ = function(tagName) +{ + return $$(tagName); +} +// *is* - tells whether node is a part of the active DOM tree (that is displayed on page). Node may exist only in memory before appending or after removing when references, in which case node.is() will return false. +ACNode.prototype.is = function() +{ + return this.$ && null != this.$.parentNode; +} +// *i*d +ACNode.prototype.i = function(id) +{ + if ( 'undefined' == typeof id ) + { + return this.$.id||''; + } + this.$.id = id; + return this; +} +// class *n*ame +ACNode.prototype.n = function(n) +{ + if ( 'undefined' == typeof n) + { + return this.$.className||''; + } + this.$.className = n; + return this; +} +// *e*vent listener, if called with one argument only, previously registered listeners are removed +ACNode.prototype.e = function(t, c, oneUseOnly) +{ + var ser_type_id = 'fryse-'+t; + if ( !c ) + { + if ( null != this.$.getAttribute(ser_type_id) ) + { + var ser_listeners = this.$.getAttribute(ser_type_id).split(','); + // console.log('*E* removing listeners for %s, listeners: %s', t, ser_listeners); + for ( var i=0; i h ) + { + this.$.style.fontSize = '1px'; + } + return this; +} +// *s*tyle information - argument can be either "{color:'red', backgroundColor:'blue'}" or "'color:red;background-color:blue'" +ACNode.prototype.s = function(s) +{ + if ( 'undefined' == typeof s ) + { + return this.$.style; + } + if ( 'object' == typeof s ) + { + for ( var n in s ) + { + this.$.style[n] = s[n]; + } + } + else if ( 'string' == typeof s ) + { + if ( '' != s ) + { + var styles = s.split(';'); + for ( var i=0; idiv>table>tbody>tr>td>` and node at `td` +// to return second div you would call `gp('tr/tbody/table/div')` or `tr/table/div:1`, first div could be acquired using `table/div:2` etc. you can use `*` for any node. +ACNode.prototype.gp = function(q) +{ + if ( 'string' == typeof q ) + { + q = q.split('/'); + } + var fq = []; + for ( var i=0; i to && to>=from ) + { + self.clearInterval(t); + } + else + { + c(i, control); + if ( control.stopped ) + { + self.clearInterval(t); + } + } + i++; + }, interval); +} +// $dotimes +// ======== +// Repeats embedded code n times. +/* Usage: + $dotimes(20, function(i) + { + // your code, i is the counter parameter + }) +*/ +var $dotimes = function(n, c) +{ + for ( var i=0; i i ) + { + control.skip(); + return; + } + tr.n(0==i%2 ? 'even' : 'odd'); + if ( 20 < i ) + { + control.stop(); + } + }) +*/ +var $foreach = function(o, c) +{ + if ( !o ) + { + c = null; + return; + } + if ( 'undefined' == typeof o.length && 'function' != typeof o.__length ) + { + c = null; + return; + } + var n = 'function' == typeof o.__length ? o.__length() : o.length; + var control = + { + stopped:false, + stop:function() + { + this.stopped = true; + }, + skipped:false, + skip:function() + { + this.skipped = true; + }, + removed:false, + remove:function(stopAfterwards) + { + this.removed = true; + this.stopped = true == stopAfterwards; + } + } + // cannot just extend Array.prototype for `item()` method due bug in IE6 iteration mechanism. Some day (>2010 :) this might get fixed and will become obsolete + for ( var i=0; i= evt.keyCode) + { + fry.keyboard.pushKey(evt.keyCode + 32, fry.keyboard.META_KEY); + evt.preventDefault(); + return true; + } + } + } + fry.keyboard.initialized = true; + fry.keyboard.start(); +} + +fry.keyboard.start = function() +{ + fry.keyboard.stopped = false; +} + +fry.keyboard.stop = function() +{ + fry.keyboard.stopped = true; +} + +fry.keyboard.disableTextfieldsEditation = function() +{ + fry.keyboard.allowTextfieldsEditation(true); +} + +fry.keyboard.allowTextfieldsEditation = function(disable) +{ + var lst = document.getElementsByTagName('input'); + var n = lst.length; + for (var i=0; i evt.keyCode)) + { + // control code + mask++; + } + if (!fry.keyboard.pushKey(code, mask)) + { + return true; + } + } + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +fry.keyboard.paste.ff_mac = function(evt) +{ + fry.keyboard.last_down_evt = null; + // catching Command+C, Command+X, it's a FF.mac hack + if (evt.metaKey && ((67 == evt.keyCode && 0 == evt.charCode && 67 == evt.which) || (88 == evt.keyCode && 0 == evt.charCode && 88 == evt.which))) + { + return fry.keyboard.shared.copy(evt); + } + else + { + return 86 == evt.keyCode && 0 == evt.charCode && 86 == evt.which && evt.metaKey; + } +} + +fry.keyboard.down.ff_mac = function(evt) +{ + return false; +} + +fry.keyboard.press.ff_mac = function(evt) +{ + if (null != fry.keyboard.last_down_evt) + { + return; + } + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + if (!evt.charCode || (evt.charCode == evt.keyCode)) + { + // control code + fry.keyboard.pushKey(evt.keyCode, 1 + mask); + } + else + { + if (!fry.keyboard.pushKey(evt.charCode, mask)) + { + return true; + } + } + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +fry.keyboard.paste.webkit = function(evt) +{ + if ($__tune.isMac) + { + return (86 == evt.keyCode && (0 == evt.charCode || 118 == evt.charCode) && evt.metaKey); + } + else + { + return (86 == evt.keyCode && (0 == evt.charCode || 118 == evt.charCode) && evt.ctrlKey); + } +} + +fry.keyboard.down.webkit = function(evt) +{ + if (0 != evt.keyCode && (48 > evt.keyCode || (111 < evt.keyCode && 128 > evt.keyCode) || 60000 < evt.charCode)) + { + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + if (!evt.charCode || 111 < evt.keyCode || 32 > evt.charCode || 60000 < evt.charCode) + { + // control code + fry.keyboard.pushKey(evt.keyCode, 1 + mask); + } + else + { + if (!fry.keyboard.pushKey(evt.charCode, mask)) + { + return true; + } + } + evt.preventDefault(); + evt.stopPropagation(); + fry.keyboard.last_down_evt = null; + return false; + } + fry.keyboard.last_down_evt = evt; + return true; +} + +fry.keyboard.press.webkit = function(evt) +{ + if (null != fry.keyboard.last_down_evt) + { + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + var code = !evt.keyCode ? evt.charCode : evt.keyCode; + if (evt.keyCode == evt.charCode && evt.keyCode == fry.keyboard.last_down_evt.charCode && evt.keyCode > 60000) + { + code = fry.keyboard.last_down_evt.keyCode; + } + if (evt.keyCode == fry.keyboard.last_down_evt.keyCode && 48 > evt.keyCode) + { + // control code + mask++; + } + else + { + var r_mask = fry.keyboard.SHIFT_KEY + fry.keyboard.META_KEY; + if (r_mask == (mask & r_mask) && 97 <= code && 122 >= code) + { + code -= 32; + } + } + if (!fry.keyboard.pushKey(code, mask)) + { + return true; + } + } + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +fry.keyboard.paste.ie = function(evt) +{ + if (evt.ctrlKey && (67 == evt.keyCode || 88 == evt.keyCode)) + { + // ctrl+c, ctrl+x + return fry.keyboard.shared.copy(evt); + } + else + { + return false; + } +} + +fry.keyboard.down.ie = function(evt) +{ + fry.keyboard.last_down_evt = evt; + if (48 > evt.keyCode || (111 < evt.keyCode && 128 > evt.keyCode)) + { + // control code for IE + var mask = 1 + (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + return !fry.keyboard.pushKey(evt.keyCode, mask) + } + else + { + var code = evt.keyCode; + // disabling some other keys (A, F, N, R, S, T) + if (82 == evt.keyCode || 65 == evt.keyCode || 83 == evt.keyCode || 70 == evt.keyCode || 78 == evt.keyCode || 84 == evt.keyCode) + { + if (!evt.shiftKey) + { + code += 32; + } + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + return !fry.keyboard.pushKey(code, mask); + } + } + return true; +} + +fry.keyboard.press.ie = function(evt) +{ + if (null != fry.keyboard.last_down_evt) + { + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + return !fry.keyboard.pushKey(evt.keyCode, mask); + } + return false; +} + + +fry.keyboard.paste.opera = function(evt) +{ + return 86 == evt.keyCode && 86 == evt.which && evt.ctrlKey; +} + +fry.keyboard.down.opera = function(evt) +{ + fry.keyboard.last_down_evt = evt; + return false; +} + +fry.keyboard.press.opera = function(evt) +{ + var e = fry.keyboard.last_down_evt; + var mask = (evt.altKey ? 2 : 0) + (evt.ctrlKey ? 4 : 0) + (evt.shiftKey ? 8 : 0) + (evt.metaKey ? 16 : 0); + var prev_mask = (e.altKey ? 2 : 0) + (e.ctrlKey ? 4 : 0) + (e.shiftKey ? 8 : 0) + (e.metaKey ? 16 : 0); + if ((evt.keyCode == fry.keyboard.last_down_evt.keyCode || 0 == e.keyCode) && (0 == evt.which || 48 > e.keyCode || 111 < e.keyCode)) + { + mask++; + } + if (!fry.keyboard.pushKey(evt.keyCode, mask)) + { + return true; + } + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +fry.keyboard.addListener = function(listener) +{ + fry.keyboard.listener = listener; +} + +fry.keyboard.removeListener = function(listener) +{ + fry.keyboard.listener = null; +} + + +fry.keyboard.pushKey = function(code, mask) +{ + if (32 == code) + { + mask = mask & 65534; + } + var was_clipboard_copy = false; + var was_clipboard_cut = false; + if ($__tune.isMac) + { + was_clipboard_copy = (99 == code) && (fry.keyboard.META_KEY == (mask & fry.keyboard.META_KEY)); + was_clipboard_cut = (120 == code) && (fry.keyboard.META_KEY == (mask & fry.keyboard.META_KEY)); + } + else + { + was_clipboard_copy = (1 == code || 99 == code) && (fry.keyboard.CTRL_KEY == (mask & fry.keyboard.CTRL_KEY)); + was_clipboard_cut = (24 == code || 120 == code) && (fry.keyboard.CTRL_KEY == (mask & fry.keyboard.CTRL_KEY)); + } + if (was_clipboard_copy || was_clipboard_cut) + { + fry.keyboard.prepareClipboard(); + fry.keyboard.clipboard.copiedContent = ''; + var was_custom_content = false; + if (fry.keyboard.listener) + { + var content = fry.keyboard.listener(0, fry.keyboard.CONTROL_CODE | fry.keyboard.SIG_CLIPBOARD_GET); + if ('string' == typeof content) + { + was_custom_content = true; + fry.keyboard.clipboard.copiedContent = content; + } + } + if (was_custom_content) + { + fry.keyboard.clipboard.node.value = fry.keyboard.clipboard.copiedContent; + fry.keyboard.clipboard.node.select(); + fry.keyboard.clipboard.node.focus(); + } + fry.keyboard.pushKey(0, fry.keyboard.CONTROL_CODE | (was_clipboard_cut ? fry.keyboard.CUT : fry.keyboard.COPY)); + fry.keyboard.clipboard.content = fry.keyboard.clipboard.copiedContent; + // returning false will enforce propagation + return !was_custom_content; + } + if (fry.keyboard.PASTE == (mask & fry.keyboard.PASTE)) + { + fry.keyboard.clipboard.pastedContent = code; + fry.keyboard.clipboard.content = fry.keyboard.clipboard.pastedContent; + code = 0; + } + // filtering out solo-keys Ctrl, Shift, Alt that are triggered by some browsers. + if ((17 == code && 5 == (mask & 5)) || (16 == code && 9 == (mask & 9)) || (18 == code && 3 == (mask & 3))) + { + return true; + } + // filtering out Command+V on FF.mac + if (118 == code && 16 == mask) + { + return true; + } + if (fry.keyboard.listener) + { + // console.info(code); + if (13 != code && 0 != (mask & (fry.keyboard.CONTROL_CODE + fry.keyboard.META_KEY + fry.keyboard.CTRL_KEY))) + { + // some keystroke occured that we really want to know about the listener result, we have to call it immediatelly + return fry.keyboard.listener(code, mask); + } + else + { + // let's ease the pain of the browser + setTimeout('fry.keyboard.listener('+code+','+mask+')', 10); + } + } + else + { + fry.keyboard.buffer.unshift([code, mask]); + } + return true; +} + +fry.keyboard.popKey = function() +{ + return fry.keyboard.buffer.pop(); +} + +fry.keyboard.getClipboardContent = function() +{ + return fry.keyboard.clipboard.content; +} + + +/*--------*/ +var client = {conf:{fry:{backendURL:''}}}; +var eamy = +{ + snippets:[], + instances:[] +}; + +/* + * ac.Chap - Text Editing Component - Core + */ + +if ( 'undefined' == typeof ac ) +{ + var ac = {chap:{}}; +} + +ac.chap = +{ + state: + { + active:null + }, + + TOKEN_MULTIROW_COMMENT:0, + TOKEN_SINGLEROW_COMMENT:1, + TOKEN_SINGLE_QUOTED:2, + TOKEN_DOUBLE_QUOTED:3, + TOKEN_NEWROW:4, + TOKEN_WHITESPACE:5, + + ROWSTATE_NONE:0, + ROWSTATE_FOLD_START:1, + ROWSTATE_FOLD_STOP:2, + ROWSTATE_FOLD_EXPAND:4, + ROWSTATE_FOLD_COLLAPSED:8, + ROWSTATE_SELECTION:16, + ROWSTATE_BOOKMARK:32, + + CHUNK_KEYWORD:4, + CHUNK_NUMBER:5, + CHUNK_OPERATOR:6, + CHUNK_PARENTHESIS:7, + CHUNK_KEYWORD_CUSTOM:8, + CHUNK_FUNCTION_NAME:9, + CHUNK_LIBRARY:10, + CHUNK_LIBRARY_CUSTOM:11, + + ACTION_CARET:1, + ACTION_SELECTION:2, + ACTION_INSERT:3, + ACTION_UPDATE:4, + ACTION_DELETE:5, + ACTION_CLIPBOARD:6, + ACTION_UNDO:7, + ACTION_REDO:8, + ACTION_CUSTOM:9, + + ACTION_RES_REDRAWCARET:1, + ACTION_RES_REDRAWTEXT:2, + ACTION_RES_SELECTIONCHANGED:4, + ACTION_RES_SCROLLTOCARET:8, + + CKEY_NONE:0, + CKEY_ALT:2, + CKEY_CTRL:4, + CKEY_SHIFT:8, + CKEY_META:16, + + TRANSLOG_TYPE_INSERT:1, + TRANSLOG_TYPE_REMOVE:2, + + ACTION_LISTENER_BEFORE:1, + ACTION_LISTENER_AFTER:2, + ACTION_LISTENER_BOTH:3 + +} + + + +ac.chap.activeComponent = null; +ac.chap.instanceId = 1; + +ac.chap.getActiveComponent = function() +{ + return ac.chap.activeComponent; +} + +ac.chap.setActiveComponent = function(component) +{ + fry.keyboard.initialize(); + if (null != ac.chap.activeComponent) + { + ac.chap.activeComponent.blur(); + } + ac.chap.activeComponent = component; + if (null != component) + { + if (ac.widget) + { + ac.widget.focus(component); + } + ac.chap.activeComponent.focus(); + } + else + { + if (!ac.widget) + { + fry.keyboard.stop(); + } + } +} + +ac.chap.route = function(type, windowId, viewIndex, pars) +{ + if ( null == ac.chap.activeComponent ) + { + return; + } + if ( 'undefined' == typeof ac.chap.activeComponent.views[viewIndex] ) + { + return; + } + switch ( type ) + { + case 'expand-folding': + { + ac.chap.activeComponent.expandFolding(pars); + } + } +} + +ac.chap.caretThread = setInterval(function() +{ + if (null != ac.chap.activeComponent) + { + ac.chap.activeComponent.showCaret(true, true); + } +}, 600); + +$(document.documentElement).e($__tune.isSafari2?'mousedown':'click', function(evt) +{ + var elem = evt.$.$; + while ( null != elem && document.documentElement != elem ) + { + if ( 'true' == elem.getAttribute('chap-view') ) + { + evt.stop(); + return; + } + elem = elem.parentNode; + } + ac.chap.setActiveComponent(null); +}); + + +ac.chap.keyboardListener = function(code, mask) +{ + if (null == ac.chap.activeComponent) + { + return; + } + return ac.chap.activeComponent.standaloneKeyboardListener(code, mask); +} +if ('undefined' == typeof ac['widget']) +{ + // chap is not a part of Fry MVC, must handle keyboardListener itself + fry.keyboard.addListener(ac.chap.keyboardListener); +} + +$class('ac.chap.Window', +{ + construct:function(options, userId) + { + this.instanceId = ac.chap.instanceId++; + this.ident = 'ac-chap-' + this.instanceId; + this.userId = userId | 0; + this.caret = null; + this.options = null; + this.state = null; + + this.views = []; + this.activeView = null; + this.viewLayoutNodes = []; + + this.char_map = []; + this.row_id_map = []; + this.syntax_map = []; + this.style_map = []; + + this.row_id_sequence = 1; + + this.language = null; + this.keymap = null; + this.snippets = []; + this.commands = []; + + this.selection = null; + this.transaction_log = []; + this.redo_log = []; + + this.setOptions(options||{}); + this.setState(); + }, + destruct:function() + { + $delete(this.state); + $delete(this.options); + $delete(this.char_map); + $delete(this.row_id_map); + $delete(this.syntax_map); + $delete(this.style_map); + this.hide(); + $delete(this.activeView); + } +}); + +ac.chap.Window.prototype.focus = function() +{ + this.showCaret(); +} + +ac.chap.Window.prototype.blur = function() +{ + this.hideCaret(); +} + +// compatibility layer with AC Fry Widget library +ac.chap.Window.prototype.onFocus = function() +{ + ac.chap.setActiveComponent(this); + this.focus(); +} + +ac.chap.Window.prototype.onBlur = function() +{ + ac.chap.setActiveComponent(null); + this.blur(); +} + +ac.chap.Window.prototype.onResize = function(width, height) +{ +} + +ac.chap.Window.prototype.onSystemClipboardCopy = function() +{ + return this.getSelection(); +} + +ac.chap.Window.prototype.onSystemClipboardCut = function() +{ + this.runAction(ac.chap.ACTION_CLIPBOARD, {cut:true}); + return this.processActionResult(true, true); +} + +ac.chap.Window.prototype.onSystemClipboardPaste = function(content) +{ + this.runAction(ac.chap.ACTION_CLIPBOARD, {paste:true, content:content}); + return this.processActionResult(true, true); +} + +ac.chap.Window.prototype.hasKeyboardListenerActive = function() +{ + return true; +} +ac.chap.Window.prototype.onCut = function(selection, callbackOk) +{ +} + +ac.chap.Window.prototype.onPaste = function(selection, wasCut) +{ +} + +ac.chap.Window.prototype.setOptions = function(options) +{ + this.options = + { + initialCaretPosition:[0,0], + tokenizerLazyLaunch:900, + syntaxHighlightingEnabled:true, + remoteBackendURL:'', + font:{ + size:11, + family: $__tune.isMac ? "Consolas, 'Bitstream Vera Sans mono', 'Courier', 'Monaco', monospaced" : "Consolas, 'Courier New', 'Courier', monospaced", + allowedSizes: [8, 9, 10, 11, 12, 13, 14, 17, 21, 24, 27, 30, 34, 38, 42] + } + }; + if ( $isset(options.initial_caret_position) ) + { + this.options.initialCaretPosition = [options.initial_caret_position[0], options.initial_caret_position[1]]; + } + if ( $isset(options.language) ) + { + this.language = $new(options.language); + } + else + { + this.language = $new(ac.chap.Language); + } + if ( $isset(options.keymap) ) + { + this.keymap = $new(options.keymap); + } + else + { + this.keymap = $new(ac.chap.KeyMap); + } + if ( $isset(options.syntaxHighlightingEnabled) ) + { + this.options.syntaxHighlightingEnabled = options.syntaxHighlightingEnabled; + } + if ( $isset(options.remoteBackendURL) ) + { + this.options.remoteBackendURL = options.remoteBackendURL; + } + else + { + if ( client && client.conf && client.conf.fry ) + { + this.options.remoteBackendURL = client.conf.fry.backendURL; + } + } + if ($isset(options.font)) + { + if ($isset(options.font['size'])) + { + this.options.font.size = options.font.size; + } + if ($isset(options.font['family'])) + { + this.options.font.family = options.font.family; + } + } +} + +ac.chap.Window.prototype.setState = function() +{ + this.state = + { + lastKeyTimePressed:0, + caretPhase:1, + lastKeyCode:0, + lastControlKey:0, + lastCaretPosition:[], + tokenizerTimer:null, + scheduledTokenizerTime:0, + transactionLogStopped:false, + actionListeners:[], + actionListenersStopped:false, + caretListener:null, + commandListener:null, + transactionListener:[null,800], + passThroughKeysListener:null + } + this.caret = + { + position:[this.options.initialCaretPosition[0], this.options.initialCaretPosition[1]], + mode:1 // 1 normal, 2 overwrite + } +} + +ac.chap.Window.prototype.addView = function(layoutNode, options, renderAfter) +{ + var view_index = this.views.length; + this.viewLayoutNodes.push(layoutNode); + this.views.push($new(ac.chap.View, this, view_index, options||{})); + this.row_id_map[view_index] = []; + if ( 0 < view_index ) + { +// console.log(view_index); + // creating duplicate + var n = this.row_id_map[0].length; + for ( var i=0; i= 0; i--) + { + if (font_size > font_sizes[i]) + { + font_size = font_sizes[i]; + break; + } + } + } + else + { + if (font_sizes[0] <= value && value <= font_sizes[font_sizes.length-1]) + { + font_size = value; + } + } + this.options.font.size = font_size; + redraw = true; + } + else if ('font.family' == key) + { + this.options.font.family = value; + redraw = true; + } + else if ('word.wrap' == key) + { + if (this.activeView) + { + this.hideCaret(); + this.activeView.options.wordWrap = value; + this.activeView.reloadOptions(); + this.showCaret(); + } + } + if (redraw) + { + this.hideCaret(); + var num_views = this.views.length; + for (var i=0; i caret_col ) + { + caret_col++; + } + else + { + if ( 'undefined' != typeof this.char_map[caret_row+1] ) + { + caret_row++; + caret_col = 0; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'up' == direction ) + { + if ( 0 < caret_row ) + { + var move_end = this.char_map[caret_row].length == caret_col; + if ( move_end ) + { + caret_col = this.char_map[caret_row-1].length; + } + else + { + caret_col = Math.min(this.char_map[caret_row-1].length, caret_col); + } + this.setCaretPosition(caret_row-1, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( 'down' == direction ) + { + if ( 'undefined' != typeof this.char_map[caret_row+1] ) + { + var move_end = this.char_map[caret_row].length == caret_col; + if ( move_end ) + { + caret_col = this.char_map[caret_row+1].length; + } + else + { + caret_col = Math.min(this.char_map[caret_row+1].length, caret_col); + } + this.setCaretPosition(caret_row+1, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( 'prev_word' == direction ) + { + if ( 0 < caret_col ) + { + var ch = this.char_map[caret_row].charAt(caret_col-1); + var re = this.language.wordDelimiter; + var look_for_wch = re.test(ch); + while ( 0 != caret_col ) + { + ch = this.char_map[caret_row].charAt(caret_col-1); + if ( look_for_wch != re.test(ch) ) + { + break; + } + caret_col--; + } + } + else + { + if ( 0 < caret_row ) + { + caret_row--; + caret_col = this.char_map[caret_row].length; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'next_word' == direction ) + { + if ( this.char_map[caret_row].length > caret_col ) + { + var ch = this.char_map[caret_row].charAt(caret_col); + var re = this.language.wordDelimiter; + var look_for_wch = re.test(ch); + while ( this.char_map[caret_row].length > caret_col ) + { + ch = this.char_map[caret_row].charAt(caret_col); + if ( look_for_wch != re.test(ch) ) + { + break; + } + caret_col++; + } + } + else + { + if ( 'undefined' != typeof this.char_map[caret_row+1] ) + { + caret_row++; + caret_col = 0; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'prev_regexp' == direction ) + { + if ( 0 < caret_col ) + { + var row = this.char_map[caret_row].substring(0, caret_col); + var re = new RegExp(params['re'].replace('|', '\\')); + var matches = re.exec(row); + if (0 == matches.length) + { + console.warning('Invalid RE definition for `prev_regexp\' direction in ACTION_CARET.move action in keymap.'); + caret_col--; + } + else + { + caret_col -= matches[0].length + 1; + } + } + else + { + if ( 0 < caret_row ) + { + caret_row--; + caret_col = this.char_map[caret_row].length; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'next_regexp' == direction ) + { + if ( this.char_map[caret_row].length > caret_col ) + { + var row = this.char_map[caret_row].substr(caret_col + 1); + var re = new RegExp(params['re'].replace('|', '\\')); + var matches = re.exec(row); + if (0 == matches.length) + { + console.warning('Invalid RE definition for `next_regexp\' direction in ACTION_CARET.move action in keymap.'); + caret_col++; + } + else + { + caret_col += matches[0].length + 1; + } + } + else + { + if ( 'undefined' != typeof this.char_map[caret_row+1] ) + { + caret_row++; + caret_col = 0; + } + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'row_start' == direction ) + { + if ( 0 < caret_col ) + { + caret_col = 0; + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( 'row_end' == direction ) + { + if ( this.char_map[caret_row].length > caret_col ) + { + caret_col = this.char_map[caret_row].length; + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( 'page_up' == direction ) + { + if (this.activeView) + { + var row = caret_row - this.activeView.numRows; + if (0 > row) + { + row = 0; + } + this.setCaretPosition(row, 0 != caret_col ? this.char_map[row].length : 0); + return ac.chap.ACTION_RES_REDRAWCARET; + } + return 0; + } + else if ( 'page_down' == direction ) + { + if (this.activeView) + { + var row = caret_row + this.activeView.numRows; + if (this.char_map.length <= row) + { + row = this.char_map.length-1; + } + this.setCaretPosition(row, 0 != caret_col ? this.char_map[row].length : 0); + return ac.chap.ACTION_RES_REDRAWCARET; + } + return 0; + } + else if ( 'doc_start' == direction ) + { + this.setCaretPosition(0,0); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'doc_end' == direction ) + { + var last_index = this.char_map.length-1 + this.setCaretPosition(last_index, this.char_map[last_index].length-1); + return ac.chap.ACTION_RES_REDRAWCARET; + } + } + else if ( $isset(params.moveBy) ) + { + var offset = params.moveBy; + if ( 'column' == offset ) + { + //move by params.value columns, newlines are counted as column, params.value may be negative indication caret moving to the left + if ( 0 < params.value ) + { + var range_source = (this.char_map[caret_row].substr(caret_col)+'\n'+this.char_map.slice(caret_row+1).join('\n')).substr(0, params.value); + range_source = range_source.split('\n'); + caret_row += range_source.length-1; + caret_col = range_source[range_source.length-1].length + (1==range_source.length ? caret_col : 0); + } + else + { + var range_source = (this.char_map.slice(0, caret_row).join('\n')+'\n'+this.char_map[caret_row].substr(0, caret_col)); + range_source = range_source.substr(range_source.length+params.value); + range_source = range_source.split('\n'); + caret_row -= (range_source.length-1); + caret_col = (1==range_source.length ? caret_col : this.char_map[caret_row].length) - range_source[0].length; + } + this.setCaretPosition(caret_row, caret_col); + return ac.chap.ACTION_RES_REDRAWCARET; + } + else if ( 'row' == offset ) + { + // move by params.value rows + } + else if ( 'page' == offset ) + { + // move by params.value pages + } + } + else if ( $isset(params.moveTo) ) + { + // move to params.moveTo[0], params.moveTo[1] + this.setCaretPosition(params.moveTo[0], params.moveTo[1]); + return ac.chap.ACTION_RES_REDRAWCARET; + } + };break; + case ac.chap.ACTION_SELECTION: + { + if ( $isset(params.remove) ) + { + var changed = this.removeSelection(); + return ac.chap.ACTION_RES_REDRAWCARET | (changed ? ac.chap.ACTION_RES_REDRAWTEXT : 0); + } + else if ( $isset(params.add) ) + { + this.addSelection([caret_row, caret_col], this.state.lastCaretPosition); + this.renderSelection(); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED; + } + else if ( $isset(params.all) ) + { + $__tune.behavior.clearSelection(); + this.addAllSelection(); + this.renderSelection(); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED; + } + };break; + case ac.chap.ACTION_INSERT: + { + var str = null; + if ( $isset(params.row) ) + { + var ins_content = '\n'; + caret_col = 0; + // if ( 0 < caret_row ) + // { + // // indenting by previous row + // var t = this.char_map[caret_row]; + // var n = t.length; + // var re = this.language.indentIgnoreMarker; + // while ( caret_col width ) + { + // will go before [row,column] + source = this.char_map.slice(0, row).join('\n')+'\n'+this.char_map[row].substr(0, column); + return source.substr(source.length+width); + } + else + { + // will go after [row,column] + source = this.char_map.slice(row).join('\n').substr(column); + return source.substr(0, width); + } +} + +ac.chap.Window.prototype.getWordAt = function(row, column, numWords) +{ + // if numWords <0 returns words before otherwise after position. if omitted, default value is -1 that is word before caret + // also, if more than one word is required, returns array of words as result + row = $getdef(row, this.caret.position[0]); + column = $getdef(column, this.caret.position[1]); + numWords = $getdef(numWords, -1); + var words = []; + var re = this.language.wordDelimiter; + var required_words = numWords; + var direction_after = 0 < numWords; + var next_word = true; + required_words = Math.abs(-numWords); + if ( !direction_after ) + { + column--; + if ( -1 == column ) + { + row--; + if ( -1 == row ) + { + return words; + } + column = this.char_map[row].length-1; + } + } + while (true) + { + var ch = this.char_map[row].charAt(column) + if ( re.test(ch) ) + { + if ( next_word ) + { + words.push(''); + next_word = false; + } + // word character found + if ( direction_after ) + { + words[words.length-1] += ch; + } + else + { + words[words.length-1] = ch + words[words.length-1]; + } + } + else + { + if ( required_words == words.length ) + { + break; + } + next_word = true; + } + + column += (direction_after ? 1 : -1); + if ( 0 > column ) + { + row--; + if ( -1 == row ) + { + break; + } + next_word = true; + column = this.char_map[row].length-1; + } + else if ( this.char_map[row].length <= column ) + { + row++; + if ( this.char_map.length == row ) + { + break; + } + next_word = true; + column = 0; + } + } + if ( 1 == required_words ) + { + return words[0] ? words[0] : false; + } + return words; +} + +ac.chap.Window.prototype.getLineAt = function(row) +{ + return this.char_map[row]; +} + +ac.chap.Window.prototype.getText = function() +{ + return this.char_map.join('\n'); +} + +ac.chap.Window.prototype.getNumRows = function() +{ + return this.char_map.length; +} + +ac.chap.Window.prototype.getSyntaxHighlightingSource = function() +{ + if (null != this.activeView) + { + return this.activeView.getSyntaxHighlightingSource(); + } + return 'ni'; +} + +ac.chap.Window.prototype.getCaretAbsolutePosition = function() +{ + if ( null != this.activeView ) + { + var pos = this.activeView.getRenderedCharPosition(this.caret.position[0], this.caret.position[1]); + if ( null != pos ) + { + var root_pos = this.activeView.nodeScrollArea.abspos(); + return [pos[0]+root_pos.x+this.activeView.options.colWidth, pos[1]+root_pos.y+this.activeView.options.rowHeight]; + } + } + return null; +} + +ac.chap.Window.prototype.stopTransactionLog = function() +{ + this.state.transactionLogStopped = true; +} + +ac.chap.Window.prototype.startTransactionLog = function() +{ + this.state.transactionLogStopped = false; +} + +ac.chap.Window.prototype.addAllSelection = function() +{ + var num_rows = this.char_map.length; + var num_views = this.views.length; + for ( var i=0; i offset_x - mid_char_w ) + { + col_index = i; + break; + } + else if ( w - mid_char_w < offset_x && (dim[0] % w + 2*mid_char_w >= offset_x)) + { + // last char + col_index = i+1; + break; + } + } + i++; + } +// console.log('%s, %s', row_index, col_index); + if ( i == num_chars ) + { + col_index = i; + } +// console.log('CHANGE CARET to: %s', col_index); + this.hideCaret(); + if ( evt.shiftKey ) + { + this.addSelection([row_index, col_index], this.state.lastCaretPosition); + this.renderText(); + } + else + { + this.setCaretPosition(row_index, col_index); + this.state.caretPhase = 1; + view.showCaret(); + this.state.lastCaretPosition = [this.caret.position[0], this.caret.position[1]]; + if ( this.removeSelection() ) + { + this.renderText(); + } + } + ac.chap.setActiveComponent(this); +} + +ac.chap.Window.prototype.foldingize = function() +{ + var startRowIndex = 0; + + var source_rows = this.char_map.slice(startRowIndex); + + // creating folding info + var n = source_rows.length; + var foldings = []; + var foldings_index = -1; + for ( var i=0; i ixs[i][1] ) + { + found_marker_index = i; + lowest = ixs[i][1]; + } + } + } + if ( -1 == found_marker_index ) + { + break; + } + var start_index = ixs[found_marker_index][1]; + var skipped_source = source.substr(0, start_index); + var num_skipped_rows = skipped_source.split('\n').length; + cursor.row += num_skipped_rows - 1; + cursor.col = (1 == num_skipped_rows ? col_offset : 0) + skipped_source.length - ('\n'+skipped_source).lastIndexOf('\n'); + + if ( 'undefined' == typeof syntax_map[cursor.row] ) + { + syntax_map[cursor.row] = []; + } + + var start_marker_len = ixs[found_marker_index][2].length; + var end_marker_len = ixs[found_marker_index][3].length; + source = source.substr(start_index+start_marker_len); + + var token_type = ixs[found_marker_index][0]; + + var end_index = source.indexOf(ixs[found_marker_index][3]); + var sub_source = source; + var end_index_offset = 0; + var except = false; + while ( 0 < end_index && '' != ixs[found_marker_index][4] && ixs[found_marker_index][4] == sub_source.charAt(end_index-end_marker_len) ) + { + except = true; + end_index_offset += end_index + end_marker_len; + sub_source = sub_source.substr(end_index+end_marker_len); + end_index = sub_source.indexOf(ixs[found_marker_index][3]); + } + if ( except && -1 != end_index ) + { + end_index += end_index_offset; + } + if ( -1 == end_index ) + { + syntax_map[cursor.row].push([token_type, cursor.col, -1, '']); + fillRowTokens(token_type, cursor.row+1, -1); + break; + } + else + { + var block_source = source.substr(0, end_index); + var num_block_rows = '\n' == ixs[found_marker_index][3] ? 1 : block_source.split('\n').length; + var cursor_col_end = block_source.length - ('\n'+block_source).lastIndexOf('\n'); + + syntax_map[cursor.row].push([token_type, cursor.col, 1 == num_block_rows ? (cursor.col+end_index+start_marker_len+end_marker_len) : -1, ixs[found_marker_index][2]]); + fillRowTokens(token_type, cursor.row+1, cursor.row+num_block_rows-1); + if ( 1 == num_block_rows ) + { + col_offset = cursor.col + end_index + start_marker_len + end_marker_len; + if ( '\n' == ixs[found_marker_index][3] ) + { + cursor.row++; + col_offset = 0; + } + } + else + { + if ( 'undefined' == typeof syntax_map[cursor.row+num_block_rows] ) + { + syntax_map[cursor.row+num_block_rows-1] = []; + } + syntax_map[cursor.row+num_block_rows-1].push([token_type, -1, cursor_col_end + end_marker_len, '']); +// var a = block_source.split('\n'); + col_offset = cursor_col_end + end_marker_len; + cursor.row += num_block_rows -1; + } +// console.log(num_block_rows); + source = source.substr(end_index+end_marker_len); + } + } + delete ixs; + delete source; + + var n = Math.max(syntax_map.length, this.syntax_map.length); + for ( i=0; i'; + } + } + return ht; + } + var nodes = this.nodeEditArea.childNodes; + var ht = ''; + for (var i=0; i') ) + { + str = str.replace(/>/g, '>'); + } + if ( -1 != str.indexOf('<') ) + { + str = str.replace( / 1 2 3 =  1 2 3 +function ch_encode_markup_spaces(str) +{ + var n = str.length - str.replace(/ /g, '').length; + for ( var i=0; i/g, '').length); + return arguments[1]+(is_inside?'~`~`~`~`':' '); + }); + } + return str.replace(/~`~`~`~`/g, ' '); +} + + +ac.chap.View.prototype.getRenderedCharDimension = function(rowIndex, colIndex) +{ + return [this.options.colWidth, this.options.rowHeight]; +} + +ac.chap.View.prototype.getRenderedStringDimension = function(rowIndex, colIndex, width) +{ + if ( 'undefined' != typeof this.window.char_map[rowIndex] ) + { + if ( colIndex < this.window.char_map[rowIndex].length ) + { + var str = this.window.char_map[rowIndex].substr(colIndex, width); + var ix = 0; + var tab = this.options.tabelator; + while ( -1 != ix ) + { + ix = str.indexOf('\t'); + if ( -1 != ix ) + { + str = str.substr(0,ix)+tab.substr(0, tab.length-(ix % tab.length))+str.substr(ix+1); + } + } +// console.log('(getrenderedstringdimension) = [%s], ix:%s w:%s %s', this.options.colWidth*str.length, colIndex, width, str); + return [this.options.colWidth*str.length, this.options.rowHeight]; + } + } + return [0,0]; +} + +ac.chap.View.prototype.getVirtualStringDimension = function(row, colIndex, width) +{ + if ( colIndex < row.length ) + { + var str = row.substr(colIndex, width); + var ix = 0; + var tab = this.options.tabelator; + while ( -1 != ix ) + { + ix = str.indexOf('\t'); + if ( -1 != ix ) + { + str = str.substr(0,ix)+tab.substr(0, tab.length-(ix % tab.length))+str.substr(ix+1); + } + } +// console.log('(getrenderedstringdimension) = [%s], ix:%s w:%s %s', this.options.colWidth*str.length, colIndex, width, str); + return [this.options.colWidth*str.length, this.options.rowHeight]; + } + return [0,0]; +} + +ac.chap.View.prototype.getRenderedCharPosition = function(rowIndex, colIndex) +{ + var node_row = this.getRowNode(rowIndex); + if ( null != node_row && null != node_row.parentNode ) + { + var offset_x = this.getRenderedStringDimension(rowIndex, 0, colIndex)[0]; + var offset_y = 0; + var dim = this.getRenderedCharDimension(rowIndex, colIndex); + offset_x -= dim[0]; + if ( this.options.wordWrap ) + { + if ( 0 < colIndex ) + { + var w = this.options.colWidth * (this.numCols); + offset_y = this.options.rowHeight * (Math.floor(offset_x/w)); + offset_x = (offset_x) % w; + } + } + return [offset_x, node_row.offsetTop+offset_y, node_row]; + } + return null; +} + +ac.chap.View.prototype.getRowNode = function(rowIndex) +{ + return document.getElementById('row-'+this.window.instanceId+'-'+this.index+'-'+rowIndex); +} + +ac.chap.View.prototype.getVirtualCharPosition = function(nodeRow, row, colIndex) +{ + var offset_x = this.getVirtualStringDimension(row, 0, colIndex)[0]; + var offset_y = 0; + var dim = this.getRenderedCharDimension(0, colIndex); + offset_x -= dim[0]; + if ( this.options.wordWrap ) + { + if ( 0 < colIndex ) + { + var w = this.options.colWidth * (this.numCols); + offset_y = this.options.rowHeight * (Math.floor(offset_x/w)); + offset_x = (offset_x) % w; + } + } + return [offset_x, nodeRow.offsetTop+offset_y]; +} + +ac.chap.View.prototype.showCaret = function(skipScroll) +{ + var caret_row = this.window.caret.position[0]; + var caret_col = this.window.caret.position[1]; + + pos = this.getRenderedCharPosition(caret_row, caret_col); + if ( null != pos ) + { + // caret is visible + var node_row = pos[2]; + var node = document.getElementById('ac-chap-caret-'+this.window.instanceId); + if ( null != node ) + { + node.parentNode.removeChild(node); + } + if ( 1 == this.window.state.caretPhase ) + { + // displaying caret + node = document.createElement('div'); + node.id = 'ac-chap-caret-'+this.window.instanceId; + node.style.position = 'absolute'; + node.style.font = '1px arial'; // IE + node.style.width = this.options.colWidth + 'px'; + node.style.height = this.options.rowHeight + 'px'; + pos[2] = this.options.colWidth; + pos[3] = this.options.rowHeight; + pos = this.theme.adjustCaretPosition(this.window.caret.mode, pos); + node.style.left = pos[0]+'px'; + node.style.top = pos[1]+'px'; + this.theme.renderCaret(this.window.caret.mode, node); + this.nodeCaret = node_row.appendChild(node); + node_row.style.background = this.theme.caretRowStyleActive; + + if ( !skipScroll ) + { + // might be out of borders, at least partially + if ( 0 > node_row.offsetTop - (this.nodeScrollArea.$.scrollTop % this.options.rowHeight) ) + { + // top margin overlay, first rendered row is partially hidden + this.scrollToRow(caret_row); + } + else if ( node_row.offsetTop > this.options.rowHeight*(this.numRows-1)-$__tune.ui.scrollbarWidth ) + { + // bottom margin overlay + this.scrollToRow(caret_row-Math.floor(this.numRows/2)); + } + } + this.nodeCaretRow = node_row; + } + if ('undefined' != typeof this.state.lastCaretRowIndex && this.state.lastCaretRowIndex != caret_row) + { + var last_node_row = this.getRowNode(this.state.lastCaretRowIndex); + if (last_node_row) + { + last_node_row.style.background = 'transparent'; + } + } + this.state.lastCaretRowIndex = caret_row; + } + else + { + if ( !skipScroll ) + { + // scrolling into view + this.scrollToRow(caret_row - Math.floor(this.numRows/2)); + } + } +} + +ac.chap.View.prototype.hideCaret = function(skipCaretRow) +{ + if ( null != this.nodeCaret && null != this.nodeCaret.parentNode ) + { + this.nodeCaret.parentNode.removeChild(this.nodeCaret); + this.nodeCaret = null; + } + if ( !skipCaretRow && null != this.nodeCaretRow ) + { +// console.log('off(hide) caret line background for %s', this.nodeCaretRow.id); + this.nodeCaretRow.style.background = 'transparent'; + } +// console.log('hide caret'); +} + +ac.chap.View.prototype.scrollToRow = function(rowIndex, setCaretToo, dontRefreshCaret) +{ + this.nodeScrollArea.$.scrollTop = this.options.rowHeight * rowIndex - Math.floor(this.nodeRoot.$.offsetHeight/3); + if (setCaretToo) + { + this.window.runAction(ac.chap.ACTION_CARET, {moveTo:[rowIndex, 0]}); + this.window.runAction(ac.chap.ACTION_CARET, {move:'row_end'}); + } + if (!dontRefreshCaret) + { + this.window.state.caretPhase = 1; + this.showCaret(true); + } +} + +ac.chap.View.prototype.expandFolding = function(rowIndex) +{ + if ( 'undefined' == typeof this.window.row_id_map[this.index][rowIndex] ) + { + return; + } + var row_state = this.window.row_id_map[this.index][rowIndex][2]; + if (0 == (ac.chap.ROWSTATE_FOLD_EXPAND & row_state)) + { + return; + } + var end_row_index = this.window.row_id_map[this.index][rowIndex][3][1]; + this.window.row_id_map[this.index][rowIndex][2] &= (65535 - ac.chap.ROWSTATE_FOLD_EXPAND); + this.window.row_id_map[this.index][rowIndex][1] = false; + for ( var i=rowIndex+1; i<=end_row_index; i++ ) + { + this.window.row_id_map[this.index][i][1] = false; + this.window.row_id_map[this.index][i][2] &= (65535 - ac.chap.ROWSTATE_FOLD_COLLAPSED); + } + this.recalculateVisibleRows(); + var me = this; + // console.log('expanding: %i, start: %i', rowIndex, end_row_index); + $runafter(40, function(){me.renderText(true)}); +} + +ac.chap.View.prototype.resize = function() +{ + var h = this.nodeRoot.p().h(); + this.nodeRoot.h(h); + $(this.nodeSidebar).h(h); + this.nodeScrollArea.h(h); + this.nodeFillArea.h(h-$__tune.ui.scrollbarWidth); + $(this.nodeSelectionArea).h(h-$__tune.ui.scrollbarWidth+this.options.rowHeight); + this.recalculateNumRows(); + this.recalculateVisibleRows(); + this.renderText(true); +} + +ac.chap.View.prototype.reloadOptions = function() +{ + this.calculateColRowDim(); + this.recalculateNumCols(false, true); + this.recalculateNumRows(); + this.recalculateVisibleRows(); + this.renderSidebarStub(); + this.renderText(true); +} + +ac.chap.View.prototype.recalculateNumCols = function(node, withoutScrollbar) +{ + node = node || this.nodeRoot; + var w = node.$.offsetWidth; + if (withoutScrollbar) + { + w -= $__tune.ui.scrollbarWidth+61; + } + this.numCols = Math.floor(w/this.options.colWidth); +} + +ac.chap.View.prototype.recalculateNumRows = function(node) +{ + node = node || this.nodeRoot; + this.numRows = Math.floor(node.$.offsetHeight/this.options.rowHeight); +} + +ac.chap.View.prototype.showInteractiveSearch = function() +{ + this.hideInteractiveSearch(); + var pos = this.nodeRoot.abspos(); + var node = $().a($$()).pos(true).x(pos.x+58).y(pos.y).z(2000).w(this.nodeRoot.w()-$__tune.ui.scrollbarWidth-61).h(24).o(0.8); + node.s('background:#000;border:1px solid #777;border-top:0;'); + var search_key_id = 'is_key_?'.embed(this.window.ident); + var ht = '
'; + node.t(ht); + var me = this; + var status_node = node.g('td:0'); + var search_key_node = node.g('input:0'); + var original_caret_pos = [me.window.caret.position[0], me.window.caret.position[1]]; + var last_keyword = ''; + var selection = me.window.getSelection(); + search_key_node.e('keydown', function(evt) + { + evt.stopPropagation(); + if (40 == evt.keyCode) + { + me.window.runAction(ac.chap.ACTION_CUSTOM, {action:'SearchKeyword', direction:'down'}); + me.scrollToRow(me.window.caret.position[0], false, true); + me.window.processActionResult(true, true); + evt.preventDefault(); + return true; + } + else if (38 == evt.keyCode) + { + me.window.runAction(ac.chap.ACTION_CUSTOM, {action:'SearchKeyword', direction:'up'}); + me.scrollToRow(me.window.caret.position[0], false, true); + me.window.processActionResult(true, true); + evt.preventDefault(); + return true; + } + else if (27 == evt.keyCode) + { + finish(true); + evt.preventDefault(); + return true; + } + else if (13 == evt.keyCode) + { + finish(); + evt.preventDefault(); + return true; + } + }).e('keyup', function(evt) + { + evt.stopPropagation(); + search(); + + }).$.focus(); + node.g('img:0').e('click', function(evt) + { + evt.stopPropagation(); + finish(true); + }); + + function finish(canceled) + { + if (canceled) + { + me.window.removeSelection(); + me.window.runAction(ac.chap.ACTION_CARET, {moveTo:[original_caret_pos[0], original_caret_pos[1]]}); + me.scrollToRow(me.window.caret.position[0], false, true); + me.window.processActionResult(true, true); + } + ac.chap.setActiveComponent(me.window); + me.hideInteractiveSearch(); + } + + function update_status(numFound) + { + status_node.t('Found ? results.'.embed(numFound)); + } + + function search() + { + var keyword = search_key_node.$.value.trim(); + if ('' == keyword || last_keyword == keyword) + { + if ('' == keyword) + { + update_status(0); + } + return; + } + update_status(me.window.getText().split(keyword).length - 1); + last_keyword = keyword; + me.window.removeSelection(); + me.window.runAction(ac.chap.ACTION_CARET, {moveTo:[original_caret_pos[0], original_caret_pos[1]]}); + me.window.runAction(ac.chap.ACTION_CUSTOM, {action:'SetSearchKeyword', keyword:keyword}); + me.window.runAction(ac.chap.ACTION_CUSTOM, {action:'SearchKeyword', direction:'down'}); + me.scrollToRow(me.window.caret.position[0], false, true); + me.window.processActionResult(true, true); + } + + if (null != selection) + { + search_key_node.$.value = selection; + search_key_node.$.select(); + search_key_node.$.focus(); + search(); + } + + + + this.interactiveSearchNode = node; +} + +ac.chap.View.prototype.hideInteractiveSearch = function() +{ + if (this.interactiveSearchNode && this.interactiveSearchNode.is()) + { + this.interactiveSearchNode.rs(); + } +} + +ac.chap.View.prototype.render = function(node) +{ + var w = node.$.offsetWidth; + var h = node.$.offsetHeight; + this.recalculateNumRows(node); + this.recalculateNumCols(node); + node.sa('chap-view', 'true'); + var me = this; + this.nodeRoot = node.a($$()).pos(true).w(w).h(h).n('acw-chap').s('background:?'.embed(this.theme.background)); + this.interactiveSearchNode = null; + + var w_rows = 58; + w -= w_rows; + this.nodeSidebar = this.nodeRoot.a($$()).pos(true).x(0).y(0).w(w_rows).h(h).s('overflow:hidden').n('sidebar'); + + // rendering sidebar stub + this.renderSidebarStub(); + + + this.nodeScrollArea = this.nodeRoot.a($$()).pos(true).x(w_rows).y(0).w(w).h(h).n('scroll-area').s('overflow:auto'); + this.nodeScrollArea.e('scroll', function(evt) + { + var offset = Math.floor(me.nodeScrollArea.$.scrollTop/me.options.rowHeight); + var map = me.window.row_id_map[me.index]; + var row_index = 0; + for ( var i=0; i' + ch_encode_markup(token[1]) + ''; + } + else + { + rend_chunk += ch_encode_markup(token[1]); + } + i += token[1].length - 1; + // offset = i + token[1].length; + offset = i + 1; + // console.log(token, offset, rend_chunk); + } + rend_chunk += ch_encode_markup(chunk.substr(offset)); + chunk = rend_chunk; + } + else + { + chunk = ch_encode_markup(chunk); + } + return chunk; +} + +ac.chap.View.prototype.renderTextRow = function(node, rowIndex, renderedPreviously) +{ + var row = this.window.char_map[rowIndex]; + var rendered_row = ''; + var offset = 0; + var font_style = ';font:' + this.window.options.font.size + 'px ' + this.window.options.font.family; + var interpolation = this.window.language.stringInterpolation; + + var row_state = this.window.row_id_map[this.index][rowIndex][2]; + + if ( 'undefined' != typeof this.window.syntax_map[rowIndex] && 0 < this.window.syntax_map[rowIndex].length ) + { + // console.log(this.window.syntax_map[rowIndex]); + var n = this.window.syntax_map[rowIndex].length; + for ( var i=0; i' + ch_encode_markup(chunk.substring(0, m.index)) + ''; + new_chunk += this.renderChunk(chunk.substr(m.index, m[interpolation[1]].length)); + chunk = chunk.substr(m.index + m[interpolation[1]].length); + // console.warn(chunk); + } + new_chunk += '' + ch_encode_markup(chunk) + ''; + // console.info(new_chunk); + rendered_row += new_chunk; + } + else + { + rendered_row += ''+ch_encode_markup(chunk)+''; + // console.log(rendered_row); + } + } + else + { + rendered_row += ch_encode_markup(chunk); + } + offset = -1 == end_offset ? row.length : end_offset; + } + } + rendered_row += this.renderChunk(row.substr(offset)); + // console.log(rendered_row); + // rendering custom selection (search results, errors and such) + // !!!!!!!!!! + // NOT USED NOW !!!!! + // !!!!!!!!!! + // !!!!!!!!!! + // !!!!!!!!!! + // !!!!!!!!!! + if ( false && ac.chap.ROWSTATE_SELECTION == (row_state & ac.chap.ROWSTATE_SELECTION) ) + { + var range = this.window.row_id_map[this.index][rowIndex][5]; + if ( 0 == range[0] && this.window.char_map[rowIndex].length == range[1] ) + { + rendered_row = ''+rendered_row+''; + } + else + { + // console.log(range); + var raw = rendered_row; + var n = raw.length; + var col_index = 0; + var selection_started = false; + var offset = 0; + // console.log('before: %s', raw); + for ( var i=0; i'); + i += ix; + // console.log('ix: %s', ix); + + if ( selection_started ) + { + var c = ''; + rendered_row = rendered_row.substr(0, i+offset+1)+c+rendered_row.substr(i+offset+1); + offset += c.length; + } + continue; + } + if ( range[0] == col_index ) + { + selection_started = true; + var c = ''; + rendered_row = rendered_row.substr(0, i+offset)+c+rendered_row.substr(i+offset); + offset += c.length; + } + if ( selection_started && range[1]-1 < col_index ) + { + selection_started = false; + var c = ''; + rendered_row = rendered_row.substr(0, i+offset)+c+rendered_row.substr(i+offset); + break; + } + if ( '&' == ch ) + { + if ( '<' == raw.substr(i, 4) || '>' == raw.substr(i, 4) ) + { + i += 3; + } + else if ( '&' == raw.substr(i, 5) ) + { + i += 4; + } + } + col_index++; + } + if ( selection_started ) + { + rendered_row += ''; + } + } + } +// console.log(rendered_row); + // making intelligent tabelators - note, using simple replace of \t doesn't work + var ix = 0; + var tab = this.options.tabelator; + var raw = this.window.char_map[rowIndex]; + var tab_stack = []; + while ( -1 != ix ) + { + ix = raw.indexOf('\t'); + if ( -1 != ix ) + { + var tab_length = tab.length - (ix % tab.length); + raw = raw.substr(0,ix)+tab.substr(0, tab_length)+raw.substr(ix+1); + tab_stack.push(tab_length); + } + } + for ( var i=0; i'); + if ( -1 == ix ) + { + break; + } + i += ix; + continue; + } + var n_ch = 0; + if ( '&' == ch ) + { + if ( '<' == rendered_row.substr(i,4) || '>' == rendered_row.substr(i,4) ) + { + n_ch = 3; + } + else if ( '&' == rendered_row.substr(i,5) ) + { + n_ch = 4; + } + } + printable += ch.charAt(0); + if ( this.numCols == printable.length ) + { + raw = raw.substr(0, i+offset+n_ch)+'
'+raw.substr(i+offset+n_ch); + num_subrows++; + offset += 4; + printable = ''; + } + i += n_ch; + } + rendered_row = raw; + } + if ( ac.chap.ROWSTATE_FOLD_EXPAND == (row_state & ac.chap.ROWSTATE_FOLD_EXPAND) ) + { + var end_index = this.window.row_id_map[this.index][rowIndex][3][1]; + var content = ch_encode_markup(this.window.char_map.slice(rowIndex, end_index+1).join('\n').replace(/"/ig, "''")); + rendered_row += '
'.embed(content); + } + node.setAttribute('num-subrows', num_subrows); + + if ( $__tune.isIE ) + { + // IE trims input source in innerHTML + rendered_row = ch_encode_markup_spaces(rendered_row); + } + node.innerHTML = rendered_row; +} + +ac.chap.View.prototype.recalculateVisibleRows = function() +{ + var map = this.window.row_id_map[this.index]; + var n = map.length; + var i = 0; + var num_visibles = 0; + while ( i < n ) + { + var state = map[i][2]; + if ( 0 == (ac.chap.ROWSTATE_FOLD_COLLAPSED & state) ) + { + num_visibles++; + } + i++; + } + this.numVisibleRows = num_visibles; +} + +ac.chap.View.prototype.getVisibleRowIndices = function() +{ + var map = this.window.row_id_map[this.index]; + var i = 0; + var index = this.startRow; + var indices = []; + while ( i++ <= this.numRows ) + { + if ( 'undefined' == typeof this.window.row_id_map[this.index][index] ) + { + break; + } + var state = this.window.row_id_map[this.index][index][2]; + if ( ac.chap.ROWSTATE_FOLD_COLLAPSED == (ac.chap.ROWSTATE_FOLD_COLLAPSED & state) ) + { + // collapsed + i--; + index++; + continue; + } + indices.push(index); + if ( ac.chap.ROWSTATE_FOLD_EXPAND == (state & ac.chap.ROWSTATE_FOLD_EXPAND) ) + { + // collapsed folding + var refered_row_index = this.window.row_id_map[this.index][index][3][1]; + index = refered_row_index + 1; + } + else + { + index++; + } + } + return indices; +} + +ac.chap.View.prototype.renderRowSidebar = function(position, rowIndex, rowNode, forceCompleteRedraw) +{ + if (!this.nodeSidebar.firstChild.childNodes.item(position)) + { + return; + } + var bar_node = this.nodeSidebar.firstChild.childNodes.item(position).firstChild; + if ( 0 == rowNode.offsetHeight ) + { + rowNode.style.height = this.options.rowHeight; + } + var num_subrows = parseInt(rowNode.getAttribute('num-subrows')); + var cache_id = forceCompleteRedraw ? 'none' : (num_subrows+':'+this.window.row_id_map[this.index][rowIndex].join('-')); + if (bar_node.getAttribute('sidebar-cache-id') == cache_id && 'none' != cache_id) + { + return; + } + if ('none' != cache_id) + { + bar_node.setAttribute('sidebar-cache-id', cache_id); + bar_node.firstChild.style.fontSize = (this.window.options.font.size-2) + 'px'; + } + // console.log(cache_id); + + var row_height = num_subrows * this.options.rowHeight; + bar_node.parentNode.style.height = row_height + 'px'; + if (forceCompleteRedraw) + { + bar_node.firstChild.style.fontSize = (this.window.options.font.size-2) + 'px'; + } + var ht = rowIndex+1; + if ( this.options.wordWrap ) + { + var htt = '
'; + for ( var i=1; i this.numCols ) + { + ix_r++; + ix_c -= this.numCols; + } + if ( ii == range[1] ) + { + offset[2] = ix_r; + offset[3] = ix_c; + break; + } + } + if ( -1 == offset[3] ) + { + offset[2] = ix_r+1;//offset[0]+1; + } + node_row_selection.style.top = (node_row.offsetTop + this.options.rowHeight*offset[0]) + 'px'; +// console.log('%o', offset); + if ( offset[0] == offset[2] ) + { + // selection stays non-wrapped + node_row_selection.style.left = offset[1]*this.options.colWidth + 'px'; + node_row_selection.style.width = ((offset[3]-offset[1])*this.options.colWidth) + 'px'; +// console.log(node_row_selection.style.width); + } + else + { + // finishing current node + if ( -1 == offset[1] ) + { + // caret stays on the end of the row + node_row_selection.style.left = (ix_c*this.options.colWidth) + 'px'; + node_row_selection.style.width = (node_row.offsetWidth - (ix_c*this.options.colWidth)) + 'px'; + } + else + { + node_row_selection.style.left = (offset[1]*this.options.colWidth) + 'px'; + node_row_selection.style.width = (node_row.offsetWidth - (offset[1]*this.options.colWidth)) + 'px'; + } + // marking as non-cacheable + node_row_selection.removeAttribute('cachid'); + // creating additional ones + for ( ii=offset[0]+1; ii<=offset[2]; ii++ ) + { + node_row_selection = node_cache.appendChild(document.createElement('div')); + node_row_selection.style.background = this.theme.selectionStyle; + node_row_selection.style.position = 'absolute'; + node_row_selection.style.left = '0px'; + node_row_selection.style.top = (node_row.offsetTop+ii*this.options.rowHeight) + 'px'; + node_row_selection.style.height = this.options.rowHeight + 'px'; + if ( ii != offset[2] ) + { + node_row_selection.style.width = node_row.offsetWidth + 'px'; + } + else + { + node_row_selection.style.width = ((offset[3])*this.options.colWidth) + 'px'; + } + } + } + + } + else + { + var offset_x1 = this.getRenderedStringDimension(row_index, 0, range[0])[0]; + var offset_x2 = this.getRenderedStringDimension(row_index, 0, range[1]+1)[0]; + node_row_selection.style.left = offset_x1 + 'px'; + node_row_selection.style.width = (offset_x2 - offset_x1) + 'px'; + } + } + } + // console.log('selection after range: %o', this.window.row_id_map[this.index][row_index][3]); + + } + } + // console.log('%o', this.window.row_id_map[this.index][0]); + var ht = node_cache.innerHTML; + this.nodeSelectionArea.innerHTML = ht; +} + +ac.chap.View.prototype.renderText = function(forceCompleteRedraw) +{ + var row_indices = this.getVisibleRowIndices(); + var num_rows = row_indices.length; +// console.log('view: %s - row indices: %o', this.index, row_indices); + +// console.log('view: %s - num rows x cols [%s x %s]', this.index, this.numRows, this.numCols); + // checking to see if only one row changed - the most usual case + var changed_row_index = -1; + var changed_row_position = -1; + for ( var i=0; i fill_area_h ) + { + fill_area_h = this.nodeRoot.h()-$__tune.ui.scrollbarWidth; + } + this.nodeFillArea.h(fill_area_h); + + } + else + { + this.nodeEditAreaCache.innerHTML = ''; + } + if ( parseInt(this.nodeSidebar.firstChild.style.top) != top_offset ) + { + this.nodeSidebar.firstChild.style.top = (top_offset)+'px'; + this.nodeSidebar.firstChild.style.height = (this.nodeSidebar.offsetHeight - $__tune.ui.scrollbarWidth - top_offset)+'px'; + } + this.renderSelection(); +} + + + +/* + * ac.Chap - Text Editing Component widget - Settings file + */ + +if ( 'undefined' == typeof ac ) +{ + var ac = {chap:{}}; +} + + +$class('ac.chap.KeyMap', +{ + construct:function() + { + this.definition = {}; + this.isMac = true; + this.wordCompleteCache = null; + this.snippetCompleteCache = null; + this.searchKeyword = ''; + this.initDefinition(); + } +}); + +ac.chap.KeyMap.prototype.initDefinition = function() +{ +} + +ac.chap.KeyMap.prototype.importCommands = function(commands) +{ + var n = commands.length; + for ( var i=0; i looking_for_len && words_prev[i+1].substr(0, looking_for_len) == looking_for ) + { + if ( -1 == found_words_index.indexOf(' '+words_prev[i+1]) ) + { + found_words.push(words_prev[i+1]); + found_words_index += ' '+words_prev[i+1]; + } + } + if ( words_next[i] && words_next[i].length > looking_for_len && words_next[i].substr(0, looking_for_len) == looking_for ) + { + if ( -1 == found_words_index.indexOf(' '+words_next[i]) ) + { + found_words.push(words_next[i]); + found_words_index += ' '+words_next[i]; + } + } + } + if ( 1 < found_words.length ) + { +// console.log('results found: %o', found_words); + wcc.results = found_words; + wcc.index = 0; + proceed_complete = true; + } + } + else + { + proceed_complete = true; + } + var num_results = wcc.results.length; + if ( proceed_complete && 0 < num_results ) + { + var index = wcc.index; + index += params.direction ? 1 : -1; + if ( num_results <= index ) + { + index = 0; + } + else if ( 0 > index ) + { + index = num_results-1; + } +// console.log('n:%s i:%s', num_results, index); + // let's not add the following operation to the transaction/undo log + component.stopTransactionLog(); + component.runAction(ac.chap.ACTION_CARET, {move:'prev_word'}); + component.runAction(ac.chap.ACTION_SELECTION, {add:true}); + component.runAction(ac.chap.ACTION_DELETE, {character:false}); + component.runAction(ac.chap.ACTION_INSERT, {string:wcc.results[index]}); + component.startTransactionLog(); + wcc.index = index; + wcc.position = [component.caret.position[0], component.caret.position[1]]; + } + else + { + wcc.results = []; + } + this.wordCompleteCache = wcc; + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT; +} + +ac.chap.KeyMap.prototype.getAffectedRows = function(component, caretRow) +{ + var from_row = caretRow; + var to_row = caretRow; + if (null != component.selection) + { + var start_pos = component.selection.startPosition[0]; + var end_pos = component.selection.endPosition[0]; + if (-1 == component.selection.endPosition[1]) + { + end_pos--; + } + from_row = Math.min(start_pos, end_pos); + to_row = Math.max(start_pos, end_pos); + } + return [from_row, to_row]; +} + +ac.chap.KeyMap.prototype.action_Indent = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + var affected_rows = this.getAffectedRows(component, caretRow); + var tab = component.getTabelator(); + for (var i=affected_rows[0]; i<=affected_rows[1]; i++) + { + if ('right' == params.direction) + { + component.insertIntoCharacterMap(tab, i, 0); + } + else + { + var row = component.char_map[i]; + var index = 0; + while (('\t' == row.charAt(index) || ' ' == row.charAt(index)) && (row.length > index) && (tab.length > index)) index++; + if (0 < index) + { + component.removeFromCharacterMap(i, 0, i, index); + } + } + } + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_Comment = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + if (!component.language) + { + return 0; + } + var markers = component.language.singleRowCommentStartMarkers; + if (0 == markers.length) + { + return 0; + } + var marker = markers[0]; + var tab = component.getTabelator(); + var affected_rows = this.getAffectedRows(component, caretRow); + var tab = component.getTabelator(); + var prepend_text = marker + ' '; + for (var i=affected_rows[0]; i<=affected_rows[1]; i++) + { + var row = component.char_map[i]; + var index = row.indexOf(marker); + if (-1 != index && 0 == row.substring(0, index).replace(tab, '').replace(' ', '')) + { + // was commented + component.removeFromCharacterMap(i, 0, i, index+marker.length); + } + else + { + // will be commented + component.insertIntoCharacterMap(marker, i, 0); + } + } + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_RuntimeOption = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + component.setRuntimeOption(params['key'], params['value']); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_SearchInteractive = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + component.showInteractiveSearch(); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_SetSearchKeyword = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + if (params['keyword']) + { + this.searchKeyword = params['keyword']; + return 0; + } + if (component.selection && component.selection.startPosition[0] == component.selection.endPosition[0]) + { + this.searchKeyword = component.getSelection(); + return ac.chap.ACTION_RES_SELECTIONCHANGED; + } + return 0; +} + +ac.chap.KeyMap.prototype.action_SearchKeyword = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + if ('' == this.searchKeyword) + { + return 0; + } + row_index = caretRow; + var index = 0; + var search_down = 'down' == params['direction']; + do + { + var row = component.char_map[row_index]; + var offset = 0; + console.log(ac.chap.activeComponent.activeView); + if (row_index == caretRow) + { + if (search_down) + { + // if (row.substring(caretCol, this.searchKeyword.length) == this.searchKeyword) + // { + // // offset = this.searchKeyword.length; + // } + row = row.substr(caretCol) + offset += caretCol; + } + else + { + if (row.substring(caretCol-this.searchKeyword.length, caretCol)) + { + offset = this.searchKeyword.length; + } + row = row.substring(0, caretCol - offset); + offset = 0; + } + } + index = search_down ? row.indexOf(this.searchKeyword) : row.lastIndexOf(this.searchKeyword); + if (-1 != index) + { + index += offset; + component.runAction(ac.chap.ACTION_SELECTION, {remove:true}); + component.runAction(ac.chap.ACTION_CARET, {moveTo:[row_index, index]}); + component.runAction(ac.chap.ACTION_CARET, {store:true}); + component.runAction(ac.chap.ACTION_CARET, {moveTo:[row_index, index+this.searchKeyword.length]}); + component.runAction(ac.chap.ACTION_SELECTION, {add:true}); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED | ac.chap.ACTION_RES_SCROLLTOCARET; + } + row_index += search_down ? 1 : -1; + if (search_down && row_index == component.char_map.length) + { + row_index = 0; + } + else if (!search_down && -1 == row_index) + { + row_index = component.char_map.length-1; + } + } + while (caretRow != row_index); + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_SELECTIONCHANGED; +} + +ac.chap.KeyMap.prototype.action_SmartIndent = function(keyCode, controlKeysMask, caretRow, caretCol, component, params) +{ + // console.log(params); + var line = component.getLineAt(caretRow); + var m = params['indent_tab_when_ends'] ? line.match(/^([ \t]*)(.*)$/) : line.match(/^([ \t]*)([^ \t]*)/); + // console.log(m); + var prepend_text = m[1]; + if (params['indent_tab_when_ends'] || params['indent_tab_when_starts']) + { + m[2] = m[2].trim(); + var indent_when_values = params['indent_tab_when_ends'] ? params['indent_tab_when_ends'].split(' ') : params['indent_tab_when_starts'].split(' '); + for (var i=0; i#'); + code = code.substr(start_ix+4, m[3].length)+'##'+code.substr(start_ix+m[0].length-m[1].length); + any_change = true; + } + else + { + break; + } + } + code_chunks.push(code); + code = code_chunks.join(''); + } + re = /##/; + while ( true ) + { + m = re.exec(code) + if ( null == m ) + { + break; + } + tabstops[m[1]][1] = tabstops[m[1]][1].replace(/##/g, ''); + tabstops[m[1]][2] = m.index; + tabstops[m[1]][3] = tabstops[m[1]][1].length; + code = code.substr(0, m.index)+code.substr(m.index+m[0].length).replace('##', ''); + } + + // [getting mirrors] + re = /\{\$\{(\d)\:([^\}]*)\}\}/; + while ( true ) + { + m = re.exec(code) + if ( null == m ) + { + break; + } + code = code.substr(0, m.index)+m[2]+code.substr(m.index+m[0].length); + var ix_end = code.indexOf('{$'+m[1]+'}'); + if ( -1 == ix_end ) + { + console.error('Invalid snippet definition. Mirror `?` does not have `{$?}` mirrored location specified.'.embed(m[1], m[1])); + break; + } + tabstops[m[1]] = ['mi', m[2], m.index, m[2].length, ix_end]; + if ( m.index > ix_end ) + { + console.error('Unsupported feature. Mirror `?` should have mirrored location positioned AFTER itself.'.embed(m[1])); + } + code = code.substr(0, ix_end)+code.substr(ix_end+4); + } + + // [getting tabstops] + re = /(^|[^\\])\$(\d)/; + while (true) + { + m = re.exec(code); + if ( null == m ) + { + break; + } + tab_id = m[2]; + if ( tabstops[tab_id] ) + { + console.error('Invalid snippet definition. Tabstop `?` already defined as placeholder at position `?`. Snippet source: `?`.'.embed(tab_id, m.index, code)); + break; + } + var start_ix = m.index+m[1].length; + tabstops[tab_id] = ['ts', '', start_ix, 0]; + code = code.substr(0, start_ix)+code.substr(start_ix+2); + var offset = m[1].length + 2; + for ( var tab_id in tabstops ) + { + // console.log('adjusting: %s, %s < %s', tab_id, start_ix, tabstops[tab_id][2]); + if ( 'mi' == tabstops[tab_id][0] ) + { + if ( start_ix < tabstops[tab_id][2] ) + { + tabstops[tab_id][2] -= offset; + } + if ( start_ix < tabstops[tab_id][4] ) + { + tabstops[tab_id][4] -= offset; + } + } + else if ( 'ph' == tabstops[tab_id][0] && start_ix < tabstops[tab_id][2] ) + { + tabstops[tab_id][2] -= offset; + } + } + } + // $0 not defined, will be at the end of the snippet by default + if ( !tabstops[0] ) + { + tabstops[0] = ['ts', '', code.length, 0]; + } + + // [postprocessing - unescape] + code = code.replace(/\0/g, '$'); + + + for ( var tab_id in tabstops ) + { + var placeholder = tabstops[tab_id]; +// console.log('#%s : %o', tab_id, placeholder); + } + var scc = + { + firstInitialized:true, + insertCaretPosition:[caretRow, caretCol], + tabstops: tabstops, + callbackIndex: -1, + activeTabStopIndex:tabstops[1] ? 1 : 0, + activeTabStopRange:[], + activeTabStopContent:'', + activeTabStopNested:[], + wasSelection:wasSelection, + tabActivation:tabActivation + } + + if ( !scc.wasSelection ) + { + component.runAction(ac.chap.ACTION_CARET, {move:'prev_word'}); + component.runAction(ac.chap.ACTION_SELECTION, {add:true}); + component.runAction(ac.chap.ACTION_DELETE, {character:true}); + } + + var tabstop = tabstops[scc.activeTabStopIndex]; + + + component.runAction(ac.chap.ACTION_INSERT, {string:code.substr(0, tabstop[2])}); + component.runAction(ac.chap.ACTION_INSERT, {string:code.substr(tabstop[2]), skipCaretChange:true}); + + // selecting default value + var selection_changed = false; + if ( '' != tabstop[1] ) + { + component.runAction(ac.chap.ACTION_CARET, {store:true}); + component.runAction(ac.chap.ACTION_CARET, {moveBy:'column', value:tabstop[1].length}); + component.runAction(ac.chap.ACTION_SELECTION, {add:true}); + selection_changed = true; + } + + if ( 0 != scc.activeTabStopIndex ) + { + // starting action listener + this.snippetCompleteCache = scc; + this.snippetCompleteCache.callbackIndex = component.addActionListener(ac.chap.ACTION_LISTENER_BOTH, this, this.snippetCompleteActionListener); + } + } + return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT | (selection_changed ? ac.chap.ACTION_RES_SELECTIONCHANGED : 0); +// return ac.chap.ACTION_RES_REDRAWCARET | ac.chap.ACTION_RES_REDRAWTEXT; +} + +ac.chap.KeyMap.prototype.snippetCompleteActionListener = function(component, action, type, actionResult, actionType, params, caretRow, caretCol) +{ + var scc = action.snippetCompleteCache; + + if ( ac.chap.ACTION_LISTENER_BEFORE == type && !scc.firstInitialized ) + { + // before action listener + // check if we are still in the tabstop range +// var offset = component.char_map[caretRow].substr() + // console.log('activeTabStopRange: %o', scc.activeTabStopRange); + if ( caretRow < scc.activeTabStopRange[0] || caretRow > scc.activeTabStopRange[2] || caretCol < scc.activeTabStopRange[1] || caretCol > scc.activeTabStopRange[3] ) + { + // out of range, canceling whole snippet logic + component.removeActionListener(scc.callbackIndex); + component.removeSelection(); + delete action.snippetCompleteCache; + // console.log('CANCELED'); + return; + } +// console.log('before: %s - [%s,%s]', actionType, caretRow, caretCol); + } + else + { + // after action listener + if ( scc.firstInitialized ) + { + scc.firstInitialized = false; + var tabstop = scc.tabstops[scc.activeTabStopIndex]; + caretCol -= tabstop[1].length; + var code_rows = tabstop[1].split('\n'); + var num_code_rows = code_rows.length; + var to_caret_row = caretRow + num_code_rows - 1; + var to_caret_col = (to_caret_row == caretRow ? caretCol : 0) + code_rows[num_code_rows-1].length; + scc.activeTabStopRange = [caretRow, caretCol, to_caret_row, to_caret_col]; + scc.stopMarker = component.char_map[to_caret_row].substr(to_caret_col); + // creating list of nested tabstops + for ( var i in scc.tabstops ) + { + if ( i == scc.activeTabStopIndex ) + { + continue; + } + var c_tabstop = scc.tabstops[i]; + if ( c_tabstop[2] >= tabstop[2] && (c_tabstop[2] + c_tabstop[3] <= tabstop[2] + tabstop[3]) ) + { + scc.activeTabStopNested[i] = true; + } + } + if ( 'mi' != tabstop[0] ) + { + scc.activeTabStopContent = tabstop[1]; + } + scc.firstRealRun = false; + +// action.snippetCompletePostInit(component, caretRow, caretCol); + // console.log('firstRealRun: %o', scc.activeTabStopRange); + } + else + { + // console.log('next: %o', scc.activeTabStopRange); + // adjust the range by finding the stop marker + var n = component.char_map.length; + var i = scc.activeTabStopRange[0]; + var found = false; + var max_iter = 50; +// var offset_range = [scc.activeTabStopRange[2], scc.activeTabStopRange[3]]; + var new_content = ''; + var old_content = scc.activeTabStopContent; + while ( i active_offset ) + { + if ( scc.activeTabStopNested[i] ) + { + // nested + delete scc.tabstops[i]; + } + else + { + tabstop[2] += offset; + // console.log('ADJUSTING: #%s by %s, new: %s', i, offset, tabstop[2]); + } + } + } + } + } + // console.log('after %s(%s, %s) : %o', actionType, caretRow, caretCol, scc.activeTabStopRange); + } +} + + +ac.chap.KeyMap.prototype.compile = function(source) +{ + /* example: + + KEY: -13+shift + selection(add:true) + caret(move:'up') + + KEY: -27 + caret(move:'row_end') + + ... + .. + . + */ + var rows = source.split('\n'); + var n = rows.length; + var re_definition = /^KEY\:\s*[-\d]*[\+\w\s]*$/; + var re_chain = /^[^\(]*\(.*\)\s*$/; + var src = ''; + var chain = []; + var last_key_code = null; + for ( var i=0; i)/i, 0, ac.chap.CHUNK_OPERATOR], + [/(\(|\)|\[|\]|\{|\})/i, 0, ac.chap.CHUNK_PARENTHESIS] + ]; + this.wordDelimiter = /[\w\.\d]/; + this.indentIgnoreMarker = /[\.]/; +} + + +ac.chap.lang = {}; + + + + + + +/* loader stuff - you are free to modify as needed */ + + +// !! Make sure, bundle_*.js is loaded prior launching this function - the bundle defines ac.chap.langEAmy, EAmyJavaScript etc. +function showEditor(templateNode) +{ + var source = templateNode.value; + templateNode = $(templateNode); + var w = templateNode.w(); + var h = templateNode.h(); + + var node = templateNode.p().ib($$(), templateNode).w(w).h(h); + templateNode.d(false); + + var language = ac.chap.lang.JavaScript; + var keymap = ac.chap.keymap.EAmyJavaScript; + + var instance = $new(ac.chap.Window, {language:ac.chap.lang.EAmy, keymap:ac.chap.Keymap}); + instance.addView(node, {theme:ac.chap.theme.EAmy, rowHeight:11, colWidth:7, wordWrap:true, tabelator:' '}); + + instance.show(); + instance.setSnippets(eamy.snippets); + instance.keymap.importSnippets(eamy.snippets); + instance.edit(source); + eamy.instances.push(instance); + +} + +// !! Remove from here and include in your section if you want. +// document.write(''); + + +// Performed upon loading the page. You are free to remove it and call the showEditor() (or modified version of it) in order to launch the editing component. Code of the showEditor should give you enough clue. +$__tune.event.addListener(self, 'load', function(evt) +{ + // this is basically a search for any
"; + html= t.translate(html, editAreas[id]["settings"]["language"]); + span.innerHTML= html; + father= d.getElementById(id).parentNode; + next= d.getElementById(id).nextSibling; + if(next==null) + father.appendChild(span); + else + father.insertBefore(span, next); + } + + if(!editAreas[id]["initialized"]) + { + t.execCommand(id, "EA_init"); // ini callback + if(editAreas[id]["settings"]["display"]=="later"){ + editAreas[id]["initialized"]= true; + return; + } + } + + if(t.isIE){ // launch IE selection checkup + t.init_ie_textarea(id); + } + + // get toolbar content + var area=editAreas[id]; + + for(i=0; i'; + } + + // add plugins scripts if not already loaded by the compressor (but need to load language in all the case) + for(i=0; i'; + t.iframe_script+=''; + } + + + // create css link for the iframe if the whole css text has not been already loaded by the compressor + if(!t.iframe_css){ + t.iframe_css=""; + } + + + // create template + template= t.template.replace(/\[__BASEURL__\]/g, t.baseURL); + template= template.replace("[__TOOLBAR__]",html_toolbar_content); + + + // fill template with good language sentences + template= t.translate(template, area["settings"]["language"], "template"); + + // add css_code + template= template.replace("[__CSSRULES__]", t.iframe_css); + // add js_code + template= template.replace("[__JSCODE__]", t.iframe_script); + + // add version_code + template= template.replace("[__EA_VERSION__]", t.version); + //template=template.replace(/\{\$([^\}]+)\}/gm, this.traduc_template); + + //editAreas[area["settings"]["id"]]["template"]= template; + + area.textarea=d.getElementById(area["settings"]["id"]); + editAreas[area["settings"]["id"]]["textarea"]=area.textarea; + + // if removing previous instances from DOM before (fix from Marcin) + if(typeof(window.frames["frame_"+area["settings"]["id"]])!='undefined') + delete window.frames["frame_"+area["settings"]["id"]]; + + // insert template in the document after the textarea + father= area.textarea.parentNode; + /* var container= document.createElement("div"); + container.id= "EditArea_frame_container_"+area["settings"]["id"]; + */ + content= d.createElement("iframe"); + content.name= "frame_"+area["settings"]["id"]; + content.id= "frame_"+area["settings"]["id"]; + content.style.borderWidth= "0px"; + setAttribute(content, "frameBorder", "0"); // IE + content.style.overflow="hidden"; + content.style.display="none"; + + + next= area.textarea.nextSibling; + if(next==null) + father.appendChild(content); + else + father.insertBefore(content, next) ; + f=window.frames["frame_"+area["settings"]["id"]]; + f.document.open(); + f.editAreas=editAreas; + f.area_id= area["settings"]["id"]; + f.document.area_id= area["settings"]["id"]; + f.document.write(template); + f.document.close(); + + // frame.editAreaLoader=this; + //editAreas[area["settings"]["id"]]["displayed"]=true; + + }, + + toggle : function(id, toggle_to){ + + /* if((editAreas[id]["displayed"]==true && toggle_to!="on") || toggle_to=="off"){ + this.toggle_off(id); + }else if((editAreas[id]["displayed"]==false && toggle_to!="off") || toggle_to=="on"){ + this.toggle_on(id); + }*/ + if(!toggle_to) + toggle_to= (editAreas[id]["displayed"]==true)?"off":"on"; + if(editAreas[id]["displayed"]==true && toggle_to=="off"){ + this.toggle_off(id); + }else if(editAreas[id]["displayed"]==false && toggle_to=="on"){ + this.toggle_on(id); + } + + return false; + }, + + // static function + toggle_off : function(id){ + var fs=window.frames,f,t,parNod,nxtSib,selStart,selEnd,scrollTop,scrollLeft; + if(fs["frame_"+id]) + { + f = fs["frame_"+id]; + t = editAreas[id]["textarea"]; + if(f.editArea.fullscreen['isFull']) + f.editArea.toggle_full_screen(false); + editAreas[id]["displayed"]=false; + + // set wrap to off to keep same display mode (some browser get problem with this, so it need more complex operation + t.wrap = "off"; // for IE + setAttribute(t, "wrap", "off"); // for Firefox + parNod = t.parentNode; + nxtSib = t.nextSibling; + parNod.removeChild(t); + parNod.insertBefore(t, nxtSib); + + // restore values + t.value= f.editArea.textarea.value; + selStart = f.editArea.last_selection["selectionStart"]; + selEnd = f.editArea.last_selection["selectionEnd"]; + scrollTop = f.document.getElementById("result").scrollTop; + scrollLeft = f.document.getElementById("result").scrollLeft; + + + document.getElementById("frame_"+id).style.display='none'; + + t.style.display="inline"; + + try{ // IE will give an error when trying to focus an invisible or disabled textarea + t.focus(); + } catch(e){}; + if(this.isIE){ + t.selectionStart= selStart; + t.selectionEnd = selEnd; + t.focused = true; + set_IE_selection(t); + }else{ + if(this.isOpera && this.isOpera < 9.6 ){ // Opera bug when moving selection start and selection end + t.setSelectionRange(0, 0); + } + try{ + t.setSelectionRange(selStart, selEnd); + } catch(e) {}; + } + t.scrollTop= scrollTop; + t.scrollLeft= scrollLeft; + f.editArea.execCommand("toggle_off"); + + } + }, + + // static function + toggle_on : function(id){ + var fs=window.frames,f,t,selStart=0,selEnd=0,scrollTop=0,scrollLeft=0,curPos,elem; + + if(fs["frame_"+id]) + { + f = fs["frame_"+id]; + t = editAreas[id]["textarea"]; + area= f.editArea; + area.textarea.value= t.value; + + // store display values; + curPos = editAreas[id]["settings"]["cursor_position"]; + + if(t.use_last==true) + { + selStart = t.last_selectionStart; + selEnd = t.last_selectionEnd; + scrollTop = t.last_scrollTop; + scrollLeft = t.last_scrollLeft; + t.use_last=false; + } + else if( curPos == "auto" ) + { + try{ + selStart = t.selectionStart; + selEnd = t.selectionEnd; + scrollTop = t.scrollTop; + scrollLeft = t.scrollLeft; + //alert(scrollTop); + }catch(ex){} + } + + // set to good size + this.set_editarea_size_from_textarea(id, document.getElementById("frame_"+id)); + t.style.display="none"; + document.getElementById("frame_"+id).style.display="inline"; + area.execCommand("focus"); // without this focus opera doesn't manage well the iframe body height + + + // restore display values + editAreas[id]["displayed"]=true; + area.execCommand("update_size"); + + f.document.getElementById("result").scrollTop= scrollTop; + f.document.getElementById("result").scrollLeft= scrollLeft; + area.area_select(selStart, selEnd-selStart); + area.execCommand("toggle_on"); + + + } + else + { + /* if(this.isIE) + get_IE_selection(document.getElementById(id)); */ + elem= document.getElementById(id); + elem.last_selectionStart= elem.selectionStart; + elem.last_selectionEnd= elem.selectionEnd; + elem.last_scrollTop= elem.scrollTop; + elem.last_scrollLeft= elem.scrollLeft; + elem.use_last=true; + editAreaLoader.start(id); + } + }, + + set_editarea_size_from_textarea : function(id, frame){ + var elem,width,height; + elem = document.getElementById(id); + + width = Math.max(editAreas[id]["settings"]["min_width"], elem.offsetWidth)+"px"; + height = Math.max(editAreas[id]["settings"]["min_height"], elem.offsetHeight)+"px"; + if(elem.style.width.indexOf("%")!=-1) + width = elem.style.width; + if(elem.style.height.indexOf("%")!=-1) + height = elem.style.height; + //alert("h: "+height+" w: "+width); + + frame.style.width= width; + frame.style.height= height; + }, + + set_base_url : function(){ + var t=this,elems,i,docBasePath; + + if( !this.baseURL ){ + elems = document.getElementsByTagName('script'); + + for( i=0; i'; + html += ''; + return html; + }, + + get_control_html : function(button_name, lang) { + var t=this,i,but,html,si; + for (i=0; i"; + case "|": + case "separator": + return ''; + case "select_font": + html= ""; + return html; + case "syntax_selection": + html= ""; + return html; + } + + return "["+button_name+"]"; + }, + + + get_template : function(){ + if(this.template=="") + { + var xhr_object = null; + if(window.XMLHttpRequest) // Firefox + xhr_object = new XMLHttpRequest(); + else if(window.ActiveXObject) // Internet Explorer + xhr_object = new ActiveXObject("Microsoft.XMLHTTP"); + else { // XMLHttpRequest not supported + alert("XMLHTTPRequest not supported. EditArea not loaded"); + return; + } + + xhr_object.open("GET", this.baseURL+"template.html", false); + xhr_object.send(null); + if(xhr_object.readyState == 4) + this.template=xhr_object.responseText; + else + this.has_error(); + } + }, + + // translate text + translate : function(text, lang, mode){ + if(mode=="word") + text=editAreaLoader.get_word_translation(text, lang); + else if(mode="template"){ + editAreaLoader.current_language= lang; + text=text.replace(/\{\$([^\}]+)\}/gm, editAreaLoader.translate_template); + } + return text; + }, + + translate_template : function(){ + return editAreaLoader.get_word_translation(EditAreaLoader.prototype.translate_template.arguments[1], editAreaLoader.current_language); + }, + + get_word_translation : function(val, lang){ + var i; + + for( i in editAreaLoader.lang[lang]){ + if(i == val) + return editAreaLoader.lang[lang][i]; + } + return "_"+val; + }, + + load_script : function(url){ + var t=this,d=document,script,head; + + if( t.loadedFiles[url] ) + return; + //alert("load: "+url); + try{ + script= d.createElement("script"); + script.type= "text/javascript"; + script.src= url; + script.charset= "UTF-8"; + d.getElementsByTagName("head")[0].appendChild(script); + }catch(e){ + d.write(''); + } + + t.loadedFiles[url] = true; + }, + + add_event : function(obj, name, handler) { + try{ + if (obj.attachEvent) { + obj.attachEvent("on" + name, handler); + } else{ + obj.addEventListener(name, handler, false); + } + }catch(e){} + }, + + remove_event : function(obj, name, handler){ + try{ + if (obj.detachEvent) + obj.detachEvent("on" + name, handler); + else + obj.removeEventListener(name, handler, false); + }catch(e){} + }, + + + // reset all the editareas in the form that have been reseted + reset : function(e){ + var formObj,is_child,i,x; + + formObj = editAreaLoader.isIE ? window.event.srcElement : e.target; + if(formObj.tagName!='FORM') + formObj= formObj.form; + + for( i in editAreas ){ + is_child= false; + for( x=0;x old_sel["start"]) // if text was selected, cursor at the end + this.setSelectionRange(id, new_sel["end"], new_sel["end"]); + else // cursor in the middle + this.setSelectionRange(id, old_sel["start"]+open_tag.length, old_sel["start"]+open_tag.length); + }, + + // hide both EditArea and normal textarea + hide : function(id){ + var fs= window.frames,d=document,t=this,scrollTop,scrollLeft,span; + if(d.getElementById(id) && !t.hidden[id]) + { + t.hidden[id]= {}; + t.hidden[id]["selectionRange"]= t.getSelectionRange(id); + if(d.getElementById(id).style.display!="none") + { + t.hidden[id]["scrollTop"]= d.getElementById(id).scrollTop; + t.hidden[id]["scrollLeft"]= d.getElementById(id).scrollLeft; + } + + if(fs["frame_"+id]) + { + t.hidden[id]["toggle"]= editAreas[id]["displayed"]; + + if(fs["frame_"+id] && editAreas[id]["displayed"]==true){ + scrollTop = fs["frame_"+ id].document.getElementById("result").scrollTop; + scrollLeft = fs["frame_"+ id].document.getElementById("result").scrollLeft; + }else{ + scrollTop = d.getElementById(id).scrollTop; + scrollLeft = d.getElementById(id).scrollLeft; + } + t.hidden[id]["scrollTop"]= scrollTop; + t.hidden[id]["scrollLeft"]= scrollLeft; + + if(editAreas[id]["displayed"]==true) + editAreaLoader.toggle_off(id); + } + + // hide toggle button and debug box + span= d.getElementById("EditAreaArroundInfos_"+id); + if(span){ + span.style.display='none'; + } + + // hide textarea + d.getElementById(id).style.display= "none"; + } + }, + + // restore hidden EditArea and normal textarea + show : function(id){ + var fs= window.frames,d=document,t=this,span; + if((elem=d.getElementById(id)) && t.hidden[id]) + { + elem.style.display= "inline"; + elem.scrollTop= t.hidden[id]["scrollTop"]; + elem.scrollLeft= t.hidden[id]["scrollLeft"]; + span= d.getElementById("EditAreaArroundInfos_"+id); + if(span){ + span.style.display='inline'; + } + + if(fs["frame_"+id]) + { + + // restore toggle button and debug box + + + // restore textarea + elem.style.display= "inline"; + + // restore EditArea + if(t.hidden[id]["toggle"]==true) + editAreaLoader.toggle_on(id); + + scrollTop = t.hidden[id]["scrollTop"]; + scrollLeft = t.hidden[id]["scrollLeft"]; + + if(fs["frame_"+id] && editAreas[id]["displayed"]==true){ + fs["frame_"+ id].document.getElementById("result").scrollTop = scrollTop; + fs["frame_"+ id].document.getElementById("result").scrollLeft = scrollLeft; + }else{ + elem.scrollTop = scrollTop; + elem.scrollLeft = scrollLeft; + } + + } + // restore selection + sel = t.hidden[id]["selectionRange"]; + t.setSelectionRange(id, sel["start"], sel["end"]); + delete t.hidden[id]; + } + }, + + // get the current file datas (for multi file editing mode) + getCurrentFile : function(id){ + return this.execCommand(id, 'get_file', this.execCommand(id, 'curr_file')); + }, + + // get the given file datas (for multi file editing mode) + getFile : function(id, file_id){ + return this.execCommand(id, 'get_file', file_id); + }, + + // get all the openned files datas (for multi file editing mode) + getAllFiles : function(id){ + return this.execCommand(id, 'get_all_files()'); + }, + + // open a file (for multi file editing mode) + openFile : function(id, file_infos){ + return this.execCommand(id, 'open_file', file_infos); + }, + + // close the given file (for multi file editing mode) + closeFile : function(id, file_id){ + return this.execCommand(id, 'close_file', file_id); + }, + + // close the given file (for multi file editing mode) + setFileEditedMode : function(id, file_id, to){ + var reg1,reg2; + reg1 = new RegExp('\\\\', 'g'); + reg2 = new RegExp('"', 'g'); + return this.execCommand(id, 'set_file_edited_mode("'+ file_id.replace(reg1, '\\\\').replace(reg2, '\\"') +'", '+ to +')'); + }, + + + // allow to access to editarea functions and datas (for advanced users only) + execCommand : function(id, cmd, fct_param){ + switch(cmd){ + case "EA_init": + if(editAreas[id]['settings']["EA_init_callback"].length>0) + eval(editAreas[id]['settings']["EA_init_callback"]+"('"+ id +"');"); + break; + case "EA_delete": + if(editAreas[id]['settings']["EA_delete_callback"].length>0) + eval(editAreas[id]['settings']["EA_delete_callback"]+"('"+ id +"');"); + break; + case "EA_submit": + if(editAreas[id]['settings']["submit_callback"].length>0) + eval(editAreas[id]['settings']["submit_callback"]+"('"+ id +"');"); + break; + } + if(window.frames["frame_"+id] && window.frames["frame_"+ id].editArea){ + if(fct_param!=undefined) + return eval('window.frames["frame_'+ id +'"].editArea.'+ cmd +'(fct_param);'); + else + return eval('window.frames["frame_'+ id +'"].editArea.'+ cmd +';'); + } + return false; + } +}; + + var editAreaLoader= new EditAreaLoader(); + var editAreas= {}; + ADDED applications/admin/static/edit_area/elements_functions.js Index: applications/admin/static/edit_area/elements_functions.js ================================================================== --- applications/admin/static/edit_area/elements_functions.js +++ applications/admin/static/edit_area/elements_functions.js @@ -0,0 +1,336 @@ +/**** + * This page contains some general usefull functions for javascript + * + ****/ + + + // need to redefine this functiondue to IE problem + function getAttribute( elm, aName ) { + var aValue,taName,i; + try{ + aValue = elm.getAttribute( aName ); + }catch(exept){} + + if( ! aValue ){ + for( i = 0; i < elm.attributes.length; i ++ ) { + taName = elm.attributes[i] .name.toLowerCase(); + if( taName == aName ) { + aValue = elm.attributes[i] .value; + return aValue; + } + } + } + return aValue; + }; + + // need to redefine this function due to IE problem + function setAttribute( elm, attr, val ) { + if(attr=="class"){ + elm.setAttribute("className", val); + elm.setAttribute("class", val); + }else{ + elm.setAttribute(attr, val); + } + }; + + /* return a child element + elem: element we are searching in + elem_type: type of the eleemnt we are searching (DIV, A, etc...) + elem_attribute: attribute of the searched element that must match + elem_attribute_match: value that elem_attribute must match + option: "all" if must return an array of all children, otherwise return the first match element + depth: depth of search (-1 or no set => unlimited) + */ + function getChildren(elem, elem_type, elem_attribute, elem_attribute_match, option, depth) + { + if(!option) + var option="single"; + if(!depth) + var depth=-1; + if(elem){ + var children= elem.childNodes; + var result=null; + var results= []; + for (var x=0;x0){ + results= results.concat(result); + } + }else if(result!=null){ + return result; + } + } + } + } + if(option=="all") + return results; + } + return null; + }; + + function isChildOf(elem, parent){ + if(elem){ + if(elem==parent) + return true; + while(elem.parentNode != 'undefined'){ + return isChildOf(elem.parentNode, parent); + } + } + return false; + }; + + function getMouseX(e){ + + if(e!=null && typeof(e.pageX)!="undefined"){ + return e.pageX; + }else{ + return (e!=null?e.x:event.x)+ document.documentElement.scrollLeft; + } + }; + + function getMouseY(e){ + if(e!=null && typeof(e.pageY)!="undefined"){ + return e.pageY; + }else{ + return (e!=null?e.y:event.y)+ document.documentElement.scrollTop; + } + }; + + function calculeOffsetLeft(r){ + return calculeOffset(r,"offsetLeft") + }; + + function calculeOffsetTop(r){ + return calculeOffset(r,"offsetTop") + }; + + function calculeOffset(element,attr){ + var offset=0; + while(element){ + offset+=element[attr]; + element=element.offsetParent + } + return offset; + }; + + /** return the computed style + * @param: elem: the reference to the element + * @param: prop: the name of the css property + */ + function get_css_property(elem, prop) + { + if(document.defaultView) + { + return document.defaultView.getComputedStyle(elem, null).getPropertyValue(prop); + } + else if(elem.currentStyle) + { + var prop = prop.replace(/-\D/gi, function(sMatch) + { + return sMatch.charAt(sMatch.length - 1).toUpperCase(); + }); + return elem.currentStyle[prop]; + } + else return null; + } + +/**** + * Moving an element + ***/ + + var _mCE; // currently moving element + + /* allow to move an element in a window + e: the event + id: the id of the element + frame: the frame of the element + ex of use: + in html: + or + in javascript: document.getElementById("my_div").onmousedown= start_move_element + */ + function start_move_element(e, id, frame){ + var elem_id=(e.target || e.srcElement).id; + if(id) + elem_id=id; + if(!frame) + frame=window; + if(frame.event) + e=frame.event; + + _mCE= frame.document.getElementById(elem_id); + _mCE.frame=frame; + frame.document.onmousemove= move_element; + frame.document.onmouseup= end_move_element; + /*_mCE.onmousemove= move_element; + _mCE.onmouseup= end_move_element;*/ + + //alert(_mCE.frame.document.body.offsetHeight); + + mouse_x= getMouseX(e); + mouse_y= getMouseY(e); + //window.status=frame+ " elem: "+elem_id+" elem: "+ _mCE + " mouse_x: "+mouse_x; + _mCE.start_pos_x = mouse_x - (_mCE.style.left.replace("px","") || calculeOffsetLeft(_mCE)); + _mCE.start_pos_y = mouse_y - (_mCE.style.top.replace("px","") || calculeOffsetTop(_mCE)); + return false; + }; + + function end_move_element(e){ + _mCE.frame.document.onmousemove= ""; + _mCE.frame.document.onmouseup= ""; + _mCE=null; + }; + + function move_element(e){ + var newTop,newLeft,maxLeft; + + if( _mCE.frame && _mCE.frame.event ) + e=_mCE.frame.event; + newTop = getMouseY(e) - _mCE.start_pos_y; + newLeft = getMouseX(e) - _mCE.start_pos_x; + + maxLeft = _mCE.frame.document.body.offsetWidth- _mCE.offsetWidth; + max_top = _mCE.frame.document.body.offsetHeight- _mCE.offsetHeight; + newTop = Math.min(Math.max(0, newTop), max_top); + newLeft = Math.min(Math.max(0, newLeft), maxLeft); + + _mCE.style.top = newTop+"px"; + _mCE.style.left = newLeft+"px"; + return false; + }; + +/*** + * Managing a textarea (this part need the navigator infos from editAreaLoader + ***/ + + var nav= editAreaLoader.nav; + + // allow to get infos on the selection: array(start, end) + function getSelectionRange(textarea){ + return {"start": textarea.selectionStart, "end": textarea.selectionEnd}; + }; + + // allow to set the selection + function setSelectionRange(t, start, end){ + t.focus(); + + start = Math.max(0, Math.min(t.value.length, start)); + end = Math.max(start, Math.min(t.value.length, end)); + + if( nav.isOpera && nav.isOpera < 9.6 ){ // Opera bug when moving selection start and selection end + t.selectionEnd = 1; + t.selectionStart = 0; + t.selectionEnd = 1; + t.selectionStart = 0; + } + t.selectionStart = start; + t.selectionEnd = end; + //textarea.setSelectionRange(start, end); + + if(nav.isIE) + set_IE_selection(t); + }; + + + // set IE position in Firefox mode (textarea.selectionStart and textarea.selectionEnd). should work as a repeated task + function get_IE_selection(t){ + var d=document,div,range,stored_range,elem,scrollTop,relative_top,line_start,line_nb,range_start,range_end,tab; + if(t && t.focused) + { + if(!t.ea_line_height) + { // calculate the lineHeight + div= d.createElement("div"); + div.style.fontFamily= get_css_property(t, "font-family"); + div.style.fontSize= get_css_property(t, "font-size"); + div.style.visibility= "hidden"; + div.innerHTML="0"; + d.body.appendChild(div); + t.ea_line_height= div.offsetHeight; + d.body.removeChild(div); + } + //t.focus(); + range = d.selection.createRange(); + try + { + stored_range = range.duplicate(); + stored_range.moveToElementText( t ); + stored_range.setEndPoint( 'EndToEnd', range ); + if(stored_range.parentElement() == t){ + // the range don't take care of empty lines in the end of the selection + elem = t; + scrollTop = 0; + while(elem.parentNode){ + scrollTop+= elem.scrollTop; + elem = elem.parentNode; + } + + // var scrollTop= t.scrollTop + document.body.scrollTop; + + // var relative_top= range.offsetTop - calculeOffsetTop(t) + scrollTop; + relative_top= range.offsetTop - calculeOffsetTop(t)+ scrollTop; + // alert("rangeoffset: "+ range.offsetTop +"\ncalcoffsetTop: "+ calculeOffsetTop(t) +"\nrelativeTop: "+ relative_top); + line_start = Math.round((relative_top / t.ea_line_height) +1); + + line_nb = Math.round(range.boundingHeight / t.ea_line_height); + + range_start = stored_range.text.length - range.text.length; + tab = t.value.substr(0, range_start).split("\n"); + range_start += (line_start - tab.length)*2; // add missing empty lines to the selection + t.selectionStart = range_start; + + range_end = t.selectionStart + range.text.length; + tab = t.value.substr(0, range_start + range.text.length).split("\n"); + range_end += (line_start + line_nb - 1 - tab.length)*2; + t.selectionEnd = range_end; + } + } + catch(e){} + } + if( t && t.id ) + { + setTimeout("get_IE_selection(document.getElementById('"+ t.id +"'));", 50); + } + }; + + function IE_textarea_focus(){ + event.srcElement.focused= true; + } + + function IE_textarea_blur(){ + event.srcElement.focused= false; + } + + // select the text for IE (take into account the \r difference) + function set_IE_selection( t ){ + var nbLineStart,nbLineStart,nbLineEnd,range; + if(!window.closed){ + nbLineStart=t.value.substr(0, t.selectionStart).split("\n").length - 1; + nbLineEnd=t.value.substr(0, t.selectionEnd).split("\n").length - 1; + try + { + range = document.selection.createRange(); + range.moveToElementText( t ); + range.setEndPoint( 'EndToStart', range ); + range.moveStart('character', t.selectionStart - nbLineStart); + range.moveEnd('character', t.selectionEnd - nbLineEnd - (t.selectionStart - nbLineStart) ); + range.select(); + } + catch(e){} + } + }; + + + editAreaLoader.waiting_loading["elements_functions.js"]= "loaded"; ADDED applications/admin/static/edit_area/highlight.js Index: applications/admin/static/edit_area/highlight.js ================================================================== --- applications/admin/static/edit_area/highlight.js +++ applications/admin/static/edit_area/highlight.js @@ -0,0 +1,407 @@ + // change_to: "on" or "off" + EditArea.prototype.change_highlight= function(change_to){ + if(this.settings["syntax"].length==0 && change_to==false){ + this.switchClassSticky(_$("highlight"), 'editAreaButtonDisabled', true); + this.switchClassSticky(_$("reset_highlight"), 'editAreaButtonDisabled', true); + return false; + } + + if(this.do_highlight==change_to) + return false; + + + this.getIESelection(); + var pos_start= this.textarea.selectionStart; + var pos_end= this.textarea.selectionEnd; + + if(this.do_highlight===true || change_to==false) + this.disable_highlight(); + else + this.enable_highlight(); + this.textarea.focus(); + this.textarea.selectionStart = pos_start; + this.textarea.selectionEnd = pos_end; + this.setIESelection(); + + }; + + EditArea.prototype.disable_highlight= function(displayOnly){ + var t= this, a=t.textarea, new_Obj, old_class, new_class; + + t.selection_field.innerHTML=""; + t.selection_field_text.innerHTML=""; + t.content_highlight.style.visibility="hidden"; + // replacing the node is far more faster than deleting it's content in firefox + new_Obj= t.content_highlight.cloneNode(false); + new_Obj.innerHTML= ""; + t.content_highlight.parentNode.insertBefore(new_Obj, t.content_highlight); + t.content_highlight.parentNode.removeChild(t.content_highlight); + t.content_highlight= new_Obj; + old_class= parent.getAttribute( a,"class" ); + if(old_class){ + new_class= old_class.replace( "hidden","" ); + parent.setAttribute( a, "class", new_class ); + } + + a.style.backgroundColor="transparent"; // needed in order to see the bracket finders + + //var icon= document.getElementById("highlight"); + //setAttribute(icon, "class", getAttribute(icon, "class").replace(/ selected/g, "") ); + //t.restoreClass(icon); + //t.switchClass(icon,'editAreaButtonNormal'); + t.switchClassSticky(_$("highlight"), 'editAreaButtonNormal', true); + t.switchClassSticky(_$("reset_highlight"), 'editAreaButtonDisabled', true); + + t.do_highlight=false; + + t.switchClassSticky(_$("change_smooth_selection"), 'editAreaButtonSelected', true); + if(typeof(t.smooth_selection_before_highlight)!="undefined" && t.smooth_selection_before_highlight===false){ + t.change_smooth_selection_mode(false); + } + + // this.textarea.style.backgroundColor="#FFFFFF"; + }; + + EditArea.prototype.enable_highlight= function(){ + var t=this, a=t.textarea, new_class; + t.show_waiting_screen(); + + t.content_highlight.style.visibility="visible"; + new_class =parent.getAttribute(a,"class")+" hidden"; + parent.setAttribute( a, "class", new_class ); + + // IE can't manage mouse click outside text range without this + if( t.isIE ) + a.style.backgroundColor="#FFFFFF"; + + t.switchClassSticky(_$("highlight"), 'editAreaButtonSelected', false); + t.switchClassSticky(_$("reset_highlight"), 'editAreaButtonNormal', false); + + t.smooth_selection_before_highlight=t.smooth_selection; + if(!t.smooth_selection) + t.change_smooth_selection_mode(true); + t.switchClassSticky(_$("change_smooth_selection"), 'editAreaButtonDisabled', true); + + + t.do_highlight=true; + t.resync_highlight(); + + t.hide_waiting_screen(); + }; + + /** + * Ask to update highlighted text + * @param Array infos - Array of datas returned by EditArea.get_selection_infos() + */ + EditArea.prototype.maj_highlight= function(infos){ + // for speed mesure + var debug_opti="",tps_start= new Date().getTime(), tps_middle_opti=new Date().getTime(); + var t=this, hightlighted_text, updated_highlight; + var textToHighlight=infos["full_text"], doSyntaxOpti = false, doHtmlOpti = false, stay_begin="", stay_end="", trace_new , trace_last; + + if(t.last_text_to_highlight==infos["full_text"] && t.resync_highlight!==true) + return; + + // OPTIMISATION: will search to update only changed lines + if(t.reload_highlight===true){ + t.reload_highlight=false; + }else if(textToHighlight.length==0){ + textToHighlight="\n "; + }else{ + // get text change datas + changes = t.checkTextEvolution(t.last_text_to_highlight,textToHighlight); + + // check if it can only reparse the changed text + trace_new = t.get_syntax_trace(changes.newTextLine).replace(/\r/g, ''); + trace_last = t.get_syntax_trace(changes.lastTextLine).replace(/\r/g, ''); + doSyntaxOpti = ( trace_new == trace_last ); + + // check if the difference comes only from a new line created + // => we have to remember that the editor can automaticaly add tabulation or space after the new line) + if( !doSyntaxOpti && trace_new == "\n"+trace_last && /^[ \t\s]*\n[ \t\s]*$/.test( changes.newText.replace(/\r/g, '') ) && changes.lastText =="" ) + { + doSyntaxOpti = true; + } + + // we do the syntax optimisation + if( doSyntaxOpti ){ + + tps_middle_opti=new Date().getTime(); + + stay_begin= t.last_hightlighted_text.split("\n").slice(0, changes.lineStart).join("\n"); + if(changes.lineStart>0) + stay_begin+= "\n"; + stay_end= t.last_hightlighted_text.split("\n").slice(changes.lineLastEnd+1).join("\n"); + if(stay_end.length>0) + stay_end= "\n"+stay_end; + + // Final check to see that we're not in the middle of span tags + if( stay_begin.split(' trace: "+trace_new + +"\nchanged_last_text: "+ch.lastText+" => trace: "+trace_last + //debug_opti+= "\nchanged: "+ infos["full_text"].substring(ch.posStart, ch.posNewEnd); + + "\nchanged_line: "+ch.newTextLine + + "\nlast_changed_line: "+ch.lastTextLine + +"\nstay_begin: "+ stay_begin.slice(-100) + +"\nstay_end: "+ stay_end.substr( 0, 100 ); + //debug_opti="start: "+stay_begin_len+ "("+nb_line_start_unchanged+") end: "+ (stay_end_len)+ "("+(splited.length-nb_line_end_unchanged)+") "; + //debug_opti+="changed: "+ textToHighlight.substring(stay_begin_len, textToHighlight.length-stay_end_len)+" \n"; + + //debug_opti+="changed: "+ stay_begin.substr(stay_begin.length-200)+ "----------"+ textToHighlight+"------------------"+ stay_end.substr(0,200) +"\n"; + +"\n"; + } + + + // END OPTIMISATION + } + + tps_end_opti = new Date().getTime(); + + // apply highlight + updated_highlight = t.colorize_text(textToHighlight); + tpsAfterReg = new Date().getTime(); + + /*** + * see if we can optimize for updating only the required part of the HTML code + * + * The goal here will be to find the text node concerned by the modification and to update it + */ + //------------------------------------------- + + // disable latest optimization tricks (introduced in 0.8.1 and removed in 0.8.2), TODO: check for another try later + doSyntaxOpti = doHtmlOpti = false; + if( doSyntaxOpti ) + { + try + { + var replacedBloc, i, nbStart = '', nbEnd = '', newHtml, lengthOld, lengthNew; + replacedBloc = t.last_hightlighted_text.substring( stay_begin.length, t.last_hightlighted_text.length - stay_end.length ); + + lengthOld = replacedBloc.length; + lengthNew = updated_highlight.length; + + // find the identical caracters at the beginning + for( i=0; i < lengthOld && i < lengthNew && replacedBloc.charAt(i) == updated_highlight.charAt(i) ; i++ ) + { + } + nbStart = i; + // find the identical caracters at the end + for( i=0; i + nbStart < lengthOld && i + nbStart < lengthNew && replacedBloc.charAt(lengthOld-i-1) == updated_highlight.charAt(lengthNew-i-1) ; i++ ) + { + } + nbEnd = i; + //console.log( nbStart, nbEnd, replacedBloc, updated_highlight ); + // get the changes + lastHtml = replacedBloc.substring( nbStart, lengthOld - nbEnd ); + newHtml = updated_highlight.substring( nbStart, lengthNew - nbEnd ); + + // We can do the optimisation only if we havn't touch to span elements + if( newHtml.indexOf('').replace( /&/g, '&'); + + nbOpendedSpan = beginStr.split(' 0 ) + { + nbClosed--; + parentSpan = parentSpan.parentNode; + } + + // find the position of the last opended tag + while( parentSpan.parentNode != t.content_highlight && parentSpan.parentNode.tagName != 'PRE' && ( tmpMaxStartOffset = Math.max( 0, beginStr.lastIndexOf( '', maxStartOffset ) ); + + // count the number of sub spans + nbSubSpanBefore = beginStr.substr( lastEndPos ).split('' ) ) == -1 ? beginStr.length : beginStr.length - ( lastIndex + 1 ); + //nbUnchangedChars = ? beginStr.length : beginStr.substr( lastIndex + 1 ).replace( /</g, '<').replace( />/g, '>').replace( /&/g, '&').length; + + if( ( lastIndex = beginStr.lastIndexOf( '>' ) ) == -1 ) + { + nbUnchangedChars = beginStr.length; + } + else + { + nbUnchangedChars = beginStr.substr( lastIndex + 1 ).replace( /</g, '<').replace( />/g, '>').replace( /&/g, '&').length; + //nbUnchangedChars += beginStr.substr( ).replace( /&/g, '&').replace( //g, '>').length - beginStr.length; + } + //alert( nbUnchangedChars ); + // console.log( span, textNode, nbOpendedSpan,nbClosedSpan, span.nextSibling, textNode.length, nbUnchangedChars, lastHtml, lastHtml.length, newHtml, newHtml.length ); + // alert( textNode.parentNode.className +'-'+ textNode.parentNode.tagName+"\n"+ textNode.data +"\n"+ nbUnchangedChars +"\n"+ lastHtml.length +"\n"+ newHtml +"\n"+ newHtml.length ); + // console.log( nbUnchangedChars, lastIndex, beginStr.length, beginStr.replace(/&/g, '&'), lastHtml.length, '|', newHtml.replace( /\t/g, 't').replace( /\n/g, 'n').replace( /\r/g, 'r'), lastHtml.replace( /\t/g, 't').replace( /\n/g, 'n').replace( /\r/, 'r') ); + // console.log( textNode.data.replace(/&/g, '&') ); + // IE only manage \r for cariage return in textNode and not \n or \r\n + if( t.isIE ) + { + nbUnchangedChars -= ( beginStr.substr( beginStr.length - nbUnchangedChars ).split("\n").length - 1 ); + //alert( textNode.data.replace(/\r/g, '_r').replace(/\n/g, '_n')); + textNode.replaceData( nbUnchangedChars, lastHtml.replace(/\n/g, '').length, newHtml.replace(/\n/g, '') ); + } + else + { + textNode.replaceData( nbUnchangedChars, lastHtml.length, newHtml ); + } + //--------] + } + } + // an exception shouldn't occured but if replaceData failed at least it won't break everything + catch( e ) + { + // throw e; + // console.log( e ); + doHtmlOpti = false; + } + + } + + /*** END HTML update's optimisation ***/ + // end test + + // console.log( (TPS6-TPS5), (TPS5-TPS4), (TPS4-TPS3), (TPS3-TPS2), (TPS2-TPS1), _CPT ); + // get the new highlight content + tpsAfterOpti2 = new Date().getTime(); + hightlighted_text = stay_begin + updated_highlight + stay_end; + if( !doHtmlOpti ) + { + // update the content of the highlight div by first updating a clone node (as there is no display in the same time for t node it's quite faster (5*)) + var new_Obj= t.content_highlight.cloneNode(false); + if( ( t.isIE && t.isIE < 8 ) || ( t.isOpera && t.isOpera < 9.6 ) ) + new_Obj.innerHTML= "
" + hightlighted_text + "
"; + else + new_Obj.innerHTML= ""+ hightlighted_text +""; + + t.content_highlight.parentNode.replaceChild(new_Obj, t.content_highlight); + + t.content_highlight= new_Obj; + } + + t.last_text_to_highlight= infos["full_text"]; + t.last_hightlighted_text= hightlighted_text; + + tps3=new Date().getTime(); + + if(t.settings["debug"]){ + //lineNumber=tab_text.length; + //t.debug.value+=" \nNB char: "+_$("src").value.length+" Nb line: "+ lineNumber; + + t.debug.value= "Tps optimisation "+(tps_end_opti-tps_start) + +" | tps reg exp: "+ (tpsAfterReg-tps_end_opti) + +" | tps opti HTML : "+ (tpsAfterOpti2-tpsAfterReg) + ' '+ ( doHtmlOpti ? 'yes' : 'no' ) + +" | tps update highlight content: "+ (tps3-tpsAfterOpti2) + +" | tpsTotal: "+ (tps3-tps_start) + + "("+tps3+")\n"+ debug_opti; + // t.debug.value+= "highlight\n"+hightlighted_text;*/ + } + + }; + + EditArea.prototype.resync_highlight= function(reload_now){ + this.reload_highlight=true; + this.last_text_to_highlight=""; + this.focus(); + if(reload_now) + this.check_line_selection(false); + }; ADDED applications/admin/static/edit_area/images/autocompletion.gif Index: applications/admin/static/edit_area/images/autocompletion.gif ================================================================== --- applications/admin/static/edit_area/images/autocompletion.gif +++ applications/admin/static/edit_area/images/autocompletion.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/close.gif Index: applications/admin/static/edit_area/images/close.gif ================================================================== --- applications/admin/static/edit_area/images/close.gif +++ applications/admin/static/edit_area/images/close.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/fullscreen.gif Index: applications/admin/static/edit_area/images/fullscreen.gif ================================================================== --- applications/admin/static/edit_area/images/fullscreen.gif +++ applications/admin/static/edit_area/images/fullscreen.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/go_to_line.gif Index: applications/admin/static/edit_area/images/go_to_line.gif ================================================================== --- applications/admin/static/edit_area/images/go_to_line.gif +++ applications/admin/static/edit_area/images/go_to_line.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/help.gif Index: applications/admin/static/edit_area/images/help.gif ================================================================== --- applications/admin/static/edit_area/images/help.gif +++ applications/admin/static/edit_area/images/help.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/highlight.gif Index: applications/admin/static/edit_area/images/highlight.gif ================================================================== --- applications/admin/static/edit_area/images/highlight.gif +++ applications/admin/static/edit_area/images/highlight.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/load.gif Index: applications/admin/static/edit_area/images/load.gif ================================================================== --- applications/admin/static/edit_area/images/load.gif +++ applications/admin/static/edit_area/images/load.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/move.gif Index: applications/admin/static/edit_area/images/move.gif ================================================================== --- applications/admin/static/edit_area/images/move.gif +++ applications/admin/static/edit_area/images/move.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/newdocument.gif Index: applications/admin/static/edit_area/images/newdocument.gif ================================================================== --- applications/admin/static/edit_area/images/newdocument.gif +++ applications/admin/static/edit_area/images/newdocument.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/opacity.png Index: applications/admin/static/edit_area/images/opacity.png ================================================================== --- applications/admin/static/edit_area/images/opacity.png +++ applications/admin/static/edit_area/images/opacity.png cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/processing.gif Index: applications/admin/static/edit_area/images/processing.gif ================================================================== --- applications/admin/static/edit_area/images/processing.gif +++ applications/admin/static/edit_area/images/processing.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/redo.gif Index: applications/admin/static/edit_area/images/redo.gif ================================================================== --- applications/admin/static/edit_area/images/redo.gif +++ applications/admin/static/edit_area/images/redo.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/reset_highlight.gif Index: applications/admin/static/edit_area/images/reset_highlight.gif ================================================================== --- applications/admin/static/edit_area/images/reset_highlight.gif +++ applications/admin/static/edit_area/images/reset_highlight.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/save.gif Index: applications/admin/static/edit_area/images/save.gif ================================================================== --- applications/admin/static/edit_area/images/save.gif +++ applications/admin/static/edit_area/images/save.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/search.gif Index: applications/admin/static/edit_area/images/search.gif ================================================================== --- applications/admin/static/edit_area/images/search.gif +++ applications/admin/static/edit_area/images/search.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/smooth_selection.gif Index: applications/admin/static/edit_area/images/smooth_selection.gif ================================================================== --- applications/admin/static/edit_area/images/smooth_selection.gif +++ applications/admin/static/edit_area/images/smooth_selection.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/spacer.gif Index: applications/admin/static/edit_area/images/spacer.gif ================================================================== --- applications/admin/static/edit_area/images/spacer.gif +++ applications/admin/static/edit_area/images/spacer.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/statusbar_resize.gif Index: applications/admin/static/edit_area/images/statusbar_resize.gif ================================================================== --- applications/admin/static/edit_area/images/statusbar_resize.gif +++ applications/admin/static/edit_area/images/statusbar_resize.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/undo.gif Index: applications/admin/static/edit_area/images/undo.gif ================================================================== --- applications/admin/static/edit_area/images/undo.gif +++ applications/admin/static/edit_area/images/undo.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/images/word_wrap.gif Index: applications/admin/static/edit_area/images/word_wrap.gif ================================================================== --- applications/admin/static/edit_area/images/word_wrap.gif +++ applications/admin/static/edit_area/images/word_wrap.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/keyboard.js Index: applications/admin/static/edit_area/keyboard.js ================================================================== --- applications/admin/static/edit_area/keyboard.js +++ applications/admin/static/edit_area/keyboard.js @@ -0,0 +1,145 @@ +var EA_keys = {8:"Retour arriere",9:"Tabulation",12:"Milieu (pave numerique)",13:"Entrer",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"Verr Maj",27:"Esc",32:"Space",33:"Page up",34:"Page down",35:"End",36:"Begin",37:"Left",38:"Up",39:"Right",40:"Down",44:"Impr ecran",45:"Inser",46:"Suppr",91:"Menu Demarrer Windows / touche pomme Mac",92:"Menu Demarrer Windows",93:"Menu contextuel Windows",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Verr Num",145:"Arret defil"}; + + + +function keyDown(e){ + if(!e){ // if IE + e=event; + } + + // send the event to the plugins + for(var i in editArea.plugins){ + if(typeof(editArea.plugins[i].onkeydown)=="function"){ + if(editArea.plugins[i].onkeydown(e)===false){ // stop propaging + if(editArea.isIE) + e.keyCode=0; + return false; + } + } + } + + var target_id=(e.target || e.srcElement).id; + var use=false; + if (EA_keys[e.keyCode]) + letter=EA_keys[e.keyCode]; + else + letter=String.fromCharCode(e.keyCode); + + var low_letter= letter.toLowerCase(); + + if(letter=="Page up" && !AltPressed(e) && !editArea.isOpera){ + editArea.execCommand("scroll_page", {"dir": "up", "shift": ShiftPressed(e)}); + use=true; + }else if(letter=="Page down" && !AltPressed(e) && !editArea.isOpera){ + editArea.execCommand("scroll_page", {"dir": "down", "shift": ShiftPressed(e)}); + use=true; + }else if(editArea.is_editable==false){ + // do nothing but also do nothing else (allow to navigate with page up and page down) + return true; + }else if(letter=="Tabulation" && target_id=="textarea" && !CtrlPressed(e) && !AltPressed(e)){ + if(ShiftPressed(e)) + editArea.execCommand("invert_tab_selection"); + else + editArea.execCommand("tab_selection"); + + use=true; + if(editArea.isOpera || (editArea.isFirefox && editArea.isMac) ) // opera && firefox mac can't cancel tabulation events... + setTimeout("editArea.execCommand('focus');", 1); + }else if(letter=="Entrer" && target_id=="textarea"){ + if(editArea.press_enter()) + use=true; + }else if(letter=="Entrer" && target_id=="area_search"){ + editArea.execCommand("area_search"); + use=true; + }else if(letter=="Esc"){ + editArea.execCommand("close_all_inline_popup", e); + use=true; + }else if(CtrlPressed(e) && !AltPressed(e) && !ShiftPressed(e)){ + switch(low_letter){ + case "f": + editArea.execCommand("area_search"); + use=true; + break; + case "r": + editArea.execCommand("area_replace"); + use=true; + break; + case "q": + editArea.execCommand("close_all_inline_popup", e); + use=true; + break; + case "h": + editArea.execCommand("change_highlight"); + use=true; + break; + case "g": + setTimeout("editArea.execCommand('go_to_line');", 5); // the prompt stop the return false otherwise + use=true; + break; + case "e": + editArea.execCommand("show_help"); + use=true; + break; + case "z": + use=true; + editArea.execCommand("undo"); + break; + case "y": + use=true; + editArea.execCommand("redo"); + break; + default: + break; + } + } + + // check to disable the redo possibility if the textarea content change + if(editArea.next.length > 0){ + setTimeout("editArea.check_redo();", 10); + } + + setTimeout("editArea.check_file_changes();", 10); + + + if(use){ + // in case of a control that sould'nt be used by IE but that is used => THROW a javascript error that will stop key action + if(editArea.isIE) + e.keyCode=0; + return false; + } + //alert("Test: "+ letter + " ("+e.keyCode+") ALT: "+ AltPressed(e) + " CTRL "+ CtrlPressed(e) + " SHIFT "+ ShiftPressed(e)); + + return true; + +}; + + +// return true if Alt key is pressed +function AltPressed(e) { + if (window.event) { + return (window.event.altKey); + } else { + if(e.modifiers) + return (e.altKey || (e.modifiers % 2)); + else + return e.altKey; + } +}; + +// return true if Ctrl key is pressed +function CtrlPressed(e) { + if (window.event) { + return (window.event.ctrlKey); + } else { + return (e.ctrlKey || (e.modifiers==2) || (e.modifiers==3) || (e.modifiers>5)); + } +}; + +// return true if Shift key is pressed +function ShiftPressed(e) { + if (window.event) { + return (window.event.shiftKey); + } else { + return (e.shiftKey || (e.modifiers>3)); + } +}; ADDED applications/admin/static/edit_area/langs/bg.js Index: applications/admin/static/edit_area/langs/bg.js ================================================================== --- applications/admin/static/edit_area/langs/bg.js +++ applications/admin/static/edit_area/langs/bg.js @@ -0,0 +1,54 @@ +/* + * Bulgarian translation + * Author: Valentin Hristov + * Company: SOFTKIT Bulgarian + * Site: http://www.softkit-bg.com + */ +editAreaLoader.lang["bg"]={ +new_document: "нов документ", +search_button: "търсене и замяна", +search_command: "търси следващия / отвори прозорец с търсачка", +search: "търсене", +replace: "замяна", +replace_command: "замяна / отвори прозорец с търсачка", +find_next: "намери следващия", +replace_all: "замени всички", +reg_exp: "реголярни изрази", +match_case: "чуствителен към регистъра", +not_found: "няма резултат.", +occurrence_replaced: "замяната е осъществена.", +search_field_empty: "Полето за търсене е празно", +restart_search_at_begin: "До края на документа. Почни с началото.", +move_popup: "премести прозореца с търсачката", +font_size: "--Размер на шрифта--", +go_to_line: "премени към реда", +go_to_line_prompt: "премени към номера на реда:", +undo: "отмени", +redo: "върни", +change_smooth_selection: "включи/изключи някой от функциите за преглед (по красиво, но повече натоварва)", +highlight: "превключване на оцветяване на синтаксиса включена/изключена", +reset_highlight: "въстанови оцветяване на синтаксиса (ако не е синхронизиран с текста)", +word_wrap: "режим на пренасяне на дълги редове", +help: "за програмата", +save: "съхрани", +load: "зареди", +line_abbr: "Стр", +char_abbr: "Стлб", +position: "Позиция", +total: "Всичко", +close_popup: "затвори прозореца", +shortcuts: "Бързи клавиши", +add_tab: "добави табулация в текста", +remove_tab: "премахни табулацията в текста", +about_notice: "Внимание: използвайте функцията оцветяване на синтаксиса само за малки текстове", +toggle: "Превключи редактор", +accesskey: "Бърз клавиш", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Зареждане...", +fullscreen: "на цял екран", +syntax_selection: "--Синтаксис--", +close_tab: "Затвори файла" +}; ADDED applications/admin/static/edit_area/langs/cs.js Index: applications/admin/static/edit_area/langs/cs.js ================================================================== --- applications/admin/static/edit_area/langs/cs.js +++ applications/admin/static/edit_area/langs/cs.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["cs"]={ +new_document: "Nový dokument", +search_button: "Najdi a nahraď", +search_command: "Hledej další / otevři vyhledávací pole", +search: "Hledej", +replace: "Nahraď", +replace_command: "Nahraď / otevři vyhledávací pole", +find_next: "Najdi další", +replace_all: "Nahraď vše", +reg_exp: "platné výrazy", +match_case: "vyhodnocené výrazy", +not_found: "nenalezené.", +occurrence_replaced: "výskyty nahrazené.", +search_field_empty: "Pole vyhledávání je prázdné", +restart_search_at_begin: "Dosažen konec souboru, začínám od začátku.", +move_popup: "Přesuň vyhledávací okno", +font_size: "--Velikost textu--", +go_to_line: "Přejdi na řádek", +go_to_line_prompt: "Přejdi na řádek:", +undo: "krok zpět", +redo: "znovu", +change_smooth_selection: "Povolit nebo zakázat některé ze zobrazených funkcí (účelnější zobrazení požaduje větší zatížení procesoru)", +highlight: "Zvýrazňování syntaxe zap./vyp.", +reset_highlight: "Obnovit zvýraznění (v případě nesrovnalostí)", +word_wrap: "toggle word wrapping mode", +help: "O programu", +save: "Uložit", +load: "Otevřít", +line_abbr: "Ř.", +char_abbr: "S.", +position: "Pozice", +total: "Celkem", +close_popup: "Zavřít okno", +shortcuts: "Zkratky", +add_tab: "Přidat tabulování textu", +remove_tab: "Odtsranit tabulování textu", +about_notice: "Upozornění! Funkce zvýrazňování textu je k dispozici pouze pro malý text", +toggle: "Přepnout editor", +accesskey: "Přístupová klávesa", +tab: "Záložka", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Zpracovávám ...", +fullscreen: "Celá obrazovka", +syntax_selection: "--vyber zvýrazňovač--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/de.js Index: applications/admin/static/edit_area/langs/de.js ================================================================== --- applications/admin/static/edit_area/langs/de.js +++ applications/admin/static/edit_area/langs/de.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["de"]={ +new_document: "Neues Dokument", +search_button: "Suchen und Ersetzen", +search_command: "Weitersuchen / öffne Suchfeld", +search: "Suchen", +replace: "Ersetzen", +replace_command: "Ersetzen / öffne Suchfeld", +find_next: "Weitersuchen", +replace_all: "Ersetze alle Treffer", +reg_exp: "reguläre Ausdrücke", +match_case: "passt auf den Begriff
", +not_found: "Nicht gefunden.", +occurrence_replaced: "Die Vorkommen wurden ersetzt.", +search_field_empty: "Leeres Suchfeld", +restart_search_at_begin: "Ende des zu durchsuchenden Bereiches erreicht. Es wird die Suche von Anfang an fortgesetzt.", //find a shorter translation +move_popup: "Suchfenster bewegen", +font_size: "--Schriftgröße--", +go_to_line: "Gehe zu Zeile", +go_to_line_prompt: "Gehe zu Zeilennummmer:", +undo: "Rückgängig", +redo: "Wiederherstellen", +change_smooth_selection: "Aktiviere/Deaktiviere einige Features (weniger Bildschirmnutzung aber mehr CPU-Belastung)", +highlight: "Syntax Highlighting an- und ausschalten", +reset_highlight: "Highlighting zurücksetzen (falls mit Text nicht konform)", +word_wrap: "Toggle word wrapping mode", +help: "Info", +save: "Speichern", +load: "Öffnen", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Position", +total: "Gesamt", +close_popup: "Popup schließen", +shortcuts: "Shortcuts", +add_tab: "Tab zum Text hinzufügen", +remove_tab: "Tab aus Text entfernen", +about_notice: "Bemerkung: Syntax Highlighting ist nur für kurze Texte", +toggle: "Editor an- und ausschalten", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "In Bearbeitung...", +fullscreen: "Full-Screen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/dk.js Index: applications/admin/static/edit_area/langs/dk.js ================================================================== --- applications/admin/static/edit_area/langs/dk.js +++ applications/admin/static/edit_area/langs/dk.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["dk"]={ +new_document: "nyt tomt dokument", +search_button: "søg og erstat", +search_command: "find næste / åben søgefelt", +search: "søg", +replace: "erstat", +replace_command: "erstat / åben søgefelt", +find_next: "find næste", +replace_all: "erstat alle", +reg_exp: "regular expressions", +match_case: "forskel på store/små bogstaver
", +not_found: "not found.", +occurrence_replaced: "occurences replaced.", +search_field_empty: "Search field empty", +restart_search_at_begin: "End of area reached. Restart at begin.", +move_popup: "flyt søgepopup", +font_size: "--Skriftstørrelse--", +go_to_line: "gå til linie", +go_to_line_prompt: "gå til linienummer:", +undo: "fortryd", +redo: "gentag", +change_smooth_selection: "slå display funktioner til/fra (smartere display men mere CPU krævende)", +highlight: "slå syntax highlight til/fra", +reset_highlight: "nulstil highlight (hvis den er desynkroniseret fra teksten)", +word_wrap: "toggle word wrapping mode", +help: "om", +save: "gem", +load: "hent", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Position", +total: "Total", +close_popup: "luk popup", +shortcuts: "Genveje", +add_tab: "tilføj tabulation til tekst", +remove_tab: "fjern tabulation fra tekst", +about_notice: "Husk: syntax highlight funktionen bør kun bruge til små tekster", +toggle: "Slå editor til / fra", +accesskey: "Accesskey", +tab: "Tab", +shift: "Skift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Processing...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/en.js Index: applications/admin/static/edit_area/langs/en.js ================================================================== --- applications/admin/static/edit_area/langs/en.js +++ applications/admin/static/edit_area/langs/en.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["en"]={ +new_document: "new empty document", +search_button: "search and replace", +search_command: "search next / open search area", +search: "search", +replace: "replace", +replace_command: "replace / open search area", +find_next: "find next", +replace_all: "replace all", +reg_exp: "regular expressions", +match_case: "match case", +not_found: "not found.", +occurrence_replaced: "occurences replaced.", +search_field_empty: "Search field empty", +restart_search_at_begin: "End of area reached. Restart at begin.", +move_popup: "move search popup", +font_size: "--Font size--", +go_to_line: "go to line", +go_to_line_prompt: "go to line number:", +undo: "undo", +redo: "redo", +change_smooth_selection: "enable/disable some display features (smarter display but more CPU charge)", +highlight: "toggle syntax highlight on/off", +reset_highlight: "reset highlight (if desyncronized from text)", +word_wrap: "toggle word wrapping mode", +help: "about", +save: "save", +load: "load", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Position", +total: "Total", +close_popup: "close popup", +shortcuts: "Shortcuts", +add_tab: "add tabulation to text", +remove_tab: "remove tabulation to text", +about_notice: "Notice: syntax highlight function is only for small text", +toggle: "Toggle editor", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Processing...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/eo.js Index: applications/admin/static/edit_area/langs/eo.js ================================================================== --- applications/admin/static/edit_area/langs/eo.js +++ applications/admin/static/edit_area/langs/eo.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["eo"]={ +new_document: "nova dokumento (vakigas la enhavon)", +search_button: "serĉi / anstataŭigi", +search_command: "pluserĉi / malfermi la serĉo-fenestron", +search: "serĉi", +replace: "anstataŭigi", +replace_command: "anstataŭigi / malfermi la serĉo-fenestron", +find_next: "serĉi", +replace_all: "anstataŭigi ĉion", +reg_exp: "regula esprimo", +match_case: "respekti la usklecon", +not_found: "ne trovita.", +occurrence_replaced: "anstataŭigoj plenumitaj.", +search_field_empty: "La kampo estas malplena.", +restart_search_at_begin: "Fino de teksto ĝisrirata, ĉu daŭrigi el la komenco?", +move_popup: "movi la serĉo-fenestron", +font_size: "--Tipara grando--", +go_to_line: "iri al la linio", +go_to_line_prompt: "iri al la linio numero:", +undo: "rezigni", +redo: "refari", +change_smooth_selection: "ebligi/malebligi la funkcioj de vidigo (pli bona vidigo, sed pli da ŝarĝo de la ĉeforgano)", +highlight: "ebligi/malebligi la sintaksan kolorigon", +reset_highlight: "repravalorizi la sintaksan kolorigon (se malsinkronigon de la teksto)", +word_wrap: "toggle word wrapping mode", +help: "pri", +save: "registri", +load: "ŝarĝi", +line_abbr: "Ln", +char_abbr: "Sg", +position: "Pozicio", +total: "Sumo", +close_popup: "fermi la ŝprucfenestron", +shortcuts: "Fulmoklavo", +add_tab: "aldoni tabon en la tekston", +remove_tab: "forigi tablon el la teksto", +about_notice: "Noto: la sintaksa kolorigo estas nur prikalkulita por mallongaj tekstoj.", +toggle: "baskuligi la redaktilon", +accesskey: "Fulmoklavo", +tab: "Tab", +shift: "Maj", +ctrl: "Ktrl", +esc: "Esk", +processing: "ŝargante...", +fullscreen: "plenekrane", +syntax_selection: "--Sintakso--", +close_tab: "Fermi la dosieron" +}; ADDED applications/admin/static/edit_area/langs/es.js Index: applications/admin/static/edit_area/langs/es.js ================================================================== --- applications/admin/static/edit_area/langs/es.js +++ applications/admin/static/edit_area/langs/es.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["es"]={ +new_document: "nuevo documento vacío", +search_button: "buscar y reemplazar", +search_command: "buscar siguiente / abrir área de búsqueda", +search: "buscar", +replace: "reemplazar", +replace_command: "reemplazar / abrir área de búsqueda", +find_next: "encontrar siguiente", +replace_all: "reemplazar todos", +reg_exp: "expresiones regulares", +match_case: "coincidir capitalización", +not_found: "no encontrado.", +occurrence_replaced: "ocurrencias reemplazadas.", +search_field_empty: "Campo de búsqueda vacío", +restart_search_at_begin: "Se ha llegado al final del área. Se va a seguir desde el principio.", +move_popup: "mover la ventana de búsqueda", +font_size: "--Tamaño de la fuente--", +go_to_line: "ir a la línea", +go_to_line_prompt: "ir a la línea número:", +undo: "deshacer", +redo: "rehacer", +change_smooth_selection: "activar/desactivar algunas características de visualización (visualización más inteligente pero más carga de CPU)", +highlight: "intercambiar resaltado de sintaxis", +reset_highlight: "reinicializar resaltado (si no esta sincronizado con el texto)", +word_wrap: "toggle word wrapping mode", +help: "acerca", +save: "guardar", +load: "cargar", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Posición", +total: "Total", +close_popup: "recuadro de cierre", +shortcuts: "Atajos", +add_tab: "añadir tabulado al texto", +remove_tab: "borrar tabulado del texto", +about_notice: "Aviso: el resaltado de sintaxis sólo funciona para texto pequeño", +toggle: "Cambiar editor", +accesskey: "Tecla de acceso", +tab: "Tab", +shift: "Mayúsc", +ctrl: "Ctrl", +esc: "Esc", +processing: "Procesando...", +fullscreen: "pantalla completa", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/fi.js Index: applications/admin/static/edit_area/langs/fi.js ================================================================== --- applications/admin/static/edit_area/langs/fi.js +++ applications/admin/static/edit_area/langs/fi.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["fi"]={ +new_document: "uusi tyhjä dokumentti", +search_button: "etsi ja korvaa", +search_command: "etsi seuraava / avaa etsintävalikko", +search: "etsi", +replace: "korvaa", +replace_command: "korvaa / avaa etsintävalikko", +find_next: "etsi seuraava", +replace_all: "korvaa kaikki", +reg_exp: "säännölliset lausekkeet", +match_case: "täsmää kirjainkokoon", +not_found: "ei löytynyt.", +occurrence_replaced: "esiintymää korvattu.", +search_field_empty: "Haettava merkkijono on tyhjä", +restart_search_at_begin: "Alueen loppu saavutettiin. Aloitetaan alusta.", +move_popup: "siirrä etsintävalikkoa", +font_size: "--Fontin koko--", +go_to_line: "siirry riville", +go_to_line_prompt: "mene riville:", +undo: "peruuta", +redo: "tee uudelleen", +change_smooth_selection: "kytke/sammuta joitakin näyttötoimintoja (Älykkäämpi toiminta, mutta suurempi CPU kuormitus)", +highlight: "kytke syntaksikorostus päälle/pois", +reset_highlight: "resetoi syntaksikorostus (jos teksti ei ole synkassa korostuksen kanssa)", +word_wrap: "toggle word wrapping mode", +help: "tietoja", +save: "tallenna", +load: "lataa", +line_abbr: "Rv", +char_abbr: "Pos", +position: "Paikka", +total: "Yhteensä", +close_popup: "sulje valikko", +shortcuts: "Pikatoiminnot", +add_tab: "lisää sisennys tekstiin", +remove_tab: "poista sisennys tekstistä", +about_notice: "Huomautus: syntaksinkorostus toimii vain pienelle tekstille", +toggle: "Kytke editori", +accesskey: "Pikanäppäin", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Odota...", +fullscreen: "koko ruutu", +syntax_selection: "--Syntaksi--", +close_tab: "Sulje tiedosto" +}; ADDED applications/admin/static/edit_area/langs/fr.js Index: applications/admin/static/edit_area/langs/fr.js ================================================================== --- applications/admin/static/edit_area/langs/fr.js +++ applications/admin/static/edit_area/langs/fr.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["fr"]={ +new_document: "nouveau document (efface le contenu)", +search_button: "rechercher / remplacer", +search_command: "rechercher suivant / ouvrir la fenêtre de recherche", +search: "rechercher", +replace: "remplacer", +replace_command: "remplacer / ouvrir la fenêtre de recherche", +find_next: "rechercher", +replace_all: "tout remplacer", +reg_exp: "expr. régulière", +match_case: "respecter la casse", +not_found: "pas trouvé.", +occurrence_replaced: "remplacements éffectués.", +search_field_empty: "Le champ de recherche est vide.", +restart_search_at_begin: "Fin du texte atteint, poursuite au début.", +move_popup: "déplacer la fenêtre de recherche", +font_size: "--Taille police--", +go_to_line: "aller à la ligne", +go_to_line_prompt: "aller a la ligne numero:", +undo: "annuler", +redo: "refaire", +change_smooth_selection: "activer/désactiver des fonctions d'affichage (meilleur affichage mais plus de charge processeur)", +highlight: "activer/désactiver la coloration syntaxique", +reset_highlight: "réinitialiser la coloration syntaxique (si désyncronisée du texte)", +word_wrap: "activer/désactiver les retours à la ligne automatiques", +help: "à propos", +save: "sauvegarder", +load: "charger", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Position", +total: "Total", +close_popup: "fermer le popup", +shortcuts: "Racourcis clavier", +add_tab: "ajouter une tabulation dans le texte", +remove_tab: "retirer une tabulation dans le texte", +about_notice: "Note: la coloration syntaxique n'est prévue que pour de courts textes.", +toggle: "basculer l'éditeur", +accesskey: "Accesskey", +tab: "Tab", +shift: "Maj", +ctrl: "Ctrl", +esc: "Esc", +processing: "chargement...", +fullscreen: "plein écran", +syntax_selection: "--Syntaxe--", +close_tab: "Fermer le fichier" +}; ADDED applications/admin/static/edit_area/langs/hr.js Index: applications/admin/static/edit_area/langs/hr.js ================================================================== --- applications/admin/static/edit_area/langs/hr.js +++ applications/admin/static/edit_area/langs/hr.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["hr"]={ +new_document: "Novi dokument", +search_button: "Traži i izmijeni", +search_command: "Traži dalje / Otvori prozor za traženje", +search: "Traži", +replace: "Izmijeni", +replace_command: "Izmijeni / Otvori prozor za traženje", +find_next: "Traži dalje", +replace_all: "Izmjeni sve", +reg_exp: "Regularni izrazi", +match_case: "Bitna vel. slova", +not_found: "nije naðeno.", +occurrence_replaced: "izmjenjenih.", +search_field_empty: "Prazno polje za traženje!", +restart_search_at_begin: "Došao do kraja. Poèeo od poèetka.", +move_popup: "Pomakni prozor", +font_size: "--Velièina teksta--", +go_to_line: "Odi na redak", +go_to_line_prompt: "Odi na redak:", +undo: "Vrati natrag", +redo: "Napravi ponovo", +change_smooth_selection: "Ukljuèi/iskljuèi neke moguænosti prikaza (pametniji prikaz, ali zagušeniji CPU)", +highlight: "Ukljuèi/iskljuèi bojanje sintakse", +reset_highlight: "Ponovi kolorizaciju (ako je nesinkronizirana s tekstom)", +word_wrap: "toggle word wrapping mode", +help: "O edit_area", +save: "Spremi", +load: "Uèitaj", +line_abbr: "Ln", +char_abbr: "Zn", +position: "Pozicija", +total: "Ukupno", +close_popup: "Zatvori prozor", +shortcuts: "Kratice", +add_tab: "Dodaj tabulaciju", +remove_tab: "Makni tabulaciju", +about_notice: "Napomena: koloriziranje sintakse je samo za kratke kodove", +toggle: "Prebaci naèin ureðivanja", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Procesiram...", +fullscreen: "Cijeli prozor", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/it.js Index: applications/admin/static/edit_area/langs/it.js ================================================================== --- applications/admin/static/edit_area/langs/it.js +++ applications/admin/static/edit_area/langs/it.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["it"]={ +new_document: "nuovo documento vuoto", +search_button: "cerca e sostituisci", +search_command: "trova successivo / apri finestra di ricerca", +search: "cerca", +replace: "sostituisci", +replace_command: "sostituisci / apri finestra di ricerca", +find_next: "trova successivo", +replace_all: "sostituisci tutti", +reg_exp: "espressioni regolari", +match_case: "confronta maiuscole/minuscole
", +not_found: "non trovato.", +occurrence_replaced: "occorrenze sostituite.", +search_field_empty: "Campo ricerca vuoto", +restart_search_at_begin: "Fine del testo raggiunta. Ricomincio dall'inizio.", +move_popup: "sposta popup di ricerca", +font_size: "-- Dimensione --", +go_to_line: "vai alla linea", +go_to_line_prompt: "vai alla linea numero:", +undo: "annulla", +redo: "ripeti", +change_smooth_selection: "abilita/disabilita alcune caratteristiche della visualizzazione", +highlight: "abilita/disabilita colorazione della sintassi", +reset_highlight: "aggiorna colorazione (se non sincronizzata)", +word_wrap: "toggle word wrapping mode", +help: "informazioni su...", +save: "salva", +load: "carica", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Posizione", +total: "Totale", +close_popup: "chiudi popup", +shortcuts: "Scorciatoie", +add_tab: "aggiungi tabulazione", +remove_tab: "rimuovi tabulazione", +about_notice: "Avviso: la colorazione della sintassi vale solo con testo piccolo", +toggle: "Abilita/disabilita editor", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "In corso...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/ja.js Index: applications/admin/static/edit_area/langs/ja.js ================================================================== --- applications/admin/static/edit_area/langs/ja.js +++ applications/admin/static/edit_area/langs/ja.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["ja"]={ +new_document: "新規作成", +search_button: "検索・置換", +search_command: "次を検索 / 検索窓を表示", +search: "検索", +replace: "置換", +replace_command: "置換 / 置換窓を表示", +find_next: "次を検索", +replace_all: "全置換", +reg_exp: "正規表現", +match_case: "大文字小文字の区別", +not_found: "見つかりません。", +occurrence_replaced: "置換しました。", +search_field_empty: "検索対象文字列が空です。", +restart_search_at_begin: "終端に達しました、始めに戻ります", +move_popup: "検索窓を移動", +font_size: "--フォントサイズ--", +go_to_line: "指定行へ移動", +go_to_line_prompt: "指定行へ移動します:", +undo: "元に戻す", +redo: "やり直し", +change_smooth_selection: "スムース表示の切り替え(CPUを使います)", +highlight: "構文強調表示の切り替え", +reset_highlight: "構文強調表示のリセット", +word_wrap: "toggle word wrapping mode", +help: "ヘルプを表示", +save: "保存", +load: "読み込み", +line_abbr: "行", +char_abbr: "文字", +position: "位置", +total: "合計", +close_popup: "ポップアップを閉じる", +shortcuts: "ショートカット", +add_tab: "タブを挿入する", +remove_tab: "タブを削除する", +about_notice: "注意:構文強調表示は短いテキストでしか有効に機能しません。", +toggle: "テキストエリアとeditAreaの切り替え", +accesskey: "アクセスキー", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "処理中です...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/mk.js Index: applications/admin/static/edit_area/langs/mk.js ================================================================== --- applications/admin/static/edit_area/langs/mk.js +++ applications/admin/static/edit_area/langs/mk.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["mk"]={ +new_document: "Нов документ", +search_button: "Најди и замени", +search_command: "Барај следно / Отвори нов прозорец за пребарување", +search: "Барај", +replace: "Замени", +replace_command: "Замени / Отвори прозорец за пребарување", +find_next: "најди следно", +replace_all: "Замени ги сите", +reg_exp: "Регуларни изрази", +match_case: "Битна е големината на буквите", +not_found: "не е пронајдено.", +occurrence_replaced: "замени.", +search_field_empty: "Полето за пребарување е празно", +restart_search_at_begin: "Крај на областа. Стартувај од почеток.", +move_popup: "Помести го прозорецот", +font_size: "--Големина на текстот--", +go_to_line: "Оди на линија", +go_to_line_prompt: "Оди на линија со број:", +undo: "Врати", +redo: "Повтори", +change_smooth_selection: "Вклучи/исклучи некои карактеристики за приказ (попаметен приказ, но поголемо оптеретување за процесорот)", +highlight: "Вклучи/исклучи осветлување на синтакса", +reset_highlight: "Ресетирај го осветлувањето на синтакса (доколку е десинхронизиранo со текстот)", +word_wrap: "toggle word wrapping mode", +help: "За", +save: "Зачувај", +load: "Вчитај", +line_abbr: "Лн", +char_abbr: "Зн", +position: "Позиција", +total: "Вкупно", +close_popup: "Затвори го прозорецот", +shortcuts: "Кратенки", +add_tab: "Додај табулација на текстот", +remove_tab: "Отстрани ја табулацијата", +about_notice: "Напомена: Осветлувањето на синтанса е само за краток текст", +toggle: "Смени начин на уредување", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Обработувам...", +fullscreen: "Цел прозорец", +syntax_selection: "--Синтакса--", +close_tab: "Избери датотека" +}; ADDED applications/admin/static/edit_area/langs/nl.js Index: applications/admin/static/edit_area/langs/nl.js ================================================================== --- applications/admin/static/edit_area/langs/nl.js +++ applications/admin/static/edit_area/langs/nl.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["nl"]={ +new_document: "nieuw leeg document", +search_button: "zoek en vervang", +search_command: "zoek volgende / zoekscherm openen", +search: "zoek", +replace: "vervang", +replace_command: "vervang / zoekscherm openen", +find_next: "volgende vinden", +replace_all: "alles vervangen", +reg_exp: "reguliere expressies", +match_case: "hoofdletter gevoelig", +not_found: "niet gevonden.", +occurrence_replaced: "object vervangen.", +search_field_empty: "Zoek veld leeg", +restart_search_at_begin: "Niet meer instanties gevonden, begin opnieuw", +move_popup: "versleep zoek scherm", +font_size: "--Letter grootte--", +go_to_line: "Ga naar regel", +go_to_line_prompt: "Ga naar regel nummer:", +undo: "Ongedaan maken", +redo: "Opnieuw doen", +change_smooth_selection: "zet wat schermopties aan/uit (kan langzamer zijn)", +highlight: "zet syntax highlight aan/uit", +reset_highlight: "reset highlight (indien gedesynchronizeerd)", +word_wrap: "toggle word wrapping mode", +help: "informatie", +save: "opslaan", +load: "laden", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Positie", +total: "Totaal", +close_popup: "Popup sluiten", +shortcuts: "Snelkoppelingen", +add_tab: "voeg tabs toe in tekst", +remove_tab: "verwijder tabs uit tekst", +about_notice: "Notitie: syntax highlight functie is alleen voor kleine tekst", +toggle: "geavanceerde bewerkingsopties", +accesskey: "Accessknop", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Verwerken...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/pl.js Index: applications/admin/static/edit_area/langs/pl.js ================================================================== --- applications/admin/static/edit_area/langs/pl.js +++ applications/admin/static/edit_area/langs/pl.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["pl"]={ +new_document: "nowy dokument", +search_button: "znajdź i zamień", +search_command: "znajdź następny", +search: "znajdź", +replace: "zamień", +replace_command: "zamień", +find_next: "następny", +replace_all: "zamień wszystko", +reg_exp: "wyrażenie regularne", +match_case: "uwzględnij wielkość liter
", +not_found: "nie znaleziono.", +occurrence_replaced: "wystąpień zamieniono.", +search_field_empty: "Nie wprowadzono tekstu", +restart_search_at_begin: "Koniec dokumentu. Wyszukiwanie od początku.", +move_popup: "przesuń okienko wyszukiwania", +font_size: "Rozmiar", +go_to_line: "idź do linii", +go_to_line_prompt: "numer linii:", +undo: "cofnij", +redo: "przywróć", +change_smooth_selection: "włącz/wyłącz niektóre opcje wyglądu (zaawansowane opcje wyglądu obciążają procesor)", +highlight: "włącz/wyłącz podświetlanie składni", +reset_highlight: "odśwież podświetlanie składni (jeśli rozsynchronizowało się z tekstem)", +word_wrap: "toggle word wrapping mode", +help: "o programie", +save: "zapisz", +load: "otwórz", +line_abbr: "Ln", +char_abbr: "Zn", +position: "Pozycja", +total: "W sumie", +close_popup: "zamknij okienko", +shortcuts: "Skróty klawiaturowe", +add_tab: "dodaj wcięcie do zaznaczonego tekstu", +remove_tab: "usuń wcięcie", +about_notice: "Uwaga: podświetlanie składni nie jest zalecane dla długich tekstów", +toggle: "Włącz/wyłącz edytor", +accesskey: "Alt+", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Przetwarzanie...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/pt.js Index: applications/admin/static/edit_area/langs/pt.js ================================================================== --- applications/admin/static/edit_area/langs/pt.js +++ applications/admin/static/edit_area/langs/pt.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["pt"]={ +new_document: "Novo documento", +search_button: "Localizar e substituir", +search_command: "Localizar próximo", +search: "Localizar", +replace: "Substituir", +replace_command: "Substituir", +find_next: "Localizar", +replace_all: "Subst. tudo", +reg_exp: "Expressões regulares", +match_case: "Diferenciar maiúsculas e minúsculas", +not_found: "Não encontrado.", +occurrence_replaced: "Ocorrências substituidas", +search_field_empty: "Campo localizar vazio.", +restart_search_at_begin: "Fim das ocorrências. Recomeçar do inicio.", +move_popup: "Mover janela", +font_size: "--Tamanho da fonte--", +go_to_line: "Ir para linha", +go_to_line_prompt: "Ir para a linha:", +undo: "Desfazer", +redo: "Refazer", +change_smooth_selection: "Opções visuais", +highlight: "Cores de sintaxe", +reset_highlight: "Resetar cores (se não sincronizado)", +word_wrap: "toggle word wrapping mode", +help: "Sobre", +save: "Salvar", +load: "Carregar", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Posição", +total: "Total", +close_popup: "Fechar", +shortcuts: "Shortcuts", +add_tab: "Adicionar tabulação", +remove_tab: "Remover tabulação", +about_notice: "Atenção: Cores de sintaxe são indicados somente para textos pequenos", +toggle: "Exibir editor", +accesskey: "Accesskey", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Processando...", +fullscreen: "fullscreen", +syntax_selection: "--Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/ru.js Index: applications/admin/static/edit_area/langs/ru.js ================================================================== --- applications/admin/static/edit_area/langs/ru.js +++ applications/admin/static/edit_area/langs/ru.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["ru"]={ +new_document: "новый пустой документ", +search_button: "поиск и замена", +search_command: "искать следующий / открыть панель поиска", +search: "поиск", +replace: "замена", +replace_command: "заменить / открыть панель поиска", +find_next: "найти следующее", +replace_all: "заменить все", +reg_exp: "регулярное выражение", +match_case: "учитывать регистр", +not_found: "не найдено.", +occurrence_replaced: "вхождение заменено.", +search_field_empty: "Поле поиска пустое", +restart_search_at_begin: "Достигнут конец документа. Начинаю с начала.", +move_popup: "переместить окно поиска", +font_size: "--Размер шрифта--", +go_to_line: "перейти к строке", +go_to_line_prompt: "перейти к строке номер:", +undo: "отменить", +redo: "вернуть", +change_smooth_selection: "включить/отключить некоторые функции просмотра (более красиво, но больше использует процессор)", +highlight: "переключить подсветку синтаксиса включена/выключена", +reset_highlight: "восстановить подсветку (если разсинхронизирована от текста)", +word_wrap: "toggle word wrapping mode", +help: "о программе", +save: "сохранить", +load: "загрузить", +line_abbr: "Стр", +char_abbr: "Стлб", +position: "Позиция", +total: "Всего", +close_popup: "закрыть всплывающее окно", +shortcuts: "Горячие клавиши", +add_tab: "добавить табуляцию в текст", +remove_tab: "убрать табуляцию из текста", +about_notice: "Внимание: функция подсветки синтаксиса только для небольших текстов", +toggle: "Переключить редактор", +accesskey: "Горячая клавиша", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Обработка...", +fullscreen: "полный экран", +syntax_selection: "--Синтакс--", +close_tab: "Закрыть файл" +}; ADDED applications/admin/static/edit_area/langs/sk.js Index: applications/admin/static/edit_area/langs/sk.js ================================================================== --- applications/admin/static/edit_area/langs/sk.js +++ applications/admin/static/edit_area/langs/sk.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["sk"]={ +new_document: "nový prázdy dokument", +search_button: "vyhľadaj a nahraď", +search_command: "hľadaj ďalsšie / otvor vyhľadávacie pole", +search: "hľadaj", +replace: "nahraď", +replace_command: "nahraď / otvor vyhľadávacie pole", +find_next: "nájdi ďalšie", +replace_all: "nahraď všetko", +reg_exp: "platné výrazy", +match_case: "zhodujúce sa výrazy", +not_found: "nenájdené.", +occurrence_replaced: "výskyty nahradené.", +search_field_empty: "Pole vyhľadávanie je prádzne", +restart_search_at_begin: "End of area reached. Restart at begin.", +move_popup: "presuň vyhľadávacie okno", +font_size: "--Veľkosť textu--", +go_to_line: "prejdi na riadok", +go_to_line_prompt: "prejdi na riadok:", +undo: "krok späť", +redo: "prepracovať", +change_smooth_selection: "povoliť/zamietnúť niektoré zo zobrazených funkcií (účelnejšie zobrazenie vyžaduje väčšie zaťaženie procesora CPU)", +highlight: "prepnúť zvýrazňovanie syntaxe zap/vyp", +reset_highlight: "zrušiť zvýrazňovanie (ak je nesynchronizované s textom)", +word_wrap: "toggle word wrapping mode", +help: "o programe", +save: "uložiť", +load: "načítať", +line_abbr: "Ln", +char_abbr: "Ch", +position: "Pozícia", +total: "Spolu", +close_popup: "zavrieť okno", +shortcuts: "Skratky", +add_tab: "pridať tabulovanie textu", +remove_tab: "odstrániť tabulovanie textu", +about_notice: "Upozornenie: funkcia zvýrazňovania syntaxe je dostupná iba pre malý text", +toggle: "Prepnúť editor", +accesskey: "Accesskey", +tab: "Záložka", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "Spracúvam...", +fullscreen: "cel=a obrazovka", +syntax_selection: "--Vyber Syntax--", +close_tab: "Close file" +}; ADDED applications/admin/static/edit_area/langs/zh.js Index: applications/admin/static/edit_area/langs/zh.js ================================================================== --- applications/admin/static/edit_area/langs/zh.js +++ applications/admin/static/edit_area/langs/zh.js @@ -0,0 +1,48 @@ +editAreaLoader.lang["zh"]={ +new_document: "新建空白文档", +search_button: "查找与替换", +search_command: "查找下一个 / 打开查找框", +search: "查找", +replace: "替换", +replace_command: "替换 / 打开查找框", +find_next: "查找下一个", +replace_all: "全部替换", +reg_exp: "正则表达式", +match_case: "匹配大小写", +not_found: "未找到.", +occurrence_replaced: "处被替换.", +search_field_empty: "查找框没有内容", +restart_search_at_begin: "已到到文档末尾. 从头重新查找.", +move_popup: "移动查找对话框", +font_size: "--字体大小--", +go_to_line: "转到行", +go_to_line_prompt: "转到行:", +undo: "恢复", +redo: "重做", +change_smooth_selection: "启用/禁止一些显示特性(更好看但更耗费资源)", +highlight: "启用/禁止语法高亮", +reset_highlight: "重置语法高亮(当文本显示不同步时)", +word_wrap: "toggle word wrapping mode", +help: "关于", +save: "保存", +load: "加载", +line_abbr: "行", +char_abbr: "字符", +position: "位置", +total: "总计", +close_popup: "关闭对话框", +shortcuts: "快捷键", +add_tab: "添加制表符(Tab)", +remove_tab: "移除制表符(Tab)", +about_notice: "注意:语法高亮功能仅用于较少内容的文本(文件内容太大会导致浏览器反应慢)", +toggle: "切换编辑器", +accesskey: "快捷键", +tab: "Tab", +shift: "Shift", +ctrl: "Ctrl", +esc: "Esc", +processing: "正在处理中...", +fullscreen: "全屏编辑", +syntax_selection: "--语法--", +close_tab: "关闭文件" +}; ADDED applications/admin/static/edit_area/license.txt Index: applications/admin/static/edit_area/license.txt ================================================================== --- applications/admin/static/edit_area/license.txt +++ applications/admin/static/edit_area/license.txt ADDED applications/admin/static/edit_area/license_apache.txt Index: applications/admin/static/edit_area/license_apache.txt ================================================================== --- applications/admin/static/edit_area/license_apache.txt +++ applications/admin/static/edit_area/license_apache.txt @@ -0,0 +1,7 @@ +Copyright 2008 Christophe Dolivet + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ADDED applications/admin/static/edit_area/license_bsd.txt Index: applications/admin/static/edit_area/license_bsd.txt ================================================================== --- applications/admin/static/edit_area/license_bsd.txt +++ applications/admin/static/edit_area/license_bsd.txt @@ -0,0 +1,10 @@ +Copyright (c) 2008, Christophe Dolivet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of EditArea nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ADDED applications/admin/static/edit_area/license_lgpl.txt Index: applications/admin/static/edit_area/license_lgpl.txt ================================================================== --- applications/admin/static/edit_area/license_lgpl.txt +++ applications/admin/static/edit_area/license_lgpl.txt @@ -0,0 +1,458 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS ADDED applications/admin/static/edit_area/manage_area.js Index: applications/admin/static/edit_area/manage_area.js ================================================================== --- applications/admin/static/edit_area/manage_area.js +++ applications/admin/static/edit_area/manage_area.js @@ -0,0 +1,623 @@ + EditArea.prototype.focus = function() { + this.textarea.focus(); + this.textareaFocused=true; + }; + + + EditArea.prototype.check_line_selection= function(timer_checkup){ + var changes, infos, new_top, new_width,i; + + var t1=t2=t2_1=t3=tLines=tend= new Date().getTime(); + // l'editeur n'existe plus => on quitte + if(!editAreas[this.id]) + return false; + + if(!this.smooth_selection && !this.do_highlight) + { + //do nothing + } + else if(this.textareaFocused && editAreas[this.id]["displayed"]==true && this.isResizing==false) + { + infos = this.get_selection_infos(); + changes = this.checkTextEvolution( typeof( this.last_selection['full_text'] ) == 'undefined' ? '' : this.last_selection['full_text'], infos['full_text'] ); + + t2= new Date().getTime(); + + // if selection change + if(this.last_selection["line_start"] != infos["line_start"] || this.last_selection["line_nb"] != infos["line_nb"] || infos["full_text"] != this.last_selection["full_text"] || this.reload_highlight || this.last_selection["selectionStart"] != infos["selectionStart"] || this.last_selection["selectionEnd"] != infos["selectionEnd"] || !timer_checkup ) + { + // move and adjust text selection elements + new_top = this.getLinePosTop( infos["line_start"] ); + new_width = Math.max(this.textarea.scrollWidth, this.container.clientWidth -50); + this.selection_field.style.top=this.selection_field_text.style.top=new_top+"px"; + if(!this.settings['word_wrap']){ + this.selection_field.style.width=this.selection_field_text.style.width=this.test_font_size.style.width=new_width+"px"; + } + + // usefull? => _$("cursor_pos").style.top=new_top+"px"; + + if(this.do_highlight==true) + { + // fill selection elements + var curr_text = infos["full_text"].split("\n"); + var content = ""; + //alert("length: "+curr_text.length+ " i: "+ Math.max(0,infos["line_start"]-1)+ " end: "+Math.min(curr_text.length, infos["line_start"]+infos["line_nb"]-1)+ " line: "+infos["line_start"]+" [0]: "+curr_text[0]+" [1]: "+curr_text[1]); + var start = Math.max(0,infos["line_start"]-1); + var end = Math.min(curr_text.length, infos["line_start"]+infos["line_nb"]-1); + + //curr_text[start]= curr_text[start].substr(0,infos["curr_pos"]-1) +"¤_overline_¤"+ curr_text[start].substr(infos["curr_pos"]-1); + for(i=start; i< end; i++){ + content+= curr_text[i]+"\n"; + } + + // add special chars arround selected characters + selLength = infos['selectionEnd'] - infos['selectionStart']; + content = content.substr( 0, infos["curr_pos"] - 1 ) + "\r\r" + content.substr( infos["curr_pos"] - 1, selLength ) + "\r\r" + content.substr( infos["curr_pos"] - 1 + selLength ); + content = ''+ content.replace(/&/g,"&").replace(//g,">").replace("\r\r", '').replace("\r\r", '') +''; + + if( this.isIE || ( this.isOpera && this.isOpera < 9.6 ) ) { + this.selection_field.innerHTML= "
" + content.replace(/^\r?\n/, "
") + "
"; + } else { + this.selection_field.innerHTML= content; + } + this.selection_field_text.innerHTML = this.selection_field.innerHTML; + t2_1 = new Date().getTime(); + // check if we need to update the highlighted background + if(this.reload_highlight || (infos["full_text"] != this.last_text_to_highlight && (this.last_selection["line_start"]!=infos["line_start"] || this.show_line_colors || this.settings['word_wrap'] || this.last_selection["line_nb"]!=infos["line_nb"] || this.last_selection["nb_line"]!=infos["nb_line"]) ) ) + { + this.maj_highlight(infos); + } + } + } + t3= new Date().getTime(); + + // manage line heights + if( this.settings['word_wrap'] && infos["full_text"] != this.last_selection["full_text"]) + { + // refresh only 1 line if text change concern only one line and that the total line number has not changed + if( changes.newText.split("\n").length == 1 && this.last_selection['nb_line'] && infos['nb_line'] == this.last_selection['nb_line'] ) + { + this.fixLinesHeight( infos['full_text'], changes.lineStart, changes.lineStart ); + } + else + { + this.fixLinesHeight( infos['full_text'], changes.lineStart, -1 ); + } + } + + tLines= new Date().getTime(); + // manage bracket finding + if( infos["line_start"] != this.last_selection["line_start"] || infos["curr_pos"] != this.last_selection["curr_pos"] || infos["full_text"].length!=this.last_selection["full_text"].length || this.reload_highlight || !timer_checkup ) + { + // move _cursor_pos + var selec_char= infos["curr_line"].charAt(infos["curr_pos"]-1); + var no_real_move=true; + if(infos["line_nb"]==1 && (this.assocBracket[selec_char] || this.revertAssocBracket[selec_char]) ){ + + no_real_move=false; + //findEndBracket(infos["line_start"], infos["curr_pos"], selec_char); + if(this.findEndBracket(infos, selec_char) === true){ + _$("end_bracket").style.visibility ="visible"; + _$("cursor_pos").style.visibility ="visible"; + _$("cursor_pos").innerHTML = selec_char; + _$("end_bracket").innerHTML = (this.assocBracket[selec_char] || this.revertAssocBracket[selec_char]); + }else{ + _$("end_bracket").style.visibility ="hidden"; + _$("cursor_pos").style.visibility ="hidden"; + } + }else{ + _$("cursor_pos").style.visibility ="hidden"; + _$("end_bracket").style.visibility ="hidden"; + } + //alert("move cursor"); + this.displayToCursorPosition("cursor_pos", infos["line_start"], infos["curr_pos"]-1, infos["curr_line"], no_real_move); + if(infos["line_nb"]==1 && infos["line_start"]!=this.last_selection["line_start"]) + this.scroll_to_view(); + } + this.last_selection=infos; + } + + tend= new Date().getTime(); + //if( (tend-t1) > 7 ) + // console.log( "tps total: "+ (tend-t1) + " tps get_infos: "+ (t2-t1)+ " tps selec: "+ (t2_1-t2)+ " tps highlight: "+ (t3-t2_1) +" tps lines: "+ (tLines-t3) +" tps cursor+lines: "+ (tend-tLines)+" \n" ); + + + if(timer_checkup){ + setTimeout("editArea.check_line_selection(true)", this.check_line_selection_timer); + } + }; + + + EditArea.prototype.get_selection_infos= function(){ + var sel={}, start, end, len, str; + + this.getIESelection(); + start = this.textarea.selectionStart; + end = this.textarea.selectionEnd; + + if( this.last_selection["selectionStart"] == start && this.last_selection["selectionEnd"] == end && this.last_selection["full_text"] == this.textarea.value ) + { + return this.last_selection; + } + + if(this.tabulation!="\t" && this.textarea.value.indexOf("\t")!=-1) + { // can append only after copy/paste + len = this.textarea.value.length; + this.textarea.value = this.replace_tab(this.textarea.value); + start = end = start+(this.textarea.value.length-len); + this.area_select( start, 0 ); + } + + sel["selectionStart"] = start; + sel["selectionEnd"] = end; + sel["full_text"] = this.textarea.value; + sel["line_start"] = 1; + sel["line_nb"] = 1; + sel["curr_pos"] = 0; + sel["curr_line"] = ""; + sel["indexOfCursor"] = 0; + sel["selec_direction"] = this.last_selection["selec_direction"]; + + //return sel; + var splitTab= sel["full_text"].split("\n"); + var nbLine = Math.max(0, splitTab.length); + var nbChar = Math.max(0, sel["full_text"].length - (nbLine - 1)); // (remove \n caracters from the count) + if( sel["full_text"].indexOf("\r") != -1 ) + nbChar = nbChar - ( nbLine - 1 ); // (remove \r caracters from the count) + sel["nb_line"] = nbLine; + sel["nb_char"] = nbChar; + + if(start>0){ + str = sel["full_text"].substr(0,start); + sel["curr_pos"] = start - str.lastIndexOf("\n"); + sel["line_start"] = Math.max(1, str.split("\n").length); + }else{ + sel["curr_pos"]=1; + } + if(end>start){ + sel["line_nb"]=sel["full_text"].substring(start,end).split("\n").length; + } + sel["indexOfCursor"]=start; + sel["curr_line"]=splitTab[Math.max(0,sel["line_start"]-1)]; + + // determine in which direction the selection grow + if(sel["selectionStart"] == this.last_selection["selectionStart"]){ + if(sel["selectionEnd"]>this.last_selection["selectionEnd"]) + sel["selec_direction"]= "down"; + else if(sel["selectionEnd"] == this.last_selection["selectionStart"]) + sel["selec_direction"]= this.last_selection["selec_direction"]; + }else if(sel["selectionStart"] == this.last_selection["selectionEnd"] && sel["selectionEnd"]>this.last_selection["selectionEnd"]){ + sel["selec_direction"]= "down"; + }else{ + sel["selec_direction"]= "up"; + } + + _$("nbLine").innerHTML = nbLine; + _$("nbChar").innerHTML = nbChar; + _$("linePos").innerHTML = sel["line_start"]; + _$("currPos").innerHTML = sel["curr_pos"]; + + return sel; + }; + + // set IE position in Firefox mode (textarea.selectionStart and textarea.selectionEnd) + EditArea.prototype.getIESelection= function(){ + var selectionStart, selectionEnd, range, stored_range; + + if( !this.isIE ) + return false; + + // make it work as nowrap mode (easier for range manipulation with lineHeight) + if( this.settings['word_wrap'] ) + this.textarea.wrap='off'; + + try{ + range = document.selection.createRange(); + stored_range = range.duplicate(); + stored_range.moveToElementText( this.textarea ); + stored_range.setEndPoint( 'EndToEnd', range ); + if( stored_range.parentElement() != this.textarea ) + throw "invalid focus"; + + // the range don't take care of empty lines in the end of the selection + var scrollTop = this.result.scrollTop + document.body.scrollTop; + var relative_top= range.offsetTop - parent.calculeOffsetTop(this.textarea) + scrollTop; + var line_start = Math.round((relative_top / this.lineHeight) +1); + var line_nb = Math.round( range.boundingHeight / this.lineHeight ); + + selectionStart = stored_range.text.length - range.text.length; + selectionStart += ( line_start - this.textarea.value.substr(0, selectionStart).split("\n").length)*2; // count missing empty \r to the selection + selectionStart -= ( line_start - this.textarea.value.substr(0, selectionStart).split("\n").length ) * 2; + + selectionEnd = selectionStart + range.text.length; + selectionEnd += (line_start + line_nb - 1 - this.textarea.value.substr(0, selectionEnd ).split("\n").length)*2; + + this.textarea.selectionStart = selectionStart; + this.textarea.selectionEnd = selectionEnd; + } + catch(e){} + + // restore wrap mode + if( this.settings['word_wrap'] ) + this.textarea.wrap='soft'; + }; + + // select the text for IE (and take care of \r caracters) + EditArea.prototype.setIESelection= function(){ + var a = this.textarea, nbLineStart, nbLineEnd, range; + + if( !this.isIE ) + return false; + + nbLineStart = a.value.substr(0, a.selectionStart).split("\n").length - 1; + nbLineEnd = a.value.substr(0, a.selectionEnd).split("\n").length - 1; + range = document.selection.createRange(); + range.moveToElementText( a ); + range.setEndPoint( 'EndToStart', range ); + + range.moveStart('character', a.selectionStart - nbLineStart); + range.moveEnd('character', a.selectionEnd - nbLineEnd - (a.selectionStart - nbLineStart) ); + range.select(); + }; + + + + EditArea.prototype.checkTextEvolution=function(lastText,newText){ + // ch will contain changes datas + var ch={},baseStep=200, cpt=0, end, step,tStart=new Date().getTime(); + + end = Math.min(newText.length, lastText.length); + step = baseStep; + // find how many chars are similar at the begin of the text + while( cpt=1 ){ + if(lastText.substr(cpt, step) == newText.substr(cpt, step)){ + cpt+= step; + }else{ + step= Math.floor(step/2); + } + } + + ch.posStart = cpt; + ch.lineStart= newText.substr(0, ch.posStart).split("\n").length -1; + + cpt_last = lastText.length; + cpt = newText.length; + step = baseStep; + // find how many chars are similar at the end of the text + while( cpt>=0 && cpt_last>=0 && step>=1 ){ + if(lastText.substr(cpt_last-step, step) == newText.substr(cpt-step, step)){ + cpt-= step; + cpt_last-= step; + }else{ + step= Math.floor(step/2); + } + } + + ch.posNewEnd = cpt; + ch.posLastEnd = cpt_last; + if(ch.posNewEnd<=ch.posStart){ + if(lastText.length < newText.length){ + ch.posNewEnd= ch.posStart + newText.length - lastText.length; + ch.posLastEnd= ch.posStart; + }else{ + ch.posLastEnd= ch.posStart + lastText.length - newText.length; + ch.posNewEnd= ch.posStart; + } + } + ch.newText = newText.substring(ch.posStart, ch.posNewEnd); + ch.lastText = lastText.substring(ch.posStart, ch.posLastEnd); + + ch.lineNewEnd = newText.substr(0, ch.posNewEnd).split("\n").length -1; + ch.lineLastEnd = lastText.substr(0, ch.posLastEnd).split("\n").length -1; + + ch.newTextLine = newText.split("\n").slice(ch.lineStart, ch.lineNewEnd+1).join("\n"); + ch.lastTextLine = lastText.split("\n").slice(ch.lineStart, ch.lineLastEnd+1).join("\n"); + //console.log( ch ); + return ch; + }; + + EditArea.prototype.tab_selection= function(){ + if(this.is_tabbing) + return; + this.is_tabbing=true; + //infos=getSelectionInfos(); + //if( document.selection ){ + this.getIESelection(); + /* Insertion du code de formatage */ + var start = this.textarea.selectionStart; + var end = this.textarea.selectionEnd; + var insText = this.textarea.value.substring(start, end); + + /* Insert tabulation and ajust cursor position */ + var pos_start=start; + var pos_end=end; + if (insText.length == 0) { + // if only one line selected + this.textarea.value = this.textarea.value.substr(0, start) + this.tabulation + this.textarea.value.substr(end); + pos_start = start + this.tabulation.length; + pos_end=pos_start; + } else { + start= Math.max(0, this.textarea.value.substr(0, start).lastIndexOf("\n")+1); + endText=this.textarea.value.substr(end); + startText=this.textarea.value.substr(0, start); + tmp= this.textarea.value.substring(start, end).split("\n"); + insText= this.tabulation+tmp.join("\n"+this.tabulation); + this.textarea.value = startText + insText + endText; + pos_start = start; + pos_end= this.textarea.value.indexOf("\n", startText.length + insText.length); + if(pos_end==-1) + pos_end=this.textarea.value.length; + //pos = start + repdeb.length + insText.length + ; + } + this.textarea.selectionStart = pos_start; + this.textarea.selectionEnd = pos_end; + + //if( document.selection ){ + if(this.isIE) + { + this.setIESelection(); + setTimeout("editArea.is_tabbing=false;", 100); // IE can't accept to make 2 tabulation without a little break between both + } + else + { + this.is_tabbing=false; + } + + }; + + EditArea.prototype.invert_tab_selection= function(){ + var t=this, a=this.textarea; + if(t.is_tabbing) + return; + t.is_tabbing=true; + //infos=getSelectionInfos(); + //if( document.selection ){ + t.getIESelection(); + + var start = a.selectionStart; + var end = a.selectionEnd; + var insText = a.value.substring(start, end); + + /* Tab remove and cursor seleciton adjust */ + var pos_start=start; + var pos_end=end; + if (insText.length == 0) { + if(a.value.substring(start-t.tabulation.length, start)==t.tabulation) + { + a.value = a.value.substr(0, start-t.tabulation.length) + a.value.substr(end); + pos_start = Math.max(0, start-t.tabulation.length); + pos_end = pos_start; + } + /* + a.value = a.value.substr(0, start) + t.tabulation + insText + a.value.substr(end); + pos_start = start + t.tabulation.length; + pos_end=pos_start;*/ + } else { + start = a.value.substr(0, start).lastIndexOf("\n")+1; + endText = a.value.substr(end); + startText = a.value.substr(0, start); + tmp = a.value.substring(start, end).split("\n"); + insText = ""; + for(i=0; i=0; ){ + if(infos["full_text"].charAt(i)==endBracket){ + nbBracketOpen--; + if(nbBracketOpen<=0){ + //i=infos["full_text"].length; + end=i; + break; + } + }else if(infos["full_text"].charAt(i)==bracket) + nbBracketOpen++; + if(normal_order) + i++; + else + i--; + } + + //end=infos["full_text"].indexOf("}", start); + if(end==-1) + return false; + var endLastLine=infos["full_text"].substr(0, end).lastIndexOf("\n"); + if(endLastLine==-1) + line=1; + else + line= infos["full_text"].substr(0, endLastLine).split("\n").length + 1; + + var curPos= end - endLastLine - 1; + var endLineLength = infos["full_text"].substring(end).split("\n")[0].length; + this.displayToCursorPosition("end_bracket", line, curPos, infos["full_text"].substring(endLastLine +1, end + endLineLength)); + return true; + }; + + EditArea.prototype.displayToCursorPosition= function(id, start_line, cur_pos, lineContent, no_real_move){ + var elem,dest,content,posLeft=0,posTop,fixPadding,topOffset,endElem; + + elem = this.test_font_size; + dest = _$(id); + content = ""+lineContent.substr(0, cur_pos).replace(/&/g,"&").replace(/"+lineContent.substr(cur_pos).replace(/&/g,"&").replace(/"; + if( this.isIE || ( this.isOpera && this.isOpera < 9.6 ) ) { + elem.innerHTML= "
" + content.replace(/^\r?\n/, "
") + "
"; + } else { + elem.innerHTML= content; + } + + + endElem = _$('endTestFont'); + topOffset = endElem.offsetTop; + fixPadding = parseInt( this.content_highlight.style.paddingLeft.replace("px", "") ); + posLeft = 45 + endElem.offsetLeft + ( !isNaN( fixPadding ) && topOffset > 0 ? fixPadding : 0 ); + posTop = this.getLinePosTop( start_line ) + topOffset;// + Math.floor( ( endElem.offsetHeight - 1 ) / this.lineHeight ) * this.lineHeight; + + // detect the case where the span start on a line but has no display on it + if( this.isIE && cur_pos > 0 && endElem.offsetLeft == 0 ) + { + posTop += this.lineHeight; + } + if(no_real_move!=true){ // when the cursor is hidden no need to move him + dest.style.top=posTop+"px"; + dest.style.left=posLeft+"px"; + } + // usefull for smarter scroll + dest.cursor_top=posTop; + dest.cursor_left=posLeft; + // _$(id).style.marginLeft=posLeft+"px"; + }; + + EditArea.prototype.getLinePosTop= function(start_line){ + var elem= _$('line_'+ start_line), posTop=0; + if( elem ) + posTop = elem.offsetTop; + else + posTop = this.lineHeight * (start_line-1); + return posTop; + }; + + + // return the dislpayed height of a text (take word-wrap into account) + EditArea.prototype.getTextHeight= function(text){ + var t=this,elem,height; + elem = t.test_font_size; + content = text.replace(/&/g,"&").replace(/") + ""; + } else { + elem.innerHTML= content; + } + height = elem.offsetHeight; + height = Math.max( 1, Math.floor( elem.offsetHeight / this.lineHeight ) ) * this.lineHeight; + return height; + }; + + /** + * Fix line height for the given lines + * @param Integer linestart + * @param Integer lineEnd End line or -1 to cover all lines + */ + EditArea.prototype.fixLinesHeight= function( textValue, lineStart,lineEnd ){ + var aText = textValue.split("\n"); + if( lineEnd == -1 ) + lineEnd = aText.length-1; + for( var i = Math.max(0, lineStart); i <= lineEnd; i++ ) + { + if( elem = _$('line_'+ ( i+1 ) ) ) + { + elem.style.height= typeof( aText[i] ) != "undefined" ? this.getTextHeight( aText[i] )+"px" : this.lineHeight; + } + } + }; + + EditArea.prototype.area_select= function(start, length){ + this.textarea.focus(); + + start = Math.max(0, Math.min(this.textarea.value.length, start)); + end = Math.max(start, Math.min(this.textarea.value.length, start+length)); + + if(this.isIE) + { + this.textarea.selectionStart = start; + this.textarea.selectionEnd = end; + this.setIESelection(); + } + else + { + // Opera bug when moving selection start and selection end + if(this.isOpera && this.isOpera < 9.6 ) + { + this.textarea.setSelectionRange(0, 0); + } + this.textarea.setSelectionRange(start, end); + } + this.check_line_selection(); + }; + + + EditArea.prototype.area_get_selection= function(){ + var text=""; + if( document.selection ){ + var range = document.selection.createRange(); + text=range.text; + }else{ + text= this.textarea.value.substring(this.textarea.selectionStart, this.textarea.selectionEnd); + } + return text; + }; ADDED applications/admin/static/edit_area/plugins/charmap/charmap.js Index: applications/admin/static/edit_area/plugins/charmap/charmap.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/charmap.js +++ applications/admin/static/edit_area/plugins/charmap/charmap.js @@ -0,0 +1,90 @@ +/** + * Charmap plugin + * by Christophe Dolivet + * v0.1 (2006/09/22) + * + * + * This plugin allow to use a visual keyboard allowing to insert any UTF-8 characters in the text. + * + * - plugin name to add to the plugin list: "charmap" + * - plugin name to add to the toolbar list: "charmap" + * - possible parameters to add to EditAreaLoader.init(): + * "charmap_default": (String) define the name of the default character range displayed on popup display + * (default: "arrows") + * + * + */ + +var EditArea_charmap= { + /** + * Get called once this file is loaded (editArea still not initialized) + * + * @return nothing + */ + init: function(){ + this.default_language="Arrows"; + } + + /** + * Returns the HTML code for a specific control string or false if this plugin doesn't have that control. + * A control can be a button, select list or any other HTML item to present in the EditArea user interface. + * Language variables such as {$lang_somekey} will also be replaced with contents from + * the language packs. + * + * @param {string} ctrl_name: the name of the control to add + * @return HTML code for a specific control or false. + * @type string or boolean + */ + ,get_control_html: function(ctrl_name){ + switch(ctrl_name){ + case "charmap": + // Control id, button img, command + return parent.editAreaLoader.get_button_html('charmap_but', 'charmap.gif', 'charmap_press', false, this.baseURL); + } + return false; + } + /** + * Get called once EditArea is fully loaded and initialised + * + * @return nothing + */ + ,onload: function(){ + if(editArea.settings["charmap_default"] && editArea.settings["charmap_default"].length>0) + this.default_language= editArea.settings["charmap_default"]; + } + + /** + * Is called each time the user touch a keyboard key. + * + * @param (event) e: the keydown event + * @return true - pass to next handler in chain, false - stop chain execution + * @type boolean + */ + ,onkeydown: function(e){ + + } + + /** + * Executes a specific command, this function handles plugin commands. + * + * @param {string} cmd: the name of the command being executed + * @param {unknown} param: the parameter of the command + * @return true - pass to next handler in chain, false - stop chain execution + * @type boolean + */ + ,execCommand: function(cmd, param){ + // Handle commands + switch(cmd){ + case "charmap_press": + win= window.open(this.baseURL+"popup.html", "charmap", "width=500,height=270,scrollbars=yes,resizable=yes"); + win.focus(); + return false; + } + // Pass to next handler in chain + return true; + } + +}; + +// Adds the plugin class to the list of available EditArea plugins +editArea.add_plugin("charmap", EditArea_charmap); ADDED applications/admin/static/edit_area/plugins/charmap/css/charmap.css Index: applications/admin/static/edit_area/plugins/charmap/css/charmap.css ================================================================== --- applications/admin/static/edit_area/plugins/charmap/css/charmap.css +++ applications/admin/static/edit_area/plugins/charmap/css/charmap.css @@ -0,0 +1,64 @@ +body{ + background-color: #F0F0EE; + font: 12px monospace, sans-serif; +} + +select{ + background-color: #F9F9F9; + border: solid 1px #888888; +} + +h1, h2, h3, h4, h5, h6{ + margin: 0; + padding: 0; + color: #2B6FB6; +} + +h1{ + font-size: 1.5em; +} + +div#char_list{ + height: 200px; + overflow: auto; + padding: 1px; + border: 1px solid #0A246A; + background-color: #F9F9F9; + clear: both; + margin-top: 5px; +} + +a.char{ + display: block; + float: left; + width: 20px; + height: 20px; + line-height: 20px; + margin: 1px; + border: solid 1px #888888; + text-align: center; + cursor: pointer; +} + +a.char:hover{ + background-color: #CCCCCC; +} + +.preview{ + border: solid 1px #888888; + width: 50px; + padding: 2px 5px; + height: 35px; + line-height: 35px; + text-align:center; + background-color: #CCCCCC; + font-size: 2em; + float: right; + font-weight: bold; + margin: 0 0 5px 5px; +} + +#preview_code{ + font-size: 1.1em; + width: 70px; +} ADDED applications/admin/static/edit_area/plugins/charmap/images/charmap.gif Index: applications/admin/static/edit_area/plugins/charmap/images/charmap.gif ================================================================== --- applications/admin/static/edit_area/plugins/charmap/images/charmap.gif +++ applications/admin/static/edit_area/plugins/charmap/images/charmap.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/plugins/charmap/jscripts/map.js Index: applications/admin/static/edit_area/plugins/charmap/jscripts/map.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/jscripts/map.js +++ applications/admin/static/edit_area/plugins/charmap/jscripts/map.js @@ -0,0 +1,373 @@ +var editArea; + + +/** + * UTF-8 list taken from http://www.utf8-chartable.de/unicode-utf8-table.pl?utf8=dec + */ + + +/* +var char_range_list={ +"Basic Latin":"0021,007F", +"Latin-1 Supplement":"0080,00FF", +"Latin Extended-A":"0100,017F", +"Latin Extended-B":"0180,024F", +"IPA Extensions":"0250,02AF", +"Spacing Modifier Letters":"02B0,02FF", + +"Combining Diacritical Marks":"0300,036F", +"Greek and Coptic":"0370,03FF", +"Cyrillic":"0400,04FF", +"Cyrillic Supplement":"0500,052F", +"Armenian":"0530,058F", +"Hebrew":"0590,05FF", +"Arabic":"0600,06FF", +"Syriac":"0700,074F", +"Arabic Supplement":"0750,077F", + +"Thaana":"0780,07BF", +"Devanagari":"0900,097F", +"Bengali":"0980,09FF", +"Gurmukhi":"0A00,0A7F", +"Gujarati":"0A80,0AFF", +"Oriya":"0B00,0B7F", +"Tamil":"0B80,0BFF", +"Telugu":"0C00,0C7F", +"Kannada":"0C80,0CFF", + +"Malayalam":"0D00,0D7F", +"Sinhala":"0D80,0DFF", +"Thai":"0E00,0E7F", +"Lao":"0E80,0EFF", +"Tibetan":"0F00,0FFF", +"Myanmar":"1000,109F", +"Georgian":"10A0,10FF", +"Hangul Jamo":"1100,11FF", +"Ethiopic":"1200,137F", + +"Ethiopic Supplement":"1380,139F", +"Cherokee":"13A0,13FF", +"Unified Canadian Aboriginal Syllabics":"1400,167F", +"Ogham":"1680,169F", +"Runic":"16A0,16FF", +"Tagalog":"1700,171F", +"Hanunoo":"1720,173F", +"Buhid":"1740,175F", +"Tagbanwa":"1760,177F", + +"Khmer":"1780,17FF", +"Mongolian":"1800,18AF", +"Limbu":"1900,194F", +"Tai Le":"1950,197F", +"New Tai Lue":"1980,19DF", +"Khmer Symbols":"19E0,19FF", +"Buginese":"1A00,1A1F", +"Phonetic Extensions":"1D00,1D7F", +"Phonetic Extensions Supplement":"1D80,1DBF", + +"Combining Diacritical Marks Supplement":"1DC0,1DFF", +"Latin Extended Additional":"1E00,1EFF", +"Greek Extended":"1F00,1FFF", +"General Punctuation":"2000,206F", +"Superscripts and Subscripts":"2070,209F", +"Currency Symbols":"20A0,20CF", +"Combining Diacritical Marks for Symbols":"20D0,20FF", +"Letterlike Symbols":"2100,214F", +"Number Forms":"2150,218F", + +"Arrows":"2190,21FF", +"Mathematical Operators":"2200,22FF", +"Miscellaneous Technical":"2300,23FF", +"Control Pictures":"2400,243F", +"Optical Character Recognition":"2440,245F", +"Enclosed Alphanumerics":"2460,24FF", +"Box Drawing":"2500,257F", +"Block Elements":"2580,259F", +"Geometric Shapes":"25A0,25FF", + +"Miscellaneous Symbols":"2600,26FF", +"Dingbats":"2700,27BF", +"Miscellaneous Mathematical Symbols-A":"27C0,27EF", +"Supplemental Arrows-A":"27F0,27FF", +"Braille Patterns":"2800,28FF", +"Supplemental Arrows-B":"2900,297F", +"Miscellaneous Mathematical Symbols-B":"2980,29FF", +"Supplemental Mathematical Operators":"2A00,2AFF", +"Miscellaneous Symbols and Arrows":"2B00,2BFF", + +"Glagolitic":"2C00,2C5F", +"Coptic":"2C80,2CFF", +"Georgian Supplement":"2D00,2D2F", +"Tifinagh":"2D30,2D7F", +"Ethiopic Extended":"2D80,2DDF", +"Supplemental Punctuation":"2E00,2E7F", +"CJK Radicals Supplement":"2E80,2EFF", +"Kangxi Radicals":"2F00,2FDF", +"Ideographic Description Characters":"2FF0,2FFF", + +"CJK Symbols and Punctuation":"3000,303F", +"Hiragana":"3040,309F", +"Katakana":"30A0,30FF", +"Bopomofo":"3100,312F", +"Hangul Compatibility Jamo":"3130,318F", +"Kanbun":"3190,319F", +"Bopomofo Extended":"31A0,31BF", +"CJK Strokes":"31C0,31EF", +"Katakana Phonetic Extensions":"31F0,31FF", + +"Enclosed CJK Letters and Months":"3200,32FF", +"CJK Compatibility":"3300,33FF", +"CJK Unified Ideographs Extension A":"3400,4DBF", +"Yijing Hexagram Symbols":"4DC0,4DFF", +"CJK Unified Ideographs":"4E00,9FFF", +"Yi Syllables":"A000,A48F", +"Yi Radicals":"A490,A4CF", +"Modifier Tone Letters":"A700,A71F", +"Syloti Nagri":"A800,A82F", + +"Hangul Syllables":"AC00,D7AF", +"High Surrogates":"D800,DB7F", +"High Private Use Surrogates":"DB80,DBFF", +"Low Surrogates":"DC00,DFFF", +"Private Use Area":"E000,F8FF", +"CJK Compatibility Ideographs":"F900,FAFF", +"Alphabetic Presentation Forms":"FB00,FB4F", +"Arabic Presentation Forms-A":"FB50,FDFF", +"Variation Selectors":"FE00,FE0F", + +"Vertical Forms":"FE10,FE1F", +"Combining Half Marks":"FE20,FE2F", +"CJK Compatibility Forms":"FE30,FE4F", +"Small Form Variants":"FE50,FE6F", +"Arabic Presentation Forms-B":"FE70,FEFF", +"Halfwidth and Fullwidth Forms":"FF00,FFEF", +"Specials":"FFF0,FFFF", +"Linear B Syllabary":"10000,1007F", +"Linear B Ideograms":"10080,100FF", + +"Aegean Numbers":"10100,1013F", +"Ancient Greek Numbers":"10140,1018F", +"Old Italic":"10300,1032F", +"Gothic":"10330,1034F", +"Ugaritic":"10380,1039F", +"Old Persian":"103A0,103DF", +"Deseret":"10400,1044F", +"Shavian":"10450,1047F", +"Osmanya":"10480,104AF", + +"Cypriot Syllabary":"10800,1083F", +"Kharoshthi":"10A00,10A5F", +"Byzantine Musical Symbols":"1D000,1D0FF", +"Musical Symbols":"1D100,1D1FF", +"Ancient Greek Musical Notation":"1D200,1D24F", +"Tai Xuan Jing Symbols":"1D300,1D35F", +"Mathematical Alphanumeric Symbols":"1D400,1D7FF", +"CJK Unified Ideographs Extension B":"20000,2A6DF", +"CJK Compatibility Ideographs Supplement":"2F800,2FA1F", +"Tags":"E0000,E007F", +"Variation Selectors Supplement":"E0100,E01EF" +}; +*/ +var char_range_list={ +"Aegean Numbers":"10100,1013F", +"Alphabetic Presentation Forms":"FB00,FB4F", +"Ancient Greek Musical Notation":"1D200,1D24F", +"Ancient Greek Numbers":"10140,1018F", +"Arabic":"0600,06FF", +"Arabic Presentation Forms-A":"FB50,FDFF", +"Arabic Presentation Forms-B":"FE70,FEFF", +"Arabic Supplement":"0750,077F", +"Armenian":"0530,058F", +"Arrows":"2190,21FF", +"Basic Latin":"0020,007F", +"Bengali":"0980,09FF", +"Block Elements":"2580,259F", +"Bopomofo Extended":"31A0,31BF", +"Bopomofo":"3100,312F", +"Box Drawing":"2500,257F", +"Braille Patterns":"2800,28FF", +"Buginese":"1A00,1A1F", +"Buhid":"1740,175F", +"Byzantine Musical Symbols":"1D000,1D0FF", +"CJK Compatibility Forms":"FE30,FE4F", +"CJK Compatibility Ideographs Supplement":"2F800,2FA1F", +"CJK Compatibility Ideographs":"F900,FAFF", +"CJK Compatibility":"3300,33FF", +"CJK Radicals Supplement":"2E80,2EFF", +"CJK Strokes":"31C0,31EF", +"CJK Symbols and Punctuation":"3000,303F", +"CJK Unified Ideographs Extension A":"3400,4DBF", +"CJK Unified Ideographs Extension B":"20000,2A6DF", +"CJK Unified Ideographs":"4E00,9FFF", +"Cherokee":"13A0,13FF", +"Combining Diacritical Marks Supplement":"1DC0,1DFF", +"Combining Diacritical Marks for Symbols":"20D0,20FF", +"Combining Diacritical Marks":"0300,036F", +"Combining Half Marks":"FE20,FE2F", +"Control Pictures":"2400,243F", +"Coptic":"2C80,2CFF", +"Currency Symbols":"20A0,20CF", +"Cypriot Syllabary":"10800,1083F", +"Cyrillic Supplement":"0500,052F", +"Cyrillic":"0400,04FF", +"Deseret":"10400,1044F", +"Devanagari":"0900,097F", +"Dingbats":"2700,27BF", +"Enclosed Alphanumerics":"2460,24FF", +"Enclosed CJK Letters and Months":"3200,32FF", +"Ethiopic Extended":"2D80,2DDF", +"Ethiopic Supplement":"1380,139F", +"Ethiopic":"1200,137F", +"General Punctuation":"2000,206F", +"Geometric Shapes":"25A0,25FF", +"Georgian Supplement":"2D00,2D2F", +"Georgian":"10A0,10FF", +"Glagolitic":"2C00,2C5F", +"Gothic":"10330,1034F", +"Greek Extended":"1F00,1FFF", +"Greek and Coptic":"0370,03FF", +"Gujarati":"0A80,0AFF", +"Gurmukhi":"0A00,0A7F", +"Halfwidth and Fullwidth Forms":"FF00,FFEF", +"Hangul Compatibility Jamo":"3130,318F", +"Hangul Jamo":"1100,11FF", +"Hangul Syllables":"AC00,D7AF", +"Hanunoo":"1720,173F", +"Hebrew":"0590,05FF", +"High Private Use Surrogates":"DB80,DBFF", +"High Surrogates":"D800,DB7F", +"Hiragana":"3040,309F", +"IPA Extensions":"0250,02AF", +"Ideographic Description Characters":"2FF0,2FFF", +"Kanbun":"3190,319F", +"Kangxi Radicals":"2F00,2FDF", +"Kannada":"0C80,0CFF", +"Katakana Phonetic Extensions":"31F0,31FF", +"Katakana":"30A0,30FF", +"Kharoshthi":"10A00,10A5F", +"Khmer Symbols":"19E0,19FF", +"Khmer":"1780,17FF", +"Lao":"0E80,0EFF", +"Latin Extended Additional":"1E00,1EFF", +"Latin Extended-A":"0100,017F", +"Latin Extended-B":"0180,024F", +"Latin-1 Supplement":"0080,00FF", +"Letterlike Symbols":"2100,214F", +"Limbu":"1900,194F", +"Linear B Ideograms":"10080,100FF", +"Linear B Syllabary":"10000,1007F", +"Low Surrogates":"DC00,DFFF", +"Malayalam":"0D00,0D7F", +"Mathematical Alphanumeric Symbols":"1D400,1D7FF", +"Mathematical Operators":"2200,22FF", +"Miscellaneous Mathematical Symbols-A":"27C0,27EF", +"Miscellaneous Mathematical Symbols-B":"2980,29FF", +"Miscellaneous Symbols and Arrows":"2B00,2BFF", +"Miscellaneous Symbols":"2600,26FF", +"Miscellaneous Technical":"2300,23FF", +"Modifier Tone Letters":"A700,A71F", +"Mongolian":"1800,18AF", +"Musical Symbols":"1D100,1D1FF", +"Myanmar":"1000,109F", +"New Tai Lue":"1980,19DF", +"Number Forms":"2150,218F", +"Ogham":"1680,169F", +"Old Italic":"10300,1032F", +"Old Persian":"103A0,103DF", +"Optical Character Recognition":"2440,245F", +"Oriya":"0B00,0B7F", +"Osmanya":"10480,104AF", +"Phonetic Extensions Supplement":"1D80,1DBF", +"Phonetic Extensions":"1D00,1D7F", +"Private Use Area":"E000,F8FF", +"Runic":"16A0,16FF", +"Shavian":"10450,1047F", +"Sinhala":"0D80,0DFF", +"Small Form Variants":"FE50,FE6F", +"Spacing Modifier Letters":"02B0,02FF", +"Specials":"FFF0,FFFF", +"Superscripts and Subscripts":"2070,209F", +"Supplemental Arrows-A":"27F0,27FF", +"Supplemental Arrows-B":"2900,297F", +"Supplemental Mathematical Operators":"2A00,2AFF", +"Supplemental Punctuation":"2E00,2E7F", +"Syloti Nagri":"A800,A82F", +"Syriac":"0700,074F", +"Tagalog":"1700,171F", +"Tagbanwa":"1760,177F", +"Tags":"E0000,E007F", +"Tai Le":"1950,197F", +"Tai Xuan Jing Symbols":"1D300,1D35F", +"Tamil":"0B80,0BFF", +"Telugu":"0C00,0C7F", +"Thaana":"0780,07BF", +"Thai":"0E00,0E7F", +"Tibetan":"0F00,0FFF", +"Tifinagh":"2D30,2D7F", +"Ugaritic":"10380,1039F", +"Unified Canadian Aboriginal Syllabics":"1400,167F", +"Variation Selectors Supplement":"E0100,E01EF", +"Variation Selectors":"FE00,FE0F", +"Vertical Forms":"FE10,FE1F", +"Yi Radicals":"A490,A4CF", +"Yi Syllables":"A000,A48F", +"Yijing Hexagram Symbols":"4DC0,4DFF" +}; + +var insert="charmap_insert"; + +function map_load(){ + editArea=opener.editArea; + // translate the document + insert= editArea.get_translation(insert, "word"); + //alert(document.title); + document.title= editArea.get_translation(document.title, "template"); + document.body.innerHTML= editArea.get_translation(document.body.innerHTML, "template"); + //document.title= editArea.get_translation(document.getElementBytitle, "template"); + + var selected_lang=opener.EditArea_charmap.default_language.toLowerCase(); + var selected=0; + + var select= document.getElementById("select_range") + for(var i in char_range_list){ + if(i.toLowerCase()==selected_lang) + selected=select.options.length; + select.options[select.options.length]=new Option(i, char_range_list[i]); + } + select.options[selected].selected=true; +/* start=0; + end=127; + content=""; + for(var i=start; i"+ String.fromCharCode(i) +""; + } + document.getElementById("char_list").innerHTML= html; + document.getElementById("preview_char").innerHTML=""; +} + +function previewChar(i){ + document.getElementById("preview_char").innerHTML= String.fromCharCode(i); + document.getElementById("preview_code").innerHTML= "&#"+ i +";"; +} + +function insertChar(i){ + opener.parent.editAreaLoader.setSelectedText(editArea.id, String.fromCharCode( i)); + range= opener.parent.editAreaLoader.getSelectionRange(editArea.id); + opener.parent.editAreaLoader.setSelectionRange(editArea.id, range["end"], range["end"]); + window.focus(); +} ADDED applications/admin/static/edit_area/plugins/charmap/langs/bg.js Index: applications/admin/static/edit_area/plugins/charmap/langs/bg.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/bg.js +++ applications/admin/static/edit_area/plugins/charmap/langs/bg.js @@ -0,0 +1,12 @@ +/* + * Bulgarian translation + * Author: Valentin Hristov + * Company: SOFTKIT Bulgarian + * Site: http://www.softkit-bg.com + */ +editArea.add_lang("bg",{ +charmap_but: "Виртуална клавиатура", +charmap_title: "Виртуална клавиатура", +charmap_choose_block: "избери езиков блок", +charmap_insert:"постави този символ" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/cs.js Index: applications/admin/static/edit_area/plugins/charmap/langs/cs.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/cs.js +++ applications/admin/static/edit_area/plugins/charmap/langs/cs.js @@ -0,0 +1,6 @@ +editArea.add_lang("cs",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/de.js Index: applications/admin/static/edit_area/plugins/charmap/langs/de.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/de.js +++ applications/admin/static/edit_area/plugins/charmap/langs/de.js @@ -0,0 +1,6 @@ +editArea.add_lang("de",{ +charmap_but: "Sonderzeichen", +charmap_title: "Sonderzeichen", +charmap_choose_block: "Bereich auswählen", +charmap_insert: "dieses Zeichen einfügen" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/dk.js Index: applications/admin/static/edit_area/plugins/charmap/langs/dk.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/dk.js +++ applications/admin/static/edit_area/plugins/charmap/langs/dk.js @@ -0,0 +1,6 @@ +editArea.add_lang("dk",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/en.js Index: applications/admin/static/edit_area/plugins/charmap/langs/en.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/en.js +++ applications/admin/static/edit_area/plugins/charmap/langs/en.js @@ -0,0 +1,6 @@ +editArea.add_lang("en",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/eo.js Index: applications/admin/static/edit_area/plugins/charmap/langs/eo.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/eo.js +++ applications/admin/static/edit_area/plugins/charmap/langs/eo.js @@ -0,0 +1,6 @@ +editArea.add_lang("eo",{ +charmap_but: "Ekranklavaro", +charmap_title: "Ekranklavaro", +charmap_choose_block: "Elekto de lingvo", +charmap_insert:"enmeti tiun signaron" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/es.js Index: applications/admin/static/edit_area/plugins/charmap/langs/es.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/es.js +++ applications/admin/static/edit_area/plugins/charmap/langs/es.js @@ -0,0 +1,6 @@ +editArea.add_lang("es",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/fr.js Index: applications/admin/static/edit_area/plugins/charmap/langs/fr.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/fr.js +++ applications/admin/static/edit_area/plugins/charmap/langs/fr.js @@ -0,0 +1,6 @@ +editArea.add_lang("fr",{ +charmap_but: "Clavier visuel", +charmap_title: "Clavier visuel", +charmap_choose_block: "choix du language", +charmap_insert:"insérer ce caractère" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/hr.js Index: applications/admin/static/edit_area/plugins/charmap/langs/hr.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/hr.js +++ applications/admin/static/edit_area/plugins/charmap/langs/hr.js @@ -0,0 +1,6 @@ +editArea.add_lang("hr",{ +charmap_but: "Virtualna tipkovnica", +charmap_title: "Virtualna tipkovnica", +charmap_choose_block: "Odaberi blok s jezikom", +charmap_insert:"Ubaci taj znak" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/it.js Index: applications/admin/static/edit_area/plugins/charmap/langs/it.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/it.js +++ applications/admin/static/edit_area/plugins/charmap/langs/it.js @@ -0,0 +1,6 @@ +editArea.add_lang("it",{ +charmap_but: "Tastiera visuale", +charmap_title: "Tastiera visuale", +charmap_choose_block: "seleziona blocco", +charmap_insert:"inserisci questo carattere" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/ja.js Index: applications/admin/static/edit_area/plugins/charmap/langs/ja.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/ja.js +++ applications/admin/static/edit_area/plugins/charmap/langs/ja.js @@ -0,0 +1,6 @@ +editArea.add_lang("ja",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/mk.js Index: applications/admin/static/edit_area/plugins/charmap/langs/mk.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/mk.js +++ applications/admin/static/edit_area/plugins/charmap/langs/mk.js @@ -0,0 +1,6 @@ +editArea.add_lang("mkn",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/nl.js Index: applications/admin/static/edit_area/plugins/charmap/langs/nl.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/nl.js +++ applications/admin/static/edit_area/plugins/charmap/langs/nl.js @@ -0,0 +1,6 @@ +editArea.add_lang("nl",{ +charmap_but: "Visueel toetsenbord", +charmap_title: "Visueel toetsenbord", +charmap_choose_block: "Kies een taal blok", +charmap_insert:"Voeg dit symbool in" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/pl.js Index: applications/admin/static/edit_area/plugins/charmap/langs/pl.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/pl.js +++ applications/admin/static/edit_area/plugins/charmap/langs/pl.js @@ -0,0 +1,6 @@ +editArea.add_lang("pl",{ +charmap_but: "Klawiatura ekranowa", +charmap_title: "Klawiatura ekranowa", +charmap_choose_block: "wybierz grupę znaków", +charmap_insert:"wstaw ten znak" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/pt.js Index: applications/admin/static/edit_area/plugins/charmap/langs/pt.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/pt.js +++ applications/admin/static/edit_area/plugins/charmap/langs/pt.js @@ -0,0 +1,6 @@ +editArea.add_lang("pt",{ +charmap_but: "Visual keyboard", +charmap_title: "Visual keyboard", +charmap_choose_block: "select language block", +charmap_insert:"insert this character" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/ru.js Index: applications/admin/static/edit_area/plugins/charmap/langs/ru.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/ru.js +++ applications/admin/static/edit_area/plugins/charmap/langs/ru.js @@ -0,0 +1,6 @@ +editArea.add_lang("ru",{ +charmap_but: "Визуальная клавиатура", +charmap_title: "Визуальная клавиатура", +charmap_choose_block: "выбрать языковой блок", +charmap_insert:"вставить этот символ" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/sk.js Index: applications/admin/static/edit_area/plugins/charmap/langs/sk.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/sk.js +++ applications/admin/static/edit_area/plugins/charmap/langs/sk.js @@ -0,0 +1,6 @@ +editArea.add_lang("sk",{ +charmap_but: "Vizuálna klávesnica", +charmap_title: "Vizuálna klávesnica", +charmap_choose_block: "vyber jazykový blok", +charmap_insert: "vlož tento znak" +}); ADDED applications/admin/static/edit_area/plugins/charmap/langs/zh.js Index: applications/admin/static/edit_area/plugins/charmap/langs/zh.js ================================================================== --- applications/admin/static/edit_area/plugins/charmap/langs/zh.js +++ applications/admin/static/edit_area/plugins/charmap/langs/zh.js @@ -0,0 +1,6 @@ +editArea.add_lang("zh",{ +charmap_but: "软键盘", +charmap_title: "软键盘", +charmap_choose_block: "选择一个语言块", +charmap_insert:"插入此字符" +}); ADDED applications/admin/static/edit_area/plugins/charmap/popup.html Index: applications/admin/static/edit_area/plugins/charmap/popup.html ================================================================== --- applications/admin/static/edit_area/plugins/charmap/popup.html +++ applications/admin/static/edit_area/plugins/charmap/popup.html @@ -0,0 +1,24 @@ + + + + +{$charmap_title} + + + + + +
+
+

{$charmap_title}:

+ +
+ +
+ + + + + ADDED applications/admin/static/edit_area/plugins/test/css/test.css Index: applications/admin/static/edit_area/plugins/test/css/test.css ================================================================== --- applications/admin/static/edit_area/plugins/test/css/test.css +++ applications/admin/static/edit_area/plugins/test/css/test.css @@ -0,0 +1,3 @@ +select#test_select{ + background-color: #FF0000; +} ADDED applications/admin/static/edit_area/plugins/test/images/test.gif Index: applications/admin/static/edit_area/plugins/test/images/test.gif ================================================================== --- applications/admin/static/edit_area/plugins/test/images/test.gif +++ applications/admin/static/edit_area/plugins/test/images/test.gif cannot compute difference between binary files ADDED applications/admin/static/edit_area/plugins/test/langs/bg.js Index: applications/admin/static/edit_area/plugins/test/langs/bg.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/bg.js +++ applications/admin/static/edit_area/plugins/test/langs/bg.js @@ -0,0 +1,10 @@ +/* + * Bulgarian translation + * Author: Valentin Hristov + * Company: SOFTKIT Bulgarian + * Site: http://www.softkit-bg.com + */ +editArea.add_lang("bg",{ +test_select: "избери таг", +test_but: "тествай копието" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/cs.js Index: applications/admin/static/edit_area/plugins/test/langs/cs.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/cs.js +++ applications/admin/static/edit_area/plugins/test/langs/cs.js @@ -0,0 +1,4 @@ +editArea.add_lang("cs",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/de.js Index: applications/admin/static/edit_area/plugins/test/langs/de.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/de.js +++ applications/admin/static/edit_area/plugins/test/langs/de.js @@ -0,0 +1,4 @@ +editArea.add_lang("de",{ +test_select: "Tag auswählen", +test_but: "Test Button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/dk.js Index: applications/admin/static/edit_area/plugins/test/langs/dk.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/dk.js +++ applications/admin/static/edit_area/plugins/test/langs/dk.js @@ -0,0 +1,4 @@ +editArea.add_lang("dk",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/en.js Index: applications/admin/static/edit_area/plugins/test/langs/en.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/en.js +++ applications/admin/static/edit_area/plugins/test/langs/en.js @@ -0,0 +1,4 @@ +editArea.add_lang("en",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/eo.js Index: applications/admin/static/edit_area/plugins/test/langs/eo.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/eo.js +++ applications/admin/static/edit_area/plugins/test/langs/eo.js @@ -0,0 +1,4 @@ +editArea.add_lang("eo",{ +test_select:"elekto de marko", +test_but: "provo-butono" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/es.js Index: applications/admin/static/edit_area/plugins/test/langs/es.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/es.js +++ applications/admin/static/edit_area/plugins/test/langs/es.js @@ -0,0 +1,4 @@ +editArea.add_lang("es",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/fr.js Index: applications/admin/static/edit_area/plugins/test/langs/fr.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/fr.js +++ applications/admin/static/edit_area/plugins/test/langs/fr.js @@ -0,0 +1,4 @@ +editArea.add_lang("fr",{ +test_select:"choix balise", +test_but: "bouton de test" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/hr.js Index: applications/admin/static/edit_area/plugins/test/langs/hr.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/hr.js +++ applications/admin/static/edit_area/plugins/test/langs/hr.js @@ -0,0 +1,4 @@ +editArea.add_lang("hr",{ +test_select: "Odaberi tag", +test_but: "Probna tipka" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/it.js Index: applications/admin/static/edit_area/plugins/test/langs/it.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/it.js +++ applications/admin/static/edit_area/plugins/test/langs/it.js @@ -0,0 +1,4 @@ +editArea.add_lang("it",{ +test_select: "seleziona tag", +test_but: "pulsante di test" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/ja.js Index: applications/admin/static/edit_area/plugins/test/langs/ja.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/ja.js +++ applications/admin/static/edit_area/plugins/test/langs/ja.js @@ -0,0 +1,4 @@ +editArea.add_lang("ja",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/mk.js Index: applications/admin/static/edit_area/plugins/test/langs/mk.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/mk.js +++ applications/admin/static/edit_area/plugins/test/langs/mk.js @@ -0,0 +1,4 @@ +editArea.add_lang("mk",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/nl.js Index: applications/admin/static/edit_area/plugins/test/langs/nl.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/nl.js +++ applications/admin/static/edit_area/plugins/test/langs/nl.js @@ -0,0 +1,4 @@ +editArea.add_lang("nl",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/pl.js Index: applications/admin/static/edit_area/plugins/test/langs/pl.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/pl.js +++ applications/admin/static/edit_area/plugins/test/langs/pl.js @@ -0,0 +1,4 @@ +editArea.add_lang("pl",{ +test_select: "wybierz tag", +test_but: "test" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/pt.js Index: applications/admin/static/edit_area/plugins/test/langs/pt.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/pt.js +++ applications/admin/static/edit_area/plugins/test/langs/pt.js @@ -0,0 +1,4 @@ +editArea.add_lang("pt",{ +test_select: "select tag", +test_but: "test button" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/ru.js Index: applications/admin/static/edit_area/plugins/test/langs/ru.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/ru.js +++ applications/admin/static/edit_area/plugins/test/langs/ru.js @@ -0,0 +1,4 @@ +editArea.add_lang("ru",{ +test_select: "выбрать тэг", +test_but: "тестировать кнопку" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/sk.js Index: applications/admin/static/edit_area/plugins/test/langs/sk.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/sk.js +++ applications/admin/static/edit_area/plugins/test/langs/sk.js @@ -0,0 +1,4 @@ +editArea.add_lang("sk",{ +test_select: "vyber tag", +test_but: "testovacie tlačidlo" +}); ADDED applications/admin/static/edit_area/plugins/test/langs/zh.js Index: applications/admin/static/edit_area/plugins/test/langs/zh.js ================================================================== --- applications/admin/static/edit_area/plugins/test/langs/zh.js +++ applications/admin/static/edit_area/plugins/test/langs/zh.js @@ -0,0 +1,4 @@ +editArea.add_lang("zh",{ +test_select: "选择标签", +test_but: "测试按钮" +}); ADDED applications/admin/static/edit_area/plugins/test/test.js Index: applications/admin/static/edit_area/plugins/test/test.js ================================================================== --- applications/admin/static/edit_area/plugins/test/test.js +++ applications/admin/static/edit_area/plugins/test/test.js @@ -0,0 +1,110 @@ +/** + * Plugin designed for test prupose. It add a button (that manage an alert) and a select (that allow to insert tags) in the toolbar. + * This plugin also disable the "f" key in the editarea, and load a CSS and a JS file + */ +var EditArea_test= { + /** + * Get called once this file is loaded (editArea still not initialized) + * + * @return nothing + */ + init: function(){ + // alert("test init: "+ this._someInternalFunction(2, 3)); + editArea.load_css(this.baseURL+"css/test.css"); + editArea.load_script(this.baseURL+"test2.js"); + } + /** + * Returns the HTML code for a specific control string or false if this plugin doesn't have that control. + * A control can be a button, select list or any other HTML item to present in the EditArea user interface. + * Language variables such as {$lang_somekey} will also be replaced with contents from + * the language packs. + * + * @param {string} ctrl_name: the name of the control to add + * @return HTML code for a specific control or false. + * @type string or boolean + */ + ,get_control_html: function(ctrl_name){ + switch(ctrl_name){ + case "test_but": + // Control id, button img, command + return parent.editAreaLoader.get_button_html('test_but', 'test.gif', 'test_cmd', false, this.baseURL); + case "test_select": + html= ""; + return html; + } + return false; + } + /** + * Get called once EditArea is fully loaded and initialised + * + * @return nothing + */ + ,onload: function(){ + alert("test load"); + } + + /** + * Is called each time the user touch a keyboard key. + * + * @param (event) e: the keydown event + * @return true - pass to next handler in chain, false - stop chain execution + * @type boolean + */ + ,onkeydown: function(e){ + var str= String.fromCharCode(e.keyCode); + // desactivate the "f" character + if(str.toLowerCase()=="f"){ + return true; + } + return false; + } + + /** + * Executes a specific command, this function handles plugin commands. + * + * @param {string} cmd: the name of the command being executed + * @param {unknown} param: the parameter of the command + * @return true - pass to next handler in chain, false - stop chain execution + * @type boolean + */ + ,execCommand: function(cmd, param){ + // Handle commands + switch(cmd){ + case "test_select_change": + var val= document.getElementById("test_select").value; + if(val!=-1) + parent.editAreaLoader.insertTags(editArea.id, "<"+val+">", ""); + document.getElementById("test_select").options[0].selected=true; + return false; + case "test_cmd": + alert("user clicked on test_cmd"); + return false; + } + // Pass to next handler in chain + return true; + } + + /** + * This is just an internal plugin method, prefix all internal methods with a _ character. + * The prefix is needed so they doesn't collide with future EditArea callback functions. + * + * @param {string} a Some arg1. + * @param {string} b Some arg2. + * @return Some return. + * @type unknown + */ + ,_someInternalFunction : function(a, b) { + return a+b; + } +}; + +// Adds the plugin class to the list of available EditArea plugins +editArea.add_plugin("test", EditArea_test); ADDED applications/admin/static/edit_area/plugins/test/test2.js Index: applications/admin/static/edit_area/plugins/test/test2.js ================================================================== --- applications/admin/static/edit_area/plugins/test/test2.js +++ applications/admin/static/edit_area/plugins/test/test2.js @@ -0,0 +1,1 @@ +alert("test2.js is loaded from test plugin"); ADDED applications/admin/static/edit_area/plugins/zencoding/core.js Index: applications/admin/static/edit_area/plugins/zencoding/core.js ================================================================== --- applications/admin/static/edit_area/plugins/zencoding/core.js +++ applications/admin/static/edit_area/plugins/zencoding/core.js @@ -0,0 +1,4 @@ +var zen_settings={"variables":{"lang":"en","locale":"en-US","charset":"UTF-8","profile":"xhtml","indentation":"\t"},"css":{"snippets":{"@i":"@import url(|);","@m":"@media print {\n\t|\n}","@f":"@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}","!":"!important","pos":"position:|;","pos:s":"position:static;","pos:a":"position:absolute;","pos:r":"position:relative;","pos:f":"position:fixed;","t":"top:|;","t:a":"top:auto;","r":"right:|;","r:a":"right:auto;","b":"bottom:|;","b:a":"bottom:auto;","l":"left:|;","l:a":"left:auto;","z":"z-index:|;","z:a":"z-index:auto;","fl":"float:|;","fl:n":"float:none;","fl:l":"float:left;","fl:r":"float:right;","cl":"clear:|;","cl:n":"clear:none;","cl:l":"clear:left;","cl:r":"clear:right;","cl:b":"clear:both;","d":"display:|;","d:n":"display:none;","d:b":"display:block;","d:ib":"display:inline;","d:li":"display:list-item;","d:ri":"display:run-in;","d:cp":"display:compact;","d:tb":"display:table;","d:itb":"display:inline-table;","d:tbcp":"display:table-caption;","d:tbcl":"display:table-column;","d:tbclg":"display:table-column-group;","d:tbhg":"display:table-header-group;","d:tbfg":"display:table-footer-group;","d:tbr":"display:table-row;","d:tbrg":"display:table-row-group;","d:tbc":"display:table-cell;","d:rb":"display:ruby;","d:rbb":"display:ruby-base;","d:rbbg":"display:ruby-base-group;","d:rbt":"display:ruby-text;","d:rbtg":"display:ruby-text-group;","v":"visibility:|;","v:v":"visibility:visible;","v:h":"visibility:hidden;","v:c":"visibility:collapse;","ov":"overflow:|;","ov:v":"overflow:visible;","ov:h":"overflow:hidden;","ov:s":"overflow:scroll;","ov:a":"overflow:auto;","ovx":"overflow-x:|;","ovx:v":"overflow-x:visible;","ovx:h":"overflow-x:hidden;","ovx:s":"overflow-x:scroll;","ovx:a":"overflow-x:auto;","ovy":"overflow-y:|;","ovy:v":"overflow-y:visible;","ovy:h":"overflow-y:hidden;","ovy:s":"overflow-y:scroll;","ovy:a":"overflow-y:auto;","ovs":"overflow-style:|;","ovs:a":"overflow-style:auto;","ovs:s":"overflow-style:scrollbar;","ovs:p":"overflow-style:panner;","ovs:m":"overflow-style:move;","ovs:mq":"overflow-style:marquee;","zoo":"zoom:1;","cp":"clip:|;","cp:a":"clip:auto;","cp:r":"clip:rect(|);","bxz":"box-sizing:|;","bxz:cb":"box-sizing:content-box;","bxz:bb":"box-sizing:border-box;","bxsh":"box-shadow:|;","bxsh:n":"box-shadow:none;","bxsh:w":"-webkit-box-shadow:0 0 0 #000;","bxsh:m":"-moz-box-shadow:0 0 0 0 #000;","m":"margin:|;","m:a":"margin:auto;","m:0":"margin:0;","m:2":"margin:0 0;","m:3":"margin:0 0 0;","m:4":"margin:0 0 0 0;","mt":"margin-top:|;","mt:a":"margin-top:auto;","mr":"margin-right:|;","mr:a":"margin-right:auto;","mb":"margin-bottom:|;","mb:a":"margin-bottom:auto;","ml":"margin-left:|;","ml:a":"margin-left:auto;","p":"padding:|;","p:0":"padding:0;","p:2":"padding:0 0;","p:3":"padding:0 0 0;","p:4":"padding:0 0 0 0;","pt":"padding-top:|;","pr":"padding-right:|;","pb":"padding-bottom:|;","pl":"padding-left:|;","w":"width:|;","w:a":"width:auto;","h":"height:|;","h:a":"height:auto;","maw":"max-width:|;","maw:n":"max-width:none;","mah":"max-height:|;","mah:n":"max-height:none;","miw":"min-width:|;","mih":"min-height:|;","o":"outline:|;","o:n":"outline:none;","oo":"outline-offset:|;","ow":"outline-width:|;","os":"outline-style:|;","oc":"outline-color:#000;","oc:i":"outline-color:invert;","bd":"border:|;","bd+":"border:1px solid #000;","bd:n":"border:none;","bdbk":"border-break:|;","bdbk:c":"border-break:close;","bdcl":"border-collapse:|;","bdcl:c":"border-collapse:collapse;","bdcl:s":"border-collapse:separate;","bdc":"border-color:#000;","bdi":"border-image:url(|);","bdi:n":"border-image:none;","bdi:w":"-webkit-border-image:url(|) 0 0 0 0 stretch stretch;","bdi:m":"-moz-border-image:url(|) 0 0 0 0 stretch stretch;","bdti":"border-top-image:url(|);","bdti:n":"border-top-image:none;","bdri":"border-right-image:url(|);","bdri:n":"border-right-image:none;","bdbi":"border-bottom-image:url(|);","bdbi:n":"border-bottom-image:none;","bdli":"border-left-image:url(|);","bdli:n":"border-left-image:none;","bdci":"border-corner-image:url(|);","bdci:n":"border-corner-image:none;","bdci:c":"border-corner-image:continue;","bdtli":"border-top-left-image:url(|);","bdtli:n":"border-top-left-image:none;","bdtli:c":"border-top-left-image:continue;","bdtri":"border-top-right-image:url(|);","bdtri:n":"border-top-right-image:none;","bdtri:c":"border-top-right-image:continue;","bdbri":"border-bottom-right-image:url(|);","bdbri:n":"border-bottom-right-image:none;","bdbri:c":"border-bottom-right-image:continue;","bdbli":"border-bottom-left-image:url(|);","bdbli:n":"border-bottom-left-image:none;","bdbli:c":"border-bottom-left-image:continue;","bdf":"border-fit:|;","bdf:c":"border-fit:clip;","bdf:r":"border-fit:repeat;","bdf:sc":"border-fit:scale;","bdf:st":"border-fit:stretch;","bdf:ow":"border-fit:overwrite;","bdf:of":"border-fit:overflow;","bdf:sp":"border-fit:space;","bdl":"border-length:|;","bdl:a":"border-length:auto;","bdsp":"border-spacing:|;","bds":"border-style:|;","bds:n":"border-style:none;","bds:h":"border-style:hidden;","bds:dt":"border-style:dotted;","bds:ds":"border-style:dashed;","bds:s":"border-style:solid;","bds:db":"border-style:double;","bds:dtds":"border-style:dot-dash;","bds:dtdtds":"border-style:dot-dot-dash;","bds:w":"border-style:wave;","bds:g":"border-style:groove;","bds:r":"border-style:ridge;","bds:i":"border-style:inset;","bds:o":"border-style:outset;","bdw":"border-width:|;","bdt":"border-top:|;","bdt+":"border-top:1px solid #000;","bdt:n":"border-top:none;","bdtw":"border-top-width:|;","bdts":"border-top-style:|;","bdts:n":"border-top-style:none;","bdtc":"border-top-color:#000;","bdr":"border-right:|;","bdr+":"border-right:1px solid #000;","bdr:n":"border-right:none;","bdrw":"border-right-width:|;","bdrs":"border-right-style:|;","bdrs:n":"border-right-style:none;","bdrc":"border-right-color:#000;","bdb":"border-bottom:|;","bdb+":"border-bottom:1px solid #000;","bdb:n":"border-bottom:none;","bdbw":"border-bottom-width:|;","bdbs":"border-bottom-style:|;","bdbs:n":"border-bottom-style:none;","bdbc":"border-bottom-color:#000;","bdl":"border-left:|;","bdl+":"border-left:1px solid #000;","bdl:n":"border-left:none;","bdlw":"border-left-width:|;","bdls":"border-left-style:|;","bdls:n":"border-left-style:none;","bdlc":"border-left-color:#000;","bdrs":"border-radius:|;","bdtrrs":"border-top-right-radius:|;","bdtlrs":"border-top-left-radius:|;","bdbrrs":"border-bottom-right-radius:|;","bdblrs":"border-bottom-left-radius:|;","bg":"background:|;","bg+":"background:#FFF url(|) 0 0 no-repeat;","bg:n":"background:none;","bg:ie":"filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='|x.png');","bgc":"background-color:#FFF;","bgi":"background-image:url(|);","bgi:n":"background-image:none;","bgr":"background-repeat:|;","bgr:n":"background-repeat:no-repeat;","bgr:x":"background-repeat:repeat-x;","bgr:y":"background-repeat:repeat-y;","bga":"background-attachment:|;","bga:f":"background-attachment:fixed;","bga:s":"background-attachment:scroll;","bgp":"background-position:0 0;","bgpx":"background-position-x:|;","bgpy":"background-position-y:|;","bgbk":"background-break:|;","bgbk:bb":"background-break:bounding-box;","bgbk:eb":"background-break:each-box;","bgbk:c":"background-break:continuous;","bgcp":"background-clip:|;","bgcp:bb":"background-clip:border-box;","bgcp:pb":"background-clip:padding-box;","bgcp:cb":"background-clip:content-box;","bgcp:nc":"background-clip:no-clip;","bgo":"background-origin:|;","bgo:pb":"background-origin:padding-box;","bgo:bb":"background-origin:border-box;","bgo:cb":"background-origin:content-box;","bgz":"background-size:|;","bgz:a":"background-size:auto;","bgz:ct":"background-size:contain;","bgz:cv":"background-size:cover;","c":"color:#000;","tbl":"table-layout:|;","tbl:a":"table-layout:auto;","tbl:f":"table-layout:fixed;","cps":"caption-side:|;","cps:t":"caption-side:top;","cps:b":"caption-side:bottom;","ec":"empty-cells:|;","ec:s":"empty-cells:show;","ec:h":"empty-cells:hide;","lis":"list-style:|;","lis:n":"list-style:none;","lisp":"list-style-position:|;","lisp:i":"list-style-position:inside;","lisp:o":"list-style-position:outside;","list":"list-style-type:|;","list:n":"list-style-type:none;","list:d":"list-style-type:disc;","list:c":"list-style-type:circle;","list:s":"list-style-type:square;","list:dc":"list-style-type:decimal;","list:dclz":"list-style-type:decimal-leading-zero;","list:lr":"list-style-type:lower-roman;","list:ur":"list-style-type:upper-roman;","lisi":"list-style-image:|;","lisi:n":"list-style-image:none;","q":"quotes:|;","q:n":"quotes:none;","q:ru":"quotes:'\00AB' '\00BB' '\201E' '\201C';","q:en":"quotes:'\201C' '\201D' '\2018' '\2019';","ct":"content:|;","ct:n":"content:normal;","ct:oq":"content:open-quote;","ct:noq":"content:no-open-quote;","ct:cq":"content:close-quote;","ct:ncq":"content:no-close-quote;","ct:a":"content:attr(|);","ct:c":"content:counter(|);","ct:cs":"content:counters(|);","coi":"counter-increment:|;","cor":"counter-reset:|;","va":"vertical-align:|;","va:sup":"vertical-align:super;","va:t":"vertical-align:top;","va:tt":"vertical-align:text-top;","va:m":"vertical-align:middle;","va:bl":"vertical-align:baseline;","va:b":"vertical-align:bottom;","va:tb":"vertical-align:text-bottom;","va:sub":"vertical-align:sub;","ta":"text-align:|;","ta:l":"text-align:left;","ta:c":"text-align:center;","ta:r":"text-align:right;","tal":"text-align-last:|;","tal:a":"text-align-last:auto;","tal:l":"text-align-last:left;","tal:c":"text-align-last:center;","tal:r":"text-align-last:right;","td":"text-decoration:|;","td:n":"text-decoration:none;","td:u":"text-decoration:underline;","td:o":"text-decoration:overline;","td:l":"text-decoration:line-through;","te":"text-emphasis:|;","te:n":"text-emphasis:none;","te:ac":"text-emphasis:accent;","te:dt":"text-emphasis:dot;","te:c":"text-emphasis:circle;","te:ds":"text-emphasis:disc;","te:b":"text-emphasis:before;","te:a":"text-emphasis:after;","th":"text-height:|;","th:a":"text-height:auto;","th:f":"text-height:font-size;","th:t":"text-height:text-size;","th:m":"text-height:max-size;","ti":"text-indent:|;","ti:-":"text-indent:-9999px;","tj":"text-justify:|;","tj:a":"text-justify:auto;","tj:iw":"text-justify:inter-word;","tj:ii":"text-justify:inter-ideograph;","tj:ic":"text-justify:inter-cluster;","tj:d":"text-justify:distribute;","tj:k":"text-justify:kashida;","tj:t":"text-justify:tibetan;","to":"text-outline:|;","to+":"text-outline:0 0 #000;","to:n":"text-outline:none;","tr":"text-replace:|;","tr:n":"text-replace:none;","tt":"text-transform:|;","tt:n":"text-transform:none;","tt:c":"text-transform:capitalize;","tt:u":"text-transform:uppercase;","tt:l":"text-transform:lowercase;","tw":"text-wrap:|;","tw:n":"text-wrap:normal;","tw:no":"text-wrap:none;","tw:u":"text-wrap:unrestricted;","tw:s":"text-wrap:suppress;","tsh":"text-shadow:|;","tsh+":"text-shadow:0 0 0 #000;","tsh:n":"text-shadow:none;","lh":"line-height:|;","whs":"white-space:|;","whs:n":"white-space:normal;","whs:p":"white-space:pre;","whs:nw":"white-space:nowrap;","whs:pw":"white-space:pre-wrap;","whs:pl":"white-space:pre-line;","whsc":"white-space-collapse:|;","whsc:n":"white-space-collapse:normal;","whsc:k":"white-space-collapse:keep-all;","whsc:l":"white-space-collapse:loose;","whsc:bs":"white-space-collapse:break-strict;","whsc:ba":"white-space-collapse:break-all;","wob":"word-break:|;","wob:n":"word-break:normal;","wob:k":"word-break:keep-all;","wob:l":"word-break:loose;","wob:bs":"word-break:break-strict;","wob:ba":"word-break:break-all;","wos":"word-spacing:|;","wow":"word-wrap:|;","wow:nm":"word-wrap:normal;","wow:n":"word-wrap:none;","wow:u":"word-wrap:unrestricted;","wow:s":"word-wrap:suppress;","lts":"letter-spacing:|;","f":"font:|;","f+":"font:1em Arial,sans-serif;","fw":"font-weight:|;","fw:n":"font-weight:normal;","fw:b":"font-weight:bold;","fw:br":"font-weight:bolder;","fw:lr":"font-weight:lighter;","fs":"font-style:|;","fs:n":"font-style:normal;","fs:i":"font-style:italic;","fs:o":"font-style:oblique;","fv":"font-variant:|;","fv:n":"font-variant:normal;","fv:sc":"font-variant:small-caps;","fz":"font-size:|;","fza":"font-size-adjust:|;","fza:n":"font-size-adjust:none;","ff":"font-family:|;","ff:s":"font-family:serif;","ff:ss":"font-family:sans-serif;","ff:c":"font-family:cursive;","ff:f":"font-family:fantasy;","ff:m":"font-family:monospace;","fef":"font-effect:|;","fef:n":"font-effect:none;","fef:eg":"font-effect:engrave;","fef:eb":"font-effect:emboss;","fef:o":"font-effect:outline;","fem":"font-emphasize:|;","femp":"font-emphasize-position:|;","femp:b":"font-emphasize-position:before;","femp:a":"font-emphasize-position:after;","fems":"font-emphasize-style:|;","fems:n":"font-emphasize-style:none;","fems:ac":"font-emphasize-style:accent;","fems:dt":"font-emphasize-style:dot;","fems:c":"font-emphasize-style:circle;","fems:ds":"font-emphasize-style:disc;","fsm":"font-smooth:|;","fsm:a":"font-smooth:auto;","fsm:n":"font-smooth:never;","fsm:aw":"font-smooth:always;","fst":"font-stretch:|;","fst:n":"font-stretch:normal;","fst:uc":"font-stretch:ultra-condensed;","fst:ec":"font-stretch:extra-condensed;","fst:c":"font-stretch:condensed;","fst:sc":"font-stretch:semi-condensed;","fst:se":"font-stretch:semi-expanded;","fst:e":"font-stretch:expanded;","fst:ee":"font-stretch:extra-expanded;","fst:ue":"font-stretch:ultra-expanded;","op":"opacity:|;","op:ie":"filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);","op:ms":"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';","rz":"resize:|;","rz:n":"resize:none;","rz:b":"resize:both;","rz:h":"resize:horizontal;","rz:v":"resize:vertical;","cur":"cursor:|;","cur:a":"cursor:auto;","cur:d":"cursor:default;","cur:c":"cursor:crosshair;","cur:ha":"cursor:hand;","cur:he":"cursor:help;","cur:m":"cursor:move;","cur:p":"cursor:pointer;","cur:t":"cursor:text;","pgbb":"page-break-before:|;","pgbb:au":"page-break-before:auto;","pgbb:al":"page-break-before:always;","pgbb:l":"page-break-before:left;","pgbb:r":"page-break-before:right;","pgbi":"page-break-inside:|;","pgbi:au":"page-break-inside:auto;","pgbi:av":"page-break-inside:avoid;","pgba":"page-break-after:|;","pgba:au":"page-break-after:auto;","pgba:al":"page-break-after:always;","pgba:l":"page-break-after:left;","pgba:r":"page-break-after:right;","orp":"orphans:|;","wid":"widows:|;"}},"html":{"snippets":{"cc:ie6":"","cc:ie":"","cc:noie":"\n\t${child}|\n","html:4t":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:4s":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:xt":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:xs":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:xxs":'\n'+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+"","html:5":"\n"+'\n'+"\n"+" \n"+' \n'+"\n"+"\n\t${child}|\n\n"+""},"abbreviations":{"a":'',"a:link":'',"a:mail":'',"abbr":'',"acronym":'',"base":'',"bdo":'',"bdo:r":'',"bdo:l":'',"link:css":'',"link:print":'',"link:favicon":'',"link:touch":'',"link:rss":'',"link:atom":'',"meta:utf":'',"meta:win":'',"meta:compat":'',"style":'',"script":'