summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--AUTHORS6
-rw-r--r--LICENSE339
-rw-r--r--README14
-rw-r--r--__init__.py0
-rw-r--r--common/__init__.py0
-rw-r--r--common/models.py50
-rw-r--r--common/templatetags/__init__.py0
-rw-r--r--common/templatetags/validation.py12
-rw-r--r--data/pkgmaint_guide.txt164
-rw-r--r--devel/__init__.py0
-rw-r--r--devel/views.py67
-rw-r--r--feeds.py29
-rw-r--r--lib/__init__.py0
-rw-r--r--lib/markdown.py1874
-rw-r--r--local_settings.py.example33
-rwxr-xr-xmanage.py11
-rw-r--r--media/PythonPowered.pngbin0 -> 945 bytes
-rw-r--r--media/arch.css384
-rw-r--r--media/calendar.css265
-rw-r--r--media/calendar.js2133
-rw-r--r--media/favicon.icobin0 -> 1150 bytes
-rw-r--r--media/forms/_library/cmxform.css57
-rw-r--r--media/forms/cmxform.css30
-rw-r--r--media/forms/cmxform.js22
-rw-r--r--media/forms/core.css22
-rw-r--r--media/forms/images/cmxform-divider.gifbin0 -> 43 bytes
-rw-r--r--media/forms/images/cmxform-fieldset.gifbin0 -> 2926 bytes
-rw-r--r--media/forms/reset.css62
-rw-r--r--media/forms/screen.css16
-rw-r--r--media/gnu-head-tiny.jpgbin0 -> 3049 bytes
-rw-r--r--media/jquery.js14
-rw-r--r--media/logo.pngbin0 -> 15730 bytes
-rw-r--r--media/mailman-large.jpgbin0 -> 6150 bytes
-rw-r--r--media/mailman.jpgbin0 -> 2022 bytes
-rw-r--r--media/mm-icon.pngbin0 -> 281 bytes
-rw-r--r--media/rss.pngbin0 -> 725 bytes
-rw-r--r--media/tab.pngbin0 -> 107 bytes
-rw-r--r--media/title.pngbin0 -> 6465 bytes
-rw-r--r--media/title_back.pngbin0 -> 168 bytes
-rw-r--r--news/__init__.py0
-rw-r--r--news/models.py19
-rw-r--r--news/views.py82
-rw-r--r--packages/__init__.py0
-rw-r--r--packages/models.py91
-rw-r--r--packages/templatetags/__init__.py0
-rw-r--r--packages/templatetags/package_extras.py28
-rw-r--r--packages/views.py172
-rw-r--r--public/__init__.py0
-rw-r--r--public/views.py56
-rw-r--r--scripts/daily_cleanup.py14
-rwxr-xr-xscripts/djangoshell.sh6
-rw-r--r--settings.py89
-rw-r--r--templates/403.html9
-rw-r--r--templates/404.html9
-rw-r--r--templates/500.html9
-rw-r--r--templates/base.html84
-rw-r--r--templates/devel/index.html75
-rw-r--r--templates/devel/profile.html32
-rw-r--r--templates/error_page.html8
-rw-r--r--templates/errors.html5
-rw-r--r--templates/feeds/news_description.html1
-rw-r--r--templates/feeds/news_title.html1
-rw-r--r--templates/feeds/packages_description.html1
-rw-r--r--templates/feeds/packages_title.html1
-rw-r--r--templates/news/add.html26
-rw-r--r--templates/news/delete.html16
-rw-r--r--templates/news/list.html26
-rw-r--r--templates/news/view.html13
-rw-r--r--templates/packages/details.html77
-rw-r--r--templates/packages/files.html11
-rw-r--r--templates/packages/flag.html26
-rw-r--r--templates/packages/flaghelp.html14
-rw-r--r--templates/packages/outofdate.txt13
-rw-r--r--templates/packages/search.html120
-rw-r--r--templates/public/about.html82
-rw-r--r--templates/public/art.html28
-rw-r--r--templates/public/blank.html10
-rw-r--r--templates/public/cvs.html48
-rw-r--r--templates/public/denied.html11
-rw-r--r--templates/public/developers.html67
-rw-r--r--templates/public/donate.html59
-rw-r--r--templates/public/download.html74
-rw-r--r--templates/public/index.html174
-rw-r--r--templates/public/index.html.bak151
-rw-r--r--templates/public/irc.html39
-rw-r--r--templates/public/moreforums.html78
-rw-r--r--templates/public/press.html33
-rw-r--r--templates/public/projects.html36
-rw-r--r--templates/registration/login.html23
-rw-r--r--templates/registration/logout.html10
-rw-r--r--templates/status_page.html8
-rw-r--r--templates/todolists/add.html25
-rw-r--r--templates/todolists/list.html30
-rw-r--r--templates/todolists/view.html31
-rw-r--r--templates/wiki/base.html30
-rw-r--r--templates/wiki/edit.html22
-rw-r--r--templates/wiki/home.html9
-rw-r--r--templates/wiki/page.html17
-rw-r--r--todolists/__init__.py0
-rw-r--r--todolists/models.py31
-rw-r--r--todolists/views.py64
-rw-r--r--urls.py71
-rw-r--r--utils.py69
-rw-r--r--wiki/__init__.py0
-rw-r--r--wiki/models.py16
-rw-r--r--wiki/templatetags/__init__.py0
-rw-r--r--wiki/templatetags/wikitags.py57
-rw-r--r--wiki/views.py61
109 files changed, 8206 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..33b2f642
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+*.swp
+local_settings.py
+
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 00000000..3917c18b
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,6 @@
+# AUTHORS
+Judd Vinet <judd@archlinux.org>
+Simo Leone <simo@archlinux.org>
+eliott <eliott@cactuswax.net>
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..d511905c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <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.
diff --git a/README b/README
new file mode 100644
index 00000000..b0067e87
--- /dev/null
+++ b/README
@@ -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 00000000..e69de29b
--- /dev/null
+++ b/__init__.py
diff --git a/common/__init__.py b/common/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/common/__init__.py
diff --git a/common/models.py b/common/models.py
new file mode 100644
index 00000000..883675b6
--- /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 00000000..e69de29b
--- /dev/null
+++ b/common/templatetags/__init__.py
diff --git a/common/templatetags/validation.py b/common/templatetags/validation.py
new file mode 100644
index 00000000..40c48da9
--- /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 00000000..85106eab
--- /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 00000000..e69de29b
--- /dev/null
+++ b/devel/__init__.py
diff --git a/devel/views.py b/devel/views.py
new file mode 100644
index 00000000..dea652be
--- /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 00000000..bddf9419
--- /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 00000000..e69de29b
--- /dev/null
+++ b/lib/__init__.py
diff --git a/lib/markdown.py b/lib/markdown.py
new file mode 100644
index 00000000..6b3e57a2
--- /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 = [ #("&", "&amp;"),
+ ("<", "&lt;"),
+ (">", "&gt;"),
+ ("\"", "&quot;")]
+
+ 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('"', "&quot;")
+ 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]*;)' # &amp;
+
+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("&", "&amp;")
+ 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('"', "&quot;")
+ 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("&", "&amp;")
+ 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, "&#8617;")
+
+ # 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 00000000..e2024435
--- /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 00000000..008aeeb7
--- /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
new file mode 100644
index 00000000..2e9d99c2
--- /dev/null
+++ b/media/PythonPowered.png
Binary files differ
diff --git a/media/arch.css b/media/arch.css
new file mode 100644
index 00000000..c8bfe36b
--- /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 00000000..fa5c0932
--- /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 00000000..c4f388d2
--- /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("&#x00d7;", 1, 200).ttip = Calendar._TT["CLOSE"];
+ }
+
+ row = Calendar.createElement("tr", thead);
+ row.className = "headrow";
+
+ this._nav_py = hh("&#x00ab;", 1, -2);
+ this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
+
+ this._nav_pm = hh("&#x2039;", 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("&#x203a;", 1, 1);
+ this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
+
+ this._nav_ny = hh("&#x00bb;", 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"] || "&nbsp;";
+
+ 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 = "&nbsp;";
+
+ 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 = "&nbsp;";
+ 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
new file mode 100644
index 00000000..3a6d2ab9
--- /dev/null
+++ b/media/favicon.ico
Binary files differ
diff --git a/media/forms/_library/cmxform.css b/media/forms/_library/cmxform.css
new file mode 100644
index 00000000..ff362864
--- /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 00000000..f713714f
--- /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 00000000..0b7973d6
--- /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 00000000..bdeac7a4
--- /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
new file mode 100644
index 00000000..718a977c
--- /dev/null
+++ b/media/forms/images/cmxform-divider.gif
Binary files differ
diff --git a/media/forms/images/cmxform-fieldset.gif b/media/forms/images/cmxform-fieldset.gif
new file mode 100644
index 00000000..0590c894
--- /dev/null
+++ b/media/forms/images/cmxform-fieldset.gif
Binary files differ
diff --git a/media/forms/reset.css b/media/forms/reset.css
new file mode 100644
index 00000000..c557f24c
--- /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 00000000..d21a36a3
--- /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
new file mode 100644
index 00000000..441be50d
--- /dev/null
+++ b/media/gnu-head-tiny.jpg
Binary files differ
diff --git a/media/jquery.js b/media/jquery.js
new file mode 100644
index 00000000..cd439dad
--- /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
new file mode 100644
index 00000000..b2b6d863
--- /dev/null
+++ b/media/logo.png
Binary files differ
diff --git a/media/mailman-large.jpg b/media/mailman-large.jpg
new file mode 100644
index 00000000..e184f3c6
--- /dev/null
+++ b/media/mailman-large.jpg
Binary files differ
diff --git a/media/mailman.jpg b/media/mailman.jpg
new file mode 100644
index 00000000..94a4c011
--- /dev/null
+++ b/media/mailman.jpg
Binary files differ
diff --git a/media/mm-icon.png b/media/mm-icon.png
new file mode 100644
index 00000000..42c3cf75
--- /dev/null
+++ b/media/mm-icon.png
Binary files differ
diff --git a/media/rss.png b/media/rss.png
new file mode 100644
index 00000000..c9164592
--- /dev/null
+++ b/media/rss.png
Binary files differ
diff --git a/media/tab.png b/media/tab.png
new file mode 100644
index 00000000..56d2e617
--- /dev/null
+++ b/media/tab.png
Binary files differ
diff --git a/media/title.png b/media/title.png
new file mode 100644
index 00000000..e16971a1
--- /dev/null
+++ b/media/title.png
Binary files differ
diff --git a/media/title_back.png b/media/title_back.png
new file mode 100644
index 00000000..95c98d7a
--- /dev/null
+++ b/media/title_back.png
Binary files differ
diff --git a/news/__init__.py b/news/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/news/__init__.py
diff --git a/news/models.py b/news/models.py
new file mode 100644
index 00000000..acd9667c
--- /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 00000000..29f20445
--- /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 00000000..e69de29b
--- /dev/null
+++ b/packages/__init__.py
diff --git a/packages/models.py b/packages/models.py
new file mode 100644
index 00000000..c1b6c84d
--- /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 00000000..e69de29b
--- /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 00000000..8b55221b
--- /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 00000000..f9cecf1b
--- /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 00000000..e69de29b
--- /dev/null
+++ b/public/__init__.py
diff --git a/public/views.py b/public/views.py
new file mode 100644
index 00000000..577efcbc
--- /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 00000000..98f997bf
--- /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 00000000..457c8827
--- /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 00000000..3de4a89d
--- /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 00000000..c853fef1
--- /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 00000000..bc50f69b
--- /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 00000000..9566fc29
--- /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 00000000..507e8abb
--- /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 &lt;<a href="mailto:jvinet@zeroflux.org">jvinet@zeroflux.org</a>&gt;<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 00000000..2617de9f
--- /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
+ &nbsp; &nbsp;
+ <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 00000000..60837356
--- /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 00000000..575ac416
--- /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 00000000..84f98d79
--- /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 00000000..26e1cbbe
--- /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 00000000..d355de5b
--- /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 00000000..6b9c47b3
--- /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 00000000..a2731625
--- /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 00000000..702d7ab7
--- /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 00000000..7ac5e250
--- /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>&nbsp; &nbsp; <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 00000000..e188e3c9
--- /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 00000000..5d100450
--- /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 00000000..65de079a
--- /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 }}&amp;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 />&nbsp; &nbsp; <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>&nbsp;</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 00000000..24f55819
--- /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 00000000..215b6fa8
--- /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 00000000..09f7530b
--- /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 00000000..7b863608
--- /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 00000000..030289d4
--- /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>&nbsp;</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 }}">&lt;&lt;&lt; Prev</a>{% endif %}</td>
+ <td colspan="2">&nbsp;</td>
+ <td colspan="2" style="font-size:x-small;text-align:right">{% if nextpage %}<br /><a href="{{ nextpage }}">Next &gt;&gt;&gt;</a>{% endif %}</td>
+ </tr>
+ {% if not user.is_anonymous %}
+ <tr>
+ <td colspan="3">&nbsp;</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">&nbsp;</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 00000000..0712b5d0
--- /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 00000000..0fab30a8
--- /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 00000000..d1e7b71b
--- /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 00000000..715d00d3
--- /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 00000000..c59c0f73
--- /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 00000000..6b1fe8b7
--- /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> &nbsp;
+ {% 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 %}&nbsp;{% 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 00000000..bdb9adef
--- /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 00000000..be840d3a
--- /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>&nbsp;</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 00000000..2e0d0056
--- /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:&nbsp;&nbsp;<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 &amp; 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 00000000..a41196df
--- /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:&nbsp;&nbsp;<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 &amp; 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 00000000..cbd00daa
--- /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 00000000..f8a13624
--- /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 00000000..b652c8dc
--- /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 &amp; 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 00000000..c669d048
--- /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 00000000..b3b264c3
--- /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 00000000..2b829869
--- /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 00000000..558c6544
--- /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 00000000..a53637ce
--- /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 00000000..0e659f93
--- /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 00000000..89a07a99
--- /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 00000000..90d93156
--- /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 00000000..421a313f
--- /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 00000000..6c0fb6ef
--- /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 00000000..e0e6ddd9
--- /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 00000000..e69de29b
--- /dev/null
+++ b/todolists/__init__.py
diff --git a/todolists/models.py b/todolists/models.py
new file mode 100644
index 00000000..0a4f445e
--- /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 00000000..e364f36e
--- /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)
diff --git a/urls.py b/urls.py
new file mode 100644
index 00000000..bf6f18de
--- /dev/null
+++ b/urls.py
@@ -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 00000000..7848fceb
--- /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 00000000..e69de29b
--- /dev/null
+++ b/wiki/__init__.py
diff --git a/wiki/models.py b/wiki/models.py
new file mode 100644
index 00000000..2b8b16fc
--- /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 00000000..e69de29b
--- /dev/null
+++ b/wiki/templatetags/__init__.py
diff --git a/wiki/templatetags/wikitags.py b/wiki/templatetags/wikitags.py
new file mode 100644
index 00000000..bdf8df61
--- /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 00000000..c78da37d
--- /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/")