diff options
109 files changed, 8206 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33b2f64 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +*.swp +local_settings.py + @@ -0,0 +1,6 @@ +# AUTHORS +Judd Vinet <judd@archlinux.org> +Simo Leone <simo@archlinux.org> +eliott <eliott@cactuswax.net> + + @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. @@ -0,0 +1,14 @@ +# License + See LICENSE file. + +# Authors + See AUTHORS file. + +# Installation + +## Dependencies + - python + - mysql-python + - Django >= 0.95 + + diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/__init__.py diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/common/__init__.py diff --git a/common/models.py b/common/models.py new file mode 100644 index 0000000..883675b --- /dev/null +++ b/common/models.py @@ -0,0 +1,50 @@ +from django.db import models +from django.contrib.auth.models import User + +class Mirror(models.Model): + id = models.AutoField(primary_key=True) + domain = models.CharField(maxlength=255) + country = models.CharField(maxlength=255) + url = models.CharField(maxlength=255) + protocol_list = models.CharField(maxlength=255, null=True, blank=True) + admin_email = models.CharField(maxlength=255, null=True, blank=True) + def __str__(self): + return self.domain + class Admin: + list_display = ('domain', 'country') + list_filter = ('country',) + ordering = ['domain'] + search_fields = ('domain') + pass + +class Donator(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(maxlength=255) + def __str__(self): + return self.name + class Admin: + ordering = ['name'] + search_fields = ('name') + pass + +class UserProfile(models.Model): + id = models.AutoField(primary_key=True) # not technically needed + notify = models.BooleanField("Send notifications", default=True, help_text="When enabled, user will recieve 'flag out of date' notifications") + alias = models.CharField(core=True, maxlength=50, help_text="Required field") + public_email = models.CharField(core=True, maxlength=50, help_text="Required field") + other_contact = models.CharField(maxlength=100, null=True, blank=True) + website = models.URLField(null=True, blank=True) + yob = models.IntegerField(null=True, blank=True) + location = models.CharField(maxlength=50, null=True, blank=True) + languages = models.CharField(maxlength=50, null=True, blank=True) + interests = models.CharField(maxlength=255, null=True, blank=True) + occupation = models.CharField(maxlength=50, null=True, blank=True) + roles = models.CharField(maxlength=255, null=True, blank=True) + favorite_distros = models.CharField(maxlength=255, null=True, blank=True) + picture = models.FileField(upload_to='devs', default='devs/silhouette.png') + user = models.ForeignKey(User, edit_inline=models.STACKED, num_in_admin=1, min_num_in_admin=1, max_num_in_admin=1, num_extra_on_change=0, unique=True) + class Meta: + db_table = 'user_profiles' + verbose_name = 'Additional Profile Data' + verbose_name_plural = 'Additional Profile Data' + diff --git a/common/templatetags/__init__.py b/common/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/common/templatetags/__init__.py diff --git a/common/templatetags/validation.py b/common/templatetags/validation.py new file mode 100644 index 0000000..40c48da --- /dev/null +++ b/common/templatetags/validation.py @@ -0,0 +1,12 @@ +from django import template + +register = template.Library() + +@register.inclusion_tag('errors.html') +def print_errors(errors): + errs = [] + for e,msg in errors.iteritems(): + errmsg = str(msg[0]) + # hack -- I'm a python idiot + errs.append( (e, errmsg[2:-2]) ) + return {'errors': errs} diff --git a/data/pkgmaint_guide.txt b/data/pkgmaint_guide.txt new file mode 100644 index 0000000..85106ea --- /dev/null +++ b/data/pkgmaint_guide.txt @@ -0,0 +1,164 @@ +============================================================================= + THE QUICK AND DIRTY ON HOW TO BE A PACKAGE MAINTAINER +============================================================================= + questions to jvinet@zeroflux.org + +1. Follow Package Guidelines + + Package guidelines can be found in the Arch Linux documentation. + Please follow them closely. + +2. How To Use CVS + + The example commands below assume the module 'extra'. + + 2.1 Make sure your CVSROOT environment variable is set properly. If the + CVS repository is on the same box: + # export CVSROOT=/home/cvs-extra + + If you want to access the repository from the different box via SSH: + # export CVS_RSH=ssh + # export CVSROOT=:ext:user@cvs.archlinux.org:/home/cvs-extra + + 2.2 Checkout the repository. This will download the entire repository to + your local machine: + # cvs co extra + + 2.3 Updating the repository. This syncs your local repository with the + master. You should do this often, especially if other people could be + working on the same repository. + # cd extra + # cvs -q update -d + + 2.4 Adding files/directories to the repository. When you want to add a new + package you should create a directory under the respective category and + place the new PKGBUILD in it. For example, to add fvwm to the repo: + # cd extra/x11 + # mkdir fvwm + # cd fvwm + # cp /var/abs/PKGBUILD.proto ./PKGBUILD + <edit, test, build, etc> + # cd .. + # cvs add fvwm + # cvs add fvwm/PKGBUILD + + 2.5 Committing changes. Files are not written to the master repository until + you commit. Never forget to commit! + # cd extra + # cvs commit + + This will find all modified files, then throw you into vi where you can + add a log message describing your changes. Save and exit from vi when + you're done and cvs will update the files in the master repository. + + 2.6 Removing files. If you need to remove a file (eg, an old patch that + isn't needed anymore), you can do the following: + # cd extra/x11/fvwm + # rm old.patch + # cvs remove old.patch + # cvs commit -m "removed old.patch" old.patch + also remove the CURRENT/STABLE tags from the file so it does not appear + in ABS any more: + # cvs tag -d CURRENT old.patch + + Don't forget to commit afterwards! Remember that no changes are made + to the master until you commit. + + 2.7 Removing directories cannot be done easily. If you really need to + remove a directory, email the sysadmin (Judd) and I'll help you out. + + 2.8 Tagging files. Every file in CVS has tags associated with it, which + allows us to select certain versions of scripts. The db generation + scripts will only look at files that are tagged as CURRENT, so you need + to tag all files after you commit them: + # cd extra/x11/fvwm + # cvs tag -c -F -R CURRENT + + NOTE: When tagging, you should be sure to ONLY tag the updated files, + not the entire repository. Otherwise, if parts of your checkout are + out-of-date, you may actually be tagging an OLDER version of a file, + reversing someone else's tag procedure. + +3. The Process + + 3.1 Checkout/update your local repository from cvs.archlinux.org + 3.2 Make any changes you need to + 3.3 Put your new packages in your local staging directory on archlinux.org. + Suggested syntax is: + scp name-ver-rel.pkg.tar.gz you@archlinux.org:staging/extra/add + 3.4 Commit your changes (section 2.5) + 3.5 Update the CURRENT tags to new revisions (section 2.8) + 3.6 Log in to archlinux.org and run the /arch/db-extra script, which + will re-generate the sync db and place it in /home/ftp/extra, then + move the new/updated packages from your staging directory to the + FTP tree. + 3.7 Remove any older versions of packages from /home/ftp/extra to + save diskspace, they should be noted when the db generation script + finishes. + + Make sure you do things in this order, mixing them up can break things + temporarily. For example, if you remove older versions from the ftp + tree before you update the database or update the database before + uploading new packages, arch users trying to download the package at + that time will get "file not found" errors. + +4. Staging Directories + + As mentioned in Section 3, packages need to be uploaded to the proper + staging directory before running a db generation script. The staging + area (located in your home dir) looks like so: + + staging + |-- arch + | |-- add + | `-- del + |-- extra + | |-- add + | `-- del + |-- testing + | |-- add + | `-- del + `-- unstable + |-- add + `-- del + + As you can see, each repository has two staging directories: "add" and + "del". When you want to add or update a package, you'll place it in the + "add" directory for the repository you're working in. Then run the db-gen + script. + + When you want to remove a package, you will move the package OUT OF the FTP + directory (eg, /home/ftp/extra/os/i686/) and INTO the "del" directory for + the repository you're working in. Once moved, you can run the db-gen + script -- it will see that the file has left the FTP tree and will remove + it from the package database. + +5. Miscellaneous Stuff + + 5.1 If you are creating a daemon you need to include an rc.d startup + script for it. Look at /var/abs/daemons/esd for a simple example. + 5.2 Please include a line that says '# $Id: pkgmaint_guide.txt,v 1.3 2006/10/05 20:52:01 judd Exp $' at the top of each + PKGBUILD. This will be parsed by cvs during a commit, and replaced + with user/timestamp data. + 5.3 Please do some rudimentary checks of the package before making it + 'live'. Try installing it and see if there are any file conflicts. + Check for dependencies by running 'ldd' against the binaries and + looking through the .so files it requires. For example, + 'ldd /usr/bin/gvim' returns a big list of libs, one of which is + libgtk-x11-2.0.so.0, so gtk2 should be one of the dependencies for + gvim. Also, namcap is available in the extra repository. Running it + against a package will print dependancy warnings as well as possible + configuration problems. Namcap is not the final word, if ldd or + runtime show otherwise, believe them instead. + 5.4 When creating a package description for a package, do not include + the package name in a self-referencing way, as it is redundant. + For example, "Nedit is a text editor for X11" could be simplified to + "A text editor for X11". Also try to keep the descriptions to ~80 + characters or less. + 5.5 When entering cvs log messages for new/upgraded packages, please use + these tags so they can be easily parsed for changelog generation: + if the package is upgrade use: 'upgpkg: pkgname newpkgver' + if the package is new use: 'newpkg: pkgname newpkgver' + + +$Id: pkgmaint_guide.txt,v 1.3 2006/10/05 20:52:01 judd Exp $ diff --git a/devel/__init__.py b/devel/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/devel/__init__.py diff --git a/devel/views.py b/devel/views.py new file mode 100644 index 0000000..dea652b --- /dev/null +++ b/devel/views.py @@ -0,0 +1,67 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from django.core import validators +from archlinux.utils import render_template +from archlinux.packages.models import Package +from archlinux.todolists.models import Todolist, TodolistPkg +from archlinux.settings import DATA_DIR +from archlinux.utils import validate +from archlinux.common.models import UserProfile + +@login_required +def index(request): + try: + thismaint = User.objects.get(username=request.user.username) + except User.DoesNotExist: + # weird, we don't have a maintainer record for this logged-in user + thismaint = None + + # get a list of incomplete package todo lists + todos = Todolist.objects.get_incomplete() + # get flagged-package stats for all maintainers + stats = Package.objects.get_flag_stats() + if thismaint: + # get list of flagged packages for this maintainer + pkgs = Package.objects.filter(maintainer=thismaint.id).filter(needupdate=True).order_by('repo', 'pkgname') + else: + pkgs = None + + return render_template('devel/index.html', request, + {'stats':stats, 'pkgs':pkgs, 'todos':todos, 'maint':thismaint}) + +@login_required +#@is_maintainer +def change_notify(request): + maint = User.objects.get(username=request.user.username) + notify = request.POST.get('notify', 'no') + try: + maint.get_profile().notify = notify == 'yes' + except UserProfile.DoesNotExist: + UserProfile(user_id=maint.id ,notify=notify == 'yes').save() + maint.get_profile().save() + return HttpResponseRedirect('/devel/') + +@login_required +def change_profile(request): + errors = {} + if request.POST: + passwd1, passwd2 = request.POST['passwd'], request.POST['passwd2'] + email = request.POST['email'] + # validate + if passwd1 != passwd2: + errors['password'] = [' Passwords do not match. '] + validate(errors, 'Email', email, validators.isValidEmail, False, request) + # apply changes + if not errors: + request.user.email = email + if passwd1: + request.user.set_password(passwd1) + request.user.save() + return HttpResponseRedirect('/devel/') + return render_template('devel/profile.html', request, {'errors':errors,'email':request.user.email}) + +@login_required +def guide(request): + return HttpResponse(file(DATA_DIR + '/pkgmaint_guide.txt').read(), + mimetype='text/plain') diff --git a/feeds.py b/feeds.py new file mode 100644 index 0000000..bddf941 --- /dev/null +++ b/feeds.py @@ -0,0 +1,29 @@ +from django.contrib.syndication.feeds import Feed +from archlinux.packages.models import Package +from archlinux.news.models import News +#from datetime import datetime + +class PackageFeed(Feed): + title = 'Recent Package Updates' + link = '/packages/' + description = 'Recent Package Updates' + + def items(self): + return Package.objects.order_by('-last_update')[:10] + + def item_pubdate(self, item): + return item.last_update + + def item_categories(self, item): + return (item.repo.name,item.category.category) + +class NewsFeed(Feed): + title = 'Recent News Updates' + link = '/news/' + description = 'Recent News Updates' + + def items(self): + return News.objects.order_by('-postdate', '-id')[:10] + + def item_pubdate(self, item): + return item.postdate diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/__init__.py diff --git a/lib/markdown.py b/lib/markdown.py new file mode 100644 index 0000000..6b3e57a --- /dev/null +++ b/lib/markdown.py @@ -0,0 +1,1874 @@ +#!/usr/bin/env python + +SPEED_TEST = 0 + +""" +==================================================================== +IF YOU ARE LOOKING TO EXTEND MARKDOWN, SEE THE "FOOTNOTES" SECTION +==================================================================== + +Python-Markdown +=============== + +Converts Markdown to HTML. Basic usage as a module: + + import markdown + html = markdown.markdown(your_text_string) + +Started by [Manfred Stienstra](http://www.dwerg.net/). Continued and +maintained by [Yuri Takhteyev](http://www.freewisdom.org). + +Project website: http://www.freewisdom.org/projects/python-markdown +Contact: yuri [at] freewisdom.org + +License: GPL 2 (http://www.gnu.org/copyleft/gpl.html) or BSD + +Version: 1.5 (May 15, 2006) + +For changelog, see end of file +""" + +import re, sys, os, random + +# set debug level: 3 none, 2 critical, 1 informative, 0 all +(VERBOSE, INFO, CRITICAL, NONE) = range(4) + +MESSAGE_THRESHOLD = CRITICAL + +def message(level, text) : + if level >= MESSAGE_THRESHOLD : + print text + + +# --------------- CONSTANTS YOU MIGHT WANT TO MODIFY ----------------- + +# all tabs will be expanded to up to this many spaces +TAB_LENGTH = 4 +ENABLE_ATTRIBUTES = 1 +SMART_EMPHASIS = 1 + +# --------------- CONSTANTS YOU _SHOULD NOT_ HAVE TO CHANGE ---------- + +FN_BACKLINK_TEXT = "zz1337820767766393qq" +# a template for html placeholders +HTML_PLACEHOLDER_PREFIX = "qaodmasdkwaspemas" +HTML_PLACEHOLDER = HTML_PLACEHOLDER_PREFIX + "%dajkqlsmdqpakldnzsdfls" + +BLOCK_LEVEL_ELEMENTS = ['p', 'div', 'blockquote', 'pre', 'table', + 'dl', 'ol', 'ul', 'script', 'noscript', + 'form', 'fieldset', 'iframe', 'math', 'ins', + 'del', 'hr', 'hr/'] + +def is_block_level (tag) : + return ( (tag in BLOCK_LEVEL_ELEMENTS) or + (tag[0] == 'h' and tag[1] in "0123456789") ) + +""" +====================================================================== +========================== NANODOM =================================== +====================================================================== + +The three classes below implement some of the most basic DOM +methods. I use this instead of minidom because I need a simpler +functionality and do not want to require additional libraries. + +Importantly, NanoDom does not do normalization, which is what we +want. It also adds extra white space when converting DOM to string +""" + + +class Document : + + def appendChild(self, child) : + self.documentElement = child + child.parent = self + self.entities = {} + + def createElement(self, tag, textNode=None) : + el = Element(tag) + el.doc = self + if textNode : + el.appendChild(self.createTextNode(textNode)) + return el + + def createTextNode(self, text) : + node = TextNode(text) + node.doc = self + return node + + def createEntityReference(self, entity): + if entity not in self.entities: + self.entities[entity] = EntityReference(entity) + return self.entities[entity] + + def toxml (self) : + return self.documentElement.toxml() + + def normalizeEntities(self, text) : + + pairs = [ #("&", "&"), + ("<", "<"), + (">", ">"), + ("\"", """)] + + for old, new in pairs : + text = text.replace(old, new) + return text + + def find(self, test) : + return self.documentElement.find(test) + + def unlink(self) : + self.documentElement.unlink() + self.documentElement = None + + +class Element : + + type = "element" + + def __init__ (self, tag) : + + self.nodeName = tag + self.attributes = [] + self.attribute_values = {} + self.childNodes = [] + + def unlink(self) : + for child in self.childNodes : + if child.type == "element" : + child.unlink() + self.childNodes = None + + def setAttribute(self, attr, value) : + if not attr in self.attributes : + self.attributes.append(attr) + + self.attribute_values[attr] = value + + def insertChild(self, position, child) : + self.childNodes.insert(position, child) + child.parent = self + + def removeChild(self, child) : + self.childNodes.remove(child) + + def replaceChild(self, oldChild, newChild) : + position = self.childNodes.index(oldChild) + self.removeChild(oldChild) + self.insertChild(position, newChild) + + def appendChild(self, child) : + self.childNodes.append(child) + child.parent = self + + def handleAttributes(self) : + pass + + def find(self, test, depth=0) : + """ Returns a list of descendants that pass the test function """ + matched_nodes = [] + for child in self.childNodes : + if test(child) : + matched_nodes.append(child) + if child.type == "element" : + matched_nodes += child.find(test, depth+1) + return matched_nodes + + def toxml(self): + if ENABLE_ATTRIBUTES : + for child in self.childNodes: + child.handleAttributes() + buffer = "" + if self.nodeName in ['h1', 'h2', 'h3', 'h4'] : + buffer += "\n" + elif self.nodeName in ['li'] : + buffer += "\n " + buffer += "<" + self.nodeName + for attr in self.attributes : + value = self.attribute_values[attr] + value = self.doc.normalizeEntities(value) + buffer += ' %s="%s"' % (attr, value) + if self.childNodes or self.nodeName in ['blockquote']: + buffer += ">" + for child in self.childNodes : + buffer += child.toxml() + if self.nodeName == 'p' : + buffer += "\n" + elif self.nodeName == 'li' : + buffer += "\n " + buffer += "</%s>" % self.nodeName + else : + buffer += "/>" + if self.nodeName in ['p', 'li', 'ul', 'ol', + 'h1', 'h2', 'h3', 'h4'] : + buffer += "\n" + + return buffer + + +class TextNode : + + type = "text" + attrRegExp = re.compile(r'\{@([^\}]*)=([^\}]*)}') # {@id=123} + + def __init__ (self, text) : + self.value = text + + def attributeCallback(self, match) : + self.parent.setAttribute(match.group(1), match.group(2)) + + def handleAttributes(self) : + self.value = self.attrRegExp.sub(self.attributeCallback, self.value) + + def toxml(self) : + text = self.value + if not text.startswith(HTML_PLACEHOLDER_PREFIX): + if self.parent.nodeName == "p" : + text = text.replace("\n", "\n ") + elif (self.parent.nodeName == "li" + and self.parent.childNodes[0]==self): + text = "\n " + text.replace("\n", "\n ") + text = self.doc.normalizeEntities(text) + return text + + +class EntityReference: + + type = "entity_ref" + + def __init__(self, entity): + self.entity = entity + + def handleAttributes(self): + pass + + def toxml(self): + return "&" + self.entity + ";" + + +""" +====================================================================== +========================== PRE-PROCESSORS ============================ +====================================================================== + +Preprocessors munge source text before we start doing anything too +complicated. + +Each preprocessor implements a "run" method that takes a pointer to +a list of lines of the document, modifies it as necessary and +returns either the same pointer or a pointer to a new list. +""" + +class HeaderPreprocessor : + + """ + Replaces underlined headers with hashed headers to avoid + the nead for lookahead later. + """ + + def run (self, lines) : + + for i in range(len(lines)) : + if not lines[i] : + continue + + if lines[i].startswith("#") : + lines.insert(i+1, "\n") + + if (i+1 <= len(lines) + and lines[i+1] + and lines[i+1][0] in ['-', '=']) : + + underline = lines[i+1].strip() + + if underline == "="*len(underline) : + lines[i] = "# " + lines[i].strip() + lines[i+1] = "" + elif underline == "-"*len(underline) : + lines[i] = "## " + lines[i].strip() + lines[i+1] = "" + + return lines + +HEADER_PREPROCESSOR = HeaderPreprocessor() + +class LinePreprocessor : + """Deals with HR lines (needs to be done before processing lists)""" + + def run (self, lines) : + for i in range(len(lines)) : + if self._isLine(lines[i]) : + lines[i] = "<hr />" + return lines + + def _isLine(self, block) : + """Determines if a block should be replaced with an <HR>""" + if block.startswith(" ") : return 0 # a code block + text = "".join([x for x in block if not x.isspace()]) + if len(text) <= 2 : + return 0 + for pattern in ['isline1', 'isline2', 'isline3'] : + m = RE.regExp[pattern].match(text) + if (m and m.group(1)) : + return 1 + else: + return 0 + +LINE_PREPROCESSOR = LinePreprocessor() + + +class LineBreaksPreprocessor : + """Replaces double spaces at the end of the lines with <br/ >.""" + + def run (self, lines) : + for i in range(len(lines)) : + if (lines[i].endswith(" ") + and not RE.regExp['tabbed'].match(lines[i]) ): + lines[i] += "<br />" + return lines + +LINE_BREAKS_PREPROCESSOR = LineBreaksPreprocessor() + + +class HtmlBlockPreprocessor : + """Removes html blocks from self.lines""" + + def run (self, lines) : + new_blocks = [] + text = "\n".join(lines) + for block in text.split("\n\n") : + if block.startswith("\n") : + block = block[1:] + if ( (block.startswith("<") and block.rstrip().endswith(">")) + and (block[1] in ["!", "?", "@", "%"] + or is_block_level( block[1:].replace(">", " ") + .split()[0].lower()))) : + new_blocks.append( + self.stash.store(block.strip())) + else : + new_blocks.append(block) + return "\n\n".join(new_blocks).split("\n") + +HTML_BLOCK_PREPROCESSOR = HtmlBlockPreprocessor() + + +class ReferencePreprocessor : + + def run (self, lines) : + new_text = []; + for line in lines: + m = RE.regExp['reference-def'].match(line) + if m: + id = m.group(2).strip().lower() + title = dequote(m.group(4).strip()) #.replace('"', """) + self.references[id] = (m.group(3), title) + else: + new_text.append(line) + return new_text #+ "\n" + +REFERENCE_PREPROCESSOR = ReferencePreprocessor() + +""" +====================================================================== +========================== INLINE PATTERNS =========================== +====================================================================== + +Inline patterns such as *emphasis* are handled by means of auxiliary +objects, one per pattern. Each pattern object uses a single regular +expression and needs support the following methods: + + pattern.getCompiledRegExp() - returns a regular expression + + pattern.handleMatch(m, doc) - takes a match object and returns + a NanoDom node (as a part of the provided + doc) or None + +All of python markdown's built-in patterns subclass from BasePatter, +but you can add additional patterns that don't. + +Also note that all the regular expressions used by inline must +capture the whole block. For this reason, they all start with +'^(.*)' and end with '(.*)!'. In case with built-in expression +BasePattern takes care of adding the "^(.*)" and "(.*)!". + +Finally, the order in which regular expressions are applied is very +important - e.g. if we first replace http://.../ links with <a> tags +and _then_ try to replace inline html, we would end up with a mess. +So, we apply the expressions in the following order: + + * escape and backticks have to go before everything else, so + that we can preempt any markdown patterns by escaping them. + + * then we handle auto-links (must be done before inline html) + + * then we handle inline HTML. At this point we will simply + replace all inline HTML strings with a placeholder and add + the actual HTML to a hash. + + * then inline images (must be done before links) + + * then bracketed links, first regular then reference-style + + * finally we apply strong and emphasis +""" + +NOBRACKET = r'[^\]\[]*' +BRK = ( r'\[(' + + (NOBRACKET + r'(\['+NOBRACKET)*6 + + (NOBRACKET+ r'\])*'+NOBRACKET)*6 + + NOBRACKET + r')\]' ) + +BACKTICK_RE = r'\`([^\`]*)\`' # `e= m*c^2` +DOUBLE_BACKTICK_RE = r'\`\`(.*)\`\`' # ``e=f("`")`` +ESCAPE_RE = r'\\(.)' # \< +EMPHASIS_RE = r'\*([^\*]*)\*' # *emphasis* +STRONG_RE = r'\*\*(.*)\*\*' # **strong** +STRONG_EM_RE = r'\*\*\*([^_]*)\*\*\*' # ***strong*** + +if SMART_EMPHASIS: + EMPHASIS_2_RE = r'(?<!\S)_(\S[^_]*)_' # _emphasis_ +else : + EMPHASIS_2_RE = r'_([^_]*)_' # _emphasis_ + +STRONG_2_RE = r'__([^_]*)__' # __strong__ +STRONG_EM_2_RE = r'___([^_]*)___' # ___strong___ + +LINK_RE = BRK + r'\s*\(([^\)]*)\)' # [text](url) +LINK_ANGLED_RE = BRK + r'\s*\(<([^\)]*)>\)' # [text](<url>) +IMAGE_LINK_RE = r'\!' + BRK + r'\s*\(([^\)]*)\)' # ![alttxt](http://x.com/) +REFERENCE_RE = BRK+ r'\s*\[([^\]]*)\]' # [Google][3] +IMAGE_REFERENCE_RE = r'\!' + BRK + '\s*\[([^\]]*)\]' # ![alt text][2] +NOT_STRONG_RE = r'( \* )' # stand-alone * or _ +AUTOLINK_RE = r'<(http://[^>]*)>' # <http://www.123.com> +AUTOMAIL_RE = r'<([^> ]*@[^> ]*)>' # <me@example.com> +HTML_RE = r'(\<[^\>]*\>)' # <...> +ENTITY_RE = r'(&[\#a-zA-Z0-9]*;)' # & + +class BasePattern: + + def __init__ (self, pattern) : + self.pattern = pattern + self.compiled_re = re.compile("^(.*)%s(.*)$" % pattern, re.DOTALL) + + def getCompiledRegExp (self) : + return self.compiled_re + +class SimpleTextPattern (BasePattern) : + + def handleMatch(self, m, doc) : + return doc.createTextNode(m.group(2)) + +class SimpleTagPattern (BasePattern): + + def __init__ (self, pattern, tag) : + BasePattern.__init__(self, pattern) + self.tag = tag + + def handleMatch(self, m, doc) : + el = doc.createElement(self.tag) + el.appendChild(doc.createTextNode(m.group(2))) + return el + +class BacktickPattern (BasePattern): + + def __init__ (self, pattern): + BasePattern.__init__(self, pattern) + self.tag = "code" + + def handleMatch(self, m, doc) : + el = doc.createElement(self.tag) + text = m.group(2).strip() + text = text.replace("&", "&") + el.appendChild(doc.createTextNode(text)) + return el + + +class DoubleTagPattern (SimpleTagPattern) : + + def handleMatch(self, m, doc) : + tag1, tag2 = self.tag.split(",") + el1 = doc.createElement(tag1) + el2 = doc.createElement(tag2) + el1.appendChild(el2) + el2.appendChild(doc.createTextNode(m.group(2))) + return el1 + + +class HtmlPattern (BasePattern): + + def handleMatch (self, m, doc) : + place_holder = self.stash.store(m.group(2)) + return doc.createTextNode(place_holder) + + +class LinkPattern (BasePattern): + + def handleMatch(self, m, doc) : + el = doc.createElement('a') + el.appendChild(doc.createTextNode(m.group(2))) + parts = m.group(9).split() + # We should now have [], [href], or [href, title] + if parts : + el.setAttribute('href', parts[0]) + else : + el.setAttribute('href', "") + if len(parts) > 1 : + # we also got a title + title = " ".join(parts[1:]).strip() + title = dequote(title) #.replace('"', """) + el.setAttribute('title', title) + return el + + +class ImagePattern (BasePattern): + + def handleMatch(self, m, doc): + el = doc.createElement('img') + src_parts = m.group(9).split() + el.setAttribute('src', src_parts[0]) + if len(src_parts) > 1 : + el.setAttribute('title', dequote(" ".join(src_parts[1:]))) + if ENABLE_ATTRIBUTES : + text = doc.createTextNode(m.group(2)) + el.appendChild(text) + text.handleAttributes() + truealt = text.value + el.childNodes.remove(text) + else: + truealt = m.group(2) + el.setAttribute('alt', truealt) + return el + +class ReferencePattern (BasePattern): + + def handleMatch(self, m, doc): + if m.group(9) : + id = m.group(9).lower() + else : + # if we got something like "[Google][]" + # we'll use "google" as the id + id = m.group(2).lower() + if not self.references.has_key(id) : # ignore undefined refs + return None + href, title = self.references[id] + text = m.group(2) + return self.makeTag(href, title, text, doc) + + def makeTag(self, href, title, text, doc): + el = doc.createElement('a') + el.setAttribute('href', href) + if title : + el.setAttribute('title', title) + el.appendChild(doc.createTextNode(text)) + return el + + +class ImageReferencePattern (ReferencePattern): + + def makeTag(self, href, title, text, doc): + el = doc.createElement('img') + el.setAttribute('src', href) + if title : + el.setAttribute('title', title) + el.setAttribute('alt', text) + return el + + +class AutolinkPattern (BasePattern): + + def handleMatch(self, m, doc): + el = doc.createElement('a') + el.setAttribute('href', m.group(2)) + el.appendChild(doc.createTextNode(m.group(2))) + return el + +class AutomailPattern (BasePattern): + + def handleMatch(self, m, doc) : + el = doc.createElement('a') + email = m.group(2) + if email.startswith("mailto:"): + email = email[len("mailto:"):] + for letter in email: + entity = doc.createEntityReference("#%d" % ord(letter)) + el.appendChild(entity) + mailto = "mailto:" + email + mailto = "".join(['&#%d;' % ord(letter) for letter in mailto]) + el.setAttribute('href', mailto) + return el + +ESCAPE_PATTERN = SimpleTextPattern(ESCAPE_RE) +NOT_STRONG_PATTERN = SimpleTextPattern(NOT_STRONG_RE) + +BACKTICK_PATTERN = BacktickPattern(BACKTICK_RE) +DOUBLE_BACKTICK_PATTERN = BacktickPattern(DOUBLE_BACKTICK_RE) +STRONG_PATTERN = SimpleTagPattern(STRONG_RE, 'strong') +STRONG_PATTERN_2 = SimpleTagPattern(STRONG_2_RE, 'strong') +EMPHASIS_PATTERN = SimpleTagPattern(EMPHASIS_RE, 'em') +EMPHASIS_PATTERN_2 = SimpleTagPattern(EMPHASIS_2_RE, 'em') + +STRONG_EM_PATTERN = DoubleTagPattern(STRONG_EM_RE, 'strong,em') +STRONG_EM_PATTERN_2 = DoubleTagPattern(STRONG_EM_2_RE, 'strong,em') + +LINK_PATTERN = LinkPattern(LINK_RE) +LINK_ANGLED_PATTERN = LinkPattern(LINK_ANGLED_RE) +IMAGE_LINK_PATTERN = ImagePattern(IMAGE_LINK_RE) +IMAGE_REFERENCE_PATTERN = ImageReferencePattern(IMAGE_REFERENCE_RE) +REFERENCE_PATTERN = ReferencePattern(REFERENCE_RE) + +HTML_PATTERN = HtmlPattern(HTML_RE) +ENTITY_PATTERN = HtmlPattern(ENTITY_RE) + +AUTOLINK_PATTERN = AutolinkPattern(AUTOLINK_RE) +AUTOMAIL_PATTERN = AutomailPattern(AUTOMAIL_RE) + + +""" +====================================================================== +========================== POST-PROCESSORS =========================== +====================================================================== + +Markdown also allows post-processors, which are similar to +preprocessors in that they need to implement a "run" method. Unlike +pre-processors, they take a NanoDom document as a parameter and work +with that. +# +There are currently no standard post-processors, but the footnote +extension below uses one. +""" +""" +====================================================================== +========================== MISC AUXILIARY CLASSES ==================== +====================================================================== +""" + +class HtmlStash : + """This class is used for stashing HTML objects that we extract + in the beginning and replace with place-holders.""" + + def __init__ (self) : + self.html_counter = 0 # for counting inline html segments + self.rawHtmlBlocks=[] + + def store(self, html) : + """Saves an HTML segment for later reinsertion. Returns a + placeholder string that needs to be inserted into the + document. + + @param html: an html segment + @returns : a placeholder string """ + self.rawHtmlBlocks.append(html) + placeholder = HTML_PLACEHOLDER % self.html_counter + self.html_counter += 1 + return placeholder + + +class BlockGuru : + + def _findHead(self, lines, fn, allowBlank=0) : + + """Functional magic to help determine boundaries of indented + blocks. + + @param lines: an array of strings + @param fn: a function that returns a substring of a string + if the string matches the necessary criteria + @param allowBlank: specifies whether it's ok to have blank + lines between matching functions + @returns: a list of post processes items and the unused + remainder of the original list""" + + items = [] + item = -1 + + i = 0 # to keep track of where we are + + for line in lines : + + if not line.strip() and not allowBlank: + return items, lines[i:] + + if not line.strip() and allowBlank: + # If we see a blank line, this _might_ be the end + i += 1 + + # Find the next non-blank line + for j in range(i, len(lines)) : + if lines[j].strip() : + next = lines[j] + break + else : + # There is no more text => this is the end + break + + # Check if the next non-blank line is still a part of the list + + part = fn(next) + + if part : + items.append("") + continue + else : + break # found end of the list + + part = fn(line) + + if part : + items.append(part) + i += 1 + continue + else : + return items, lines[i:] + else : + i += 1 + + return items, lines[i:] + + + def detabbed_fn(self, line) : + """ An auxiliary method to be passed to _findHead """ + m = RE.regExp['tabbed'].match(line) + if m: + return m.group(4) + else : + return None + + + def detectTabbed(self, lines) : + + return self._findHead(lines, self.detabbed_fn, + allowBlank = 1) + + +def print_error(string): + """Print an error string to stderr""" + sys.stderr.write(string +'\n') + + +def dequote(string) : + """ Removes quotes from around a string """ + if ( ( string.startswith('"') and string.endswith('"')) + or (string.startswith("'") and string.endswith("'")) ) : + return string[1:-1] + else : + return string + +""" +====================================================================== +========================== CORE MARKDOWN ============================= +====================================================================== + +This stuff is ugly, so if you are thinking of extending the syntax, +see first if you can do it via pre-processors, post-processors, +inline patterns or a combination of the three. +""" + +class CorePatterns : + """This class is scheduled for removal as part of a refactoring + effort.""" + + patterns = { + 'header': r'(#*)([^#]*)(#*)', # # A title + 'reference-def' : r'(\ ?\ ?\ ?)\[([^\]]*)\]:\s*([^ ]*)(.*)', + # [Google]: http://www.google.com/ + 'containsline': r'([-]*)$|^([=]*)', # -----, =====, etc. + 'ol': r'[ ]{0,3}[\d]*\.\s+(.*)', # 1. text + 'ul': r'[ ]{0,3}[*+-]\s+(.*)', # "* text" + 'isline1': r'(\**)', # *** + 'isline2': r'(\-*)', # --- + 'isline3': r'(\_*)', # ___ + 'tabbed': r'((\t)|( ))(.*)', # an indented line + 'quoted' : r'> ?(.*)', # a quoted block ("> ...") + } + + def __init__ (self) : + + self.regExp = {} + for key in self.patterns.keys() : + self.regExp[key] = re.compile("^%s$" % self.patterns[key], + re.DOTALL) + + self.regExp['containsline'] = re.compile(r'^([-]*)$|^([=]*)$', re.M) + +RE = CorePatterns() + + +class Markdown: + """ Markdown formatter class for creating an html document from + Markdown text """ + + + def __init__(self, source=None): + """Creates a new Markdown instance. + + @param source: The text in Markdown format. """ + + if isinstance(source, unicode): + source = source.encode('utf8') + self.source = source + self.blockGuru = BlockGuru() + self.registeredExtensions = [] + self.stripTopLevelTags = 1 + + self.preprocessors = [ HEADER_PREPROCESSOR, + LINE_PREPROCESSOR, + HTML_BLOCK_PREPROCESSOR, + LINE_BREAKS_PREPROCESSOR, + # A footnote preprocessor will + # get inserted here + REFERENCE_PREPROCESSOR ] + + + self.postprocessors = [] # a footnote postprocessor will get + # inserted later + + self.prePatterns = [] + + + self.inlinePatterns = [ DOUBLE_BACKTICK_PATTERN, + BACKTICK_PATTERN, + ESCAPE_PATTERN, + IMAGE_LINK_PATTERN, + IMAGE_REFERENCE_PATTERN, + REFERENCE_PATTERN, + LINK_ANGLED_PATTERN, + LINK_PATTERN, + AUTOLINK_PATTERN, + AUTOMAIL_PATTERN, + HTML_PATTERN, + ENTITY_PATTERN, + NOT_STRONG_PATTERN, + STRONG_EM_PATTERN, + STRONG_EM_PATTERN_2, + STRONG_PATTERN, + STRONG_PATTERN_2, + EMPHASIS_PATTERN, + EMPHASIS_PATTERN_2 + # The order of the handlers matters!!! + ] + + self.reset() + + def registerExtension(self, extension) : + self.registeredExtensions.append(extension) + + def reset(self) : + """Resets all state variables so that we can start + with a new text.""" + self.references={} + self.htmlStash = HtmlStash() + + HTML_BLOCK_PREPROCESSOR.stash = self.htmlStash + REFERENCE_PREPROCESSOR.references = self.references + HTML_PATTERN.stash = self.htmlStash + ENTITY_PATTERN.stash = self.htmlStash + REFERENCE_PATTERN.references = self.references + IMAGE_REFERENCE_PATTERN.references = self.references + + for extension in self.registeredExtensions : + extension.reset() + + + def _transform(self): + """Transforms the Markdown text into a XHTML body document + + @returns: A NanoDom Document """ + + # Setup the document + + self.doc = Document() + self.top_element = self.doc.createElement("span") + self.top_element.appendChild(self.doc.createTextNode('\n')) + self.top_element.setAttribute('class', 'markdown') + self.doc.appendChild(self.top_element) + + # Fixup the source text + text = self.source.strip() + text = text.replace("\r\n", "\n").replace("\r", "\n") + text += "\n\n" + text = text.expandtabs(TAB_LENGTH) + + # Split into lines and run the preprocessors that will work with + # self.lines + + self.lines = text.split("\n") + + # Run the pre-processors on the lines + for prep in self.preprocessors : + self.lines = prep.run(self.lines) + + # Create a NanoDom tree from the lines and attach it to Document + + + buffer = [] + for line in self.lines : + if line.startswith("#") : + self._processSection(self.top_element, buffer) + buffer = [line] + else : + buffer.append(line) + self._processSection(self.top_element, buffer) + + #self._processSection(self.top_element, self.lines) + + # Not sure why I put this in but let's leave it for now. + self.top_element.appendChild(self.doc.createTextNode('\n')) + + # Run the post-processors + for postprocessor in self.postprocessors : + postprocessor.run(self.doc) + + return self.doc + + + def _processSection(self, parent_elem, lines, + inList = 0, looseList = 0) : + + """Process a section of a source document, looking for high + level structural elements like lists, block quotes, code + segments, html blocks, etc. Some those then get stripped + of their high level markup (e.g. get unindented) and the + lower-level markup is processed recursively. + + @param parent_elem: A NanoDom element to which the content + will be added + @param lines: a list of lines + @param inList: a level + @returns: None""" + + if not lines : + return + + # Check if this section starts with a list, a blockquote or + # a code block + + processFn = { 'ul' : self._processUList, + 'ol' : self._processOList, + 'quoted' : self._processQuote, + 'tabbed' : self._processCodeBlock } + + for regexp in ['ul', 'ol', 'quoted', 'tabbed'] : + m = RE.regExp[regexp].match(lines[0]) + if m : + processFn[regexp](parent_elem, lines, inList) + return + + # We are NOT looking at one of the high-level structures like + # lists or blockquotes. So, it's just a regular paragraph + # (though perhaps nested inside a list or something else). If + # we are NOT inside a list, we just need to look for a blank + # line to find the end of the block. If we ARE inside a + # list, however, we need to consider that a sublist does not + # need to be separated by a blank line. Rather, the following + # markup is legal: + # + # * The top level list item + # + # Another paragraph of the list. This is where we are now. + # * Underneath we might have a sublist. + # + + if inList : + + start, theRest = self._linesUntil(lines, (lambda line: + RE.regExp['ul'].match(line) + or RE.regExp['ol'].match(line) + or not line.strip())) + + self._processSection(parent_elem, start, + inList - 1, looseList = looseList) + self._processSection(parent_elem, theRest, + inList - 1, looseList = looseList) + + + else : # Ok, so it's just a simple block + + paragraph, theRest = self._linesUntil(lines, lambda line: + not line.strip()) + + if len(paragraph) and paragraph[0].startswith('#') : + m = RE.regExp['header'].match(paragraph[0]) + if m : + level = len(m.group(1)) + h = self.doc.createElement("h%d" % level) + parent_elem.appendChild(h) + for item in self._handleInlineWrapper2(m.group(2).strip()) : + h.appendChild(item) + else : + message(CRITICAL, "We've got a problem header!") + + elif paragraph : + + list = self._handleInlineWrapper2("\n".join(paragraph)) + + if ( parent_elem.nodeName == 'li' + and not (looseList or parent_elem.childNodes)): + + #and not parent_elem.childNodes) : + # If this is the first paragraph inside "li", don't + # put <p> around it - append the paragraph bits directly + # onto parent_elem + el = parent_elem + else : + # Otherwise make a "p" element + el = self.doc.createElement("p") + parent_elem.appendChild(el) + + for item in list : + el.appendChild(item) + + if theRest : + theRest = theRest[1:] # skip the first (blank) line + + self._processSection(parent_elem, theRest, inList) + + + + def _processUList(self, parent_elem, lines, inList) : + self._processList(parent_elem, lines, inList, + listexpr='ul', tag = 'ul') + + def _processOList(self, parent_elem, lines, inList) : + self._processList(parent_elem, lines, inList, + listexpr='ol', tag = 'ol') + + + def _processList(self, parent_elem, lines, inList, listexpr, tag) : + """Given a list of document lines starting with a list item, + finds the end of the list, breaks it up, and recursively + processes each list item and the remainder of the text file. + + @param parent_elem: A dom element to which the content will be added + @param lines: a list of lines + @param inList: a level + @returns: None""" + + ul = self.doc.createElement(tag) # ul might actually be '<ol>' + parent_elem.appendChild(ul) + + looseList = 0 + + # Make a list of list items + items = [] + item = -1 + + i = 0 # a counter to keep track of where we are + + for line in lines : + + loose = 0 + if not line.strip() : + # If we see a blank line, this _might_ be the end of the list + i += 1 + loose = 1 + + # Find the next non-blank line + for j in range(i, len(lines)) : + if lines[j].strip() : + next = lines[j] + break + else : + # There is no more text => end of the list + break + + # Check if the next non-blank line is still a part of the list + if ( RE.regExp['ul'].match(next) or + RE.regExp['ol'].match(next) or + RE.regExp['tabbed'].match(next) ): + # get rid of any white space in the line + items[item].append(line.strip()) + looseList = loose or looseList + continue + else : + break # found end of the list + + # Now we need to detect list items (at the current level) + # while also detabing child elements if necessary + + for expr in ['ul', 'ol', 'tabbed']: + + m = RE.regExp[expr].match(line) + if m : + if expr in ['ul', 'ol'] : # We are looking at a new item + if m.group(1) : + items.append([m.group(1)]) + item += 1 + elif expr == 'tabbed' : # This line needs to be detabbed + items[item].append(m.group(4)) #after the 'tab' + + i += 1 + break + else : + items[item].append(line) # Just regular continuation + i += 1 # added on 2006.02.25 + else : + i += 1 + + # Add the dom elements + for item in items : + li = self.doc.createElement("li") + ul.appendChild(li) + + self._processSection(li, item, inList + 1, looseList = looseList) + + # Process the remaining part of the section + + self._processSection(parent_elem, lines[i:], inList) + + + def _linesUntil(self, lines, condition) : + """ A utility function to break a list of lines upon the + first line that satisfied a condition. The condition + argument should be a predicate function. + """ + + i = -1 + for line in lines : + i += 1 + if condition(line) : break + else : + i += 1 + return lines[:i], lines[i:] + + def _processQuote(self, parent_elem, lines, inList) : + """Given a list of document lines starting with a quote finds + the end of the quote, unindents it and recursively + processes the body of the quote and the remainder of the + text file. + + @param parent_elem: DOM element to which the content will be added + @param lines: a list of lines + @param inList: a level + @returns: None """ + + dequoted = [] + i = 0 + for line in lines : + m = RE.regExp['quoted'].match(line) + if m : + dequoted.append(m.group(1)) + i += 1 + else : + break + else : + i += 1 + + blockquote = self.doc.createElement('blockquote') + parent_elem.appendChild(blockquote) + + self._processSection(blockquote, dequoted, inList) + self._processSection(parent_elem, lines[i:], inList) + + + + + def _processCodeBlock(self, parent_elem, lines, inList) : + """Given a list of document lines starting with a code block + finds the end of the block, puts it into the dom verbatim + wrapped in ("<pre><code>") and recursively processes the + the remainder of the text file. + + @param parent_elem: DOM element to which the content will be added + @param lines: a list of lines + @param inList: a level + @returns: None""" + + detabbed, theRest = self.blockGuru.detectTabbed(lines) + + pre = self.doc.createElement('pre') + code = self.doc.createElement('code') + parent_elem.appendChild(pre) + pre.appendChild(code) + text = "\n".join(detabbed).rstrip()+"\n" + text = text.replace("&", "&") + code.appendChild(self.doc.createTextNode(text)) + self._processSection(parent_elem, theRest, inList) + + + def _handleInlineWrapper2 (self, line) : + + + parts = [line] + + #if not(line): + # return [self.doc.createTextNode(' ')] + + for pattern in self.inlinePatterns : + + #print + #print self.inlinePatterns.index(pattern) + + i = 0 + + #print parts + while i < len(parts) : + + x = parts[i] + #print i + if isinstance(x, (str, unicode)) : + result = self._applyPattern(x, pattern) + #print result + #print result + #print parts, i + if result : + i -= 1 + parts.remove(x) + for y in result : + parts.insert(i+1,y) + + i += 1 + + for i in range(len(parts)) : + x = parts[i] + if isinstance(x, (str, unicode)) : + parts[i] = self.doc.createTextNode(x) + + return parts + + + + def _handleInlineWrapper (self, line) : + + # A wrapper around _handleInline to avoid recursion + + parts = [line] + + i = 0 + + while i < len(parts) : + x = parts[i] + if isinstance(x, (str, unicode)) : + parts.remove(x) + result = self._handleInline(x) + for y in result : + parts.insert(i,y) + else : + i += 1 + + return parts + + def _handleInline(self, line): + """Transform a Markdown line with inline elements to an XHTML + fragment. + + This function uses auxiliary objects called inline patterns. + See notes on inline patterns above. + + @param item: A block of Markdown text + @return: A list of NanoDom nodes """ + + if not(line): + return [self.doc.createTextNode(' ')] + + for pattern in self.inlinePatterns : + list = self._applyPattern( line, pattern) + if list: return list + + return [self.doc.createTextNode(line)] + + def _applyPattern(self, line, pattern) : + """ Given a pattern name, this function checks if the line + fits the pattern, creates the necessary elements, and returns + back a list consisting of NanoDom elements and/or strings. + + @param line: the text to be processed + @param pattern: the pattern to be checked + + @returns: the appropriate newly created NanoDom element if the + pattern matches, None otherwise. + """ + + # match the line to pattern's pre-compiled reg exp. + # if no match, move on. + + m = pattern.getCompiledRegExp().match(line) + if not m : + return None + + # if we got a match let the pattern make us a NanoDom node + # if it doesn't, move on + node = pattern.handleMatch(m, self.doc) + + if node : + # Those are in the reverse order! + return ( m.groups()[-1], # the string to the left + node, # the new node + m.group(1)) # the string to the right of the match + + else : + return None + + def __str__(self): + """Return the document in XHTML format. + + @returns: A serialized XHTML body.""" + #try : + doc = self._transform() + xml = doc.toxml() + #finally: + # doc.unlink() + + # Let's stick in all the raw html pieces + + for i in range(self.htmlStash.html_counter) : + xml = xml.replace("<p>%s\n</p>" % (HTML_PLACEHOLDER % i), + self.htmlStash.rawHtmlBlocks[i] + "\n") + xml = xml.replace(HTML_PLACEHOLDER % i, + self.htmlStash.rawHtmlBlocks[i]) + + xml = xml.replace(FN_BACKLINK_TEXT, "↩") + + # And return everything but the top level tag + + if self.stripTopLevelTags : + xml = xml.strip()[23:-7] + + if isinstance(xml, unicode) : + xml = xml.encode("utf8") + + return xml + + + toString = __str__ + + +""" +========================= FOOTNOTES ================================= + +This section adds footnote handling to markdown. It can be used as +an example for extending python-markdown with relatively complex +functionality. While in this case the extension is included inside +the module itself, it could just as easily be added from outside the +module. Not that all markdown classes above are ignorant about +footnotes. All footnote functionality is provided separately and +then added to the markdown instance at the run time. + +Footnote functionality is attached by calling extendMarkdown() +method of FootnoteExtension. The method also registers the +extension to allow it's state to be reset by a call to reset() +method. +""" + +class FootnoteExtension : + + DEF_RE = re.compile(r'(\ ?\ ?\ ?)\[\^([^\]]*)\]:\s*(.*)') + SHORT_USE_RE = re.compile(r'\[\^([^\]]*)\]', re.M) # [^a] + + FN_PLACE_MARKER = "///Footnotes Go Here///" + + def __init__ (self) : + self.reset() + + def extendMarkdown(self, md) : + + self.md = md + + # Stateless extensions do not need to be registered + md.registerExtension(self) + + # Insert a preprocessor before ReferencePreprocessor + index = md.preprocessors.index(REFERENCE_PREPROCESSOR) + preprocessor = FootnotePreprocessor(self) + preprocessor.md = md + md.preprocessors.insert(index, preprocessor) + + # Insert an inline pattern before ImageReferencePattern + FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah + index = md.inlinePatterns.index(IMAGE_REFERENCE_PATTERN) + md.inlinePatterns.insert(index, FootnotePattern(FOOTNOTE_RE, self)) + + # Insert a post-processor that would actually add the footnote div + postprocessor = FootnotePostprocessor(self) + postprocessor.extension = self + + md.postprocessors.append(postprocessor) + + + def reset(self) : + # May be called by Markdown is state reset is desired + + self.footnote_suffix = "-" + str(int(random.random()*1000000000)) + self.used_footnotes={} + self.footnotes = {} + + def findFootnotesPlaceholder(self, doc) : + def findFootnotePlaceholderFn(node=None, indent=0): + if node.type == 'text': + if node.value.find(self.FN_PLACE_MARKER) > -1 : + return True + + fn_div_list = doc.find(findFootnotePlaceholderFn) + if fn_div_list : + return fn_div_list[0] + + + def setFootnote(self, id, text) : + self.footnotes[id] = text + + def makeFootnoteId(self, num) : + return 'fn%d%s' % (num, self.footnote_suffix) + + def makeFootnoteRefId(self, num) : + return 'fnr%d%s' % (num, self.footnote_suffix) + + def makeFootnotesDiv (self, doc) : + """Creates the div with class='footnote' and populates it with + the text of the footnotes. + + @returns: the footnote div as a dom element """ + + if not self.footnotes.keys() : + return None + + div = doc.createElement("div") + div.setAttribute('class', 'footnote') + hr = doc.createElement("hr") + div.appendChild(hr) + ol = doc.createElement("ol") + div.appendChild(ol) + + footnotes = [(self.used_footnotes[id], id) + for id in self.footnotes.keys()] + footnotes.sort() + + for i, id in footnotes : + li = doc.createElement('li') + li.setAttribute('id', self.makeFootnoteId(i)) + + self.md._processSection(li, self.footnotes[id].split("\n")) + + #li.appendChild(doc.createTextNode(self.footnotes[id])) + + backlink = doc.createElement('a') + backlink.setAttribute('href', '#' + self.makeFootnoteRefId(i)) + backlink.setAttribute('class', 'footnoteBackLink') + backlink.setAttribute('title', + 'Jump back to footnote %d in the text' % 1) + backlink.appendChild(doc.createTextNode(FN_BACKLINK_TEXT)) + + if li.childNodes : + node = li.childNodes[-1] + if node.type == "text" : + node = li + node.appendChild(backlink) + + ol.appendChild(li) + + return div + + +class FootnotePreprocessor : + + def __init__ (self, footnotes) : + self.footnotes = footnotes + + def run(self, lines) : + + self.blockGuru = BlockGuru() + lines = self._handleFootnoteDefinitions (lines) + + # Make a hash of all footnote marks in the text so that we + # know in what order they are supposed to appear. (This + # function call doesn't really substitute anything - it's just + # a way to get a callback for each occurence. + + text = "\n".join(lines) + self.footnotes.SHORT_USE_RE.sub(self.recordFootnoteUse, text) + + return text.split("\n") + + + def recordFootnoteUse(self, match) : + + id = match.group(1) + id = id.strip() + nextNum = len(self.footnotes.used_footnotes.keys()) + 1 + self.footnotes.used_footnotes[id] = nextNum + + + def _handleFootnoteDefinitions(self, lines) : + """Recursively finds all footnote definitions in the lines. + + @param lines: a list of lines of text + @returns: a string representing the text with footnote + definitions removed """ + + i, id, footnote = self._findFootnoteDefinition(lines) + + if id : + + plain = lines[:i] + + detabbed, theRest = self.blockGuru.detectTabbed(lines[i+1:]) + + self.footnotes.setFootnote(id, + footnote + "\n" + + "\n".join(detabbed)) + + more_plain = self._handleFootnoteDefinitions(theRest) + return plain + [""] + more_plain + + else : + return lines + + def _findFootnoteDefinition(self, lines) : + """Finds the first line of a footnote definition. + + @param lines: a list of lines of text + @returns: the index of the line containing a footnote definition """ + + counter = 0 + for line in lines : + m = self.footnotes.DEF_RE.match(line) + if m : + return counter, m.group(2), m.group(3) + counter += 1 + return counter, None, None + + +class FootnotePattern (BasePattern) : + + def __init__ (self, pattern, footnotes) : + + BasePattern.__init__(self, pattern) + self.footnotes = footnotes + + def handleMatch(self, m, doc) : + sup = doc.createElement('sup') + a = doc.createElement('a') + sup.appendChild(a) + id = m.group(2) + num = self.footnotes.used_footnotes[id] + sup.setAttribute('id', self.footnotes.makeFootnoteRefId(num)) + a.setAttribute('href', '#' + self.footnotes.makeFootnoteId(num)) + a.appendChild(doc.createTextNode(str(num))) + return sup + +class FootnotePostprocessor : + + def __init__ (self, footnotes) : + self.footnotes = footnotes + + def run(self, doc) : + footnotesDiv = self.footnotes.makeFootnotesDiv(doc) + if footnotesDiv : + fnPlaceholder = self.extension.findFootnotesPlaceholder(doc) + if fnPlaceholder : + fnPlaceholder.parent.replaceChild(fnPlaceholder, footnotesDiv) + else : + doc.documentElement.appendChild(footnotesDiv) + +# ==================================================================== + +def markdown(text) : + message(VERBOSE, "in markdown.py, received text:\n%s" % text) + return Markdown(text).toString() + +def markdownWithFootnotes(text): + message(VERBOSE, "Running markdown with footnotes, " + + "received text:\n%s" % text) + md = Markdown() + footnoteExtension = FootnoteExtension() + footnoteExtension.extendMarkdown(md) + md.source = text + + return str(md) + +def test_markdown(args): + """test markdown at the command line. + in each test, arg 0 is the module name""" + print "\nTEST 1: no arguments on command line" + cmd_line(["markdown.py"]) + print "\nTEST 2a: 1 argument on command line: a good option" + cmd_line(["markdown.py","-footnotes"]) + print "\nTEST 2b: 1 argument on command line: a bad option" + cmd_line(["markdown.py","-foodnotes"]) + print "\nTEST 3: 1 argument on command line: non-existent input file" + cmd_line(["markdown.py","junk.txt"]) + print "\nTEST 4: 1 argument on command line: existing input file" + lines = """ +Markdown text with[^1]: + +2. **bold text**, +3. *italic text*. + +Then more: + + beginning of code block; + another line of code block. + + a second paragraph of code block. + +more text to end our file. + +[^1]: "italic" means emphasis. +""" + fid = "markdown-test.txt" + f1 = open(fid, 'w+') + f1.write(lines) + f1.close() + cmd_line(["markdown.py",fid]) + print "\nTEST 5: 2 arguments on command line: nofootnotes and input file" + cmd_line(["markdown.py","-nofootnotes", fid]) + print "\nTEST 6: 2 arguments on command line: footnotes and input file" + cmd_line(["markdown.py","-footnotes", fid]) + print "\nTEST 7: 3 arguments on command line: nofootnotes,inputfile, outputfile" + fidout = "markdown-test.html" + cmd_line(["markdown.py","-nofootnotes", fid, fidout]) + + +def get_vars(args): + """process the command-line args received; return usable variables""" + #firstly get the variables + + message(VERBOSE, "in get_vars(), args: %s" % args) + + if len(args) <= 1: + option, inFile, outFile = (None, None, None) + elif len(args) >= 4: + option, inFile, outFile = args[1:4] + elif len(args) == 3: + temp1, temp2 = args[1:3] + if temp1[0] == '-': + #then we have an option and inFile + option, inFile, outFile = temp1, temp2, None + else: + #we have no option, so we must have inFile and outFile + option, inFile, outFile = None, temp1, temp2 + else: + #len(args) = 2 + #we have only one usable arg: might be an option or a file + temp1 = args[1] + + message(VERBOSE, "our single arg is: %s" % str(temp1)) + + if temp1[0] == '-': + #then we have an option + option, inFile, outFile = temp1, None, None + else: + #we have no option, so we must have inFile + option, inFile, outFile = None, temp1, None + + message(VERBOSE, + "prior to validation, option: %s, inFile: %s, outFile: %s" % + (str(option), str(inFile), str(outFile),)) + + return option, inFile, outFile + + +USAGE = """ +\nUsing markdown.py: + + python markdown.py [option] input_file_with_markdown.txt [output_file.html] + +Options: + + -footnotes or -fn : generate markdown with footnotes + -test or -t : run a self-test + -help or -h : print this message + +""" + +VALID_OPTIONS = ['footnotes','nofootnotes', 'fn', 'test', 't', 'f', + 'help', 'h'] + +EXPANDED_OPTIONS = { "fn" : "footnotes", + "t" : "test", + "h" : "help" } + + +def validate_option(option) : + + """ Check if the option makes sense and print an appropriate message + if it isn't. + + @return: valid option string or None + """ + + #now validate the variables + if (option is not None): + if (len(option) > 1 and option[1:] in VALID_OPTIONS) : + option = option[1:] + + if option in EXPANDED_OPTIONS.keys() : + option = EXPANDED_OPTIONS[option] + return option + else: + message(CRITICAL, + "\nSorry, I don't understand option %s" % option) + message(CRITICAL, USAGE) + return None + + +def validate_input_file(inFile) : + """ Check if the input file is specified and exists. + + @return: valid input file path or None + """ + + if not inFile : + message(CRITICAL, + "\nI need an input filename.\n") + message(CRITICAL, USAGE) + return None + + + if os.access(inFile, os.R_OK): + return inFile + else : + message(CRITICAL, "Sorry, I can't find input file %s" % str(inFile)) + return None + + + + +def cmd_line(args): + + message(VERBOSE, "in cmd_line with args: %s" % args) + + option, inFile, outFile = get_vars(args) + + if option : + option = validate_option(option) + if not option : return + + if option == "help" : + message(CRITICAL, USAGE) + return + elif option == "test" : + test_markdown(None) + return + + inFile = validate_input_file(inFile) + if not inFile : + return + else : + input = file(inFile).read() + + message(VERBOSE, "Validated command line parameters:" + + "\n\toption: %s, \n\tinFile: %s, \n\toutFile: %s" % ( + str(option), str(inFile), str(outFile),)) + + if option == "footnotes" : + md_function = markdownWithFootnotes + else : + md_function = markdown + + if outFile is None: + print md_function(input) + else: + output = md_function(input) + f1 = open(outFile, "w+") + f1.write(output) + f1.close() + + if os.access(outFile, os.F_OK): + message(INFO, "Successfully wrote %s" % outFile) + else: + message(INFO, "Failed to write %s" % outFile) + + +if __name__ == '__main__': + """ Run Markdown from the command line. + Set debug = 3 at top of file to get diagnostic output""" + args = sys.argv + + #set testing=1 to test the command-line response of markdown.py + testing = 0 + if testing: + test_markdown(args) + else: + import time + t0 = time.time() + #for x in range(10) : + cmd_line(args) + #import profile + #profile.run('cmd_line(args)', 'profile') + t1 = time.time() + #print "Time: %f - %f = %f" % (t1, t0, t1-t0) + +""" +CHANGELOG +========= + +May 15, 2006: A bug with lists, recursion on block-level elements, +run-in headers, spaces before headers, unicode input (thanks to Aaron +Swartz). Sourceforge tracker #s: 1489313, 1489312, 1489311, 1488370, +1485178, 1485176. (v. 1.5) + +Mar. 24, 2006: Switched to a not-so-recursive algorithm with +_handleInline. (Version 1.4) + +Mar. 15, 2006: Replaced some instance variables with class variables +(a patch from Stelios Xanthakis). Chris Clark's new regexps that do +not trigger midword underlining. + +Feb. 28, 2006: Clean-up and command-line handling by Stewart +Midwinter. (Version 1.3) + +Feb. 24, 2006: Fixed a bug with the last line of the list appearing +again as a separate paragraph. Incorporated Chris Clark's "mailto" +patch. Added support for <br /> at the end of lines ending in two or +more spaces. Fixed a crashing bug when using ImageReferencePattern. +Added several utility methods to Nanodom. (Version 1.2) + +Jan. 31, 2006: Added "hr" and "hr/" to BLOCK_LEVEL_ELEMENTS and +changed <hr/> to <hr />. (Thanks to Sergej Chodarev.) + +Nov. 26, 2005: Fixed a bug with certain tabbed lines inside lists +getting wrapped in <pre><code>. (v. 1.1) + +Nov. 19, 2005: Made "<!...", "<?...", etc. behave like block-level +HTML tags. + +Nov. 14, 2005: Added entity code and email autolink fix by Tiago +Cogumbreiro. Fixed some small issues with backticks to get 100% +compliance with John's test suite. (v. 1.0) + +Nov. 7, 2005: Added an unlink method for documents to aid with memory +collection (per Doug Sauder's suggestion). + +Oct. 29, 2005: Restricted a set of html tags that get treated as +block-level elements. + +Sept. 18, 2005: Refactored the whole script to make it easier to +customize it and made footnote functionality into an extension. +(v. 0.9) + +Sept. 5, 2005: Fixed a bug with multi-paragraph footnotes. Added +attribute support. + +Sept. 1, 2005: Changed the way headers are handled to allow inline +syntax in headers (e.g. links) and got the lists to use p-tags +correctly (v. 0.8) + +Aug. 29, 2005: Added flexible tabs, fixed a few small issues, added +basic support for footnotes. Got rid of xml.dom.minidom and added +pretty-printing. (v. 0.7) + +Aug. 13, 2005: Fixed a number of small bugs in order to conform to the +test suite. (v. 0.6) + +Aug. 11, 2005: Added support for inline html and entities, inline +images, autolinks, underscore emphasis. Cleaned up and refactored the +code, added some more comments. + +Feb. 19, 2005: Rewrote the handling of high-level elements to allow +multi-line list items and all sorts of nesting. + +Feb. 3, 2005: Reference-style links, single-line lists, backticks, +escape, emphasis in the beginning of the paragraph. + +Nov. 2004: Added links, blockquotes, html blocks to Manfred +Stienstra's code + +Apr. 2004: Manfred's version at http://www.dwerg.net/projects/markdown/ + +""" + + + + + + diff --git a/local_settings.py.example b/local_settings.py.example new file mode 100644 index 0000000..e202443 --- /dev/null +++ b/local_settings.py.example @@ -0,0 +1,33 @@ +# Django settings for archlinux project. + +## Debug settings +DEBUG = False + +## Notification admins +ADMINS = ( + ('Joe Admin', 'joeadmin@example.com'), +) + +## Database settings +DATABASE_ENGINE = 'mysql' +DATABASE_NAME = 'archlinux' +DATABASE_USER = 'archlinux' +DATABASE_PASSWORD = 'archlinux' +DATABASE_HOST = '' +DATABASE_PORT = '' + +### Eanbles/disables caching +ENABLE_CACHE = False + +## location for saving dev pictures +MEDIA_ROOT = '/var/www/archlinux/htdocs/img/devs/' + +### web url for serving image files +MEDIA_URL = 'http://www.archlinux.org/img/devs/' + +# do not put a / at the end +DEPLOY_PATH = '/var/www/archlinux/archlinux' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '00000000000000000000000000000000000000000000000' + diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..008aeeb --- /dev/null +++ b/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/media/PythonPowered.png b/media/PythonPowered.png Binary files differnew file mode 100644 index 0000000..2e9d99c --- /dev/null +++ b/media/PythonPowered.png diff --git a/media/arch.css b/media/arch.css new file mode 100644 index 0000000..c8bfe36 --- /dev/null +++ b/media/arch.css @@ -0,0 +1,384 @@ +* { margin: 0; padding: 0; } +* ul { padding: 20px; } +body { + background: url("/media/title_back.png") repeat-x top left #fbf8f1; + padding: 0 30px; + color: #46494d; + font-family: Bitstream Vera Sans, Lucida Grande, Arial, sans-serif; +} +/* + * Divs + */ +#head_container { + height: 175px; +} +#main_nav ul { + list-style: none; + padding-right: 10px; + padding-top: 48px; +} +#main_nav ul li { + display: block; + float: right; + width: 67px; + height: 20px; + padding-top: 2px; + margin-left: 3px; + background: url("/media/tab.png") repeat-x bottom left #fbf8f1; + text-align: center; + font-size: 13px; +} +#main_nav ul li[class~=selected] { + background: #fbf8f1; +} +#main_nav ul li a { + text-decoration: none; + display: block; +} +#dev_nav ul li a { + text-decoration: none; + display: block; +} +#dev_nav ul { + list-style: none; + padding-right: 10px; + padding-top: 5px; +} +#dev_nav ul li { + display: block; + float: right; + width: 87px; + height: 20px; + padding-top: 2px; + margin-left: 3px; + background: url("/media/tab.png") repeat-x bottom left #fbf8f1; + border: 1px solid #cccccc; + text-align: center; + font-size: 13px; +} +#dev_nav ul li[class~=selected] { + background: #fbf8f1; +} +#dev_nav ul li a { + text-decoration: none; +} +#title { + height: 140px; + float: left; +} +#updates { + font-size: small; + /*position: relative;*/ + top: 0px; + background: #f6efe0; + border: 1px solid #eee4cb; + padding: 10px; +} +#ads { + float: right; +} +#logo { + float: left; + width: 140px; + height: 140px; +} +#titleimg { + float: left; +} +#devlist { + width: 80%; + padding: 10px; + margin-left: auto; + margin-right: auto; + border-top: 1px dashed black; + border-bottom: 1px dashed black; + text-align: center; +} +.clear { + clear: both; + margin: 0; + padding: 0; +} +.right { + float: right; + width: 320px; + padding: 0 10px 10px 0; +} +.left { + padding: 10px; + margin: 0 360px 0 0; +} +.left p { + text-align: justify; + padding-bottom: 10px; +} +.box { + padding: 10px; + background: #e1e3e6; + border: 1px solid #8faecd; +} +.greybox { + padding: 10px; + background: #f6efe0; + border: 1px solid #eee4cb; +} +div.listing { + padding-right: 10px; + border-left: 1px solid #387cbf; +} +.error { + color: #dd0000; + font-size: small; +} +.foot { + clear: both; + text-align: center; + font-size: 0.8em; +} +#search { + float: right; + position: relative; + top: -2em; + font-size: 0.8em; +} +#search input { + background: #f6efe0; + border: 1px solid #eee4cb; +} +.smalltext { + text-align: right; + font-size: x-small; +} +/* + * Headers + */ +h2 { + margin: 20px 0 10px 0; +} +h2.title { + border-bottom: 1px solid #46494d; +} +h3 { + margin-bottom: 10px; +} +h3.title { + text-align: right; + border-bottom: 1px solid #46494d; +} +h4.title { + text-align: left; + border-bottom: 1px solid #46494d; +} +h4.news { + border-bottom: 1px dotted #8faecd; +} +div.listing h4 { + background: #d1d3d6; + border-top: 1px double #387cbf; + padding: 3px; +} +/* + * Paragraphs, Anchors, Images + */ +p { + padding-bottom: 20px; +} +p.news { + text-align: left; + font-size: small; +} +a { + color: #35526f; + font-weight: bold; + text-decoration: underline; +} +.news a { + text-decoration: none; +} +#about { + position: relative; + top: -9px; +} +#about a { + text-decoration: none; +} +.community a { + text-decoration: none; +} +ol { + padding-left: 45px; +} +ul.small { + list-style: none; + font-size: x-small; +} +ul.links { + list-style: none; + font-size: small; + padding: 0px 0px 20px 20px; +} +img { + border: none; +} +hr { + border: none; + border-top: 1px solid #46494d; +} +.greybox input, button, textarea, select { + background: #e1e3e6; + border: 1px solid #8faecd; +} +.box input, button { + padding: 2px; + background: #c1c3f6; + font-size: x-small; + border: 1px solid #8faecd; +} +button#f_trigger { + background: #e1e3e6; +} +/* + * Table stuff + */ +table.center { + margin-left: auto; + margin-right: auto; +} +table#releases { + font-size: small; + width: 100%; +} +table#releases td { + padding-right: 20px; +} +table#repolinks { + font-size: small; + width: 100%; +} +table#repolinks td { + text-align: right; +} +table#repolinks th { + text-align: left; +} +table#art { + text-align: center; + margin-left: auto; + margin-right: auto; +} +.devpic { + vertical-align: top; + padding-right: 15px; +} +table.deventry { + padding-bottom: 25px; +} +.deventry th { + text-align: left; + vertical-align: top; + white-space: nowrap; +} +.deventry td { + border-bottom: 1px solid black; + width: 100%; +} +table.results { + padding: 0px; + border-collapse: collapse; +} +.results th { + background: #e1e3e6; + border-bottom: 1px solid #46494d; + border-top: 1px solid #46494d; + text-align: left; + padding-top: 0px; + padding-bottom: 0px; + padding-right: 5px; +} +.results th>a { + text-decoration: none; + color: #46494d; +} +.results td { + padding-right: 5px; + vertical-align: top; + font-size: 0.8em; +} +.listing th { + background: #d1d3d6; + border-left: 1px solid #387cbf; + font-size: small; + vertical-align: top; + text-align: left; + padding: 2px; +} +.listing td { + font-size: small; + padding: 2px; +} +blockquote.code { + background: #c1c3f6; + border: 1px solid #8faecd; + margin-left: auto; + margin-right: auto; + white-space: nowrap; + padding: 5px; + font-family: Courier, Courier New, Monospace; +} +/* + * Wiki Styles + */ +h1.wiki { + border-bottom: 1px solid #46494d; +} +div.wikifoot_l { + font-size: x-small; + text-align: left; + padding-top: 25px; +} +div.wikifoot_r { + font-size: x-small; + text-align: right; + float: right; + padding-top: 25px; +} +.wikibody { + padding-top: 15px; +} +.wikibody ol { + padding-left: 28px; + padding-top: 0px; +} +.wikibody ul { + padding-left: 25px; + padding-top: 0px; +} +.wikibody dd { + padding-left: 30px; +} +.wikibody pre code { + background: #c1c3f6; + border: 1px solid #8faecd; + margin-left: auto; + margin-right: auto; + white-space: nowrap; + padding: 5px; + font-family: Courier, Courier New, Monospace; +} +.wikibody blockquote { + padding-left: 30px; +} +.wikibody td { + padding: 5px; + border: 1px solid black; +} + +/* Used by Django's FormWrappers */ +textarea.vLargeTextField { + width: 450px; + height: 250px; +} +.pkgr2 { + background-color: #eee4cb; +} + diff --git a/media/calendar.css b/media/calendar.css new file mode 100644 index 0000000..fa5c093 --- /dev/null +++ b/media/calendar.css @@ -0,0 +1,265 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + 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: #c8d0d4; + 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: #c8d0d4; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + 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 url(menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #788084; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row <TR> containing navigation buttons */ +} + +.calendar thead .daynames { /* Row <TR> containing the day names */ +} + +.calendar thead .name { /* Cells <TD> containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #e8f0f4; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #d8e0e4; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #b8c0c4; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells <TD> containing month days dates */ + 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: #e8f4f0; +} + +.calendar tbody .rowhilite td { + background: #d8e4e0; +} + +.calendar tbody .rowhilite td.wn { + background: #c8d4d0; +} + +.calendar tbody td.hilite { /* Hovered cells <TD> */ + padding: 1px 3px 1px 1px; + border: 1px solid; + border-color: #fff #000 #000 #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells <TD> */ + padding: 2px 2px 0px 2px; + border: 1px solid; + border-color: #000 #fff #fff #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + padding: 2px 2px 0px 2px; + border: 1px solid; + border-color: #000 #fff #fff #000; + background: #d8e0e4; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */ + background: #e8f0f4; + padding: 1px; + border: 1px solid #000; + background: #788084; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #d8e0e4; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #d8e0e4; + 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: #c8d0d4; + padding: 0px; + 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: #aef; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #e8f0f4; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #667; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/media/calendar.js b/media/calendar.js new file mode 100644 index 0000000..c4f388d --- /dev/null +++ b/media/calendar.js @@ -0,0 +1,2133 @@ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- + * + * The DHTML Calendar, version 1.0 "It is happening again" + * + * Details and latest version at: + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + */ + +// $Id: calendar.js,v 1.1 2005/09/06 08:38:43 ben Exp $ + +/** The Calendar object constructor. */ +Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { + // member variables + this.activeDiv = null; + this.currentDateEl = null; + this.getDateStatus = null; + this.getDateToolTip = null; + this.getDateText = null; + this.timeout = null; + this.onSelected = onSelected || null; + this.onClose = onClose || null; + this.dragging = false; + this.hidden = false; + this.minYear = 1970; + this.maxYear = 2050; + this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"]; + this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; + this.isPopup = true; + this.weekNumbers = true; + this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. + this.showsOtherMonths = false; + this.dateStr = dateStr; + this.ar_days = null; + this.showsTime = false; + this.time24 = true; + this.yearStep = 2; + this.hiliteToday = true; + this.multiple = null; + // HTML elements + this.table = null; + this.element = null; + this.tbody = null; + this.firstdayname = null; + // Combo boxes + this.monthsCombo = null; + this.yearsCombo = null; + this.hilitedMonth = null; + this.activeMonth = null; + this.hilitedYear = null; + this.activeYear = null; + // Information + this.dateClicked = false; + + // one-time initializations + if (typeof Calendar._SDN == "undefined") { + // table of short day names + if (typeof Calendar._SDN_len == "undefined") + Calendar._SDN_len = 3; + var ar = new Array(); + for (var i = 8; i > 0;) { + ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); + } + Calendar._SDN = ar; + // table of short month names + if (typeof Calendar._SMN_len == "undefined") + Calendar._SMN_len = 3; + ar = new Array(); + for (var i = 12; i > 0;) { + ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); + } + Calendar._SMN = ar; + } +}; + +// ** constants + +/// "static", needed for event handlers. +Calendar._C = null; + +/// detect a special case of "web browser" +Calendar.is_ie = ( /msie/i.test(navigator.userAgent) && + !/opera/i.test(navigator.userAgent) ); + +Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) ); + +/// detect Opera browser +Calendar.is_opera = /opera/i.test(navigator.userAgent); + +/// detect KHTML-based browsers +Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent); + +// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate +// library, at some point. + +Calendar.getAbsolutePos = function(el) { + var SL = 0, ST = 0; + var is_div = /^div$/i.test(el.tagName); + if (is_div && el.scrollLeft) + SL = el.scrollLeft; + if (is_div && el.scrollTop) + ST = el.scrollTop; + var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; + if (el.offsetParent) { + var tmp = this.getAbsolutePos(el.offsetParent); + r.x += tmp.x; + r.y += tmp.y; + } + return r; +}; + +Calendar.isRelated = function (el, evt) { + var related = evt.relatedTarget; + if (!related) { + var type = evt.type; + if (type == "mouseover") { + related = evt.fromElement; + } else if (type == "mouseout") { + related = evt.toElement; + } + } + while (related) { + if (related == el) { + return true; + } + related = related.parentNode; + } + return false; +}; + +Calendar.removeClass = function(el, className) { + if (!(el && el.className)) { + return; + } + var cls = el.className.split(" "); + var ar = new Array(); + for (var i = cls.length; i > 0;) { + if (cls[--i] != className) { + ar[ar.length] = cls[i]; + } + } + el.className = ar.join(" "); +}; + +Calendar.addClass = function(el, className) { + Calendar.removeClass(el, className); + el.className += " " + className; +}; + +// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. +Calendar.getElement = function(ev) { + var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; + while (f.nodeType != 1 || /^div$/i.test(f.tagName)) + f = f.parentNode; + return f; +}; + +Calendar.getTargetElement = function(ev) { + var f = Calendar.is_ie ? window.event.srcElement : ev.target; + while (f.nodeType != 1) + f = f.parentNode; + return f; +}; + +Calendar.stopEvent = function(ev) { + ev || (ev = window.event); + if (Calendar.is_ie) { + ev.cancelBubble = true; + ev.returnValue = false; + } else { + ev.preventDefault(); + ev.stopPropagation(); + } + return false; +}; + +Calendar.addEvent = function(el, evname, func) { + if (el.attachEvent) { // IE + el.attachEvent("on" + evname, func); + } else if (el.addEventListener) { // Gecko / W3C + el.addEventListener(evname, func, true); + } else { + el["on" + evname] = func; + } +}; + +Calendar.removeEvent = function(el, evname, func) { + if (el.detachEvent) { // IE + el.detachEvent("on" + evname, func); + } else if (el.removeEventListener) { // Gecko / W3C + el.removeEventListener(evname, func, true); + } else { + el["on" + evname] = null; + } +}; + +Calendar.createElement = function(type, parent) { + var el = null; + if (document.createElementNS) { + // use the XHTML namespace; IE won't normally get here unless + // _they_ "fix" the DOM2 implementation. + el = document.createElementNS("http://www.w3.org/1999/xhtml", type); + } else { + el = document.createElement(type); + } + if (typeof parent != "undefined") { + parent.appendChild(el); + } + return el; +}; + +// END: UTILITY FUNCTIONS + +// BEGIN: CALENDAR STATIC FUNCTIONS + +/** Internal -- adds a set of events to make some element behave like a button. */ +Calendar._add_evs = function(el) { + with (Calendar) { + addEvent(el, "mouseover", dayMouseOver); + addEvent(el, "mousedown", dayMouseDown); + addEvent(el, "mouseout", dayMouseOut); + if (is_ie) { + addEvent(el, "dblclick", dayMouseDblClick); + el.setAttribute("unselectable", true); + } + } +}; + +Calendar.findMonth = function(el) { + if (typeof el.month != "undefined") { + return el; + } else if (typeof el.parentNode.month != "undefined") { + return el.parentNode; + } + return null; +}; + +Calendar.findYear = function(el) { + if (typeof el.year != "undefined") { + return el; + } else if (typeof el.parentNode.year != "undefined") { + return el.parentNode; + } + return null; +}; + +Calendar.showMonthsCombo = function () { + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var mc = cal.monthsCombo; + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + if (cal.activeMonth) { + Calendar.removeClass(cal.activeMonth, "active"); + } + var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; + Calendar.addClass(mon, "active"); + cal.activeMonth = mon; + var s = mc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var mcw = mc.offsetWidth; + if (typeof mcw == "undefined") + // Konqueror brain-dead techniques + mcw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px"; + } + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; +}; + +Calendar.showYearsCombo = function (fwd) { + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var yc = cal.yearsCombo; + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + if (cal.activeYear) { + Calendar.removeClass(cal.activeYear, "active"); + } + cal.activeYear = null; + var Y = cal.date.getFullYear() + (fwd ? 1 : -1); + var yr = yc.firstChild; + var show = false; + for (var i = 12; i > 0; --i) { + if (Y >= cal.minYear && Y <= cal.maxYear) { + yr.innerHTML = Y; + yr.year = Y; + yr.style.display = "block"; + show = true; + } else { + yr.style.display = "none"; + } + yr = yr.nextSibling; + Y += fwd ? cal.yearStep : -cal.yearStep; + } + if (show) { + var s = yc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var ycw = yc.offsetWidth; + if (typeof ycw == "undefined") + // Konqueror brain-dead techniques + ycw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px"; + } + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; + } +}; + +// event handlers + +Calendar.tableMouseUp = function(ev) { + var cal = Calendar._C; + if (!cal) { + return false; + } + if (cal.timeout) { + clearTimeout(cal.timeout); + } + var el = cal.activeDiv; + if (!el) { + return false; + } + var target = Calendar.getTargetElement(ev); + ev || (ev = window.event); + Calendar.removeClass(el, "active"); + if (target == el || target.parentNode == el) { + Calendar.cellClick(el, ev); + } + var mon = Calendar.findMonth(target); + var date = null; + if (mon) { + date = new Date(cal.date); + if (mon.month != date.getMonth()) { + date.setMonth(mon.month); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } else { + var year = Calendar.findYear(target); + if (year) { + date = new Date(cal.date); + if (year.year != date.getFullYear()) { + date.setFullYear(year.year); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } + } + with (Calendar) { + removeEvent(document, "mouseup", tableMouseUp); + removeEvent(document, "mouseover", tableMouseOver); + removeEvent(document, "mousemove", tableMouseOver); + cal._hideCombos(); + _C = null; + return stopEvent(ev); + } +}; + +Calendar.tableMouseOver = function (ev) { + var cal = Calendar._C; + if (!cal) { + return; + } + var el = cal.activeDiv; + var target = Calendar.getTargetElement(ev); + if (target == el || target.parentNode == el) { + Calendar.addClass(el, "hilite active"); + Calendar.addClass(el.parentNode, "rowhilite"); + } else { + if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) + Calendar.removeClass(el, "active"); + Calendar.removeClass(el, "hilite"); + Calendar.removeClass(el.parentNode, "rowhilite"); + } + ev || (ev = window.event); + if (el.navtype == 50 && target != el) { + var pos = Calendar.getAbsolutePos(el); + var w = el.offsetWidth; + var x = ev.clientX; + var dx; + var decrease = true; + if (x > pos.x + w) { + dx = x - pos.x - w; + decrease = false; + } else + dx = pos.x - x; + + if (dx < 0) dx = 0; + var range = el._range; + var current = el._current; + var count = Math.floor(dx / 10) % range.length; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + while (count-- > 0) + if (decrease) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = newval; + + cal.onUpdateTime(); + } + var mon = Calendar.findMonth(target); + if (mon) { + if (mon.month != cal.date.getMonth()) { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + Calendar.addClass(mon, "hilite"); + cal.hilitedMonth = mon; + } else if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + } else { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + var year = Calendar.findYear(target); + if (year) { + if (year.year != cal.date.getFullYear()) { + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + Calendar.addClass(year, "hilite"); + cal.hilitedYear = year; + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } + return Calendar.stopEvent(ev); +}; + +Calendar.tableMouseDown = function (ev) { + if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) { + return Calendar.stopEvent(ev); + } +}; + +Calendar.calDragIt = function (ev) { + var cal = Calendar._C; + if (!(cal && cal.dragging)) { + return false; + } + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posX = ev.pageX; + posY = ev.pageY; + } + cal.hideShowCovered(); + var st = cal.element.style; + st.left = (posX - cal.xOffs) + "px"; + st.top = (posY - cal.yOffs) + "px"; + return Calendar.stopEvent(ev); +}; + +Calendar.calDragEnd = function (ev) { + var cal = Calendar._C; + if (!cal) { + return false; + } + cal.dragging = false; + with (Calendar) { + removeEvent(document, "mousemove", calDragIt); + removeEvent(document, "mouseup", calDragEnd); + tableMouseUp(ev); + } + cal.hideShowCovered(); +}; + +Calendar.dayMouseDown = function(ev) { + var el = Calendar.getElement(ev); + if (el.disabled) { + return false; + } + var cal = el.calendar; + cal.activeDiv = el; + Calendar._C = cal; + if (el.navtype != 300) with (Calendar) { + if (el.navtype == 50) { + el._current = el.innerHTML; + addEvent(document, "mousemove", tableMouseOver); + } else + addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); + addClass(el, "hilite active"); + addEvent(document, "mouseup", tableMouseUp); + } else if (cal.isPopup) { + cal._dragStart(ev); + } + if (el.navtype == -1 || el.navtype == 1) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); + } else if (el.navtype == -2 || el.navtype == 2) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); + } else { + cal.timeout = null; + } + return Calendar.stopEvent(ev); +}; + +Calendar.dayMouseDblClick = function(ev) { + Calendar.cellClick(Calendar.getElement(ev), ev || window.event); + if (Calendar.is_ie) { + document.selection.empty(); + } +}; + +Calendar.dayMouseOver = function(ev) { + var el = Calendar.getElement(ev); + if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) { + return false; + } + if (el.ttip) { + if (el.ttip.substr(0, 1) == "_") { + el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); + } + el.calendar.tooltips.innerHTML = el.ttip; + } + if (el.navtype != 300) { + Calendar.addClass(el, "hilite"); + if (el.caldate) { + Calendar.addClass(el.parentNode, "rowhilite"); + } + } + return Calendar.stopEvent(ev); +}; + +Calendar.dayMouseOut = function(ev) { + with (Calendar) { + var el = getElement(ev); + if (isRelated(el, ev) || _C || el.disabled) + return false; + removeClass(el, "hilite"); + if (el.caldate) + removeClass(el.parentNode, "rowhilite"); + if (el.calendar) + el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; + return stopEvent(ev); + } +}; + +/** + * A generic "click" handler :) handles all types of buttons defined in this + * calendar. + */ +Calendar.cellClick = function(el, ev) { + var cal = el.calendar; + var closing = false; + var newdate = false; + var date = null; + if (typeof el.navtype == "undefined") { + if (cal.currentDateEl) { + Calendar.removeClass(cal.currentDateEl, "selected"); + Calendar.addClass(el, "selected"); + closing = (cal.currentDateEl == el); + if (!closing) { + cal.currentDateEl = el; + } + } + cal.date.setDateOnly(el.caldate); + date = cal.date; + var other_month = !(cal.dateClicked = !el.otherMonth); + if (!other_month && !cal.currentDateEl) + cal._toggleMultipleDate(new Date(date)); + else + newdate = !el.disabled; + // a date was clicked + if (other_month) + cal._init(cal.firstDayOfWeek, date); + } else { + if (el.navtype == 200) { + Calendar.removeClass(el, "hilite"); + cal.callCloseHandler(); + return; + } + date = new Date(cal.date); + if (el.navtype == 0) + date.setDateOnly(new Date()); // TODAY + // unless "today" was clicked, we assume no date was clicked so + // the selected handler will know not to close the calenar when + // in single-click mode. + // cal.dateClicked = (el.navtype == 0); + cal.dateClicked = false; + var year = date.getFullYear(); + var mon = date.getMonth(); + function setMonth(m) { + var day = date.getDate(); + var max = date.getMonthDays(m); + if (day > max) { + date.setDate(max); + } + date.setMonth(m); + }; + switch (el.navtype) { + case 400: + Calendar.removeClass(el, "hilite"); + var text = Calendar._TT["ABOUT"]; + if (typeof text != "undefined") { + text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; + } else { + // FIXME: this should be removed as soon as lang files get updated! + text = "Help and about box text is not translated into this language.\n" + + "If you know this language and you feel generous please update\n" + + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + + "and send it back to <mihai_bazon@yahoo.com> to get it into the distribution ;-)\n\n" + + "Thank you!\n" + + "http://dynarch.com/mishoo/calendar.epl\n"; + } + alert(text); + return; + case -2: + if (year > cal.minYear) { + date.setFullYear(year - 1); + } + break; + case -1: + if (mon > 0) { + setMonth(mon - 1); + } else if (year-- > cal.minYear) { + date.setFullYear(year); + setMonth(11); + } + break; + case 1: + if (mon < 11) { + setMonth(mon + 1); + } else if (year < cal.maxYear) { + date.setFullYear(year + 1); + setMonth(0); + } + break; + case 2: + if (year < cal.maxYear) { + date.setFullYear(year + 1); + } + break; + case 100: + cal.setFirstDayOfWeek(el.fdow); + return; + case 50: + var range = el._range; + var current = el.innerHTML; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + if (ev && ev.shiftKey) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = newval; + cal.onUpdateTime(); + return; + case 0: + // TODAY will bring us here + if ((typeof cal.getDateStatus == "function") && + cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { + return false; + } + break; + } + if (!date.equalsTo(cal.date)) { + cal.setDate(date); + newdate = true; + } else if (el.navtype == 0) + newdate = closing = true; + } + if (newdate) { + ev && cal.callHandler(); + } + if (closing) { + Calendar.removeClass(el, "hilite"); + ev && cal.callCloseHandler(); + } +}; + +// END: CALENDAR STATIC FUNCTIONS + +// BEGIN: CALENDAR OBJECT FUNCTIONS + +/** + * This function creates the calendar inside the given parent. If _par is + * null than it creates a popup calendar inside the BODY element. If _par is + * an element, be it BODY, then it creates a non-popup calendar (still + * hidden). Some properties need to be set before calling this function. + */ +Calendar.prototype.create = function (_par) { + var parent = null; + if (! _par) { + // default parent is the document body, in which case we create + // a popup calendar. + parent = document.getElementsByTagName("body")[0]; + this.isPopup = true; + } else { + parent = _par; + this.isPopup = false; + } + this.date = this.dateStr ? new Date(this.dateStr) : new Date(); + + var table = Calendar.createElement("table"); + this.table = table; + table.cellSpacing = 0; + table.cellPadding = 0; + table.calendar = this; + Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown); + + var div = Calendar.createElement("div"); + this.element = div; + div.className = "calendar"; + if (this.isPopup) { + div.style.position = "absolute"; + div.style.display = "none"; + } + div.appendChild(table); + + var thead = Calendar.createElement("thead", table); + var cell = null; + var row = null; + + var cal = this; + var hh = function (text, cs, navtype) { + cell = Calendar.createElement("td", row); + cell.colSpan = cs; + cell.className = "button"; + if (navtype != 0 && Math.abs(navtype) <= 2) + cell.className += " nav"; + Calendar._add_evs(cell); + cell.calendar = cal; + cell.navtype = navtype; + cell.innerHTML = "<div unselectable='on'>" + text + "</div>"; + return cell; + }; + + row = Calendar.createElement("tr", thead); + var title_length = 6; + (this.isPopup) && --title_length; + (this.weekNumbers) && ++title_length; + + hh("?", 1, 400).ttip = Calendar._TT["INFO"]; + this.title = hh("", title_length, 300); + this.title.className = "title"; + if (this.isPopup) { + this.title.ttip = Calendar._TT["DRAG_TO_MOVE"]; + this.title.style.cursor = "move"; + hh("×", 1, 200).ttip = Calendar._TT["CLOSE"]; + } + + row = Calendar.createElement("tr", thead); + row.className = "headrow"; + + this._nav_py = hh("«", 1, -2); + this._nav_py.ttip = Calendar._TT["PREV_YEAR"]; + + this._nav_pm = hh("‹", 1, -1); + this._nav_pm.ttip = Calendar._TT["PREV_MONTH"]; + + this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0); + this._nav_now.ttip = Calendar._TT["GO_TODAY"]; + + this._nav_nm = hh("›", 1, 1); + this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"]; + + this._nav_ny = hh("»", 1, 2); + this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"]; + + // day names + row = Calendar.createElement("tr", thead); + row.className = "daynames"; + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + cell.className = "name wn"; + cell.innerHTML = Calendar._TT["WK"]; + } + for (var i = 7; i > 0; --i) { + cell = Calendar.createElement("td", row); + if (!i) { + cell.navtype = 100; + cell.calendar = this; + Calendar._add_evs(cell); + } + } + this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild; + this._displayWeekdays(); + + var tbody = Calendar.createElement("tbody", table); + this.tbody = tbody; + + for (i = 6; i > 0; --i) { + row = Calendar.createElement("tr", tbody); + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + } + for (var j = 7; j > 0; --j) { + cell = Calendar.createElement("td", row); + cell.calendar = this; + Calendar._add_evs(cell); + } + } + + if (this.showsTime) { + row = Calendar.createElement("tr", tbody); + row.className = "time"; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + cell.innerHTML = Calendar._TT["TIME"] || " "; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = this.weekNumbers ? 4 : 3; + + (function(){ + function makeTimePart(className, init, range_start, range_end) { + var part = Calendar.createElement("span", cell); + part.className = className; + part.innerHTML = init; + part.calendar = cal; + part.ttip = Calendar._TT["TIME_PART"]; + part.navtype = 50; + part._range = []; + if (typeof range_start != "number") + part._range = range_start; + else { + for (var i = range_start; i <= range_end; ++i) { + var txt; + if (i < 10 && range_end >= 10) txt = '0' + i; + else txt = '' + i; + part._range[part._range.length] = txt; + } + } + Calendar._add_evs(part); + return part; + }; + var hrs = cal.date.getHours(); + var mins = cal.date.getMinutes(); + var t12 = !cal.time24; + var pm = (hrs > 12); + if (t12 && pm) hrs -= 12; + var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); + var span = Calendar.createElement("span", cell); + span.innerHTML = ":"; + span.className = "colon"; + var M = makeTimePart("minute", mins, 0, 59); + var AP = null; + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + if (t12) + AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); + else + cell.innerHTML = " "; + + cal.onSetTime = function() { + var pm, hrs = this.date.getHours(), + mins = this.date.getMinutes(); + if (t12) { + pm = (hrs >= 12); + if (pm) hrs -= 12; + if (hrs == 0) hrs = 12; + AP.innerHTML = pm ? "pm" : "am"; + } + H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; + M.innerHTML = (mins < 10) ? ("0" + mins) : mins; + }; + + cal.onUpdateTime = function() { + var date = this.date; + var h = parseInt(H.innerHTML, 10); + if (t12) { + if (/pm/i.test(AP.innerHTML) && h < 12) + h += 12; + else if (/am/i.test(AP.innerHTML) && h == 12) + h = 0; + } + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + date.setHours(h); + date.setMinutes(parseInt(M.innerHTML, 10)); + date.setFullYear(y); + date.setMonth(m); + date.setDate(d); + this.dateClicked = false; + this.callHandler(); + }; + })(); + } else { + this.onSetTime = this.onUpdateTime = function() {}; + } + + var tfoot = Calendar.createElement("tfoot", table); + + row = Calendar.createElement("tr", tfoot); + row.className = "footrow"; + + cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300); + cell.className = "ttip"; + if (this.isPopup) { + cell.ttip = Calendar._TT["DRAG_TO_MOVE"]; + cell.style.cursor = "move"; + } + this.tooltips = cell; + + div = Calendar.createElement("div", this.element); + this.monthsCombo = div; + div.className = "combo"; + for (i = 0; i < Calendar._MN.length; ++i) { + var mn = Calendar.createElement("div"); + mn.className = Calendar.is_ie ? "label-IEfix" : "label"; + mn.month = i; + mn.innerHTML = Calendar._SMN[i]; + div.appendChild(mn); + } + + div = Calendar.createElement("div", this.element); + this.yearsCombo = div; + div.className = "combo"; + for (i = 12; i > 0; --i) { + var yr = Calendar.createElement("div"); + yr.className = Calendar.is_ie ? "label-IEfix" : "label"; + div.appendChild(yr); + } + + this._init(this.firstDayOfWeek, this.date); + parent.appendChild(this.element); +}; + +/** keyboard navigation, only for popup calendars */ +Calendar._keyEvent = function(ev) { + var cal = window._dynarch_popupCalendar; + if (!cal || cal.multiple) + return false; + (Calendar.is_ie) && (ev = window.event); + var act = (Calendar.is_ie || ev.type == "keypress"), + K = ev.keyCode; + if (ev.ctrlKey) { + switch (K) { + case 37: // KEY left + act && Calendar.cellClick(cal._nav_pm); + break; + case 38: // KEY up + act && Calendar.cellClick(cal._nav_py); + break; + case 39: // KEY right + act && Calendar.cellClick(cal._nav_nm); + break; + case 40: // KEY down + act && Calendar.cellClick(cal._nav_ny); + break; + default: + return false; + } + } else switch (K) { + case 32: // KEY space (now) + Calendar.cellClick(cal._nav_now); + break; + case 27: // KEY esc + act && cal.callCloseHandler(); + break; + case 37: // KEY left + case 38: // KEY up + case 39: // KEY right + case 40: // KEY down + if (act) { + var prev, x, y, ne, el, step; + prev = K == 37 || K == 38; + step = (K == 37 || K == 39) ? 1 : 7; + function setVars() { + el = cal.currentDateEl; + var p = el.pos; + x = p & 15; + y = p >> 4; + ne = cal.ar_days[y][x]; + };setVars(); + function prevMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() - step); + cal.setDate(date); + }; + function nextMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() + step); + cal.setDate(date); + }; + while (1) { + switch (K) { + case 37: // KEY left + if (--x >= 0) + ne = cal.ar_days[y][x]; + else { + x = 6; + K = 38; + continue; + } + break; + case 38: // KEY up + if (--y >= 0) + ne = cal.ar_days[y][x]; + else { + prevMonth(); + setVars(); + } + break; + case 39: // KEY right + if (++x < 7) + ne = cal.ar_days[y][x]; + else { + x = 0; + K = 40; + continue; + } + break; + case 40: // KEY down + if (++y < cal.ar_days.length) + ne = cal.ar_days[y][x]; + else { + nextMonth(); + setVars(); + } + break; + } + break; + } + if (ne) { + if (!ne.disabled) + Calendar.cellClick(ne); + else if (prev) + prevMonth(); + else + nextMonth(); + } + } + break; + case 13: // KEY enter + if (act) + Calendar.cellClick(cal.currentDateEl, ev); + break; + default: + return false; + } + return Calendar.stopEvent(ev); +}; + +/** + * (RE)Initializes the calendar to the given date and firstDayOfWeek + */ +Calendar.prototype._init = function (firstDayOfWeek, date) { + var today = new Date(), + TY = today.getFullYear(), + TM = today.getMonth(), + TD = today.getDate(); + this.table.style.visibility = "hidden"; + var year = date.getFullYear(); + if (year < this.minYear) { + year = this.minYear; + date.setFullYear(year); + } else if (year > this.maxYear) { + year = this.maxYear; + date.setFullYear(year); + } + this.firstDayOfWeek = firstDayOfWeek; + this.date = new Date(date); + var month = date.getMonth(); + var mday = date.getDate(); + var no_days = date.getMonthDays(); + + // calendar voodoo for computing the first day that would actually be + // displayed in the calendar, even if it's from the previous month. + // WARNING: this is magic. ;-) + date.setDate(1); + var day1 = (date.getDay() - this.firstDayOfWeek) % 7; + if (day1 < 0) + day1 += 7; + date.setDate(-day1); + date.setDate(date.getDate() + 1); + + var row = this.tbody.firstChild; + var MN = Calendar._SMN[month]; + var ar_days = this.ar_days = new Array(); + var weekend = Calendar._TT["WEEKEND"]; + var dates = this.multiple ? (this.datesCells = {}) : null; + for (var i = 0; i < 6; ++i, row = row.nextSibling) { + var cell = row.firstChild; + if (this.weekNumbers) { + cell.className = "day wn"; + cell.innerHTML = date.getWeekNumber(); + cell = cell.nextSibling; + } + row.className = "daysrow"; + var hasdays = false, iday, dpos = ar_days[i] = []; + for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { + iday = date.getDate(); + var wday = date.getDay(); + cell.className = "day"; + cell.pos = i << 4 | j; + dpos[j] = cell; + var current_month = (date.getMonth() == month); + if (!current_month) { + if (this.showsOtherMonths) { + cell.className += " othermonth"; + cell.otherMonth = true; + } else { + cell.className = "emptycell"; + cell.innerHTML = " "; + cell.disabled = true; + continue; + } + } else { + cell.otherMonth = false; + hasdays = true; + } + cell.disabled = false; + cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; + if (dates) + dates[date.print("%Y%m%d")] = cell; + if (this.getDateStatus) { + var status = this.getDateStatus(date, year, month, iday); + if (this.getDateToolTip) { + var toolTip = this.getDateToolTip(date, year, month, iday); + if (toolTip) + cell.title = toolTip; + } + if (status === true) { + cell.className += " disabled"; + cell.disabled = true; + } else { + if (/disabled/i.test(status)) + cell.disabled = true; + cell.className += " " + status; + } + } + if (!cell.disabled) { + cell.caldate = new Date(date); + cell.ttip = "_"; + if (!this.multiple && current_month + && iday == mday && this.hiliteToday) { + cell.className += " selected"; + this.currentDateEl = cell; + } + if (date.getFullYear() == TY && + date.getMonth() == TM && + iday == TD) { + cell.className += " today"; + cell.ttip += Calendar._TT["PART_TODAY"]; + } + if (weekend.indexOf(wday.toString()) != -1) + cell.className += cell.otherMonth ? " oweekend" : " weekend"; + } + } + if (!(hasdays || this.showsOtherMonths)) + row.className = "emptyrow"; + } + this.title.innerHTML = Calendar._MN[month] + ", " + year; + this.onSetTime(); + this.table.style.visibility = "visible"; + this._initMultipleDates(); + // PROFILE + // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms"; +}; + +Calendar.prototype._initMultipleDates = function() { + if (this.multiple) { + for (var i in this.multiple) { + var cell = this.datesCells[i]; + var d = this.multiple[i]; + if (!d) + continue; + if (cell) + cell.className += " selected"; + } + } +}; + +Calendar.prototype._toggleMultipleDate = function(date) { + if (this.multiple) { + var ds = date.print("%Y%m%d"); + var cell = this.datesCells[ds]; + if (cell) { + var d = this.multiple[ds]; + if (!d) { + Calendar.addClass(cell, "selected"); + this.multiple[ds] = date; + } else { + Calendar.removeClass(cell, "selected"); + delete this.multiple[ds]; + } + } + } +}; + +Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { + this.getDateToolTip = unaryFunction; +}; + +/** + * Calls _init function above for going to a certain date (but only if the + * date is different than the currently selected one). + */ +Calendar.prototype.setDate = function (date) { + if (!date.equalsTo(this.date)) { + this._init(this.firstDayOfWeek, date); + } +}; + +/** + * Refreshes the calendar. Useful if the "disabledHandler" function is + * dynamic, meaning that the list of disabled date can change at runtime. + * Just * call this function if you think that the list of disabled dates + * should * change. + */ +Calendar.prototype.refresh = function () { + this._init(this.firstDayOfWeek, this.date); +}; + +/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */ +Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) { + this._init(firstDayOfWeek, this.date); + this._displayWeekdays(); +}; + +/** + * Allows customization of what dates are enabled. The "unaryFunction" + * parameter must be a function object that receives the date (as a JS Date + * object) and returns a boolean value. If the returned value is true then + * the passed date will be marked as disabled. + */ +Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) { + this.getDateStatus = unaryFunction; +}; + +/** Customization of allowed year range for the calendar. */ +Calendar.prototype.setRange = function (a, z) { + this.minYear = a; + this.maxYear = z; +}; + +/** Calls the first user handler (selectedHandler). */ +Calendar.prototype.callHandler = function () { + if (this.onSelected) { + this.onSelected(this, this.date.print(this.dateFormat)); + } +}; + +/** Calls the second user handler (closeHandler). */ +Calendar.prototype.callCloseHandler = function () { + if (this.onClose) { + this.onClose(this); + } + this.hideShowCovered(); +}; + +/** Removes the calendar object from the DOM tree and destroys it. */ +Calendar.prototype.destroy = function () { + var el = this.element.parentNode; + el.removeChild(this.element); + Calendar._C = null; + window._dynarch_popupCalendar = null; +}; + +/** + * Moves the calendar element to a different section in the DOM tree (changes + * its parent). + */ +Calendar.prototype.reparent = function (new_parent) { + var el = this.element; + el.parentNode.removeChild(el); + new_parent.appendChild(el); +}; + +// This gets called when the user presses a mouse button anywhere in the +// document, if the calendar is shown. If the click was outside the open +// calendar this function closes it. +Calendar._checkCalendar = function(ev) { + var calendar = window._dynarch_popupCalendar; + if (!calendar) { + return false; + } + var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); + for (; el != null && el != calendar.element; el = el.parentNode); + if (el == null) { + // calls closeHandler which should hide the calendar. + window._dynarch_popupCalendar.callCloseHandler(); + return Calendar.stopEvent(ev); + } +}; + +/** Shows the calendar. */ +Calendar.prototype.show = function () { + var rows = this.table.getElementsByTagName("tr"); + for (var i = rows.length; i > 0;) { + var row = rows[--i]; + Calendar.removeClass(row, "rowhilite"); + var cells = row.getElementsByTagName("td"); + for (var j = cells.length; j > 0;) { + var cell = cells[--j]; + Calendar.removeClass(cell, "hilite"); + Calendar.removeClass(cell, "active"); + } + } + this.element.style.display = "block"; + this.hidden = false; + if (this.isPopup) { + window._dynarch_popupCalendar = this; + Calendar.addEvent(document, "keydown", Calendar._keyEvent); + Calendar.addEvent(document, "keypress", Calendar._keyEvent); + Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); + } + this.hideShowCovered(); +}; + +/** + * Hides the calendar. Also removes any "hilite" from the class of any TD + * element. + */ +Calendar.prototype.hide = function () { + if (this.isPopup) { + Calendar.removeEvent(document, "keydown", Calendar._keyEvent); + Calendar.removeEvent(document, "keypress", Calendar._keyEvent); + Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar); + } + this.element.style.display = "none"; + this.hidden = true; + this.hideShowCovered(); +}; + +/** + * Shows the calendar at a given absolute position (beware that, depending on + * the calendar element style -- position property -- this might be relative + * to the parent's containing rectangle). + */ +Calendar.prototype.showAt = function (x, y) { + var s = this.element.style; + s.left = x + "px"; + s.top = y + "px"; + this.show(); +}; + +/** Shows the calendar near a given element. */ +Calendar.prototype.showAtElement = function (el, opts) { + var self = this; + var p = Calendar.getAbsolutePos(el); + if (!opts || typeof opts != "string") { + this.showAt(p.x, p.y + el.offsetHeight); + return true; + } + function fixPosition(box) { + if (box.x < 0) + box.x = 0; + if (box.y < 0) + box.y = 0; + var cp = document.createElement("div"); + var s = cp.style; + s.position = "absolute"; + s.right = s.bottom = s.width = s.height = "0px"; + document.body.appendChild(cp); + var br = Calendar.getAbsolutePos(cp); + document.body.removeChild(cp); + if (Calendar.is_ie) { + br.y += document.body.scrollTop; + br.x += document.body.scrollLeft; + } else { + br.y += window.scrollY; + br.x += window.scrollX; + } + var tmp = box.x + box.width - br.x; + if (tmp > 0) box.x -= tmp; + tmp = box.y + box.height - br.y; + if (tmp > 0) box.y -= tmp; + }; + this.element.style.display = "block"; + Calendar.continuation_for_the_fucking_khtml_browser = function() { + var w = self.element.offsetWidth; + var h = self.element.offsetHeight; + self.element.style.display = "none"; + var valign = opts.substr(0, 1); + var halign = "l"; + if (opts.length > 1) { + halign = opts.substr(1, 1); + } + // vertical alignment + switch (valign) { + case "T": p.y -= h; break; + case "B": p.y += el.offsetHeight; break; + case "C": p.y += (el.offsetHeight - h) / 2; break; + case "t": p.y += el.offsetHeight - h; break; + case "b": break; // already there + } + // horizontal alignment + switch (halign) { + case "L": p.x -= w; break; + case "R": p.x += el.offsetWidth; break; + case "C": p.x += (el.offsetWidth - w) / 2; break; + case "l": p.x += el.offsetWidth - w; break; + case "r": break; // already there + } + p.width = w; + p.height = h + 40; + self.monthsCombo.style.display = "none"; + fixPosition(p); + self.showAt(p.x, p.y); + }; + if (Calendar.is_khtml) + setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); + else + Calendar.continuation_for_the_fucking_khtml_browser(); +}; + +/** Customizes the date format. */ +Calendar.prototype.setDateFormat = function (str) { + this.dateFormat = str; +}; + +/** Customizes the tooltip date format. */ +Calendar.prototype.setTtDateFormat = function (str) { + this.ttDateFormat = str; +}; + +/** + * Tries to identify the date represented in a string. If successful it also + * calls this.setDate which moves the calendar to the given date. + */ +Calendar.prototype.parseDate = function(str, fmt) { + if (!fmt) + fmt = this.dateFormat; + this.setDate(Date.parseDate(str, fmt)); +}; + +Calendar.prototype.hideShowCovered = function () { + if (!Calendar.is_ie && !Calendar.is_opera) + return; + function getVisib(obj){ + var value = obj.style.visibility; + if (!value) { + if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C + if (!Calendar.is_khtml) + value = document.defaultView. + getComputedStyle(obj, "").getPropertyValue("visibility"); + else + value = ''; + } else if (obj.currentStyle) { // IE + value = obj.currentStyle.visibility; + } else + value = ''; + } + return value; + }; + + var tags = new Array("applet", "iframe", "select"); + var el = this.element; + + var p = Calendar.getAbsolutePos(el); + var EX1 = p.x; + var EX2 = el.offsetWidth + EX1; + var EY1 = p.y; + var EY2 = el.offsetHeight + EY1; + + for (var k = tags.length; k > 0; ) { + var ar = document.getElementsByTagName(tags[--k]); + var cc = null; + + for (var i = ar.length; i > 0;) { + cc = ar[--i]; + + p = Calendar.getAbsolutePos(cc); + var CX1 = p.x; + var CX2 = cc.offsetWidth + CX1; + var CY1 = p.y; + var CY2 = cc.offsetHeight + CY1; + + if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = cc.__msh_save_visibility; + } else { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = "hidden"; + } + } + } +}; + +/** Internal function; it displays the bar with the names of the weekday. */ +Calendar.prototype._displayWeekdays = function () { + var fdow = this.firstDayOfWeek; + var cell = this.firstdayname; + var weekend = Calendar._TT["WEEKEND"]; + for (var i = 0; i < 7; ++i) { + cell.className = "day name"; + var realday = (i + fdow) % 7; + if (i) { + cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); + cell.navtype = 100; + cell.calendar = this; + cell.fdow = realday; + Calendar._add_evs(cell); + } + if (weekend.indexOf(realday.toString()) != -1) { + Calendar.addClass(cell, "weekend"); + } + cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; + cell = cell.nextSibling; + } +}; + +/** Internal function. Hides all combo boxes that might be displayed. */ +Calendar.prototype._hideCombos = function () { + this.monthsCombo.style.display = "none"; + this.yearsCombo.style.display = "none"; +}; + +/** Internal function. Starts dragging the element. */ +Calendar.prototype._dragStart = function (ev) { + if (this.dragging) { + return; + } + this.dragging = true; + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posY = ev.clientY + window.scrollY; + posX = ev.clientX + window.scrollX; + } + var st = this.element.style; + this.xOffs = posX - parseInt(st.left); + this.yOffs = posY - parseInt(st.top); + with (Calendar) { + addEvent(document, "mousemove", calDragIt); + addEvent(document, "mouseup", calDragEnd); + } +}; + +// BEGIN: DATE OBJECT PATCHES + +/** Adds the number of days array to the Date object. */ +Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); + +/** Constants used for time computations */ +Date.SECOND = 1000 /* milliseconds */; +Date.MINUTE = 60 * Date.SECOND; +Date.HOUR = 60 * Date.MINUTE; +Date.DAY = 24 * Date.HOUR; +Date.WEEK = 7 * Date.DAY; + +Date.parseDate = function(str, fmt) { + var today = new Date(); + var y = 0; + var m = -1; + var d = 0; + var a = str.split(/\W+/); + var b = fmt.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%b": + case "%B": + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } + } + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + else if (/am/i.test(a[i]) && hr >= 12) + hr -= 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (isNaN(y)) y = today.getFullYear(); + if (isNaN(m)) m = today.getMonth(); + if (isNaN(d)) d = today.getDate(); + if (isNaN(hr)) hr = today.getHours(); + if (isNaN(min)) min = today.getMinutes(); + if (y != 0 && m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + y = 0; m = -1; d = 0; + for (i = 0; i < a.length; ++i) { + if (a[i].search(/[a-zA-Z]+/) != -1) { + var t = -1; + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; } + } + if (t != -1) { + if (m != -1) { + d = m+1; + } + m = t; + } + } else if (parseInt(a[i], 10) <= 12 && m == -1) { + m = a[i]-1; + } else if (parseInt(a[i], 10) > 31 && y == 0) { + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + } else if (d == 0) { + d = a[i]; + } + } + if (y == 0) + y = today.getFullYear(); + if (m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + return today; +}; + +/** Returns the number of days in the current month */ +Date.prototype.getMonthDays = function(month) { + var year = this.getFullYear(); + if (typeof month == "undefined") { + month = this.getMonth(); + } + if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) { + return 29; + } else { + return Date._MD[month]; + } +}; + +/** Returns the number of day in the year. */ +Date.prototype.getDayOfYear = function() { + var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0); + var time = now - then; + return Math.floor(time / Date.DAY); +}; + +/** Returns the number of the week in year, as defined in ISO 8601. */ +Date.prototype.getWeekNumber = function() { + var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var DoW = d.getDay(); + d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu + var ms = d.valueOf(); // GMT + d.setMonth(0); + d.setDate(4); // Thu in Week 1 + return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; +}; + +/** Checks date and time equality */ +Date.prototype.equalsTo = function(date) { + return ((this.getFullYear() == date.getFullYear()) && + (this.getMonth() == date.getMonth()) && + (this.getDate() == date.getDate()) && + (this.getHours() == date.getHours()) && + (this.getMinutes() == date.getMinutes())); +}; + +/** Set only the year, month, date parts (keep existing time) */ +Date.prototype.setDateOnly = function(date) { + var tmp = new Date(date); + this.setDate(1); + this.setFullYear(tmp.getFullYear()); + this.setMonth(tmp.getMonth()); + this.setDate(tmp.getDate()); +}; + +/** Prints the date in a string according to the given format. */ +Date.prototype.print = function (str) { + var m = this.getMonth(); + var d = this.getDate(); + var y = this.getFullYear(); + var wn = this.getWeekNumber(); + var w = this.getDay(); + var s = {}; + var hr = this.getHours(); + var pm = (hr >= 12); + var ir = (pm) ? (hr - 12) : hr; + var dy = this.getDayOfYear(); + if (ir == 0) + ir = 12; + var min = this.getMinutes(); + var sec = this.getSeconds(); + s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] + s["%A"] = Calendar._DN[w]; // full weekday name + s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] + s["%B"] = Calendar._MN[m]; // full month name + // FIXME: %c : preferred date and time representation for the current locale + s["%C"] = 1 + Math.floor(y / 100); // the century number + s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) + s["%e"] = d; // the day of the month (range 1 to 31) + // FIXME: %D : american date style: %m/%d/%y + // FIXME: %E, %F, %G, %g, %h (man strftime) + s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) + s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) + s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) + s["%k"] = hr; // hour, range 0 to 23 (24h format) + s["%l"] = ir; // hour, range 1 to 12 (12h format) + s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 + s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 + s["%n"] = "\n"; // a newline character + s["%p"] = pm ? "PM" : "AM"; + s["%P"] = pm ? "pm" : "am"; + // FIXME: %r : the time in am/pm notation %I:%M:%S %p + // FIXME: %R : the time in 24-hour notation %H:%M + s["%s"] = Math.floor(this.getTime() / 1000); + s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 + s["%t"] = "\t"; // a tab character + // FIXME: %T : the time in 24-hour notation (%H:%M:%S) + s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; + s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) + s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) + // FIXME: %x : preferred date representation for the current locale without the time + // FIXME: %X : preferred time representation for the current locale without the date + s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) + s["%Y"] = y; // year with the century + s["%%"] = "%"; // a literal '%' character + + var re = /%./g; + if (!Calendar.is_ie5 && !Calendar.is_khtml) + return str.replace(re, function (par) { return s[par] || par; }); + + var a = str.match(re); + for (var i = 0; i < a.length; i++) { + var tmp = s[a[i]]; + if (tmp) { + re = new RegExp(a[i], 'g'); + str = str.replace(re, tmp); + } + } + + return str; +}; + +Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear; +Date.prototype.setFullYear = function(y) { + var d = new Date(this); + d.__msh_oldSetFullYear(y); + if (d.getMonth() != this.getMonth()) + this.setDate(28); + this.__msh_oldSetFullYear(y); +}; + +// END: DATE OBJECT PATCHES + + +// global object that remembers the calendar +window._dynarch_popupCalendar = null; +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, <mihai_bazon@yahoo.com> +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + +// full month names +Calendar._MN = new Array +("January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "About the calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; +Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; +Calendar._TT["GO_TODAY"] = "Go Today"; +Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)"; +Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)"; +Calendar._TT["SEL_DATE"] = "Select date"; +Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; +Calendar._TT["PART_TODAY"] = " (today)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Display %s first"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Close"; +Calendar._TT["TODAY"] = "Today"; +Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Time:"; +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * --------------------------------------------------------------------------- + * + * The DHTML Calendar + * + * Details and latest version at: + * http://dynarch.com/mishoo/calendar.epl + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + * + * This file defines helper functions for setting up the calendar. They are + * intended to help non-programmers get a working calendar on their site + * quickly. This script should not be seen as part of the calendar. It just + * shows you what one can do with the calendar, while in the same time + * providing a quick and simple method for setting it up. If you need + * exhaustive customization of the calendar creation process feel free to + * modify this code to suit your needs (this is recommended and much better + * than modifying calendar.js itself). + */ + +// $Id: calendar-setup.js,v 1.1 2005/09/06 08:38:43 ben Exp $ + +/** + * This function "patches" an input field (or other element) to use a calendar + * widget for date selection. + * + * The "params" is a single object that can have the following properties: + * + * prop. name | description + * ------------------------------------------------------------------------------------------------- + * inputField | the ID of an input field to store the date + * displayArea | the ID of a DIV or other element to show the date + * button | ID of a button or other element that will trigger the calendar + * eventName | event that will trigger the calendar, without the "on" prefix (default: "click") + * ifFormat | date format that will be stored in the input field + * daFormat | the date format that will be used to display the date in displayArea + * singleClick | (true/false) wether the calendar is in single click mode or not (default: true) + * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc. + * align | alignment (default: "Br"); if you don't know what's this see the calendar documentation + * range | array with 2 elements. Default: [1900, 2999] -- the range of years available + * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers + * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID + * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar) + * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar + * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay) + * onClose | function that gets called when the calendar is closed. [default] + * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar. + * date | the date that the calendar will be initially displayed to + * showsTime | default: false; if true the calendar will include a time selector + * timeFormat | the time format; can be "12" or "24", default is "12" + * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close + * step | configures the step of the years in drop-down boxes; default: 2 + * position | configures the calendar absolute position; default: null + * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible + * showOthers | if "true" (but default: "false") it will show days from other months too + * + * None of them is required, they all have default values. However, if you + * pass none of "inputField", "displayArea" or "button" you'll get a warning + * saying "nothing to setup". + */ +Calendar.setup = function (params) { + function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } }; + + param_default("inputField", null); + param_default("displayArea", null); + param_default("button", null); + param_default("eventName", "click"); + param_default("ifFormat", "%Y/%m/%d"); + param_default("daFormat", "%Y/%m/%d"); + param_default("singleClick", true); + param_default("disableFunc", null); + param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined + param_default("dateText", null); + param_default("firstDay", null); + param_default("align", "Br"); + param_default("range", [1900, 2999]); + param_default("weekNumbers", true); + param_default("flat", null); + param_default("flatCallback", null); + param_default("onSelect", null); + param_default("onClose", null); + param_default("onUpdate", null); + param_default("date", null); + param_default("showsTime", false); + param_default("timeFormat", "24"); + param_default("electric", true); + param_default("step", 2); + param_default("position", null); + param_default("cache", false); + param_default("showOthers", false); + param_default("multiple", null); + + var tmp = ["inputField", "displayArea", "button"]; + for (var i in tmp) { + if (typeof params[tmp[i]] == "string") { + params[tmp[i]] = document.getElementById(params[tmp[i]]); + } + } + if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) { + alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code"); + return false; + } + + function onSelect(cal) { + var p = cal.params; + var update = (cal.dateClicked || p.electric); + if (update && p.inputField) { + p.inputField.value = cal.date.print(p.ifFormat); + if (typeof p.inputField.onchange == "function") + p.inputField.onchange(); + } + if (update && p.displayArea) + p.displayArea.innerHTML = cal.date.print(p.daFormat); + if (update && typeof p.onUpdate == "function") + p.onUpdate(cal); + if (update && p.flat) { + if (typeof p.flatCallback == "function") + p.flatCallback(cal); + } + if (update && p.singleClick && cal.dateClicked) + cal.callCloseHandler(); + }; + + if (params.flat != null) { + if (typeof params.flat == "string") + params.flat = document.getElementById(params.flat); + if (!params.flat) { + alert("Calendar.setup:\n Flat specified but can't find parent."); + return false; + } + var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect); + cal.showsOtherMonths = params.showOthers; + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.params = params; + cal.weekNumbers = params.weekNumbers; + cal.setRange(params.range[0], params.range[1]); + cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + if (params.ifFormat) { + cal.setDateFormat(params.ifFormat); + } + if (params.inputField && typeof params.inputField.value == "string") { + cal.parseDate(params.inputField.value); + } + cal.create(params.flat); + cal.show(); + return false; + } + + var triggerEl = params.button || params.displayArea || params.inputField; + triggerEl["on" + params.eventName] = function() { + var dateEl = params.inputField || params.displayArea; + var dateFmt = params.inputField ? params.ifFormat : params.daFormat; + var mustCreate = false; + var cal = window.calendar; + if (dateEl) + params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt); + if (!(cal && params.cache)) { + window.calendar = cal = new Calendar(params.firstDay, + params.date, + params.onSelect || onSelect, + params.onClose || function(cal) { cal.hide(); }); + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.weekNumbers = params.weekNumbers; + mustCreate = true; + } else { + if (params.date) + cal.setDate(params.date); + cal.hide(); + } + if (params.multiple) { + cal.multiple = {}; + for (var i = params.multiple.length; --i >= 0;) { + var d = params.multiple[i]; + var ds = d.print("%Y%m%d"); + cal.multiple[ds] = d; + } + } + cal.showsOtherMonths = params.showOthers; + cal.yearStep = params.step; + cal.setRange(params.range[0], params.range[1]); + cal.params = params; + cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + cal.setDateFormat(dateFmt); + if (mustCreate) + cal.create(); + cal.refresh(); + if (!params.position) + cal.showAtElement(params.button || params.displayArea || params.inputField, params.align); + else + cal.showAt(params.position[0], params.position[1]); + return false; + }; + + return cal; +}; diff --git a/media/favicon.ico b/media/favicon.ico Binary files differnew file mode 100644 index 0000000..3a6d2ab --- /dev/null +++ b/media/favicon.ico diff --git a/media/forms/_library/cmxform.css b/media/forms/_library/cmxform.css new file mode 100644 index 0000000..ff36286 --- /dev/null +++ b/media/forms/_library/cmxform.css @@ -0,0 +1,57 @@ +/********************************** + +Use: cmxform template +Author: Nick Rigby + +***********************************/ + +form.cmxform fieldset { margin-bottom: 10px; } + +form.cmxform legend { + padding: 0 2px; + font-weight: bold; + _margin: 0 -7px; /* IE Win */ + } + +form.cmxform label { + display: inline-block; + line-height: 1.8; + vertical-align: top; + } + +form.cmxform fieldset ol { + margin: 0; + padding: 0; + } + +form.cmxform fieldset li { + list-style: none; + padding: 5px; + margin: 0; + } + +form.cmxform fieldset fieldset { + border: none; + margin: 3px 0 0; + } + +form.cmxform fieldset fieldset legend { + padding: 0 0 5px; + font-weight: normal; + } + +form.cmxform fieldset fieldset label { + display: block; + width: auto; + } + +form.cmxform em { + font-weight: bold; + font-style: normal; + color: #f00; + } + +form.cmxform label { width: 120px; } /* Width of labels */ +form.cmxform fieldset fieldset label { margin-left: 123px; } /* Width plus 3 (html space) */ + +/*\*//*/ form.cmxform legend { display: inline-block; } /* IE Mac legend fix */
\ No newline at end of file diff --git a/media/forms/cmxform.css b/media/forms/cmxform.css new file mode 100644 index 0000000..f713714 --- /dev/null +++ b/media/forms/cmxform.css @@ -0,0 +1,30 @@ +/********************************** + +Name: cmxform Styles +Author: Nick Rigby + +***********************************/ + +form.cmxform { + width: 370px; + font-size: 1.1em; + color: #333; + } + +form.cmxform legend { padding-left: 0; } + +form.cmxform legend, +form.cmxform label { color: #333; } + +form.cmxform fieldset { + border: none; + border-top: 1px solid #C9DCA6; + background: url(../images/cmxform-fieldset.gif) left bottom repeat-x; + } + +form.cmxform fieldset fieldset { background: none; } + +form.cmxform fieldset li { + padding: 5px 10px 7px; + background: url(../images/cmxform-divider.gif) left bottom repeat-x; + }
\ No newline at end of file diff --git a/media/forms/cmxform.js b/media/forms/cmxform.js new file mode 100644 index 0000000..0b7973d --- /dev/null +++ b/media/forms/cmxform.js @@ -0,0 +1,22 @@ +if( document.addEventListener ) document.addEventListener( 'DOMContentLoaded', cmxform, false );
+
+function cmxform(){
+ // Hide forms
+ $( 'form.cmxform' ).hide().end();
+
+ // Processing
+ $( 'form.cmxform' ).find( 'li/label' ).not( '.nocmx' ).each( function( i ){
+ var labelContent = this.innerHTML;
+ var labelWidth = document.defaultView.getComputedStyle( this, '' ).getPropertyValue( 'width' );
+ var labelSpan = document.createElement( 'span' );
+ labelSpan.style.display = 'block';
+ labelSpan.style.width = labelWidth;
+ labelSpan.innerHTML = labelContent;
+ this.style.display = '-moz-inline-box';
+ this.innerHTML = null;
+ this.appendChild( labelSpan );
+ } ).end();
+
+ // Show forms
+ $( 'form.cmxform' ).show().end();
+}
\ No newline at end of file diff --git a/media/forms/core.css b/media/forms/core.css new file mode 100644 index 0000000..bdeac7a --- /dev/null +++ b/media/forms/core.css @@ -0,0 +1,22 @@ +/********************************** + +Use: Core Styles +Author: Nick Rigby + +***********************************/ + +body { + padding: 0 10px; + font: normal 62.5% "Lucida Grande", Helvetica, Verdana, Arial; + } + +p { margin: 10px 0; } + +.sr { + position: absolute; + left: -9999em; + top: 0; + width: 1px; + height: 1px; + overflow: hidden; + }
\ No newline at end of file diff --git a/media/forms/images/cmxform-divider.gif b/media/forms/images/cmxform-divider.gif Binary files differnew file mode 100644 index 0000000..718a977 --- /dev/null +++ b/media/forms/images/cmxform-divider.gif diff --git a/media/forms/images/cmxform-fieldset.gif b/media/forms/images/cmxform-fieldset.gif Binary files differnew file mode 100644 index 0000000..0590c89 --- /dev/null +++ b/media/forms/images/cmxform-fieldset.gif diff --git a/media/forms/reset.css b/media/forms/reset.css new file mode 100644 index 0000000..c557f24 --- /dev/null +++ b/media/forms/reset.css @@ -0,0 +1,62 @@ +/********************************** + +Use: Reset Styles for all browsers +Author: Nick Rigby + +***********************************/ + +body, p, blockquote { + margin: 0; + padding: 0; + } + +a img, iframe { border: none; } + +/* Headers +------------------------------*/ + +h1, h2, h3, h4, h5, h6 { + margin: 0; + padding: 0; + font-size: 100%; + } + +/* Lists +------------------------------*/ + +ul, ol, dl, li, dt, dd { + margin: 0; + padding: 0; + } + +/* Links +------------------------------*/ + +a, a:link {} +a:visited {} +a:hover {} +a:active {} + +/* Forms +------------------------------*/ + +form, fieldset { + margin: 0; + padding: 0; + } + +fieldset { border: 1px solid #000; } + +legend { + padding: 0; + color: #000; + } + +input, textarea, select { + margin: 0; + padding: 1px; + font-size: 100%; + font-family: inherit; + } + +select { padding: 0; }
\ No newline at end of file diff --git a/media/forms/screen.css b/media/forms/screen.css new file mode 100644 index 0000000..d21a36a --- /dev/null +++ b/media/forms/screen.css @@ -0,0 +1,16 @@ +/********************************** + +Use: Main Screen Import +Author: Nick Rigby + +***********************************/ + +@import "reset.css"; +@import "core.css"; + +@import "_library/cmxform.css"; +@import "cmxform.css"; + +/* IE5 Macintosh \*//*/ +@import "_ie/mac/5.css"; +/**/
\ No newline at end of file diff --git a/media/gnu-head-tiny.jpg b/media/gnu-head-tiny.jpg Binary files differnew file mode 100644 index 0000000..441be50 --- /dev/null +++ b/media/gnu-head-tiny.jpg diff --git a/media/jquery.js b/media/jquery.js new file mode 100644 index 0000000..cd439da --- /dev/null +++ b/media/jquery.js @@ -0,0 +1,14 @@ +/* + * jQuery - Current + * http://jquery.com/ + * + * To use, download this file to your server, save as jquery.js, + * and add this HTML into the <head>...</head> of your web page: + * <script type="text/javascript" src="jquery.js"></script> + * + * Copyright (c) 2006 John Resig + * Licensed under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + */ +/* Built Fri May 12 13:01:23 2006 */ +eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('7 $(a,c){8 $a=a||$.14||R;8 $c=c&&c.$4k&&c.1l(0)||c;l(1O 4g!="2r"){l($a.N==1g){8 S=I 1i("[^a-41-6z-6y-]");l(!S.3Z($a)){$c=$c&&$c.2L||R;l($c.2t($a).q==0){8 1m=$c.25($a);l(1m!=C)k 1m}}}H l($a.N==36){k $.1w($a,7(b){l(b.N==1g)k R.25(b);k b})}}8 T={B:$.2d($a,$c),$4k:"$6x: 29 $",1E:7(){k 6.1l().q},1l:7(i){k i==C?6.B:6.B[i]},E:7(f){D(8 i=0;i<6.1E();i++)$.1f(6.1l(i),f,[i]);k 6},3e:7(a,b){k 6.E(7(){l(b==C)D(8 j 1d a)$.W(6,j,a[j]);H $.W(6,a,b)})},3f:7(h){k h==C&&6.1E()?6.1l(0).1Z:6.3e("1Z",h)},2J:7(h){k h==C&&6.1E()?6.1l(0).2R:6.3e("2R",h)},1n:7(a,b){k a.N!=1g||b?6.E(7(){l(!b)D(8 j 1d a)$.W(6.L,j,a[j]);H $.W(6.L,a,b)}):$.1n(6.1l(0),a)},2s:7(){k 6.E(7(){8 d=$.19(6,"V");l(d=="1z"||d==\'\')$(6).1v();H $(6).1u()})},1v:7(a){k 6.E(7(){6.L.V=6.$$2k?6.$$2k:\'\';l($.19(6,"V")=="1z")6.L.V=\'33\'})},1u:7(a){k 6.E(7(){6.$$2k=$.19(6,"V");l(6.$$2k=="1z")6.$$2k=\'33\';6.L.V=\'1z\'})},6w:7(c){k 6.E(7(){l($.2b(6,c))k;6.1j+=(6.1j.q>0?" ":"")+c})},6v:7(c){k 6.E(7(){6.1j=c==C?\'\':6.1j.1x(I 1i(\'(^|\\\\s*\\\\b[^-])\'+c+\'($|\\\\b(?=[^-]))\',\'g\'),\'\')})},6u:7(c){k 6.E(7(){l($.2b(6,c))6.1j=6.1j.1x(I 1i(\'(\\\\s*\\\\b[^-])\'+c+\'($|\\\\b(?=[^-]))\',\'g\'),\'\');H 6.1j+=(6.1j.q>0?" ":"")+c})},6t:7(){6.E(7(){6.U.4h(6)});6.B=[];k 6},6s:7(){8 a=$.1X(1M);k 6.E(7(){8 b=a[0].2j(Q);6.U.2M(b,6);1G(b.1W)b=b.1W;b.4j(6)})},4i:7(){8 1D=6.1E()>1;8 a=$.1X(1M);k 6.E(7(){D(8 i=0;i<a.q;i++)6.4j(1D?a[i].2j(Q):a[i])})},6r:7(){8 a=1M;k 6.E(7(){D(8 i=0;i<a.q;i++)$(a[i]).4i(6)})},6q:7(){8 1D=6.1E()>1;8 a=$.1X(1M);k 6.E(7(){D(8 i=a.q-1;i>=0;i--)6.2M(1D?a[i].2j(Q):a[i],6.1W)})},6p:7(){8 1D=6.1E()>1;8 a=$.1X(1M);k 6.E(7(){D(8 i=0;i<a.q;i++)6.U.2M(1D?a[i].2j(Q):a[i],6)})},6o:7(){8 1D=6.1E()>1;8 a=$.1X(1M);k 6.E(7(){D(8 i=a.q-1;i>=0;i--)6.U.2M(1D?a[i].2j(Q):a[i],6.6n)})},45:7(){k 6.E(7(){1G(6.1W)6.4h(6.1W)})},21:7(t,f){k 6.E(7(){1F(6,t,f)})},3B:7(t,f){k 6.E(7(){35(6,t,f)})},3A:7(t){k 6.E(7(){2U(6,t)})},2Q:7(t){8 1Y=[],F=[];6.E(7(){1Y[1Y.q]=6;F=$.Y(F,$.2d(t,6))});6.1Y=1Y;6.B=F;k 6},6m:7(){6.B=6.1Y;k 6},46:7(a){6.B=$.1w(6.B,7(d){k d.U});l(a)6.B=$.15(a,6.B).r;k 6},38:7(a){6.B=$.1w(6.B,$.38);l(a)6.B=$.15(a,6.B).r;k 6},6l:7(a){6.B=$.1w(6.B,$.12);l(a)6.B=$.15(a,6.B).r;k 6},15:7(t){6.B=$.15(t,6.B).r;k 6},2G:7(t){6.B=t.N==1g?$.15(t,6.B,16).r:$.1S(6.B,7(a){k a!=t});k 6},6k:7(t){6.B=$.Y(6.B,t.N==1g?$.2d(t):t.N==36?t:[t]);k 6},6j:7(t){k $.15(t,6.B).r.q>0},6i:7(t){k!6.s(t)}};D(8 i 1d $.G){l(T[i]!=C)T["1I"+i]=T[i];T[i]=$.G[i]}l(1O 4g!="2r"&&$a.N!=1g){l($c)$a=T.1l();D(8 i 1d T){(7(j){2P{l($a[j]==C){$a[j]=7(){k $.1f(T,T[j],1M)}}}2N(e){}})(i)}k $a}k T}$.1f=7(o,f,a){a=a||[];l(f.1f)k f.1f(o,a);H{8 p=[];D(8 i=0;i<a.q;i++)p[i]=\'a[\'+i+\']\';o.$$1C=6;8 r=2O(\'o.$$1C(\'+p.3i(\',\')+\')\');o.$$1C=C;k r}};$.19=7(e,p){l(p==\'18\'||p==\'1y\'){l($.19(e,"V")!=\'1z\')k p==\'18\'?e.3U||1J(e.L.18):e.3V||1J(e.L.1y);8 1p=e.L;8 4e=1p.2e;8 1A=1p.1P;8 4f=1p.V;1p.2e=\'1q\';1p.1P=\'3S\';1p.V=\'\';8 4d=e.6h||1J(e.L.18);8 4c=e.6g||1J(e.L.1y);1p.V=4f;1p.1P=1A;1p.2e=4e;k p==\'18\'?4d:4c}l(e.L[p])k e.L[p];H l(e.4b)k e.4b[p];H l(R.3d&&R.3d.4a){p=p.1x(/([A-Z])/g,"-$1");p=p.2W();8 s=R.3d.4a(e,"");8 r=s?s.6f(p):p;k r}H k C};$.1n=$.19;$.1X=7(a){8 r=[];D(8 i=0;i<a.q;i++)l(a[i].N==1g){8 2i=R.6e("2i");2i.1Z=a[i];D(8 j=0;j<2i.1a.q;j++)r[r.q]=2i.1a[j]}H l(a[i].q)D(8 j=0;j<a[i].q;j++)r[r.q]=a[i][j];H l(a[i]!=C)r[r.q]=a[i].2C?a[i]:R.6d(a[i].6c());k r};$.g={\'\':"m[2] == \'*\' || a.2B.39() == m[2].39()",\'#\':"a.2S == m[2]",\':\':{6b:"i < m[3]-0",6a:"i > m[3]-0",2h:"m[3] - 0 == i",69:"m[3] - 0 == i",3c:"i == 0",1e:"i == r.q - 1",49:"i % 2 == 0",48:"i % 2 == 1","3c-2f":"$.12(a,0).B","2h-2f":"(m[3] == \'49\'?$.12(a,m[3]).n % 2 == 0 :(m[3] == \'48\'?$.12(a,m[3]).n % 2 == 1:$.12(a,m[3]).B))","1e-2f":"$.12(a,0,Q).B","2h-1e-2f":"$.12(a,m[3],Q).B","3c-2g-u":"$.1T(a,0)","2h-2g-u":"$.1T(a,m[3])","1e-2g-u":"$.1T(a,0,Q)","2h-1e-2g-u":"$.1T(a,m[3],Q)","47-2g-u":"$.1T(a) == 1","47-2f":"$.12(a).q == 1",46:"a.1a.q > 0",45:"a.1a.q == 0",68:"a == ( a.44 ? a.44 : R ).2L",67:"(a.66 || a.1Z).O(m[3]) != -1",65:"(!a.u || a.u != \'1q\') && ($.19(a,\'V\') != \'1z\' && $.19(a,\'2e\') != \'1q\')",1q:"(a.u && a.u == \'1q\') || $.19(a,\'V\') == \'1z\' || $.19(a,\'2e\') == \'1q\'",3k:"a.3b == 16",3b:"a.3b",2T:"a.2T"},".":"$.2b(a,m[2])","@":{"=":"$.W(a,m[3]) == m[4]","!=":"$.W(a,m[3]) != m[4]","~=":"$.2b($.W(a,m[3]),m[4])","|=":"$.W(a,m[3]).O(m[4]) == 0","^=":"$.W(a,m[3]).O(m[4]) == 0","$=":"$.W(a,m[3]).1o( $.W(a,m[3]).q - m[4].q, m[4].q ) == m[4]","*=":"$.W(a,m[3]).O(m[4]) >= 0","":"m[3] == \'*\' ? a.64.q > 0 : $.W(a,m[3])"},"[":"$.2d(m[2],a).q > 0"};$.G={};$.2d=7(t,14){14=14||$.14||R;l(t.N!=1g)k[t];l(t.O("//")==0){14=14.2L;t=t.1o(2,t.q)}H l(t.O("/")==0){14=14.2L;t=t.1o(1,t.q);l(t.O(\'/\'))t=t.1o(t.O(\'/\'),t.q)}8 F=[14];8 1V=[];8 1e=C;1G(t.q>0&&1e!=t){8 r=[];1e=t;t=$.1L(t);8 S=I 1i("^//","i");t=t.1x(S,"");l(t.O(\'..\')==0||t.O(\'/..\')==0){l(t.O(\'/\')==0)t=t.1o(1,t.q);r=$.1w(F,7(a){k a.U});t=t.1o(2,t.q);t=$.1L(t)}H l(t.O(\'>\')==0||t.O(\'/\')==0){r=$.1w(F,7(a){k(a.1a.q>0?$.12(a.1W):C)});t=t.1o(1,t.q);t=$.1L(t)}H l(t.O(\'+\')==0){r=$.1w(F,7(a){k $.12(a).40});t=t.1o(1,t.q);t=$.1L(t)}H l(t.O(\'~\')==0){r=$.1w(F,7(a){8 r=[];8 s=$.12(a);l(s.n>0)D(8 i=s.n;i<s.q;i++)r[r.q]=s[i];k r});t=t.1o(1,t.q);t=$.1L(t)}H l(t.O(\',\')==0||t.O(\'|\')==0){l(F[0]==14)F.43();1V=$.Y(1V,F);r=F=[14];t=" "+t.1o(1,t.q)}H{8 S=I 1i("^([#.]?)([a-2H-9\\\\*1I-]*)","i");8 m=S.1C(t);l(m[1]=="#"){8 3a=R.25(m[2]);r=3a?[3a]:[];t=t.1x(S,"")}H{l(m[2]==""||m[1]==".")m[2]="*";D(8 i=0;i<F.q;i++){8 o=F[i];l(o){63(m[2]){1c\'*\':r=$.Y($.37(o),r);2K;1c\'1N\':1c\'62\':1c\'61\':1c\'1q\':1c\'60\':1c\'3C\':1c\'5Z\':1c\'5Y\':1c\'3E\':1c\'5X\':r=$.Y($.1S($.1U(o,"2p"),7(a){k a.u==m[2]}),r);2K;1c\'2p\':r=$.Y($.1U(o,"2p"),r);r=$.Y($.1U(o,"3D"),r);r=$.Y($.1U(o,"3l"),r);2K;5W:r=$.Y(r,$.1U(o,m[2]));2K}}}}}8 2J=$.15(t,r);F=r=2J.r;t=$.1L(2J.t)}l(F&&F[0]==14)F.43();1V=$.Y(1V,F);k 1V};$.1U=7(a,b){k a&&1O a.2t!="2r"?a.2t(b):[]};$.W=7(o,a,v){l(a&&a.N==1g){8 2I={\'D\':\'5V\',\'1N\':\'5U\',\'5T\':\'1j\',\'5S\':\'5R\'};a=(2I[a]&&2I[a].1x&&2I[a])||a;8 r=I 1i("-([a-z])","5Q");a=a.1x(r,7(z,b){k b.39()});l(v!=C){o[a]=v;l(o.42)o.42(a,v)}k o[a]||o.5P(a)||\'\'}H k\'\'};$.15=7(t,r,2G){8 g=$.1S;l(2G==16)8 g=7(a,f){k $.1S(a,f,Q)};1G(t.q>0&&t.5O(/^[:\\\\.#\\\\[a-41-Z\\\\*]/)){8 S=I 1i("^\\\\[ *@([a-2H-9\\\\(\\\\)1I-]+) *([~!\\\\|\\\\*$^=]*) *\'?\\"?([^\'\\"]*)\'?\\"? *\\\\]","i");8 m=S.1C(t);l(m!=C){m=[\'\',\'@\',m[2],m[1],m[3]]}H{8 S=I 1i("^(\\\\[) *([^\\\\]]*) *\\\\]","i");8 m=S.1C(t);l(m==C){8 S=I 1i("^(:)([a-2H-9\\\\*1I-]*)\\\\( *[\\"\']?([^ \\\\)\'\\"]*)[\'\\"]? *\\\\)","i");8 m=S.1C(t);l(m==C){8 S=I 1i("^([:\\\\.#]*)([a-2H-9\\\\*1I-]*)","i");8 m=S.1C(t)}}}t=t.1x(S,"");l(m[1]==":"&&m[2]=="2G")r=$.15(m[3],r,16).r;H{l($.g[m[1]].N==1g)8 f=$.g[m[1]];H l($.g[m[1]][m[2]])8 f=$.g[m[1]][m[2]];l(f!=C){2O("f = 7(a,i){k "+f+"}");r=g(r,f)}}}k{r:r,t:t}};$.38=7(a){8 b=[];8 c=a.U;1G(c!=C&&c!=R){b[b.q]=c;c=c.U}k b};$.1L=7(t){k t.1x(/^\\s+|\\s+$/g,\'\')};$.1T=7(a,n,e){8 t=$.1S($.12(a),7(b){k b.2B==a.2B});l(e)n=t.q-n-1;k n!=C?t[n]==a:t.q};$.12=7(a,n,e){8 u=[];8 2c=a.U.1a;D(8 i=0;i<2c.q;i++){l(2c[i].2C==1)u[u.q]=2c[i];l(2c[i]==a)u.n=u.q-1}l(e)n=u.q-n-1;u.B=(u[n]==a);u.5N=(u.n>0?u[u.n-1]:C);u.40=(u.n<u.q-1?u[u.n+1]:C);k u};$.2b=7(e,a){l(e==C)k 16;l(e.1j!=C)e=e.1j;k I 1i("(^|\\\\s)"+a+"(\\\\s|$)").3Z(e)};$.37=7(o,r){r=r||[];8 s=o.1a;D(8 i=0;i<s.q;i++){l(s[i].2C==1){r[r.q]=s[i];$.37(s[i],r)}}k r};$.Y=7(a,b){8 d=[];D(8 j=0;j<b.q;j++)d[j]=b[j];D(8 i=0;i<a.q;i++){8 c=Q;D(8 j=0;j<b.q;j++)l(a[i]==b[j])c=16;l(c)d[d.q]=a[i]}k d};$.1S=7(a,f,s){8 r=[];l(a!=C)D(8 i=0;i<a.q;i++)l((!s&&f(a[i],i))||(s&&!f(a[i],i)))r[r.q]=a[i];k r};$.1w=7(a,f){8 r=[];D(8 i=0;i<a.q;i++){8 t=f(a[i],i);l(t!=C){l(t.N!=36)t=[t];r=$.Y(t,r)}}k r};7 1F(K,u,1B){l(K.5M)K=23;l(!1B.$$1R)1B.$$1R=1F.1R++;l(!K.1b)K.1b={};8 1h=K.1b[u];l(!1h){1h=K.1b[u]={};l(K["2a"+u])1h[0]=K["2a"+u]}1h[1B.$$1R]=1B;K["2a"+u]=2F};1F.1R=1;7 35(K,u,1B){l(K.1b){l(u&&K.1b[u]){l(1B){3Y K.1b[u][1B.$$1R]}H{D(8 i 1d K.1b[u])3Y K.1b[u][i]}}H{D(8 i 1d K.1b)35(K,i)}}};7 2U(K,u,X){X=X||[{u:u}];l(K&&K["2a"+u])$.1f(K,K["2a"+u],X)}7 2F(11){8 2E=Q;11=11||1Q(23.11);8 1h=[];D(8 i 1d 6.1b[11.u])1h[1h.q]=6.1b[11.u][i];D(8 i=0;i<1h.q;i++){2P{l(1h[i].N==20){6.$$2F=1h[i];l(6.$$2F(11)===16){11.24();11.2D();2E=16}}}2N(e){}}k 2E};7 1Q(11){11.24=1Q.24;11.2D=1Q.2D;k 11};1Q.24=7(){6.2E=16};1Q.2D=7(){6.5L=Q};$.G.1N=7(e){e=e||6.B;8 t="";D(8 j=0;j<e.q;j++){D(8 i=0;i<e[j].1a.q;i++)t+=e[j].1a[i].2C!=1?e[j].1a[i].5K:$.G.1N(e[j].1a[i].1a)}k t};$.1K=7(s,o){l(o&&o.N==20)o={1H:o};o=o||{};8 27={"5J":5I,"5H":5G,"5F":5E,"5D":2A,"5C":5B,"5A":5z,"5y":2A};o.28=1O s=="5x"?s:27[s]||2A;k o};$.G.1u=7(a,o){o=$.1K(a,o);k a?6.E(7(){I J.2Y(6,o).1u()}):6.3X()};$.G.1v=7(a,o){o=$.1K(a,o);k a?6.E(7(){I J.2Y(6,o).1v()}):6.3W()};$.G.5w=7(a,o){o=$.1K(a,o);k 6.E(7(){I J.2v(6,o).1v("18")})};$.G.5v=7(a,o){o=$.1K(a,o);k 6.E(7(){I J.2v(6,o).1u("18")})};$.G.5u=7(a,o){o=$.1K(a,o);k a?6.E(7(){I J.2u(6,o).1u()}):6.3X()};$.G.5t=7(a,o){o=$.1K(a,o);k a?6.E(7(){I J.2u(6,o).1v()}):6.3W()};$.G.3T=7(f){k 6.E(7(){l(!f&&6.2B==\'5s\'&&!6.3V&&!6.3U){8 T=6;3O(7(){$(T).3T(Q)},13)}H{8 s=6.L;8 p=6.U;l($.1n(p,"1P")==\'5r\')p.L.1P=\'5q\';s.1P=\'3S\';s.5p=1J(($.1n(p,"1y")-$.1n(6,"1y"))/2)+"32";s.5o=1J(($.1n(p,"18")-$.1n(6,"18"))/2)+"32"}})};$.30=7(e,p){8 a=e.L[p];8 o=$.1n(e,p);e.L[p]=\'31\';8 n=$.1n(e,p);l(o!=n)e.L[p]=a};7 J(M,1A,1t,34){8 z=6;z.a=7(){z.M.L[1t]=z.1s+z.o.3Q};z.3R=7(){k z.M["2x"+1t]||z.M["5n"+34]||z.M["3F"+34]||z.B()};z.B=7(){k 1J($.19(z.M,1t))};z.1v=7(){z.27("33");z.o.31=Q;z.2y(0,z.3R())};z.1u=7(){z.M.$o=$.19(z.M,"2z");z.M["2x"+1t]=6.B();z.2y(z.B(),0)};z.27=7(a){l(y.V!=a)y.V=a};z.2s=7(){l(z.B()>0)z.1u();H z.1v()};z.2w=7(a){z.2y(z.B(),z.B()+a)};z.3P=7(){3t(z.1r);z.1r=C};z.M=M.N==1g?R.25(M):M;8 y=z.M.L;z.3N=y.2z;y.2z="1q";z.o={3Q:"32",28:(1A&&1A.28)||2A,1H:(1A&&1A.1H)||1A};z.3I=7(f,2Z){8 t=(I 3K).3J();8 p=(t-z.s)/z.o.28;l(t>=z.o.28+z.s){z.1s=2Z;z.3P();3O(7(){y.2z=z.3N;l(y.18=="3M"||y.1y=="3M")z.27("1z");l(1t!="26"&&z.o.31){$.30(z.M,"18");$.30(z.M,"1y")}l(z.o.1H.N==20){z.M.$1I=z.o.1H;z.M.$1I()}},13)}H z.1s=((-3L.5m(p*3L.5l)/2)+0.5)*(2Z-f)+f;z.a()};z.2y=7(f,t){l(z.1r)k;6.1s=f;z.a();z.2x=z.B();z.s=(I 3K).3J();z.1r=3r(7(){z.3I(f,t)},13)}}J.G=["1v","1u","2s"];J.1t=["3H","3G","5k","5j"];D(8 i 1d J.1t){(7(){8 c=J.1t[i];J[c]=7(a,b){k I J(a,b,c.2W(),c)}})()}J.2u=7(a,b){8 o=I J(a,b,"26");o.B=7(){k 5i(o.M.L.26)};o.a=7(){8 e=o.M.L;l(o.1s==1)o.1s=0.5h;l(23.2X)e.15="5g(26="+o.1s*5f+")";e.26=o.1s};o.2x=o.1s=1;o.a();k o};J.2v=7(e,o){8 z=6;8 h=I J.3H(e,o);l(o)o.1H=C;8 w=I J.3G(e,o);7 c(a,b,c){k(!a||a==c||b==c)}D(8 i 1d J.G){(7(){8 j=J.G[i];z[j]=7(a,b){l(c(a,b,"18"))h[j]();l(c(a,b,"1y"))w[j]()}})()}z.2w=7(c,d){h.2w(c);w.2w(d)}};J.2Y=7(e,o){8 z=6;8 r=I J.2v(e,o);l(o)o.1H=C;8 p=I J.2u(e,o);D(8 i 1d J.G){(7(){8 j=J.G[i];z[j]=7(a,b){p[j]();r[j](a,b)}})()}};8 e=["5e","5d","5c","2n","5b","3F","5a","3q","59","58","57","56","55","54","3z","3x","53","3E","3D","3C","52","51","50","4Z","4Y","17"];D(8 i=0;i<e.q;i++){(7(){8 o=e[i];$.G[o]=7(f){k 6.21(o,f)};$.G["4X"+o]=7(f){k 6.3B(o,f)};$.G["4W"+o]=7(){k 6.3A(o)};$.G["4V"+o]=7(f){k 6.21(o,7(e){l(6[o+f]!=C)k Q;6[o+f]++;k $.1f(6,f,[e])})};})()}$.G.3u=7(f,g){k 6.E(7(){8 1m=6;1F(6,"3z",7(e){8 p=(e.3y!=C?e.3y:e.3v);1G(p&&p!=1m)p=p.U;l(p==1m)k 16;k $.1f(1m,f,[e])});1F(6,"3x",7(e){8 p=(e.3w!=C?e.3w:e.3v);1G(p&&p!=1m)p=p.U;l(p==1m)k 16;k $.1f(1m,g,[e])})})};$.G.4U=$.G.3u;$.17=7(){l($.$$1r){3t($.$$1r);$.$$1r=C;D(8 i=0;i<$.$$17.q;i++)$.1f(R,$.$$17[i]);$.$$17=C}};l(R.3s)R.3s("4T",$.17,C);1F(23,"2n",$.17);$.G.17=7(f){k 6.E(7(){l($.$$1r){$.$$17.4S(f)}H{8 o=6;$.$$17=[f];$.$$1r=3r(7(){l(o&&o.2t&&o.25&&o.4R)$.17()},10)}})};$.G.4Q=$.G.17;$.G.2s=7(a,b){k a&&b?6.3q(7(e){6.$$1e=6.$$1e==a?b:a;e.24();k $.1f(6,6.$$1e,[e])||16}):6.4P()};l(1O 2V==\'2r\'&&1O 23.2X==\'7\'){8 2V=7(){k I 2X((4O.4N.2W().O(\'4M 5\')>=0)?"4L.3p":"4K.3p")}}$.P=7(u,1k,X,F){8 P=I 2V();l(P){P.4J(u||"2m",1k,Q);l(X)P.4I(\'4H-4G\',\'4F/x-4E-4D-4C\');P.4B=7(){l(P.4A==4){l(F)F(P);$.3n($.2q(P))}};P.4z(X)}};$.2q=7(r,u){k r.4y("4x-u").O("P")>0||u=="P"?r.4w:r.3g};$.1l=7(1k,F,u){$.P("2m",1k,C,7(r){l(F)F($.2q(r,u))})};$.4v=7(1k,F){$.1l(1k,F,"P")};$.3o=7(1k,X,F,u){$.P("3h",1k,$.2l(X),7(r){l(F)F($.2q(r,u))})};$.4u=7(1k,X,F){$.3o(1k,X,F,"P")};$.G.4t=7(2o){$.22=$.Y($.22,6.B);k 6.21(\'3m\',2o)};$.22=[];$.3n=7(X){D(8 i=0;i<$.22.q;i++)2U($.22[i],\'3m\',[X])};$.G.4s=7(2o){k 6.E(7(){8 a={};$(6).2Q("2p:2T,1q,1N,4r[@4q],3l").15(":3k").E(7(){a[6.3j||6.2S||6.U.3j||6.U.2S]=6.2R});$.P(6.4p||"2m",6.4o||"",$.2l(a),2o)})};$.2l=7(a){8 s=[];D(8 i 1d a)s[s.q]=i+"="+4n(a[i]);k s.3i("&")};$.G.2n=7(a,o,f){l(a&&a.N==20)k 6.21("2n",a);8 t="2m";l(o&&o.N==20){f=o;o=C}l(o!=C){o=$.2l(o);t="3h"}8 T=6;$.P(t,a,o,7(h){8 h=h.3g;T.3f(h).2Q("4m").E(7(){2P{2O(6.1N||6.4l||6.1Z)}2N(e){}});l(f)f(h)});k 6};',62,408,'||||||this|function|var||||||||||||return|if|||||length||||type|||||||cur|null|for|each|ret|fn|else|new|fx|element|style|el|constructor|indexOf|xml|true|document|re|self|parentNode|display|attr|data|merge|||event|sibling||context|filter|false|ready|height|getCSS|childNodes|events|case|in|last|apply|String|handlers|RegExp|className|url|get|obj|css|substr|els|hidden|timer|now|ty|hide|show|map|replace|width|none|op|handler|exec|clone|size|addEvent|while|onComplete|_|parseInt|speed|cleanSpaces|arguments|text|typeof|position|fixEvent|guid|grep|ofType|tag|done|firstChild|clean|old|innerHTML|Function|bind|ajaxHandles|window|preventDefault|getElementById|opacity|ss|duration||on|hasWord|tmp|Select|visibility|child|of|nth|div|cloneNode|oldblock|param|GET|load|callback|input|httpData|undefined|toggle|getElementsByTagName|Opacity|Resize|modify|io|custom|overflow|400|nodeName|nodeType|stopPropagation|returnValue|handleEvent|not|z0|fix|val|break|documentElement|insertBefore|catch|eval|try|find|value|id|checked|triggerEvent|XMLHttpRequest|toLowerCase|ActiveXObject|FadeSize|tt|setAuto|auto|px|block|tz|removeEvent|Array|getAll|parents|toUpperCase|oid|disabled|first|defaultView|set|html|responseText|POST|join|name|enabled|textarea|ajax|triggerAJAX|post|XMLHTTP|click|setInterval|addEventListener|clearInterval|hover|relatedTarget|toElement|mouseout|fromElement|mouseover|trigger|unbind|submit|select|reset|scroll|Width|Height|step|getTime|Date|Math|0px|oo|setTimeout|clear|unit|max|absolute|center|offsetHeight|offsetWidth|_show|_hide|delete|test|next|zA|setAttribute|shift|ownerDocument|empty|parent|only|odd|even|getComputedStyle|currentStyle|oWidth|oHeight|ov|od|Prototype|removeChild|append|appendChild|jquery|textContent|script|encodeURIComponent|action|method|selected|option|serialize|handleAJAX|postXML|getXML|responseXML|content|getResponseHeader|send|readyState|onreadystatechange|urlencoded|form|www|application|Type|Content|setRequestHeader|open|Msxml2|Microsoft|msie|userAgent|navigator|_toggle|onready|body|push|DOMContentLoaded|onhover|one|do|un|error|abort|keyup|keypress|keydown|change|mousemove|mouseleave|mouseenter|mouseup|mousedown|dblclick|unload|resize|contextmenu|focus|blur|100|alpha|9999|parseFloat|Top|Left|PI|cos|natural|top|left|relative|static|IMG|fadeIn|fadeOut|slideUp|slideDown|number|normal|75|xfast|200|fast|medium|600|slow|850|xslow|1200|crawl|nodeValue|cancelBubble|location|prev|match|getAttribute|ig|cssFloat|float|class|cssText|htmlFor|default|file|password|image|button|checkbox|radio|switch|attributes|visible|innerText|contains|root|eq|gt|lt|toString|createTextNode|createElement|getPropertyValue|clientWidth|clientHeight|isNot|is|add|siblings|end|nextSibling|after|before|prepend|appendTo|wrap|remove|toggleClass|removeClass|addClass|Rev|9_|Z0'.split('|'),0,{})) diff --git a/media/logo.png b/media/logo.png Binary files differnew file mode 100644 index 0000000..b2b6d86 --- /dev/null +++ b/media/logo.png diff --git a/media/mailman-large.jpg b/media/mailman-large.jpg Binary files differnew file mode 100644 index 0000000..e184f3c --- /dev/null +++ b/media/mailman-large.jpg diff --git a/media/mailman.jpg b/media/mailman.jpg Binary files differnew file mode 100644 index 0000000..94a4c01 --- /dev/null +++ b/media/mailman.jpg diff --git a/media/mm-icon.png b/media/mm-icon.png Binary files differnew file mode 100644 index 0000000..42c3cf7 --- /dev/null +++ b/media/mm-icon.png diff --git a/media/rss.png b/media/rss.png Binary files differnew file mode 100644 index 0000000..c916459 --- /dev/null +++ b/media/rss.png diff --git a/media/tab.png b/media/tab.png Binary files differnew file mode 100644 index 0000000..56d2e61 --- /dev/null +++ b/media/tab.png diff --git a/media/title.png b/media/title.png Binary files differnew file mode 100644 index 0000000..e16971a --- /dev/null +++ b/media/title.png diff --git a/media/title_back.png b/media/title_back.png Binary files differnew file mode 100644 index 0000000..95c98d7 --- /dev/null +++ b/media/title_back.png diff --git a/news/__init__.py b/news/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/news/__init__.py diff --git a/news/models.py b/news/models.py new file mode 100644 index 0000000..acd9667 --- /dev/null +++ b/news/models.py @@ -0,0 +1,19 @@ +from django.db import models +from django.contrib.auth.models import User +import re +from archlinux.utils import Stripper + +class News(models.Model): + id = models.AutoField(primary_key=True) + author = models.ForeignKey(User) + postdate = models.DateField(auto_now_add=True) + title = models.CharField(maxlength=255) + content = models.TextField() + class Meta: + db_table = 'news' + verbose_name_plural = 'news' + get_latest_by = 'postdate' + ordering = ['-postdate', '-id'] + + def get_absolute_url(self): + return '/news/%i/' % self.id diff --git a/news/views.py b/news/views.py new file mode 100644 index 0000000..29f2044 --- /dev/null +++ b/news/views.py @@ -0,0 +1,82 @@ +from django.http import HttpResponse, HttpResponseRedirect, Http404 +from django.shortcuts import get_object_or_404 +from django.contrib.auth.decorators import user_passes_test +from django.contrib.auth.models import User +from django import forms +from archlinux.utils import render_template +from archlinux.news.models import News +from datetime import date + +def view(request, newsid): + news = get_object_or_404(News, id=newsid) + return render_template('news/view.html', request, {'news':news}) + +def list(request): + news = News.objects.order_by('-postdate', '-id') + return render_template('news/list.html', request, {'news':news}) + +@user_passes_test(lambda u: u.has_perm('news.add_news')) +def add(request): + try: + m = User.objects.get(username=request.user.username) + except User.DoesNotExist: + return render_template('error_page.html', request, + {'errmsg': 'Cannot find a maintainer record for you! No posting allowed.'}) + + manipulator = News.AddManipulator() + if request.POST: + data = request.POST.copy() + # add in the author ID + data['author'] = m.id + errors = manipulator.get_validation_errors(data) + if not errors: + manipulator.do_html2python(data) + manipulator.save(data) + return HttpResponseRedirect('/news/') + else: + errors = {} + data = {} + + form = forms.FormWrapper(manipulator, data, errors) + return render_template('news/add.html', request, {'form': form}) + +@user_passes_test(lambda u: u.has_perm('news.delete_news')) +def delete(request, newsid): + news = get_object_or_404(News, id=newsid) + #if news.author.id != request.user.id: + # return render_template('error_page.html', request, {'errmsg': 'You do not own this news item'}) + if request.POST: + news.delete() + return HttpResponseRedirect('/news/') + return render_template('news/delete.html', request) + +@user_passes_test(lambda u: u.has_perm('news.change_news')) +def edit(request, newsid): + try: + m = User.objects.get(username=request.user.username) + except User.DoesNotExist: + return render_template('error_page.html', request, + {'errmsg': 'Cannot find a maintainer record for you! No posting allowed.'}) + try: + manipulator = News.ChangeManipulator(newsid) + except News.DoesNotExist: + raise Http404 + + news = manipulator.original_object +# if news.author != m: +# return render_template('error_page.html', request, {'errmsg': 'You do not own this news item'}) + if request.POST: + data = request.POST.copy() + # add in the author ID + data['author'] = news.author.id + errors = manipulator.get_validation_errors(data) + if not errors: + manipulator.do_html2python(data) + manipulator.save(data) + return HttpResponseRedirect('/news/') + else: + errors = {} + data = news.__dict__ + + form = forms.FormWrapper(manipulator, data, errors) + return render_template('news/add.html', request, {'form': form, 'news':news}) diff --git a/packages/__init__.py b/packages/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/packages/__init__.py diff --git a/packages/models.py b/packages/models.py new file mode 100644 index 0000000..c1b6c84 --- /dev/null +++ b/packages/models.py @@ -0,0 +1,91 @@ +from django.db import models +from django.contrib.auth.models import User +import re + +class PackageManager(models.Manager): + def get_flag_stats(self): + results = [] + # first the orphans + unflagged = self.filter(maintainer=0).count() + flagged = self.filter(maintainer=0).filter(needupdate=True).count() + results.append((User(id=0,first_name='Orphans'), unflagged, flagged)) + # now the rest + for maint in User.objects.all().order_by('first_name'): + unflagged = self.filter(maintainer=maint.id).count() + flagged = self.filter(maintainer=maint.id).filter(needupdate=True).count() + results.append((maint, unflagged, flagged)) + return results + +class Category(models.Model): + id = models.AutoField(primary_key=True) + category = models.CharField(maxlength=255) + class Meta: + db_table = 'categories' + verbose_name_plural = 'categories' + +class Repo(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(maxlength=255) + class Meta: + db_table = 'repos' + def last_update(self): + try: + latest = Package.objects.filter(repo__name__exact=self.name).order_by('-last_update')[0] + return latest.last_update + except IndexError: + return "N/A" + +class Package(models.Model): + id = models.AutoField(primary_key=True) + repo = models.ForeignKey(Repo) + maintainer = models.ForeignKey(User) + category = models.ForeignKey(Category) + needupdate = models.BooleanField(default=False) + pkgname = models.CharField(maxlength=255) + pkgver = models.CharField(maxlength=255) + pkgrel = models.CharField(maxlength=255) + pkgdesc = models.CharField(maxlength=255) + url = models.URLField() + sources = models.TextField() + depends = models.TextField() + last_update = models.DateTimeField(null=True, blank=True) + objects = PackageManager() + class Meta: + db_table = 'packages' + get_latest_by = 'last_update' + + def get_absolute_url(self): + return '/packages/%i/' % self.id + + def depends_urlize(self): + urls = '' + for dep in self.depends.split(' '): + # shave off any version qualifiers + nameonly = re.match(r"([a-z0-9-]+)", dep).group(1) + try: + p = Package.objects.filter(pkgname=nameonly)[0] + except IndexError: + # couldn't find a package in the DB -- it might be a virtual depend + urls = urls + '<li>' + dep + '</li>' + continue + url = '<li><a href="/packages/' + str(p.id) + '">' + dep + '</a></li>' + urls = urls + url + return urls + + def sources_urlize(self): + urls = '' + for source in self.sources.split(' '): + if re.search('://', source): + url = '<li><a href="' + source + '">' + source + '</a></li>' + else: + url = '<li>' + source + '</li>' + urls = urls + url + return urls + +class PackageFile(models.Model): + id = models.AutoField(primary_key=True) + pkg = models.ForeignKey(Package) + path = models.CharField(maxlength=255) + class Meta: + db_table = 'packages_files' + diff --git a/packages/templatetags/__init__.py b/packages/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/packages/templatetags/__init__.py diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py new file mode 100644 index 0000000..8b55221 --- /dev/null +++ b/packages/templatetags/package_extras.py @@ -0,0 +1,28 @@ +from django import template + +register = template.Library() + +class BuildQueryStringNode(template.Node): + def __init__(self, sortfield): + self.sortfield = sortfield + def render(self, context): + qs = context['querystring'].copy() + if qs.has_key('sort') and qs['sort'] == self.sortfield: + qs['sort'] = '-' + self.sortfield + else: + qs['sort'] = self.sortfield + return '?' + qs.urlencode() + +@register.tag(name='buildsortqs') +def do_buildsortqs(parser, token): + try: + tagname, sortfield = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError, "%r tag requires a single argument" % tagname + if not (sortfield[0] == sortfield[-1] and sortfield[0] in ('"', "'")): + raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tagname + return BuildQueryStringNode(sortfield[1:-1]) + +@register.filter(name='space2br') +def space2br(value): + return value.replace(' ', '<br />') diff --git a/packages/views.py b/packages/views.py new file mode 100644 index 0000000..f9cecf1 --- /dev/null +++ b/packages/views.py @@ -0,0 +1,172 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.core.mail import send_mail +from django.template import Context, loader +from django.core import validators +from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from archlinux.utils import validate, render_template +from datetime import datetime +from archlinux.packages.models import Package, PackageFile, Repo, Category + +def update(request): + if request.POST.has_key('adopt'): + mode = 'adopt' + message = 'Adoption was successful' + if request.POST.has_key('disown'): + mode = 'disown' + message = 'Disown was successful' + try: + maint = User.objects.get(username=request.user.username) + except User.DoesNotExist: + return render_template('error_page.html', request, {'errmsg':'No maintainer record found! Are you a maintainer?'}) + ids = request.POST.getlist('pkgid') + for id in ids: + pkg = Package.objects.get(id=id) + if mode == 'adopt' and pkg.maintainer_id == 0: + pkg.maintainer = maint + elif mode == 'disown' and pkg.maintainer == maint: + pkg.maintainer_id = 0 + else: + message = "You are not the current maintainer" + pkg.save() + return render_template('status_page.html', request, {'message':message}) + +def details(request, pkgid=0, name='', repo=''): + if pkgid == 0: + p = Package.objects.filter(pkgname=name) + if repo: p = p.filter(repo__name__exact=repo) + # if more then one result, send to the search view + if len(p) > 1: return search(request, name) + if len(p) < 1: return render_template('error_page.html', request, + {'errmsg': 'No matching packages.'}) + pkgid = p[0].id + + pkg = get_object_or_404(Package, id=pkgid) + return render_template('packages/details.html', request, {'pkg':pkg}) + +def search(request, query=''): + if request.GET.has_key('q'): + # take the q GET var over the one passed on the URL + query = request.GET['q'].strip() + + # fetch the form vars + repo = request.GET.get('repo', 'all') + category = request.GET.get('category', 'all') + lastupdate = request.GET.get('lastupdate', '') + limit = int(request.GET.get('limit', '50')) + skip = int(request.GET.get('skip', '0')) + sort = request.GET.get('sort', '') + maint = request.GET.get('maint', 'all') + + # build the form lists + repos = Repo.objects.order_by('name') + cats = Category.objects.order_by('category') + # copy GET data over and add the lists + c = request.GET.copy() + c['repos'], c['categories'] = repos, cats + c['limit'], c['skip'] = limit, skip + c['lastupdate'] = lastupdate + c['sort'] = sort + # 'q' gets renamed to 'query', so it's not in GET + c['query'] = query + + # validate + errors = {} + validate(errors, 'Last Update', lastupdate, validators.isValidANSIDate, True, request) + validate(errors, 'Page Limit', str(limit), validators.isOnlyDigits, True, request) + validate(errors, 'Page Skip', str(skip), validators.isOnlyDigits, True, request) + if errors: + c['errors'] = errors + return render_template('packages/search.html', request, c) + + if query: + res1 = Package.objects.filter(pkgname__icontains=query) + res2 = Package.objects.filter(pkgdesc__icontains=query) + results = res1 | res2 + else: + results = Package.objects.all() + if repo != 'all': results = results.filter(repo__name__exact=repo) + if category != 'all': results = results.filter(category__category__exact=category) + if maint != 'all': results = results.filter(maintainer=maint) + if lastupdate: results = results.filter(last_update__gte=datetime(int(lastupdate[0:4]),int(lastupdate[5:7]),int(lastupdate[8:10]))) + # select_related() shouldn't be needed -- we're working around a Django bug + #results = results.select_related().order_by('repos.name', 'category', 'pkgname') + + # sort results + if sort == '': + results = results.order_by('repo', 'category', 'pkgname') + else: + # duplicate sort fields shouldn't hurt anything + results = results.order_by(sort, 'repo', 'category', 'pkgname') + + qs = request.GET.copy() + # build pagination urls + if results.count() > (skip + limit): + qs['skip'] = skip + limit + c['nextpage'] = '?' + qs.urlencode() + if skip > 0: + qs['skip'] = max(0, skip - limit) + c['prevpage'] = '?' + qs.urlencode() + # pass the querystring to the template so we can build sort queries + c['querystring'] = request.GET + + # if only there's only one result, pass right to the package details view + if results.count() == 1: return details(request, results[0].id) + # limit result set + if limit > 0: results = results[skip:(skip+limit)] + + c['results'] = results + return render_template('packages/search.html', request, c) + +def files(request, pkgid): + pkg = get_object_or_404(Package, id=pkgid) + files = PackageFile.objects.filter(pkg=pkgid) + return render_template('packages/files.html', request, {'pkg':pkg,'files':files}) + +def flaghelp(request): + return render_template('packages/flaghelp.html', request) + +def flag(request, pkgid): + pkg = get_object_or_404(Package, id=pkgid) + context = {'pkg': pkg} + if request.POST.has_key('confirmemail'): + email = request.POST['confirmemail'] + if request.POST.has_key('usermessage'): + message = request.POST['usermessage'] + else: + message = None + # validate + errors = {} + validate(errors, 'Email Address', email, validators.isValidEmail, False, request) + if errors: + context['errors'] = errors + return render_template('packages/flag.html', request, context) + + context['confirmemail'] = email + pkg.needupdate = 1 + pkg.save() + if pkg.maintainer_id > 0: + # send notification email to the maintainer + t = loader.get_template('packages/outofdate.txt') + c = Context({ + 'email': request.POST['confirmemail'], + 'message': message, + 'pkgname': pkg.pkgname, + 'weburl': 'http://www.archlinux.org/packages/' + str(pkg.id) + '/' + }) + send_mail('arch: Package [%s] marked out-of-date' % pkg.pkgname, + t.render(c), + 'Arch Website Notification <nobody@archlinux.org>', + [pkg.maintainer.email], + fail_silently=True) + return render_template('packages/flag.html', request, context) + +@login_required +def unflag(request, pkgid): + pkg = get_object_or_404(Package, id=pkgid) + if pkg.maintainer.username != request.user.username: + return render_template('error_page.html', request, {'errmsg': 'You do not own this package.'}) + pkg.needupdate = 0 + pkg.save() + return HttpResponseRedirect('/packages/%d/' % (pkg.id)) diff --git a/public/__init__.py b/public/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/public/__init__.py diff --git a/public/views.py b/public/views.py new file mode 100644 index 0000000..577efcb --- /dev/null +++ b/public/views.py @@ -0,0 +1,56 @@ +from django.http import HttpResponse +from archlinux.utils import render_template +from django.contrib.auth.models import User +from archlinux.packages.models import Package, Repo +from archlinux.news.models import News +from archlinux.settings import DATA_DIR +from archlinux.common.models import Donator, Mirror + +def index(request): + # get the most recent 10 news items + news = News.objects.order_by('-postdate', '-id')[:10] + pkgs = Package.objects.exclude(repo__name__exact='Testing').order_by('-last_update')[:15] + repos = Repo.objects.order_by('name') + return render_template('public/index.html', request, {'news_updates':news,'pkg_updates':pkgs,'repos':repos}) + +def about(request): + return render_template('public/about.html', request) + +def art(request): + return render_template('public/art.html', request) + +def cvs(request): + return render_template('public/cvs.html', request) + +def developers(request): + devs = User.objects.order_by('username') + return render_template('public/developers.html', request, {'devs':devs}) + +def donate(request): + donor_count = Donator.objects.count() + splitval = donor_count / 4 + slice1 = Donator.objects.all()[:splitval] + slice2 = Donator.objects.all()[(splitval):(splitval*2)] + slice3 = Donator.objects.all()[(splitval*2):(donor_count-splitval)] + slice4 = Donator.objects.all()[(donor_count-splitval):donor_count] + return render_template('public/donate.html', request, + {'slice1':slice1,'slice2':slice2,'slice3':slice3,'slice4':slice4}) + +def download(request): + mirrors = Mirror.objects.order_by('country', 'domain') + return render_template('public/download.html', request, {'mirrors':mirrors}) + +def irc(request): + return render_template('public/irc.html', request) + +def moreforums(request): + return render_template('public/moreforums.html', request) + +def press(request): + return render_template('public/press.html', request) + +def projects(request): + return render_template('public/projects.html', request) + +def denied(request): + return render_template('public/denied.html', request) diff --git a/scripts/daily_cleanup.py b/scripts/daily_cleanup.py new file mode 100644 index 0000000..98f997b --- /dev/null +++ b/scripts/daily_cleanup.py @@ -0,0 +1,14 @@ +from django.db import backend, connection, transaction +""" Daily cleanup file + This purges the session data that is old from the session table. +""" +def clean_up(): + # Clean up old database records + cursor = connection.cursor() + cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \ + (backend.quote_name('django_session'), backend.quote_name('expire_date'))) + cursor.execute("OPTIMIZE TABLE %s" % backend.quote_name('django_session')) + transaction.commit_unless_managed() + +if __name__ == "__main__": + clean_up() diff --git a/scripts/djangoshell.sh b/scripts/djangoshell.sh new file mode 100755 index 0000000..457c882 --- /dev/null +++ b/scripts/djangoshell.sh @@ -0,0 +1,6 @@ +#!/bin/bash +cd /home/sites/archlinux/archlinux +export PYTHONPATH=/usr/local/django:/home/sites/archlinux:${PYTHONPATH} +export DJANGO_SETTINGS_MODULE=archlinux.settings +python manage.py $* + diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..3de4a89 --- /dev/null +++ b/settings.py @@ -0,0 +1,89 @@ +# Django settings for archlinux project. + +## Import local settings +from local_settings import * + +## Set the debug values +TEMPLATE_DEBUG = DEBUG + +# Set managers to admins +MANAGERS = ADMINS + +## Cache backend settings +if ENABLE_CACHE == True: + CACHE_BACKEND = 'file:///tmp/ALdjangocache?timeout=900' + CACHE_MIDDLEWARE_SECONDS = 900 + CACHE_MIDDLEWARE_KEY_PREFIX = 'arch' + CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True + +# Full path to the data directory +DATA_DIR = '%s/data' % DEPLOY_PATH + +# Local time zone for this installation. All choices can be found here: +# http://www.postgresql.org/docs/current/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +TIME_ZONE = 'US/Eastern' + +# Language code for this installation. All choices can be found here: +# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes +# http://blogs.law.harvard.edu/tech/stories/storyReader$15 +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/admin-media/' + +# URL to send users when they don't have sufficient privileges +BADPRIVS_URL = '/denied/' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + "django.contrib.sessions.middleware.SessionMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + 'django.middleware.http.ConditionalGetMiddleware', +) + +# A bit of hackery to insert caching at the right spot +if ENABLE_CACHE == True: + MIDDLEWARE_CLASSES += ('django.middleware.cache.CacheMiddleware',) + +MIDDLEWARE_CLASSES += ( + "django.middleware.common.CommonMiddleware", + "django.middleware.doc.XViewMiddleware", +) + +ROOT_URLCONF = 'archlinux.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates". + # Always use forward slashes, even on Windows. + '%s/templates' % DEPLOY_PATH, +) + +# Set django's User stuff to use our profile model +# format is app.model +AUTH_PROFILE_MODULE = 'common.UserProfile' + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'archlinux.common', + 'archlinux.news', + 'archlinux.packages', + 'archlinux.public', + 'archlinux.todolists', + 'archlinux.devel', + 'archlinux.wiki' +) + diff --git a/templates/403.html b/templates/403.html new file mode 100644 index 0000000..c853fef --- /dev/null +++ b/templates/403.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block content %} + <div class="box"> + <h2>403 - Access Forbidden</h2> + Sorry, the page you've requested is not available. + </div> +{% endblock %} + diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..bc50f69 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block content %} + <div class="box"> + <h2>404 - Page Not Found</h2> + Sorry, the page you've requested does not exist. + </div> +{% endblock %} + diff --git a/templates/500.html b/templates/500.html new file mode 100644 index 0000000..9566fc2 --- /dev/null +++ b/templates/500.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block content %} + <div class="box"> + <h2>500 - Internal Server Error</h2> + Something has gone horribly wrong. Back away slowly. + </div> +{% endblock %} + diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..507e8ab --- /dev/null +++ b/templates/base.html @@ -0,0 +1,84 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1-strict.dtd "> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>{% block title %}Arch Linux{% endblock %}</title> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <link rel="stylesheet" href="/media/arch.css" /> + <link rel="icon" href="/media/favicon.ico" type="image/x-icon" /> + <link rel="shortcut icon" href="/media/favicon.ico" type="image/x-icon" /> + {% block head %} + {% endblock %} + </head> + <body> + <div id="head_container"> + <div id="title"> + <div id="logo"><a href="/"><img src="/media/logo.png" alt="Arch Logo" /></a></div> + <div id="titleimg"><a href="/"><img src="/media/title.png" alt="Arch Linux" /></a></div> + </div> + <div style="float: right; color: #eeeeee; font-size: small"> + {% if not user.is_anonymous %} + Logged in as <strong>{{ user.username }}</strong>. + <a href="/accounts/logout/">Logout</a> + {% endif %} + </div> + <div id="main_nav"> + {% block topmenu %} + <ul> + <li{% ifequal path '/download/' %} class="selected"{% endifequal %}><a href="/download/">Get Arch</a></li> + <li><a href="http://aur.archlinux.org">AUR</a></li> + <li><a href="http://bugs.archlinux.org">Bugs</a></li> + <li><a href="http://wiki.archlinux.org">Wiki</a></li> + <li><a href="http://bbs.archlinux.org">Forums</a></li> + <li{% ifequal path '/' %} class="selected"{% endifequal %}><a href="/">Home</a></li> + </ul> + {% endblock %} + </div> + {% block ads %} + {% if not user.is_anonymous %} + <div id="dev_nav"> + <ul> + <li><a href="/devel/profile/">Profile</a></li> + <li><a href="https://www.archlinux.org/mailman/private/arch-dev/">Archives</a></li> + <li><a href="https://www.archlinux.org/wiki/">Dev Wiki</a></li> + <li><a href="/todo/">Todos</a></li> + <li><a href="/news/">News</a></li> + <li><a href="/devel/">Dashboard</a></li> + </ul> + </div> + {% else %} + <div id="ads"> + <script type="text/javascript"><!-- + google_ad_client = "pub-0403484505451360"; + google_ad_width = 468; + google_ad_height = 60; + google_ad_format = "468x60_as"; + google_color_border = "2D5893"; + google_color_border = "fbf8f1"; + google_color_bg = "fbf8f1"; + google_color_link = "6C83B0"; + google_color_url = "99AACC"; + google_color_text = "000000"; + //--></script> + <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script> + {% endif %} + </div> + {% endblock %} + </div> + <div id="content"> + {% block content %} + <div class="right"> + {% block content_right %} + {% endblock %} + </div> + <div class="left"> + {% block content_left %} + {% endblock %} + </div> + {% endblock %} + </div> + <div class="foot"> + Copyright © 2002-2007, Judd Vinet <<a href="mailto:jvinet@zeroflux.org">jvinet@zeroflux.org</a>><br /><br /> + <img src="http://www.archlinux.org/logos/button.png" alt="Arch Linux" /> + </div> + </body> +</html> diff --git a/templates/devel/index.html b/templates/devel/index.html new file mode 100644 index 0000000..2617de9 --- /dev/null +++ b/templates/devel/index.html @@ -0,0 +1,75 @@ +{% extends "base.html" %} + +{% block content %} + {% if todos %} + <div class="greybox"> + <h3 class="title">Package ToDo Lists</h3> + <table class="results" width="100%"> + <tr> + <th>Name</th> + <th>Creation Date</th> + <th>Description</th> + </tr> + {% for todo in todos %} + <tr> + <td style="white-space:nowrap"><a href="/todo/{{ todo.id }}/">{{ todo.name }}</a></td> + <td>{{ todo.date_added }}</td> + <td>{{ todo.description }}</td> + </tr> + {% endfor %} + </table> + </div> + <br /><br /> + {% endif %} + + <div class="greybox"> + <h3 class="title">Flagged Package Stats</h3> + <table class="results" width="100%"> + <tr> + <th>Maintainer</th> + <th># Package</th> + <th># Flagged</th> + </tr> + {% for maint in stats %} + <tr> + <td><a href="/packages/?maint={{ maint.0.id }}">{{ maint.0.get_full_name }}</a></td> + <td><strong>{{ maint.1 }}</strong> packages</td> + <td><strong>{{ maint.2 }}</strong> packages</td> + </tr> + {% endfor %} + </table> + </div> + <br /><br /> + + <div class="box"> + <h3 class="title">Package Maintenance</h3> + <br /> + <table width="100%"> + <tr> + <td style="vertical-align: top"> + {% if maint %} + <a href="/packages/?maint={{ maint.id }}">My Packages</a><br /> + {% endif %} + <a href="/packages/?maint=0">Orphan Packages</a><br /> + <br /> + <a href="/devel/guide/">Package Maintainer's Guide</a><br /> + </td><td style="vertical-align: top"> + {% if pkgs %} + <h4>My Flagged Packages:</h4> + <ul class="small"> + <li><form method="post" action="/devel/notify/"> + <input name="notify" type="checkbox" value="yes"{% if maint.get_profile.notify %} checked{% endif %} /> Notify me when packages are flagged + + <input type="submit" value="Update" /> + </form></li> + </ul> + <ul class="small"> + {% for pkg in pkgs %} + <li><a href="/packages/{{ pkg.id }}/">{{ pkg.repo.name }}::{{ pkg.pkgname }} {{ pkg.pkgver }}</a></li> + {% endfor %} + </ul> + {% endif %} + </td> + </tr> + </table> +{% endblock %} diff --git a/templates/devel/profile.html b/templates/devel/profile.html new file mode 100644 index 0000000..6083735 --- /dev/null +++ b/templates/devel/profile.html @@ -0,0 +1,32 @@ +{% load validation %} +{% extends "base.html" %} + +{% block content %} + <div class="greybox"> + <h2 class="title">Developer Profile</h2> + {% if errors %} + {% print_errors errors %} + {% endif %} + <form method="post" action="."> + <table> + <tr> + <td>Username:</td> + <td><strong>{{ user.username }}</strong></td> + </tr><tr> + <td>Email Address:</td> + <td><input type="text" name="email" value="{{ user.email }}" size="30"></td> + </tr><tr> + <td>New Password:</td> + <td><input type="password" name="passwd" size="30"></td> + </tr><tr> + <td>Confirm Password:</td> + <td><input type="password" name="passwd2" size="30"></td> + </tr><tr> + <td colspan="2" align="right"> + <input type="submit" value=" Save "> + </td> + </tr> + </table> + </form> + </div> +{% endblock %} diff --git a/templates/error_page.html b/templates/error_page.html new file mode 100644 index 0000000..575ac41 --- /dev/null +++ b/templates/error_page.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} + <div class="box"> + <h4>{{ errmsg }}</h4> + </div> +{% endblock %} + diff --git a/templates/errors.html b/templates/errors.html new file mode 100644 index 0000000..84f98d7 --- /dev/null +++ b/templates/errors.html @@ -0,0 +1,5 @@ +<ul class="error"> +{% for err in errors %} + <li>{{ err.0 }}: {{ err.1 }}</li> +{% endfor %} +</ul> diff --git a/templates/feeds/news_description.html b/templates/feeds/news_description.html new file mode 100644 index 0000000..26e1cbb --- /dev/null +++ b/templates/feeds/news_description.html @@ -0,0 +1 @@ +{{ obj.content }} diff --git a/templates/feeds/news_title.html b/templates/feeds/news_title.html new file mode 100644 index 0000000..d355de5 --- /dev/null +++ b/templates/feeds/news_title.html @@ -0,0 +1 @@ +{{ obj.title }} diff --git a/templates/feeds/packages_description.html b/templates/feeds/packages_description.html new file mode 100644 index 0000000..6b9c47b --- /dev/null +++ b/templates/feeds/packages_description.html @@ -0,0 +1 @@ +{{ obj.pkgdesc }} diff --git a/templates/feeds/packages_title.html b/templates/feeds/packages_title.html new file mode 100644 index 0000000..a273162 --- /dev/null +++ b/templates/feeds/packages_title.html @@ -0,0 +1 @@ +{{ obj.pkgname }} {{ obj.pkgver }}-{{ obj.pkgrel }} diff --git a/templates/news/add.html b/templates/news/add.html new file mode 100644 index 0000000..702d7ab --- /dev/null +++ b/templates/news/add.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block content %} + <div class="greybox"> + {% if news %} + <h2 class="title">Edit News</h2> + {% else %} + <h2 class="title">Add News</h2> + {% endif %} + <form method="post" action="."> + <table> + <tr> + <td>Title:</td> + <td>{{ form.title }}</td> + </tr><tr> + <td style="vertical-align:top">Content:</td> + <td>{{ form.content }}</td> + </tr><tr> + <td colspan="2" align="right"> + <input type="submit" value=" Save " /> + </td> + </tr> + </table> + </form> + </div> +{% endblock %} diff --git a/templates/news/delete.html b/templates/news/delete.html new file mode 100644 index 0000000..7ac5e25 --- /dev/null +++ b/templates/news/delete.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block content %} + <div class="greybox"> + <h2>Confirm Delete</h2> + <hr /> + <form method="post" action="."> + <table> + <tr> + <td>Are You Sure?</td> + <td> <input name="delete" type="submit" value=" Yes " /></td> + </tr> + </table> + </form> + </div> +{% endblock %} diff --git a/templates/news/list.html b/templates/news/list.html new file mode 100644 index 0000000..e188e3c --- /dev/null +++ b/templates/news/list.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} + +{% block content %} + <div class="greybox"> + {% if perms.news.add_news %} + <div style="float:right"> + <a href="/news/add/">Add News Item</a> + </div> + {% endif %} + <h2 class="title">News Updates</h2> + <table class="results" width="100%"> + {% for item in news %} + <tr> + <td>{{ item.postdate }}</td> + <td><a href="{{ item.get_absolute_url }}">{{ item.title }}</a></td> + <td> + {% comment %}{% if item.author %}{% ifequal user.username item.author.username %}{% endcomment %} + <a href="/news/edit/{{ item.id }}/">edit</a> + <a href="/news/delete/{{ item.id }}/">delete</a> + {% comment %}{% endifequal %}{% endif %}{% endcomment %} + </td> + </tr> + {% endfor %} + </table> + </div> +{% endblock %} diff --git a/templates/news/view.html b/templates/news/view.html new file mode 100644 index 0000000..5d10045 --- /dev/null +++ b/templates/news/view.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block content %} + <div class="box"> + <div style="float: right; font-size: small"> + {{ news.author.get_full_name }}<br /> + {{ news.postdate }} + </div> + <h3>{{ news.title }}</h3> + <hr /><br /> + {{ news.content|linebreaks }} + </div> +{% endblock %} diff --git a/templates/packages/details.html b/templates/packages/details.html new file mode 100644 index 0000000..65de079 --- /dev/null +++ b/templates/packages/details.html @@ -0,0 +1,77 @@ +{% load package_extras %} +{% extends "base.html" %} + +{% block content %} + <div class="box"> + <h2 class="title">{{ pkg.pkgname }} {{ pkg.pkgver }}-{{ pkg.pkgrel }}</h2> + <div style="float:right" class="listing"> + <ul class="small"> + <li><a href="http://cvs.archlinux.org/cgi-bin/viewcvs.cgi/{{ pkg.category.category }}/{{ pkg.pkgname }}/?cvsroot={{ pkg.repo.name }}&only_with_tag=CURRENT">View CVS Entries</a></li> + <li><a href="/packages/files/{{ pkg.id }}/">View File List</a></li> + <li> + {% if pkg.needupdate %} + <span style="font-size:x-small"><em>This package has been flagged out-of-date</em></span> + {% if not user.is_anonymous %}{% if pkg.maintainer %}{% ifequal user.username pkg.maintainer.username %} + <br /> <a href="/packages/unflag/{{ pkg.id }}/">Click here to unflag</a> + {% endifequal %}{% endif %}{% endif %} + {% else %} + <a href="/packages/flag/{{ pkg.id }}/" onclick="return !window.open('/packages/flag/{{ pkg.id }}/','FlagHelp','height=250,width=450,location=no,scrollbars=yes,menubars=no,toolbars=no,resizable=no');">Flag Package Out-of-Date</a> + <a href="/packages/flaghelp" onclick="return !window.open('/packages/flaghelp','FlagHelp','height=250,width=450,location=no,scrollbars=yes,menubars=no,toolbars=no,resizable=no');"><span style="font-size:x-small">(?)</span></a> + {% endif %} + </li> + {% if not user.is_anonymous %} + <li> </li> + <li> + <form name="devaction" method="post" action="/packages/update/"> + <input type="hidden" name="pkgid" value="{{ pkg.id }}" /> + <input type="submit" style="background: #e1e3e6;" name="adopt" value="Adopt Package" /> + <input type="submit" style="background: #e1e3e6;" name="disown" value="Disown Package" /> + </form> + </li> + {% endif %} + </ul> + </div> + <table class="listing"> + <tr> + <th>Repository:</th> + <td>{{ pkg.repo.name }}</td> + </tr><tr> + <th>Category:</th> + <td>{{ pkg.category.category }}</td> + </tr><tr> + <th>Description:</th> + <td>{{ pkg.pkgdesc }}</td> + </tr><tr> + <th>URL:</th> + <td><a href="{{ pkg.url }}">{{ pkg.url }}</a></td> + </tr><tr> + <th>Maintainer:</th> + <td>{% if pkg.maintainer %}{{ pkg.maintainer.get_full_name }}{% else %}None{% endif %}</td> + </tr><tr> + <th>LastUpdated:</th> + <td>{{ pkg.last_update|date:"Y-m-d" }}</td> + </tr> + </table> + <br /> + <table width="100%"> + <tr> + <td valign="top"> + <div class="listing"> + <h4>Dependencies:</h4> + <ul style="font-size:small;list-style:none"> + {{ pkg.depends_urlize }} + </ul> + </div> + </td><td colspan="2" valign="top"> + <div class="listing"> + <h4>Sources:</h4> + <ul style="font-size:small;list-style:none"> + {{ pkg.sources_urlize }} + </ul> + </div> + </td> + </tr> + </table> + </div> +{% endblock %} + diff --git a/templates/packages/files.html b/templates/packages/files.html new file mode 100644 index 0000000..24f5581 --- /dev/null +++ b/templates/packages/files.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block content %} + <div class="box"> + <h3>Viewing Files: {{ pkg.pkgname }} {{ pkg.pkgver }}-{{ pkg.pkgrel }}</h3> + {% for file in files %} + {{ file.path }}<br /> + {% endfor %} + </div> +{% endblock %} + diff --git a/templates/packages/flag.html b/templates/packages/flag.html new file mode 100644 index 0000000..215b6fa --- /dev/null +++ b/templates/packages/flag.html @@ -0,0 +1,26 @@ +{% load validation %} +<html> +<head><title>Flagging Packages</title></head> +<body> +{% if errors %} + {% print_errors errors %} +{% endif %} +<span style="font-family: verdana, arial, helvetica"> +{% if confirmemail %} + Thank you. Maintainers have been notified. +{% else %} +<form method="post" action="."> + Please confirm your flag request.<br /> + <br /> + Email Address: (required) <br /> + <input type="text" name="confirmemail" size="40" maxlength="128" /><br /> + <br /> + Message to dev: (optional)<br /> + <textarea name="usermessage" rows="3" cols="40"></textarea><br /> + <input type="submit" value=" Confirm " /> +</form> +{% endif %} +</span> +</body> +</html> + diff --git a/templates/packages/flaghelp.html b/templates/packages/flaghelp.html new file mode 100644 index 0000000..09f7530 --- /dev/null +++ b/templates/packages/flaghelp.html @@ -0,0 +1,14 @@ +<html> +<head><title>Flagging Packages</title></head> +<body> +<span style="font-family: verdana, arial, helvetica"> +If you notice that one of Arch's packages is out of date (ie, there is a newer +<b>stable</b> release available), then please notify us by using the <b>Flag</b> +button in the <i>Package Details</i> screen. This will notify the maintainer +responsible for that package so they can update it. +<br><br> +<b>Note:</b> Please do <i>not</i> use this facility if the package is broken! +Use the <a target="_blank" href='http://bugs.archlinux.org'>bugtracker</a> instead. +</span> +</body> +</html> diff --git a/templates/packages/outofdate.txt b/templates/packages/outofdate.txt new file mode 100644 index 0000000..7b86360 --- /dev/null +++ b/templates/packages/outofdate.txt @@ -0,0 +1,13 @@ + +* Note: this is an automated message + +{{ email }} wants to notify you that the following package may be out +of date: + + {{ pkgname }} ({{ weburl }}) +{% if message %} +The user provided the following additional text: + +{{ message }} +{% endif %} + diff --git a/templates/packages/search.html b/templates/packages/search.html new file mode 100644 index 0000000..030289d --- /dev/null +++ b/templates/packages/search.html @@ -0,0 +1,120 @@ +{% load validation %} +{% load package_extras %} +{% extends "base.html" %} + +{% block head %} +<script type="text/JavaScript" src="/media/calendar.js"></script> +<link href="/media/calendar.css" rel="stylesheet" type="text/css" /> +{% endblock %} + +{% block content %} + <div class="greybox"> + <h4 style="text-align: right">Search Criteria</h4> + {% if errors %} + {% print_errors errors %} + {% endif %} + <hr /> + <form method="get" action="/packages/search/"> + <table width="100%"> + <tr> + <td><span class="smalltext">Repository</span></td> + <td><span class="smalltext">Category</span></td> + <td><span class="smalltext">Keywords</span></td> + <td><span class="smalltext">Last Update</span></td> + <td><span class="smalltext">Per Page</span></td> + </tr><tr> + <td> + <select name="repo"> + <option value="all">All</option> + {% for r in repos %} + <option value="{{ r.name }}"{% ifequal repo r.name %} selected{% endifequal %}>{{ r.name|capfirst }}</option> + {% endfor %} + </select> + </td><td> + <select name="category"> + <option value="all">All</option> + {% for c in categories %} + <option value="{{ c.category }}"{% ifequal category c.category %} selected{% endifequal %}>{{ c.category|capfirst }}</option> + {% endfor %} + </select> + </td><td> + <input type="text" name="q" value="{{ query|escape }}" size="30" maxlength="200" /> + </td><td> + <input type="text" name="lastupdate" value="{{ lastupdate|escape }}" size="10" maxlength="10" id="f_lastupdate" /> <button type="reset" id="f_trigger">...</button> + <script type="text/javascript"> + Calendar.setup({ + inputField : "f_lastupdate", // id of the input field + ifFormat : "%Y-%m-%d", // format of the input field + showsTime : false, // will display a time selector + button : "f_trigger", // trigger for the calendar (button ID) + singleClick : true, // double-click mode + step : 1 // show all years in drop-down boxes (instead of every other year as default) + }); + </script> + </td><td> + <select name="limit"> + <option value="50"{% ifequal limit 50 %} selected{% endifequal %}>50</option> + <option value="100"{% ifequal limit 100 %} selected{% endifequal %}>100</option> + <option value="250"{% ifequal limit 250 %} selected{% endifequal %}>250</option> + <option value="0"{% ifequal limit 0 %} selected{% endifequal %}>All</option> + </select> + </td><td> + <input type="submit" value=" Search " /> + </td> + </tr> + </table> + </form> + </div> + <br /><br /> + + {% if results %} + <div class="greybox"> + <table class="results" width="100%"> + <tr> + {% if not user.is_anonymous %} + <form method="post" action="/packages/update/"> + <th> </th> + {% endif %} + <th><a href="{% buildsortqs "repo" %}">Repo</a></th> + <th><a href="{% buildsortqs "category" %}">Category</a></th> + <th><a href="{% buildsortqs "pkgname" %}">Name</a></th> + <th>Version</th> + <th>Description</th> + <th><a href="{% buildsortqs "last_update" %}">Last Updated</a></th> + </tr> + {% for pkg in results %} + <tr class="{% cycle pkgr1,pkgr2 %}"> + {% if not user.is_anonymous %} + <td><input type="checkbox" name="pkgid" value="{{ pkg.id }}" /></td> + {% endif %} + <td>{{ pkg.repo.name }}</td> + <td>{{ pkg.category.category }}</td> + <td><a href="{{ pkg.get_absolute_url }}">{{ pkg.pkgname }}</a></td> + {% if pkg.needupdate %} + <td><span style="color:red">{{ pkg.pkgver }}-{{ pkg.pkgrel }}</span></td> + {% else %} + <td>{{ pkg.pkgver }}-{{ pkg.pkgrel }}</td> + {% endif %} + <td>{{ pkg.pkgdesc }}</td> + <td>{{ pkg.last_update|date:"Y-m-d" }}</td> + </tr> + {% endfor %} + <tr> + <td colspan="2" style="font-size:x-small">{% if prevpage %}<br /><a href="{{ prevpage }}"><<< Prev</a>{% endif %}</td> + <td colspan="2"> </td> + <td colspan="2" style="font-size:x-small;text-align:right">{% if nextpage %}<br /><a href="{{ nextpage }}">Next >>></a>{% endif %}</td> + </tr> + {% if not user.is_anonymous %} + <tr> + <td colspan="3"> </td> + <td colspan="2" style="text-align:center"><input type="submit" name="adopt" value="Adopt Packages"></td> + <td colspan="1" style="text-align:center"><input type="submit" name="disown" value="Disown Packages"></td> + <td colspan="1"> </td> + </tr> + </form> + {% endif %} + </table> + </div> + {% endif %} +{% endblock %} + diff --git a/templates/public/about.html b/templates/public/about.html new file mode 100644 index 0000000..0712b5d --- /dev/null +++ b/templates/public/about.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">About Arch Linux</h2> + + <p> + Arch Linux is a general purpose linux distribution that can be molded to + do just about anything. It is fast, lightweight, flexible, and most of the + parts under the hood are quite simple to understand and tweak, which can + make it a good distro to "learn the ropes" on. We do not provide any + configuration helper utilities (ie, you won't find <i>linuxconf</i> in + here) so you will quickly become very proficient at configuring your system + from the shell commandline. + </p> + + <p> + Arch Linux uses i686-optimized packages which gives us improved + performance over some of our i386-optimized cousins. This means that Arch + Linux will only run on a Pentium II processor or higher. We try to stay + fairly bleeding edge, and typically have the latest stable versions of + software. + </p> + + <p> + Arch Linux uses the <a href='http://www.archlinux.org/pacman'>Pacman</a> + package manager, which couples a simple binary package format with an + easy-to-use build system, allowing the users to easily manage and customize + their packages, whether they be official Arch packages or the user's own + homegrown ones. The repository system allows users to build and maintain + their own custom package repositories, which encourages community growth and + contribution. + </p> + + <p> + Pacman can keep a system up to date by synchronizing package lists with + the master server, making it a breeze for the security-conscious system + administrator to maintain. This server/client model also allows you to + download/install packages with a simple command, complete with all required + dependencies (similar to Debian's apt-get). + </p> + + <p> + Arch's official package set is fairly streamlined, but we supplement this + with a larger, more complete "extra" repository that contains a lot of the + stuff that never made it into our core package set. This repository is + constantly growing with the help of packages submitted from our strong + community. + </p> + + <p> + Arch Linux does not provide any official support, but you will find a lot + of helpful people on our IRC channel and on our <a + href='http://bbs.archlinux.org'>user forums</a>. Chances are that some other + Archer has had the same problem/question as you and it's already been + answered. Ask around! + </p> + + <p> + Arch Linux uses a "rolling release" system which works like this: We have + two versions of our core package set at any given time, <b>Current</b> and + <b>Release</b>. The Current repository always contains the latest and + greatest versions of packages. As soon as a package is updated it is part of + the Current repository, so this is the one to follow if you want to stay very + up to date. The Release repository follows the semi-regular snapshot + releases and does not update until the next snapshot/iso has been released. + For example, the Release repository will point to all packages on the 0.5 ISO + until we release 0.6; then it will point to 0.6 packages until 0.7 is + released. This is useful if you only want to update your system when a new + release is available. + </p> + + <p> + So, to sum up: Arch Linux is a workhorse distribution designed to fit the + needs of the competent linux user. We strive to make it both powerful and + easy to manage, making it an ideal distro for servers and workstations. Take + it in any direction you like. + </p> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/art.html b/templates/public/art.html new file mode 100644 index 0000000..0fab30a --- /dev/null +++ b/templates/public/art.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} + +{% block content %} +<br /><br /> +<div class="box"> + <h2 class="title">Arch Linux Logos</h2> + <br /><br /> + <table id="art" width="90%"> + <tr> + <td><img src="/logos/archblue2.png"><br><a href="/logos/archlinux_logo_1.svg">SVG</a></td> + <td><a href="/logos/archlinux_logo_aqua.png"><img src="/logos/sml-archlinux_logo_aqua.png" border=0></a><br><a href="/logos/archlinux_logo_aqua.svg">SVG</a></td> + </tr><tr> + <td><img src="/logos/128x128/arch_linux_blue.png"><br><a href="/logos/scalable/arch_linux_blue.svg">SVG</a></td> + <td><img src="/logos/128x128/arch_linux_white.png"><br><a href="/logos/scalable/arch_linux_white.svg">SVG</a></td> + </tr><tr> + <td><img src="/logos/archblue.png"><br><a href="/logos/archlinux_logo_2.svg">SVG</a></td> + <td><a href="/logos/archstar.jpg"><img src="/logos/sml-archstar.jpg" border="0"></a></td> + </tr><tr> + <td><a href="/logos/cdlabel-0.8-2.svg"><img src="/logos/sml-cdlabel-0.8-2.png" border="0"></a></td> + <td><a href="/logos/cdlabel-0.8.svg"><img src="/logos/sml-cdlabel-0.8.png" border="0"></a></td> + </tr><tr> + <td colspan="2"><img src="/logos/archbannerglass3.jpg"></td> + </tr> + </table> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/blank.html b/templates/public/blank.html new file mode 100644 index 0000000..d1e7b71 --- /dev/null +++ b/templates/public/blank.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">Download Arch Linux</h2> + <br /><br /> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/cvs.html b/templates/public/cvs.html new file mode 100644 index 0000000..715d00d --- /dev/null +++ b/templates/public/cvs.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} + +{% block content %} +<div class="greybox" style="text-align:center"> + You can access all of our PKGBUILD files from the + <a href="http://cvs.archlinux.org">cvsweb</a> interface. +</div> +<br /><br /> +<div class="box"> + <h2 class="title">CVS Repositories</h2> + <br /><br /> + Anonymous CVS access is also available. Use <i><u>anonymous</u></i> as the + username and password.<br /><br /> + <ol class="instructions"> + <li> + Set CVSROOT to the repository you wish to access:<br /><br /> + <blockquote class="code"> +# export CVSROOT=:pserver:anonymous@cvs.archlinux.org:/home/cvs-core<br /><br /> +OR<br /><br /> +# export CVSROOT=:pserver:anonymous@cvs.archlinux.org:/home/cvs-extra<br /><br /> +OR<br /><br /> +# export CVSROOT=:pserver:anonymous@cvs.archlinux.org:/home/cvs-unstable<br /><br /> + </blockquote> + <br /><br /> + </li> + <li> + Login:<br /><br /> + <blockquote class="code"> +# touch ~/.cvspass<br /> +# cvs login<br /> +Logging in to :pserver:anonymous@cvs.archlinux.org:2401/home/cvs-core<br /> +CVS password: anonymous<br /> + </blockquote> + <br /><br /> + </li> + <li> + Check out the repository:<br /><br /> + <blockquote class="code"> +# cvs -z3 co core<br /><br /> +OR<br /><br /> +# cvs -z3 co extra<br /> + </blockquote> + </li> + </ol> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/denied.html b/templates/public/denied.html new file mode 100644 index 0000000..c59c0f7 --- /dev/null +++ b/templates/public/denied.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <br /><br /> + Sorry, you don't have sufficient privileges to perform this function. + <br /><br /> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/developers.html b/templates/public/developers.html new file mode 100644 index 0000000..6b1fe8b --- /dev/null +++ b/templates/public/developers.html @@ -0,0 +1,67 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">Arch Linux Core Developers</h2> + <br /><br /> + <div id="devlist"> + {% for dev in devs %} + <a href="#{{ dev.first_name}}">{{ dev.first_name }}</a> + {% endfor %} + </div><br /><br /> + + <table class="center" cellpadding="20"> + {% for dev in devs %} + <tr> + <td class="devpic"> + <img src="{{ dev.get_profile.get_picture_url }}" height="175" width="175" style="border:1px solid black"> + </td><td> + <a name="{{ dev.first_name }}" /> + <table class="deventry" cellspacing="5"> + <tr> + <th>Name:</th> + <td>{{ dev.get_full_name }}</td> + </tr><tr> + <th>Alias:</th> + <td>{{ dev.get_profile.alias }}</td> + </tr><tr> + <th>Email:</th> + <td>{{ dev.get_profile.public_email }}</td> + </tr><tr> + <th>Other Contact:</th> + <td>{{ dev.get_profile.other_contact }}</td> + </tr><tr> + <th>Roles:</th> + <td>{{ dev.get_profile.roles }}<br /> + </td> + </tr><tr> + <th>Website:</th> + <td>{{ dev.get_profile.website }}</td> + </tr><tr> + <th>Occupation:</th> + <td>{{ dev.get_profile.occupation }}</td> + </tr><tr> + <th>YOB:</th> + <td>{% if dev.get_profile.yob %}{{ dev.get_profile.yob }}{% else %} {% endif %}</td> + </tr><tr> + <th>Location:</th> + <td>{{ dev.get_profile.location }}</td> + </tr><tr> + <th>Languages:</th> + <td>{{ dev.get_profile.languages }}</td> + </tr><tr> + <th>Interests:</th> + <td>{{ dev.get_profile.interests }}</td> + </tr><tr> + <th>Favorite Distros:</th> + <td>{{ dev.get_profile.favorite_distros }}</td> + </tr> + </table> + </td> + </tr> + {% endfor %} + </table> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/donate.html b/templates/public/donate.html new file mode 100644 index 0000000..bdb9ade --- /dev/null +++ b/templates/public/donate.html @@ -0,0 +1,59 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">Donate to Arch Linux</h2> + <p> + Arch Linux survives because of the tireless efforts of many people in + the community and the core development circle. None of us are paid for our + work, and we don't have the personal funds to sustain server costs ourselves. + </p><p> + There are many ways to help Arch Linux. If technical development, + documentation, or support aren't your strong points, you could certainly + help us by dropping a few bucks our way. + </p><p> + Many thanks! + </p> + <div style="text-align:center"> + <!-- paypal code --> + <form action="https://www.paypal.com/cgi-bin/webscr" method="post"> + <input type="hidden" name="cmd" value="_xclick"> + <input type="hidden" name="business" value="jvinet@zeroflux.org"> + <input type="hidden" name="item_name" value="Arch Linux"> + <input type="hidden" name="image_url" value="/logos/arch-paypal.jpg"> + <input type="hidden" name="no_shipping" value="1"> + <input type="hidden" name="cn" value="Suggestions/Comments"> + <input type="hidden" name="no_note" value="1"> + <input type="image" src="https://www.paypal.com/images/x-click-butcc-donate.gif" border="0" name="submit" alt="Make payments with PayPal - it's fast, free and secure!" style="background: transparent; border: none"> + </form> + </div> + <br /><br /> + <h2 class="title">Past Donors</h2> + <table width="100%"> + <tr> + <td style="font-size:x-small;vertical-align:top"> + {% for donor in slice1 %} + {{ donor.name }}<br /> + {% endfor %} + </td> + <td style="font-size:x-small;vertical-align:top"> + {% for donor in slice2 %} + {{ donor.name }}<br /> + {% endfor %} + </td> + <td style="font-size:x-small;vertical-align:top"> + {% for donor in slice3 %} + {{ donor.name }}<br /> + {% endfor %} + </td> + <td style="font-size:x-small;vertical-align:top"> + {% for donor in slice4 %} + {{ donor.name }}<br /> + {% endfor %} + </td> + </tr> + </table> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/download.html b/templates/public/download.html new file mode 100644 index 0000000..be840d3 --- /dev/null +++ b/templates/public/download.html @@ -0,0 +1,74 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">Buy A CD</h2> + CDs are available for purchase from OSDisc.com. For each CD purchased, a + portion of the money goes to the Arch Linux Project.<br /><br /> + <div style="text-align:center"> + <a href="http://www.osdisc.com/cgi-bin/distro/index.cgi?distro=archlinux">Click here to purchase a CD</a> + </div> + <br /><br /> + + <h2 class="title">BitTorrent Download</h2> + <br /><br /> + <div style="text-align:center"> + <h3>Download with BitTorrent</h3> + If you can spare the bytes, please leave the BT client + open after your<br /> download is finished, so you can seed it back to others.<br /> + <br /> + <table class="center" cellspacing="10"> + <tr> + <th> </th> + <th>CORE</th> + <th>FTP Install</th> + </tr> + <tr> + <td>i686:</td> + <td> + <a href="ftp://ftp.archlinux.org/iso/2007.08/i686/Archlinux-i686-2007.08-2.core.iso.torrent"> + 2007.08-2 + </a> + </td> + <td> + <a href="ftp://ftp.archlinux.org/iso/2007.08/i686/Archlinux-i686-2007.08-2.ftp.iso.torrent"> + 2007.08-2 + </a> + </td> + </tr><tr> + <td>x86_64:</td> + <td> + <a href="ftp://ftp.archlinux.org/iso/2007.08/x86_64/Archlinux-x86_64-2007.08-2.core.iso.torrent"> + 2007.08-2 + </a> + </td> + <td> + <a href="ftp://ftp.archlinux.org/iso/2007.08/x86_64/Archlinux-x86_64-2007.08-2.ftp.iso.torrent"> + 2007.08-2 + </a> + </td> + </tr> + </table> + </div> + <br /><br /> + + <h2 class="title">HTTP/FTP Download</h2> + <br /><br /> + <div style="text-align:center"> + <h3>Download with HTTP/FTP</h3> + In addition to the BitTorrent links above, ISO images can also be downloaded <br /> via HTTP/FTP from the /iso/ sub-directory of mirror sites listed below.<br /> + <br /><br /> + <h3>Mirror Sites</h3> + <table class="center" cellspacing="10"> + {% for mirror in mirrors %} + <tr> + <td style="text-align:left"><a href="{{ mirror.url }}">{{ mirror.domain }}</a></td> + <td style="text-align:right">{{ mirror.country }}</td> + </tr> + {% endfor %} + </table> + </div> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/index.html b/templates/public/index.html new file mode 100644 index 0000000..2e0d005 --- /dev/null +++ b/templates/public/index.html @@ -0,0 +1,174 @@ +{% extends "base.html" %} + +{% block head %} +<link rel="alternate" type="application/rss+xml" title="Arch Linux News Updates" href="/feeds/news/" /> +<link rel="alternate" type="application/rss+xml" title="Arch Linux Package Updates" href="/feeds/packages/" /> +{% endblock %} + +{% block content_left %} + <div id="about" class="box"> + <h2>Welcome to Arch!</h2> + <p> + You've reached the website for <strong>Arch Linux</strong>, a lightweight + and flexible linux distribution that tries to Keep It Simple. + </p><p> + Currently we have official packages optimized for the i686 and x86-64 + architectures. We complement our official package sets with a + <a href="http://aur.archlinux.org">community-operated package repository</a> + that grows in size and quality each and every day. + </p><p> + Our strong community is diverse and helpful, and we pride ourselves on + the range of skillsets and uses for Arch that stem from it. Please + check out our <a href="http://bbs.archlinux.org">forums</a> and + <a href="http://www.archlinux.org/mailman/listinfo/">mailing lists</a> + to get your feet wet. Also glance through our <a href="http://wiki.archlinux.org">wiki</a> + if you want to learn more about Arch. + </p><p style="text-align: right"> + <a href="/about/"><span style="font-size:x-small">Learn more...</span></a> + </p> + </div> + <br /><br /> + <div style="float:right;position:relative;bottom:-25px"> + <a href="/feeds/news/"><img src="/media/rss.png" alt="RSS Feed" /></a> + </div> + <h2 class="title">Latest News</h2> + <div> + {% for news in news_updates %} + <br /> + <span style="float:right; font-size:x-small">{{ news.postdate }}</span> + <h4 class="news"><a href="{{ news.get_absolute_url }}">{{ news.title }}</a></h4> + <p class="news">{{ news.content|striptags|truncatewords:60 }}</p> + <br /> + {% endfor %} + <span style="float:right;font-size:x-small"><a href="/news/">More News...</a></span> + <br /><br /> + </div> +{% endblock %} + +{% block content_right %} + <div id="search"> + <form method="get" action="/packages/search/"> + <p>Package Search: <input type="text" name="q" size="20" maxlength="200" /></p> + </form> + </div> + <div id="updates"> + <table width="100%"> + <tr> + <td><h3>Recent Updates</h3></td> + <td style="vertical-align:top;text-align:right"><a href="/feeds/packages/"><img src="/media/rss.png" alt="RSS Feed" /></a></td> + </tr> + {% for pkg in pkg_updates %} + <tr> + <td><a href="{{ pkg.get_absolute_url }}">{{ pkg.pkgname }} {{ pkg.pkgver }}-{{ pkg.pkgrel }}</a></td> + <td style="text-align:right">{{ pkg.category.category }}</td> + </tr> + {% endfor %} + <tr> + <td colspan="2" style="text-align:right;font-size:x-small"><br /><a href="/packages/search?sort=-last_update">More...</a></td> + </tr> + </table> + </div> + <br /> + <div class="greybox"> + <h3>Package Repositories</h3> + <table id="repolinks"> + {% for repo in repos %} + <tr> + <th><a href="/packages/?repo={{ repo.name }}">{{ repo.name }}</a></th> + <td>{{ repo.last_update }}</td> + </tr> + {% endfor %} + </table> + </div> + <br /> + <div class="greybox"> + <h3>Releases</h3> + <table id="releases"> + <tr> + <td><a href="/packages/?repo=Core">2007.08-2</a></td> + <td><a href="/packages/?repo=Core">Don't Panic</a></td> + <td style="text-align:right">2007-10-07</td> + </tr><tr> + <td><a href="/packages/?repo=Current">2007.08.1</a></td> + <td><a href="/packages/?repo=Current">Don't Panic</a></td> + <td style="text-align:right">2007-09-10</td> + </tr><tr> + <td><a href="/packages/?repo=Current">2007.08</a></td> + <td><a href="/packages/?repo=Current">Don't Panic</a></td> + <td style="text-align:right">2007-08-05</td> + </tr><tr> + <td><a href="/packages/?repo=Current">2007.05</a></td> + <td><a href="/packages/?repo=Current">Duke</a></td> + <td style="text-align:right">2007-05-17</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.8.txt">0.8</a></td> + <td><a href="/static/pkglists/list-0.8.txt">Voodoo</a></td> + <td style="text-align:right">2007-03-31</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.7.2.txt">0.7.2</a></td> + <td><a href="/static/pkglists/list-0.7.2.txt">Gimmick</a></td> + <td style="text-align:right">2006-05-23</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.7.1.txt">0.7.1</a></td> + <td><a href="/static/pkglists/list-0.7.1.txt">Noodle</a></td> + <td style="text-align:right">2006-01-05</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.7.txt">0.7</a></td> + <td><a href="/static/pkglists/list-0.7.txt">Wombat</a></td> + <td style="text-align:right">2005-01-24</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.6.txt">0.6</a></td> + <td><a href="/static/pkglists/list-0.6.txt">Widget</a></td> + <td style="text-align:right">2004-03-01</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.5.txt">0.5</a></td> + <td><a href="/static/pkglists/list-0.5.txt">Nova</a></td> + <td style="text-align:right">2003-07-21</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.4.txt">0.4</a></td> + <td><a href="/static/pkglists/list-0.4.txt">Dragon</a></td> + <td style="text-align:right">2002-12-18</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.3.txt">0.3</a></td> + <td><a href="/static/pkglists/list-0.3.txt">Firefly</a></td> + <td style="text-align:right">2002-08-07</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.2.txt">0.2</a></td> + <td><a href="/static/pkglists/list-0.2.txt">Vega</a></td> + <td style="text-align:right">2002-04-17</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.1.txt">0.1</a></td> + <td><a href="/static/pkglists/list-0.1.txt">Homer</a></td> + <td style="text-align:right">2002-03-11</td> + </tr> + </table> + </div> + <br /> + <h3>Documentation:</h3> + <ul class="links"> + <li><a href="/static/docs/arch-install-guide.txt">Installation Guide</a></li> + <li><a href="http://wiki.archlinux.org">Wiki</a></li> + </ul> + <h3>Support Arch:</h3> + <ul class="links"> + <li><a href="/donate/">Donate</a></li> + <li><a href="http://www.cafeshops.com/archlinux/">Arch Schwag</a></li> + <li><a href="/art/">Logos & Artwork</a></li> + </ul> + <h3>Community Links:</h3> + <ul class="links"> + <li><a href="http://www.archlinux.org/mailman/listinfo/">Mailing Lists</a></li> + <li><a href="/irc/">IRC Channels</a></li> + <li><a href="http://planet.archlinux.org">Planet Arch</a></li> + <li><a href="/static/newsletters/">Newsletters</a></li> + <li><a href="/projects/">Arch-Based Projects</a></li> + <li><a href="/moreforums/">Non-English Forums</a></li> + <li><a href="/press/">Press</a></li> + </ul> + <h3>Development:</h3> + <ul class="links"> + <li><a href="/developers/">Developers</a></li> + <li><a href="http://bugs.archlinux.org">Bug Tracker</a></li> + <li><a href="/cvs/">CVS</a></li> + </ul> +{% endblock %} diff --git a/templates/public/index.html.bak b/templates/public/index.html.bak new file mode 100644 index 0000000..a41196d --- /dev/null +++ b/templates/public/index.html.bak @@ -0,0 +1,151 @@ +{% extends "base.html" %} + +{% block head %} +<link rel="alternate" type="application/rss+xml" title="Arch Linux News Updates" href="/feeds/news/" /> +<link rel="alternate" type="application/rss+xml" title="Arch Linux Package Updates" href="/feeds/packages/" /> +{% endblock %} + +{% block content_left %} + <div id="about" class="box"> + <h2>Welcome to Arch!</h2> + <p> + You've reached the website for <strong>Arch Linux</strong>, a lightweight + and flexible linux distribution that tries to Keep It Simple. + </p><p> + Currently we have official packages optimized for the i686 and x86-64 + architectures. We complement our official package sets with a + <a href="http://aur.archlinux.org">community-operated package repository</a> + that grows in size and quality each and every day. + </p><p> + Our strong community is diverse and helpful, and we pride ourselves on + the range of skillsets and uses for Arch that stem from it. Please + check out our <a href="http://bbs.archlinux.org">forums</a> and + <a href="http://www.archlinux.org/mailman/listinfo/">mailing lists</a> + to get your feet wet. Also glance through our <a href="http://wiki.archlinux.org">wiki</a> + if you want to learn more about Arch. + </p><p style="text-align: right"> + <a href="/about/"><span style="font-size:x-small">Learn more...</span></a> + </p> + </div> + <br /><br /> + <div style="float:right;position:relative;bottom:-25px"> + <a href="/feeds/news/"><img src="/media/rss.png" alt="RSS Feed" /></a> + </div> + <h2 class="title">Latest News</h2> + <div> + {% for news in news_updates %} + <span style="float:right; font-size:x-small">{{ news.postdate }}</span> + <h4 class="news"><a href="{{ news.get_absolute_url }}">{{ news.title }}</a></h4> + <p class="news">{{ news.content|striptags|truncatewords:60 }}</p><br /><br /> + {% endfor %} + </div> +{% endblock %} + +{% block content_right %} + <div id="search"> + <form method="get" action="/packages/search/"> + <p>Package Search: <input type="text" name="q" size="20" maxlength="200" /></p> + </form> + </div> + <br /><br /> + <div id="updates"> + <div style="float:right"> + <a href="/feeds/packages/"><img src="/media/rss.png" alt="RSS Feed" /></a> + </div> + <h3>Recent Updates</h3> + <table width="100%"> + {% for pkg in pkg_updates %} + <tr> + <td><a href="{{ pkg.get_absolute_url }}">{{ pkg.pkgname }} {{ pkg.pkgver }}-{{ pkg.pkgrel }}</a></td> + <td style="text-align:right">{{ pkg.category.category }}</td> + </tr> + {% endfor %} + </table> + </div> + <div class="greybox"> + <h3>Package Repositories</h3> + <table id="repolinks"> + {% for repo in repos %} + <tr> + <th><a href="/packages/?repo={{ repo.name }}">{{ repo.name }}</a></th> + <td>{{ repo.last_update }}</td> + </tr> + {% endfor %} + </table> + </div> + <br /> + <div class="greybox"> + <h3>Releases</h3> + <table id="releases"> + <tr> + <td><a href="/packages/?repo=Current">0.8</a></td> + <td><a href="/packages/?repo=Current">_________</a></td> + <td style="text-align:right"><em>pending</em></td> + </tr><tr> + <td><a href="/static/pkglists/list-0.7.2.txt">0.7.2</a></td> + <td><a href="/static/pkglists/list-0.7.2.txt">Gimmick</a></td> + <td style="text-align:right">2006-05-23</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.7.1.txt">0.7.1</a></td> + <td><a href="/static/pkglists/list-0.7.1.txt">Noodle</a></td> + <td style="text-align:right">2006-01-05</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.7.txt">0.7</a></td> + <td><a href="/static/pkglists/list-0.7.txt">Wombat</a></td> + <td style="text-align:right">2005-01-24</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.6.txt">0.6</a></td> + <td><a href="/static/pkglists/list-0.6.txt">Widget</a></td> + <td style="text-align:right">2004-03-01</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.5.txt">0.5</a></td> + <td><a href="/static/pkglists/list-0.5.txt">Nova</a></td> + <td style="text-align:right">2003-07-21</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.4.txt">0.4</a></td> + <td><a href="/static/pkglists/list-0.4.txt">Dragon</a></td> + <td style="text-align:right">2002-12-18</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.3.txt">0.3</a></td> + <td><a href="/static/pkglists/list-0.3.txt">Firefly</a></td> + <td style="text-align:right">2002-08-07</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.2.txt">0.2</a></td> + <td><a href="/static/pkglists/list-0.2.txt">Vega</a></td> + <td style="text-align:right">2002-04-17</td> + </tr><tr> + <td><a href="/static/pkglists/list-0.1.txt">0.1</a></td> + <td><a href="/static/pkglists/list-0.1.txt">Homer</a></td> + <td style="text-align:right">2002-03-11</td> + </tr> + </table> + </div> + <br /> + <h3>Documentation:</h3> + <ul class="links"> + <li><a href="/static/docs/arch-install-guide.html">Installation Guide</a></li> + </ul> + <h3>Support Arch:</h3> + <ul class="links"> + <li><a href="/donate/">Donate</a></li> + <li><a href="http://www.cafeshops.com/archlinux/">Arch Schwag</a></li> + <li><a href="/art/">Logos & Artwork</a></li> + </ul> + <h3>Community Links:</h3> + <ul class="links"> + <li><a href="http://www.archlinux.org/mailman/listinfo/">Mailing Lists</a></li> + <li><a href="/irc/">IRC Channels</a></li> + <li><a href="http://planet.archlinux.org">Planet Arch</a></li> + <li><a href="http://blog.archlinux.org">Development Blog</a></li> + <li><a href="/static/newsletters/">Newsletters</a></li> + <li><a href="/projects/">Arch-Based Projects</a></li> + <li><a href="/moreforums/">Non-English Forums</a></li> + <li><a href="/press/">Press</a></li> + </ul> + <h3>Development:</h3> + <ul class="links"> + <li><a href="/developers/">Developers</a></li> + <li><a href="http://bugs.archlinux.org">Bug Tracker</a></li> + <li><a href="/cvs/">CVS</a></li> + </ul> +{% endblock %} diff --git a/templates/public/irc.html b/templates/public/irc.html new file mode 100644 index 0000000..cbd00da --- /dev/null +++ b/templates/public/irc.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">IRC Channels</h2> + <br /><br /> + <p>You can find Arch-related discussion on the following IRC channels. + All channels are on <strong>irc.freenode.net</strong></p> + <table cellspacing="20"> + <tr> + <td><strong>#archlinux</strong></td> + <td>The main discussion channel, mostly in English</td> + </tr><tr> + <td><strong>#archlinux-bugs<strong></td> + <td>Bug-centric discussion</td> + </tr><tr> + <td><strong>#archlinuxfr</strong></td> + <td>Discussion (French)</td> + </tr><tr> + <td><strong>#archlinux.de</strong></td> + <td>Discussion (German)</td> + </tr><tr> + <td><strong>#archlinux.se</strong></td> + <td>Discussion (Swedish)</td> + </tr><tr> + <td><strong>#archlinux.dk</strong></td> + <td>Discussion (Danish)</td> + </tr><tr> + <td><strong>#archlinux-es</strong></td> + <td>Discussion (Spanish)</td> + </tr><tr> + <td><strong>#archlinux.br</strong></td> + <td>Discussion (Brazilian Community)</td> + </tr> + </table> +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/moreforums.html b/templates/public/moreforums.html new file mode 100644 index 0000000..f8a1362 --- /dev/null +++ b/templates/public/moreforums.html @@ -0,0 +1,78 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">More Arch Forums</h2> + <br /><br /> + <p> + Our main forum is located at <a href="http://bbs.archlinux.org">bbs.archlinux.org</a>. + However, there are other locale-specific forums available. + </p> + <table cellspacing="20"> + <tr> + <td width="200">Brazilian</td> + <td><a href="http://forum.archlinux-br.org/">http://forum.archlinux-br.org</a></td> + </tr> + <tr> + <td>Czech</td> + <td><a href="http://forum.archlinux.cz/">http://forum.archlinux.cz/</a></td> + </tr> + <tr> + <td>Danish</td> + <td><a href="http://forum.archlinux.dk">http://forum.archlinux.dk/</a></td> + </tr> + <tr> + <td>Dutch</td> + <td><a href="http://arch-forum.nl/">http://arch-forum.nl/</a></td> + </tr> + <tr> + <td>French</td> + <td><a href="http://forums.archlinuxfr.org/">http://forums.archlinuxfr.org</a></td> + </tr> + <tr> + <td>French</td> + <td><a href="http://forums.archlinux.fr/">http://forums.archlinux.fr</a></td> + </tr> + <tr> + <td>German</td> + <td><a href="http://forum.archlinux.de/">http://forum.archlinux.de</a></td> + </tr> + <tr> + <td>Hungarian</td> + <td><a href="http://archlinux.hu/forum/">http://archlinux.hu/forum/</a></td> + </tr> + <tr> + <td>Italian</td> + <td><a href="http://www.archlinux.it/forum/">http://www.archlinux.it/forum/</a></td> + </tr> + <tr> + <td>Polish</td> + <td><a href="http://forum.arch-linux.pl">http://forum.arch-linux.pl</a></td> + </tr> + <tr> + <td>Russian</td> + <td><a href="http://archlinux.org.ru/forum/">http://archlinux.org.ru/forum/</a></td> + </tr> + <tr> + <td>Spanish</td> + <td><a href="http://www.archlinux.com.ar/foros/">http://www.archlinux.com.ar/foros/</a></td> + </tr> + <tr> + <td>Sweden</td> + <td><a href="http://forum.archlinux.se/">http://forum.archlinux.se/</a> + </tr> + <tr> + <td>Turkish</td> + <td><a href="http://forum.linux-sevenler.org/index.php/board,65.0.html">http://forum.linux-sevenler.org/index.php/board,65.0.html</a></td> + </tr> + <tr> + <td>Ukrainian</td> + <td><a href="http://archlinux.org.ua/">http://archlinux.org.ua/</a></td> + </tr> + </table> + <br /> + If you have a forum you would like linked, please open a <a href="http://bugs.archlinux.org/">Bug Ticket</a> with the category "web site", and a relevant description. +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/press.html b/templates/public/press.html new file mode 100644 index 0000000..b652c8d --- /dev/null +++ b/templates/public/press.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">Who's Talking About Arch?</h2> + <br /><br /> + Lots of people. + <ul> + <li><a href="http://www.openaddict.com/page.php?28">Open Addict</a></li> + <li><a href="http://cutecomputer.wordpress.com/2006/11/12/review-arch-linux-072/">Myself and my Computer</a></li> + <li><a href="http://www.linuxtechdaily.com/2006/11/review-arch64-archlinux-for-64bit-processors/">Linux Tech Daily</a></li> + <li><a href="http://www.osnews.com/story.php?news_id=15075">OSNews</a></li> + <li><a href="http://www.linux-magazine.com/issue/64/Arch_Linux_Review.pdf">Linux Magazine (pdf)</a></li> + <li><a href="http://michael-and-mary.net/intro/?q=node/260">Michael & Mary</a></li> + <li><a href="http://osnews.com/story.php?news_id=10142">OSNews</a></li> + <li><a href="http://osnews.com/story.php?news_id=10047">OSNews</a></li> + <li><a href="http://os.newsforge.com/os/05/02/14/1722211.shtml?tid=2">NewsForge</a></li> + <li><a href="http://www.linuxtimes.net/modules.php?name=News&file=article&sid=774">LinuxTimes</a></li> + <li><a href="http://osnews.com/story.php?news_id=9540">OSNews</a></li> + <li><a href="http://discuss.extremetech.com/n/mb/display.asp?webtag=extremetech&msg=53648.1">Robert Burns</a></li> + <li><a href="http://www.linuxlookup.com/modules.php?op=modload&name=Reviews&file=index&req=showcontent&id=58">LinuxLookup</a></li> + <li><a href="http://www.osnews.com/story.php?news_id=5971">OSNews</a></li> + <li><a href="http://www.osnews.com/story.php?news_id=4827">OSNews</a></li> + <li><a href="http://lwn.net/Articles/40952/">LWN</a></li> + <li><a href="http://www.distrowatch.com/dwres.php?resource=interview-arch">DistroWatch</a></li> + <li><a href="http://home.nyc.rr.com/computertaijutsu/arch.html">Scott Robbins</a></li> + </ul> + <br /> + If you have some press you would like linked, please open a <a href="http://bugs.archlinux.org/">Bug Ticket</a> with the category "web site", and a relevant description. +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/public/projects.html b/templates/public/projects.html new file mode 100644 index 0000000..c669d04 --- /dev/null +++ b/templates/public/projects.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block content %} +<div class="box"> + <h2 class="title">Arch Related Projects</h2> + <br /><br /> + <p>There are a few Arch-based projects or communities that have sprung up + over the years. Here's a list of the ones we know about.</p> + <table cellspacing="20"> + <tr> + <td><a href="http://user-contributions.org/home/index.php">user-contributions.org</a></td> + <td>A website belongs to members wanting to give a little something to the free software community</td> + </tr><tr> + <td><a href="http://www.archlinux.org/~simo/archstats/">ArchStats</a></td> + <td>An opt-in system that tracks which packages each user has installed, hardware specs, etc</td> + </tr><tr> + <td><a href="http://archie.dotsrc.org/">Archie Live CD</a></td> + <td>A live CD (and live CD build scripts) based on Arch</td> + </tr> + <tr> + <td><a href="http://www.archlinuxppc.org/">ArchPPC</a></td> + <td>Arch packages optimized for PPC</td> + </tr><tr> + <td><a href="http://user-contributions.org/projects/hwd/hwd.html">Hardware Detection</a></td> + <td>Hardware detection scripts for Arch <span style="font-size:x-small">(deprecated in favor of udev's auto-detection)</span></td> + </tr><tr> + <td><a href="http://arch-egis.berlios.de/">AEGIS</a></td> + <td>Arch Environmental/Geographical Information Systems (AEGIS) Project</td> + </tr> + </table> + <br /> + If you have an Arch related project you would like linked, please open a <a href="http://bugs.archlinux.org/">Bug Ticket</a> with the category "web site", and a relevant description. +</div> +<br /><br /> +{% endblock %} + diff --git a/templates/registration/login.html b/templates/registration/login.html new file mode 100644 index 0000000..b3b264c --- /dev/null +++ b/templates/registration/login.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block content %} + +<div class="greybox"> +<h2 class="title">Developer Login</h2> +{% if form.has_errors %} +<p class="error">Your username and password didn't match. Please try again.</p> +{% endif %} +<br /><br /> + +<form method="post" action="."> +<input type="hidden" name="next" value="{% if next %}{{ next }}{% else %}/{% endif %}" /> +<table> + <tr><td><label for="id_username">Username:</label></td><td>{{ form.username }}</td></tr> + <tr><td><label for="id_password">Password:</label></td><td>{{ form.password }}</td></tr> + <tr><td colspan="2" align="right"><input type="submit" value="Login" /></td></tr> +</table> +</form> +</div> + +{% endblock %} + diff --git a/templates/registration/logout.html b/templates/registration/logout.html new file mode 100644 index 0000000..2b82986 --- /dev/null +++ b/templates/registration/logout.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} + +<div class="greybox"> +You've been logged out. +</div> + +{% endblock %} + diff --git a/templates/status_page.html b/templates/status_page.html new file mode 100644 index 0000000..558c654 --- /dev/null +++ b/templates/status_page.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block content %} + <div class="box"> + <h4>{{ message }}</h4> + </div> +{% endblock %} + diff --git a/templates/todolists/add.html b/templates/todolists/add.html new file mode 100644 index 0000000..a53637c --- /dev/null +++ b/templates/todolists/add.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block content %} + <div class="greybox"> + <h2 class="title">Add ToDo List</h2> + <form method="post" action="."> + <table> + <tr> + <td>Name:</td> + <td><input type="text" name="name" size="30" value="" /></td> + </tr><tr> + <td style="vertical-align:top">Description:</td> + <td><textarea name="description" cols="60" rows="4"></textarea></td> + </tr><tr> + <td style="vertical-align:top">List of Package Names:<br /><span style="font-size:x-small">(one per line)</span></td> + <td><textarea name="packages" cols="60" rows="20"></textarea></td> + </tr><tr> + <td colspan="2" style="text-align:right"> + <input type="submit" value=" Create List " /> + </td> + </tr> + </table> + </form> + </div> +{% endblock %} diff --git a/templates/todolists/list.html b/templates/todolists/list.html new file mode 100644 index 0000000..0e659f9 --- /dev/null +++ b/templates/todolists/list.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block content %} + <div class="greybox"> + {% if perms.todolists.add_todolist %} + <div style="float:right"> + <a href="/todo/add/">Add Todo List</a> + </div> + {% endif %} + <h2 class="title">Package ToDo lists</h2> + <table class="results" width="100%"> + <tr> + <th>Name</th> + <th>Creation Date</th> + <th>Creator</th> + <th>Description</th> + <th>Status</th> + </tr> + {% for list in lists %} + <tr> + <td style="white-space:nowrap"><a href="/todo/{{ list.id }}/">{{ list.name }}</a></td> + <td>{{ list.date_added }}</td> + <td>{{ list.creator.get_full_name }}</td> + <td>{{ list.description }}</td> + <td>{% if list.complete %}<span style="color:blue">Complete</span>{% else %}<span style="color:red">Incomplete</span>{% endif %}</td> + </tr> + {% endfor %} + </table> + </div> +{% endblock %} diff --git a/templates/todolists/view.html b/templates/todolists/view.html new file mode 100644 index 0000000..89a07a9 --- /dev/null +++ b/templates/todolists/view.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% block content %} + <div class="greybox"> + <h3 class="title">ToDo List: {{ list.name }}</h2> + <table class="results" width="100%"> + <tr> + <th>ID</th> + <th>Repo</th> + <th>Name</th> + <th>Maintainer</th> + <th>Status</th> + </tr> + {% for pkg in pkgs %} + <tr> + <td><a href="/packages/{{ pkg.pkg.id }}/">{{ pkg.pkg.id }}</a></td> + <td>{{ pkg.pkg.repo.name }}</td> + <td>{{ pkg.pkg.pkgname }}</td> + <td>{{ pkg.pkg.maintainer.get_full_name }}</td> + <td> + {% if pkg.complete %} + <a href="/todo/flag/{{ list.id }}/{{ pkg.id }}/"><span style="color:blue">Complete</span></a> + {% else %} + <a href="/todo/flag/{{ list.id }}/{{ pkg.id }}/"><span style="color:red">Incomplete</span></a> + {% endif %} + </td> + </tr> + {% endfor %} + </table> + </div> +{% endblock %} diff --git a/templates/wiki/base.html b/templates/wiki/base.html new file mode 100644 index 0000000..90d9315 --- /dev/null +++ b/templates/wiki/base.html @@ -0,0 +1,30 @@ +<html> +<head> + <style type='text/css'> +body { + background: #333; + color: #ddd; +} + +a { + color: #fff; +} + +div.body { + padding: 25px; + margin: 15px; + background: #444; +} + +div.controls { + padding: 20px; +} + </style> +<title>{% block title %}{% endblock %}</title> +</head> +<body> +{% block content %} +<h1>This is the base template. Extend it with the "extends" template tag.</h1> +{% endblock %} +</body> +</html>
\ No newline at end of file diff --git a/templates/wiki/edit.html b/templates/wiki/edit.html new file mode 100644 index 0000000..421a313 --- /dev/null +++ b/templates/wiki/edit.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% block content %} +<div class="greybox"> + <div style="float:right;font-size:x-small"> + <a href="http://daringfireball.net/projects/markdown/syntax/">Wiki Syntax</a> + </div> + <h2 class="title">Editing: {{ page.title }}</h2> + <form action="." method="post"> + <input name="title" value="{{ page.title }}" /> + <br /><br /> + <textarea name="content" cols="100" rows="32">{{ page.content }}</textarea> + <br /><br /> + <input type="submit" value="Save" /> + </form> + <!-- + <form action="/wiki/delete/" method="post"> + <input type="hidden" name="title" value="{{ page.title }}" /> + <input type="submit" value="Delete" /> + </form> + --> +</div> +{% endblock %} diff --git a/templates/wiki/home.html b/templates/wiki/home.html new file mode 100644 index 0000000..6c0fb6e --- /dev/null +++ b/templates/wiki/home.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} +{% block content %} +<div class="box"> + <h2 class="title">Wiki Index</h2> +{% for page in pages %} + <h3><a href='{{ page.title }}/'>{{ page.title }}</a></h3> +{% endfor %} +</div> +{% endblock %} diff --git a/templates/wiki/page.html b/templates/wiki/page.html new file mode 100644 index 0000000..e0e6ddd --- /dev/null +++ b/templates/wiki/page.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% load wikitags %} +{% block content %} +<div class="box"> + <h1 class="wiki">{{ page.title }}</h1> + <div class="wikibody"> +{{ page.content|wikify }} + </div> + + <div class="wikifoot_r"> + <a href='{{ page.editurl }}'>Edit this page</a> | <a href='/wiki/'>Wiki Index</a> + </div> + <div class="wikifoot_l"> + Last Author: {{ page.last_author.username }} + </div> +</div> +{% endblock %} diff --git a/todolists/__init__.py b/todolists/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/todolists/__init__.py diff --git a/todolists/models.py b/todolists/models.py new file mode 100644 index 0000000..0a4f445 --- /dev/null +++ b/todolists/models.py @@ -0,0 +1,31 @@ +from django.db import models +from django.contrib.auth.models import User +from archlinux.packages.models import Package + +class TodolistManager(models.Manager): + def get_incomplete(self): + results = [] + for l in self.all().order_by('-date_added'): + if TodolistPkg.objects.filter(list=l.id).filter(complete=False).count() > 0: + results.append(l) + return results + +class Todolist(models.Model): + id = models.AutoField(primary_key=True) + creator = models.ForeignKey(User) + name = models.CharField(maxlength=255) + description = models.TextField() + date_added = models.DateField(auto_now_add=True) + objects = TodolistManager() + class Meta: + db_table = 'todolists' + +class TodolistPkg(models.Model): + id = models.AutoField(primary_key=True) + list = models.ForeignKey(Todolist) + pkg = models.ForeignKey(Package) + complete = models.BooleanField(default=False) + class Meta: + db_table = 'todolists_pkgs' + unique_together = (('list','pkg'),) + diff --git a/todolists/views.py b/todolists/views.py new file mode 100644 index 0000000..e364f36 --- /dev/null +++ b/todolists/views.py @@ -0,0 +1,64 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.contrib.auth.decorators import login_required, user_passes_test +from django.contrib.auth.models import User +from archlinux.utils import render_template +from archlinux.todolists.models import Todolist, TodolistPkg +from archlinux.packages.models import Package + +# FIXME: ugly hackery. http://code.djangoproject.com/ticket/3450 +import django.db +IntegrityError = django.db.backend.Database.IntegrityError + +@login_required +#@is_maintainer +def flag(request, listid, pkgid): + list = get_object_or_404(Todolist, id=listid) + pkg = get_object_or_404(TodolistPkg, id=pkgid) + pkg.complete = not pkg.complete + pkg.save() + return HttpResponseRedirect('/todo/%s/' % (listid)) + +@login_required +def view(request, listid): + list = get_object_or_404(Todolist, id=listid) + pkgs = TodolistPkg.objects.filter(list=list.id).order_by('pkg') + return render_template('todolists/view.html', request, {'list':list,'pkgs':pkgs}) + +@login_required +def list(request): + lists = Todolist.objects.order_by('-date_added') + for l in lists: + l.complete = TodolistPkg.objects.filter(list=l.id,complete=False).count() == 0 + return render_template('todolists/list.html', request, {'lists':lists}) + +@login_required +#@is_maintainer +@user_passes_test(lambda u: u.has_perm('todolists.add_todolist')) +def add(request): + if request.POST: + try: + m = User.objects.get(username=request.user.username) + except User.DoesNotExist: + return render_template('error_page.html', request, + {'errmsg': 'Cannot find a maintainer record for you!'}) + # create the list + todo = Todolist( + creator = m, + name = request.POST.get('name'), + description = request.POST.get('description')) + todo.save() + # now link in packages + for p in request.POST.get('packages').split("\n"): + for pkg in Package.objects.filter(pkgname=p.strip()): + todopkg = TodolistPkg( + list = todo, + pkg = pkg) + try: + todopkg.save() + except IntegrityError, (num, desc): + if num == 1062: # duplicate entry aka dupe package on list + pass + return HttpResponseRedirect('/todo/') + + return render_template('todolists/add.html', request) @@ -0,0 +1,71 @@ +from django.conf.urls.defaults import * +from archlinux.news.models import News +from archlinux.feeds import PackageFeed, NewsFeed + +feeds = { + 'packages': PackageFeed, + 'news': NewsFeed +} + +urlpatterns = patterns('', + (r'^media/(.*)$', 'django.views.static.serve', {'document_root': '/home/jvinet/shared/work/archlinux/media'}), + +# Dynamic Stuff + (r'^packages/flag/(\d+)/$', 'archlinux.packages.views.flag'), + (r'^packages/flaghelp/$', 'archlinux.packages.views.flaghelp'), + (r'^packages/unflag/(\d+)/$', 'archlinux.packages.views.unflag'), + (r'^packages/files/(\d+)/$', 'archlinux.packages.views.files'), + (r'^packages/search/$', 'archlinux.packages.views.search'), + (r'^packages/search/([A-z0-9]+)/$', 'archlinux.packages.views.search'), + (r'^packages/update/$', 'archlinux.packages.views.update'), + (r'^packages/(?P<pkgid>\d+)/$', 'archlinux.packages.views.details'), + (r'^packages/(?P<name>[A-z0-9]+)/$', 'archlinux.packages.views.details'), + (r'^packages/(?P<repo>[A-z0-9]+)/(?P<name>[A-z0-9]+)/$', 'archlinux.packages.views.details'), + (r'^packages/$', 'archlinux.packages.views.search'), + + (r'^todo/(\d+)/$', 'archlinux.todolists.views.view'), + (r'^todo/add/$', 'archlinux.todolists.views.add'), + (r'^todo/flag/(\d+)/(\d+)/$', 'archlinux.todolists.views.flag'), + (r'^todo/$', 'archlinux.todolists.views.list'), + + (r'^news/(\d+)/$', 'archlinux.news.views.view'), + (r'^news/add/$', 'archlinux.news.views.add'), + (r'^news/edit/(\d+)/$', 'archlinux.news.views.edit'), + (r'^news/delete/(\d+)/$', 'archlinux.news.views.delete'), + (r'^news/$', 'archlinux.news.views.list'), + + (r'^devel/$', 'archlinux.devel.views.index'), + (r'^devel/notify/$', 'archlinux.devel.views.change_notify'), + (r'^devel/profile/$', 'archlinux.devel.views.change_profile'), + (r'^devel/guide/$', 'archlinux.devel.views.guide'), + + (r'^wiki/([A-Z]+[A-z0-9 :/-]+)/$', 'archlinux.wiki.views.page'), + (r'^wiki/edit/([A-Z]+[A-z0-9 :/-]+)/$', 'archlinux.wiki.views.edit'), + (r'^wiki/delete/$', 'archlinux.wiki.views.delete'), + (r'^wiki/index/$', 'archlinux.wiki.views.index'), + (r'^wiki/$', 'archlinux.wiki.views.main'), + +# Feeds + (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}), + +# (mostly) Static Pages + (r'^$', 'archlinux.public.views.index'), + (r'^about/$', 'archlinux.public.views.about'), + (r'^art/$', 'archlinux.public.views.art'), + (r'^cvs/$', 'archlinux.public.views.cvs'), + (r'^developers/$', 'archlinux.public.views.developers'), + (r'^donate/$', 'archlinux.public.views.donate'), + (r'^download/$', 'archlinux.public.views.download'), + (r'^irc/$', 'archlinux.public.views.irc'), + (r'^moreforums/$', 'archlinux.public.views.moreforums'), + (r'^press/$', 'archlinux.public.views.press'), + (r'^projects/$', 'archlinux.public.views.projects'), + +# Authentication / Admin + (r'^denied/$', 'archlinux.public.views.denied'), + (r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'registration/login.html'}), + (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'registration/login.html'}), + (r'^logout/$', 'django.contrib.auth.views.logout', {'template_name': 'registration/logout.html'}), + (r'^accounts/logout/$', 'django.contrib.auth.views.logout', {'template_name': 'registration/logout.html'}), + (r'^admin/', include('django.contrib.admin.urls')), +) diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..7848fce --- /dev/null +++ b/utils.py @@ -0,0 +1,69 @@ +from django.core import validators +from django.template import RequestContext +from django.shortcuts import render_to_response +from django.http import HttpResponseRedirect +from string import * +import sgmllib + +#from archlinux.packages.models import Maintainer +#from archlinux.settings import BADPRIVS_URL +#def is_maintainer(view_func, badprivs_url=BADPRIVS_URL): +# """ +# Decorator for views that checks that the logged-in user has a corresponding +# record in the Maintainers table. If not, the user is forwarded to a +# "bad-privileges" page. +# """ +# def _dec(view_func): +# def _checkuser(request, *args, **kwargs): +# try: +# m = Maintainer.objects.get(username=request.user.username) +# except Maintainer.DoesNotExist: +# return HttpResponseRedirect(badprivs_url) +# return view_func(request, *args, **kwargs) +# +# return _checkuser +# return _dec(view_func) + +def render_template(template, request, context=None): + """ + A shortcut to render_to_response with a RequestContext. Also includes + request.path in the context, so both 'path' and 'user' are accessible to + the template. + """ + if context: + context['path'] = request.path + return render_to_response(template, context_instance=RequestContext(request, context)) + else: + return render_to_response(template, context_instance=RequestContext(request)) + +def validate(errdict, fieldname, fieldval, validator, blankallowed, request): + """ + A helper function that allows easy access to Django's validators without + going through a Manipulator object. Will return a dict of all triggered + errors. + """ + if blankallowed and not fieldval: + return + alldata = ' '.join(request.POST.values()) + ' '.join(request.GET.values()) + try: + validator(fieldval, alldata) + except validators.ValidationError, e: + if not errdict.has_key(fieldname): errdict[fieldname] = [] + errdict[fieldname].append(e) + + +# XXX: unused right now, probably not needed +class Stripper(sgmllib.SGMLParser): + """Helper class to strip HTML tags""" + def __init__(self): + sgmllib.SGMLParser.__init__(self) + + def strip(self, some_html): + """Strips all HTML tags and leading/trailing whitespace""" + self.theString = "" + self.feed(some_html) + self.close() + return self.theString + + def handle_data(self, data): + self.theString += data diff --git a/wiki/__init__.py b/wiki/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/wiki/__init__.py diff --git a/wiki/models.py b/wiki/models.py new file mode 100644 index 0000000..2b8b16f --- /dev/null +++ b/wiki/models.py @@ -0,0 +1,16 @@ +from django.db import models +from django.contrib.auth.models import User + +class Wikipage(models.Model): + """Wiki page storage""" + title = models.CharField(maxlength=255) + content = models.TextField() + last_author = models.ForeignKey(User) + class Meta: + db_table = 'wikipages' + + def editurl(self): + return "/wiki/edit/" + self.title + "/" + + def __repr__(self): + return self.title diff --git a/wiki/templatetags/__init__.py b/wiki/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/wiki/templatetags/__init__.py diff --git a/wiki/templatetags/wikitags.py b/wiki/templatetags/wikitags.py new file mode 100644 index 0000000..bdf8df6 --- /dev/null +++ b/wiki/templatetags/wikitags.py @@ -0,0 +1,57 @@ +from django.template import Library +from django.conf import settings +from archlinux.lib import markdown +import re + +register = Library() + +class WikiProcessor: + def run(self, lines): + in_table = False + for i in range(len(lines)): + # Linebreaks + lines[i] = re.sub("%%", "<br />", lines[i]) + # Internal Links + lines[i] = re.sub("\(\(([A-z0-9 :/-]+)\)\)", "<a href=\"/wiki/\\1\">\\1</a>", lines[i]) + # Small Text + lines[i] = re.sub("----([^----]+)----", "<span style=\"font-size:x-small\">\\1</span>", lines[i]) + lines[i] = re.sub("--([^--]+)--", "<span style=\"font-size:small\">\\1</span>", lines[i]) + # TT text + lines[i] = re.sub("\{\{([^}\}]+)\}\}", "<tt>\\1</tt>", lines[i]) + # Tables + m = re.match("(\|\|)", lines[i]) + if m: + count = len(re.findall("(\|\|+)", lines[i])) + first = True + m2 = re.search("(\|\|+)", lines[i]) + while m2 and count: + count -= 1 + colspan = len(m2.group(1)) / 2 + if first: + repl = "<td colspan=\"%d\">" % (colspan) + first = False + elif count == 0: + repl = "</td>" + else: + repl = "</td><td colspan=\"%d\">" % (colspan) + lines[i] = re.sub("(\|\|+)", repl, lines[i], 1) + # find the next chunk + m2 = re.search("(\|\|+)", lines[i]) + lines[i] = "<tr>" + lines[i] + "</tr>" + if not in_table: + lines[i] = "<table>" + lines[i] + in_table = True + elif in_table: + lines[i] = "</table>" + lines[i] + in_table = False + # close leftover table, if open + if in_table: + lines[len(lines)] = lines[len(lines)] + "</table>" + return lines + +@register.filter +def wikify(value): + md = markdown.Markdown(value) + md.preprocessors.insert(0, WikiProcessor()) + html = md.toString() + return html diff --git a/wiki/views.py b/wiki/views.py new file mode 100644 index 0000000..c78da37 --- /dev/null +++ b/wiki/views.py @@ -0,0 +1,61 @@ +# +# Based on code from http://e-scribe.com/news/210 +# +from django.http import HttpResponse, HttpResponseRedirect +from django.contrib.auth.decorators import login_required +from archlinux.utils import render_template +from archlinux.wiki.models import Wikipage + +@login_required +def index(request): + """Return a list of all wiki pages""" + pages = Wikipage.objects.all().order_by('title') + return render_template('wiki/home.html', request, {'pages':pages}) + +def main(request): + """Return the Index wiki page""" + return HttpResponseRedirect("/wiki/WikiIndex/") + +@login_required +def page(request, title): + """Display page, or redirect to root if page doesn't exist yet""" + try: + page = Wikipage.objects.get(title__exact=title) + return render_template('wiki/page.html', request, {'page':page}) + except Wikipage.DoesNotExist: + return HttpResponseRedirect("/wiki/edit/%s/" % title) + +@login_required +def edit(request, title): + """Process submitted page edits (POST) or display editing form (GET)""" + if request.POST: + try: + page = Wikipage.objects.get(title__exact=title) + except Wikipage.DoesNotExist: + # Must be a new one; let's create it + page = Wikipage(title=title) + page.content = request.POST['content'] + page.title = request.POST['title'] + page.last_author = request.user + page.save() + return HttpResponseRedirect("/wiki/" + page.title + "/") + else: + try: + page = Wikipage.objects.get(title__exact=title) + except Wikipage.DoesNotExist: + # create a dummy page object -- note that it is not saved! + page = Wikipage(title=title) + page.body = "<!-- Enter content here -->" + return render_template('wiki/edit.html', request, {'page':page}) + +@login_required +def delete(request): + """Delete a page""" + if request.POST: + title = request.POST['title'] + try: + page = Wikipage.objects.get(title__exact=title) + except Wikipage.DoesNotExist: + return HttpResponseRedirect("/wiki/") + page.delete() + return HttpResponseRedirect("/wiki/") |