summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--LICENSE12
-rw-r--r--__init__.py0
-rw-r--r--cnprog.wsgi8
-rw-r--r--django_authopenid/__init__.py40
-rw-r--r--django_authopenid/admin.py9
-rw-r--r--django_authopenid/forms.py435
-rw-r--r--django_authopenid/middleware.py24
-rw-r--r--django_authopenid/mimeparse.py160
-rw-r--r--django_authopenid/models.py71
-rw-r--r--django_authopenid/urls.py27
-rw-r--r--django_authopenid/util.py145
-rw-r--r--django_authopenid/views.py787
-rw-r--r--forum/__init__.py0
-rw-r--r--forum/admin.py71
-rw-r--r--forum/auth.py443
-rw-r--r--forum/const.py87
-rw-r--r--forum/diff.py66
-rw-r--r--forum/feed.py43
-rw-r--r--forum/forms.py193
-rw-r--r--forum/management/__init__.py0
-rw-r--r--forum/management/commands/__init__.py0
-rw-r--r--forum/management/commands/base_command.py35
-rw-r--r--forum/management/commands/clean_award_badges.py58
-rw-r--r--forum/management/commands/multi_award_badges.py347
-rw-r--r--forum/management/commands/once_award_badges.py327
-rw-r--r--forum/management/commands/sample_command.py7
-rw-r--r--forum/managers.py208
-rw-r--r--forum/models.py653
-rw-r--r--forum/templatetags/__init__.py0
-rw-r--r--forum/templatetags/extra_filters.py83
-rw-r--r--forum/templatetags/extra_tags.py240
-rw-r--r--forum/user.py74
-rw-r--r--forum/views.py1962
-rw-r--r--junk7
-rw-r--r--lanai.psproj557
-rw-r--r--locale/en/LC_MESSAGES/django.po2540
-rw-r--r--locale/zh-cn/LC_MESSAGES/django.mobin0 -> 39094 bytes
-rw-r--r--locale/zh-cn/LC_MESSAGES/django.po1430
-rw-r--r--log/cnprog.log0
-rw-r--r--manage.py11
-rw-r--r--settings.py146
-rw-r--r--sql_scripts/cnprog.xml1498
-rw-r--r--sql_scripts/cnprog_new_install.sql811
-rw-r--r--sql_scripts/cnprog_new_install_2009_02_28.sql456
-rw-r--r--sql_scripts/cnprog_new_install_2009_03_31.sql891
-rw-r--r--sql_scripts/cnprog_new_install_2009_04_07.sql24
-rw-r--r--sql_scripts/cnprog_new_install_2009_04_09.sql904
-rw-r--r--sql_scripts/update_2009_01_13_001.sql62
-rw-r--r--sql_scripts/update_2009_01_13_002.sql1
-rw-r--r--sql_scripts/update_2009_01_18_001.sql62
-rw-r--r--sql_scripts/update_2009_01_24.sql2
-rw-r--r--sql_scripts/update_2009_01_25_001.sql2
-rw-r--r--sql_scripts/update_2009_02_26_001.sql19
-rw-r--r--sql_scripts/update_2009_04_10_001.sql3
-rw-r--r--sql_scripts/update_2009_12_24_001.sql5
-rw-r--r--sql_scripts/update_2009_12_27_001.sql3
-rw-r--r--sql_scripts/update_2009_12_27_002.sql1
-rw-r--r--templates/404.html49
-rw-r--r--templates/500.html35
-rw-r--r--templates/about.html21
-rw-r--r--templates/allfiles30
-rw-r--r--templates/answer_edit.html88
-rw-r--r--templates/answer_edit_tips.html54
-rw-r--r--templates/ask.html140
-rw-r--r--templates/authopenid/changeemail.html35
-rw-r--r--templates/authopenid/changeopenid.html33
-rw-r--r--templates/authopenid/changepw.html33
-rw-r--r--templates/authopenid/complete.html67
-rw-r--r--templates/authopenid/confirm_email.txt12
-rw-r--r--templates/authopenid/delete.html38
-rw-r--r--templates/authopenid/failure.html13
-rw-r--r--templates/authopenid/htmlfiles8
-rw-r--r--templates/authopenid/sendpw.html34
-rw-r--r--templates/authopenid/sendpw_email.txt14
-rw-r--r--templates/authopenid/settings.html41
-rw-r--r--templates/authopenid/signin.html95
-rw-r--r--templates/authopenid/signup.html52
-rw-r--r--templates/authopenid/yadis.xrdf14
-rw-r--r--templates/badge.html37
-rw-r--r--templates/badges.html78
-rw-r--r--templates/base.html91
-rw-r--r--templates/base_content.html86
-rw-r--r--templates/book.html152
-rw-r--r--templates/close.html36
-rw-r--r--templates/content/images/box-arrow.gifbin0 -> 69 bytes
-rw-r--r--templates/content/images/bullet_green.gifbin0 -> 64 bytes
-rw-r--r--templates/content/images/cc-88x31.pngbin0 -> 5460 bytes
-rw-r--r--templates/content/images/cc-wiki.pngbin0 -> 2333 bytes
-rw-r--r--templates/content/images/close-small-hover.pngbin0 -> 337 bytes
-rw-r--r--templates/content/images/close-small.pngbin0 -> 293 bytes
-rw-r--r--templates/content/images/cnprog_logo_200_56.gifbin0 -> 2114 bytes
-rw-r--r--templates/content/images/dash.gifbin0 -> 44 bytes
-rw-r--r--templates/content/images/djangomade124x25_grey.gifbin0 -> 2035 bytes
-rw-r--r--templates/content/images/dot-g.gifbin0 -> 61 bytes
-rw-r--r--templates/content/images/dot-list.gifbin0 -> 56 bytes
-rw-r--r--templates/content/images/edit.pngbin0 -> 758 bytes
-rw-r--r--templates/content/images/expander-arrow-hide.gifbin0 -> 126 bytes
-rw-r--r--templates/content/images/expander-arrow-show.gifbin0 -> 135 bytes
-rw-r--r--templates/content/images/favicon.gifbin0 -> 3918 bytes
-rw-r--r--templates/content/images/feed-icon-small.pngbin0 -> 689 bytes
-rw-r--r--templates/content/images/grippie.pngbin0 -> 162 bytes
-rw-r--r--templates/content/images/indicator.gifbin0 -> 2545 bytes
-rw-r--r--templates/content/images/logo.pngbin0 -> 3631 bytes
-rw-r--r--templates/content/images/logo1.pngbin0 -> 2752 bytes
-rw-r--r--templates/content/images/logo2.pngbin0 -> 2124 bytes
-rw-r--r--templates/content/images/medala.gifbin0 -> 801 bytes
-rw-r--r--templates/content/images/medala_on.gifbin0 -> 957 bytes
-rw-r--r--templates/content/images/new.gifbin0 -> 635 bytes
-rw-r--r--templates/content/images/nophoto.pngbin0 -> 696 bytes
-rw-r--r--templates/content/images/openid.gifbin0 -> 910 bytes
-rw-r--r--templates/content/images/openid/aol.gifbin0 -> 2205 bytes
-rw-r--r--templates/content/images/openid/blogger.icobin0 -> 3638 bytes
-rw-r--r--templates/content/images/openid/claimid.icobin0 -> 3638 bytes
-rw-r--r--templates/content/images/openid/facebook.gifbin0 -> 2075 bytes
-rw-r--r--templates/content/images/openid/flickr.icobin0 -> 1150 bytes
-rw-r--r--templates/content/images/openid/google.gifbin0 -> 1596 bytes
-rw-r--r--templates/content/images/openid/livejournal.icobin0 -> 5222 bytes
-rw-r--r--templates/content/images/openid/myopenid.icobin0 -> 2862 bytes
-rw-r--r--templates/content/images/openid/openid-inputicon.gifbin0 -> 237 bytes
-rw-r--r--templates/content/images/openid/openid.gifbin0 -> 740 bytes
-rw-r--r--templates/content/images/openid/technorati.icobin0 -> 2294 bytes
-rw-r--r--templates/content/images/openid/verisign.icobin0 -> 4710 bytes
-rw-r--r--templates/content/images/openid/vidoop.icobin0 -> 1406 bytes
-rw-r--r--templates/content/images/openid/wordpress.icobin0 -> 1150 bytes
-rw-r--r--templates/content/images/openid/yahoo.gifbin0 -> 1682 bytes
-rw-r--r--templates/content/images/quest-bg.gifbin0 -> 294 bytes
-rw-r--r--templates/content/images/vote-accepted-on.pngbin0 -> 1124 bytes
-rw-r--r--templates/content/images/vote-accepted.pngbin0 -> 1058 bytes
-rw-r--r--templates/content/images/vote-arrow-down-on.pngbin0 -> 905 bytes
-rw-r--r--templates/content/images/vote-arrow-down.pngbin0 -> 876 bytes
-rw-r--r--templates/content/images/vote-arrow-up-on.pngbin0 -> 906 bytes
-rw-r--r--templates/content/images/vote-arrow-up.pngbin0 -> 843 bytes
-rw-r--r--templates/content/images/vote-favorite-off.pngbin0 -> 930 bytes
-rw-r--r--templates/content/images/vote-favorite-on.pngbin0 -> 1023 bytes
-rw-r--r--templates/content/js/com.cnprog.editor.js68
-rw-r--r--templates/content/js/com.cnprog.i18n.js90
-rw-r--r--templates/content/js/com.cnprog.post.js616
-rw-r--r--templates/content/js/com.cnprog.utils.js122
-rw-r--r--templates/content/js/compress.bat6
-rw-r--r--templates/content/js/excanvas.pack.js1
-rw-r--r--templates/content/js/flot-build.bat3
-rw-r--r--templates/content/js/jquery-1.2.6.js3549
-rw-r--r--templates/content/js/jquery-1.2.6.min.js32
-rw-r--r--templates/content/js/jquery.ajaxfileupload.js195
-rw-r--r--templates/content/js/jquery.flot.js2421
-rw-r--r--templates/content/js/jquery.flot.pack.js1
-rw-r--r--templates/content/js/jquery.i18n.js133
-rw-r--r--templates/content/js/jquery.openid.js176
-rw-r--r--templates/content/js/jquery.validate.pack.js15
-rw-r--r--templates/content/js/se_hilite.js1
-rw-r--r--templates/content/js/se_hilite_src.js273
-rw-r--r--templates/content/js/wmd/images/wmd-buttons.pngbin0 -> 7465 bytes
-rw-r--r--templates/content/js/wmd/showdown-min.js1
-rw-r--r--templates/content/js/wmd/showdown.js1309
-rw-r--r--templates/content/js/wmd/wmd-min.js1
-rw-r--r--templates/content/js/wmd/wmd-test.html158
-rw-r--r--templates/content/js/wmd/wmd.css129
-rw-r--r--templates/content/js/wmd/wmd.js2388
-rw-r--r--templates/content/js/yuicompressor-2.4.2.jarbin0 -> 851219 bytes
-rw-r--r--templates/content/style/default.css1753
-rw-r--r--templates/content/style/jquery.autocomplete.css49
-rw-r--r--templates/content/style/openid.css45
-rw-r--r--templates/content/style/prettify.css27
-rw-r--r--templates/content/style/style.css1044
-rw-r--r--templates/faq.html113
-rw-r--r--templates/feeds/rss_description.html1
-rw-r--r--templates/feeds/rss_title.html1
-rw-r--r--templates/footer.html33
-rw-r--r--templates/header.html70
-rw-r--r--templates/index.html123
-rw-r--r--templates/logout.html25
-rw-r--r--templates/okfiles30
-rw-r--r--templates/pagesize.html27
-rw-r--r--templates/paginator.html39
-rw-r--r--templates/privacy.html43
-rw-r--r--templates/question.html510
-rw-r--r--templates/question_edit.html134
-rw-r--r--templates/question_edit_tips.html51
-rw-r--r--templates/questions.html158
-rw-r--r--templates/reopen.html40
-rw-r--r--templates/revisions_answer.html102
-rw-r--r--templates/revisions_question.html103
-rw-r--r--templates/tags.html64
-rw-r--r--templates/template.list18
-rw-r--r--templates/tough/faq.html110
-rw-r--r--templates/tough/question_retag.html107
-rw-r--r--templates/tough/unanswered.html115
-rw-r--r--templates/unanswered.html120
-rwxr-xr-xtemplates/upfiles/1245715031297631.pngbin0 -> 3863 bytes
-rwxr-xr-xtemplates/upfiles/12457157052552259.pngbin0 -> 3863 bytes
-rw-r--r--templates/user.html35
-rw-r--r--templates/user_edit.html91
-rw-r--r--templates/user_favorites.html9
-rw-r--r--templates/user_footer.html4
-rw-r--r--templates/user_info.html90
-rw-r--r--templates/user_preferences.html11
-rw-r--r--templates/user_recent.html27
-rw-r--r--templates/user_reputation.html41
-rw-r--r--templates/user_responses.html23
-rw-r--r--templates/user_stats.html134
-rw-r--r--templates/user_tabs.html34
-rw-r--r--templates/user_votes.html31
-rw-r--r--templates/users.html73
-rw-r--r--templates/users_questions.html66
-rw-r--r--urls.py63
-rw-r--r--utils/__init__.py0
-rw-r--r--utils/cache.py92
-rw-r--r--utils/html.py51
-rw-r--r--utils/lists.py86
209 files changed, 35140 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..e23e4b67
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,12 @@
+Ȩ(c) 2008 CNProg.com
+
+2.0汾Apache֤("֤")Ȩ
+ݱ֤ûԲʹôļ
+ûɴַ֤
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+÷Ҫͬ⣬
+ַ֤ǻ"ԭ"ṩ
+κʾĻʾı֤
+֤£ضԵĹϽȨ޺ơ \ No newline at end of file
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/__init__.py
diff --git a/cnprog.wsgi b/cnprog.wsgi
new file mode 100644
index 00000000..a1bd8039
--- /dev/null
+++ b/cnprog.wsgi
@@ -0,0 +1,8 @@
+import os
+import sys
+
+sys.path.append('/var/www/vhosts')
+os.environ['DJANGO_SETTINGS_MODULE'] = 'cnprog.settings'
+
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/django_authopenid/__init__.py b/django_authopenid/__init__.py
new file mode 100644
index 00000000..ff040ed7
--- /dev/null
+++ b/django_authopenid/__init__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007, 2008, Benoît Chesneau
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# * notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# * notice, this list of conditions and the following disclaimer in the
+# * documentation and/or other materials provided with the
+# * distribution. Neither the name of the <ORGANIZATION> nor the names
+# * of its contributors may be used to endorse or promote products
+# * derived from this software without specific prior written
+# * permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""
+Django authentification application to *with openid using django auth contrib/.
+
+This application allow a user to connect to you website with :
+ * legacy account : username/password
+ * openid url
+"""
+
+__version__ = "0.9.4"
diff --git a/django_authopenid/admin.py b/django_authopenid/admin.py
new file mode 100644
index 00000000..f64ee579
--- /dev/null
+++ b/django_authopenid/admin.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+from django.contrib import admin
+from django_authopenid.models import UserAssociation
+
+
+class UserAssociationAdmin(admin.ModelAdmin):
+ """User association admin class"""
+admin.site.register(UserAssociation, UserAssociationAdmin) \ No newline at end of file
diff --git a/django_authopenid/forms.py b/django_authopenid/forms.py
new file mode 100644
index 00000000..09fa76b1
--- /dev/null
+++ b/django_authopenid/forms.py
@@ -0,0 +1,435 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007, 2008, Benoît Chesneau
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# * notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# * notice, this list of conditions and the following disclaimer in the
+# * documentation and/or other materials provided with the
+# * distribution. Neither the name of the <ORGANIZATION> nor the names
+# * of its contributors may be used to endorse or promote products
+# * derived from this software without specific prior written
+# * permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+from django import forms
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate
+from django.utils.translation import ugettext as _
+from django.conf import settings
+
+import re
+
+
+# needed for some linux distributions like debian
+try:
+ from openid.yadis import xri
+except ImportError:
+ from yadis import xri
+
+from django_authopenid.util import clean_next
+
+__all__ = ['OpenidSigninForm', 'OpenidAuthForm', 'OpenidVerifyForm',
+ 'OpenidRegisterForm', 'RegistrationForm', 'ChangepwForm',
+ 'ChangeemailForm', 'EmailPasswordForm', 'DeleteForm',
+ 'ChangeOpenidForm', 'ChangeEmailForm', 'ChangepwForm']
+
+class OpenidSigninForm(forms.Form):
+ """ signin form """
+ openid_url = forms.CharField(max_length=255, widget=forms.widgets.TextInput(attrs={'class': 'openid-login-input', 'size':80}))
+ next = forms.CharField(max_length=255, widget=forms.HiddenInput(), required=False)
+
+ def clean_openid_url(self):
+ """ test if openid is accepted """
+ if 'openid_url' in self.cleaned_data:
+ openid_url = self.cleaned_data['openid_url']
+ if xri.identifierScheme(openid_url) == 'XRI' and getattr(
+ settings, 'OPENID_DISALLOW_INAMES', False
+ ):
+ raise forms.ValidationError(_('i-names are not supported'))
+ return self.cleaned_data['openid_url']
+
+ def clean_next(self):
+ """ validate next """
+ if 'next' in self.cleaned_data and self.cleaned_data['next'] != "":
+ self.cleaned_data['next'] = clean_next(self.cleaned_data['next'])
+ return self.cleaned_data['next']
+
+
+attrs_dict = { 'class': 'required login' }
+username_re = re.compile(r'^\w+$')
+RESERVED_NAMES = (u'fuck', u'shit', u'ass', u'sex', u'add',
+ u'edit', u'save', u'delete', u'manage', u'update', 'remove', 'new')
+
+class OpenidAuthForm(forms.Form):
+ """ legacy account signin form """
+ next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
+ required=False)
+ username = forms.CharField(max_length=30,
+ widget=forms.widgets.TextInput(attrs=attrs_dict))
+ password = forms.CharField(max_length=128,
+ widget=forms.widgets.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s',
+ prefix=None, initial=None):
+ super(OpenidAuthForm, self).__init__(data, files, auto_id,
+ prefix, initial)
+ self.user_cache = None
+
+ def clean_username(self):
+ """ validate username and test if it exists."""
+ if 'username' in self.cleaned_data and \
+ 'openid_url' not in self.cleaned_data:
+ if not username_re.search(self.cleaned_data['username']):
+ raise forms.ValidationError(_("Usernames can only contain \
+ letters, numbers and underscores"))
+ try:
+ user = User.objects.get(
+ username__exact = self.cleaned_data['username']
+ )
+ except User.DoesNotExist:
+ raise forms.ValidationError(_("This username does not exist \
+ in our database. Please choose another."))
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that username. Please try \
+ another.')
+ return self.cleaned_data['username']
+
+ def clean_password(self):
+ """" test if password is valid for this username """
+ if 'username' in self.cleaned_data and \
+ 'password' in self.cleaned_data:
+ self.user_cache = authenticate(
+ username=self.cleaned_data['username'],
+ password=self.cleaned_data['password']
+ )
+ if self.user_cache is None:
+ raise forms.ValidationError(_("Please enter a valid \
+ username and password. Note that both fields are \
+ case-sensitive."))
+ elif self.user_cache.is_active == False:
+ raise forms.ValidationError(_("This account is inactive."))
+ return self.cleaned_data['password']
+
+ def clean_next(self):
+ """ validate next url """
+ if 'next' in self.cleaned_data and \
+ self.cleaned_data['next'] != "":
+ self.cleaned_data['next'] = clean_next(self.cleaned_data['next'])
+ return self.cleaned_data['next']
+
+ def get_user(self):
+ """ get authenticated user """
+ return self.user_cache
+
+
+class OpenidRegisterForm(forms.Form):
+ """ openid signin form """
+ next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
+ required=False)
+ username = forms.CharField(max_length=30,
+ widget=forms.widgets.TextInput(attrs=attrs_dict))
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=200)), label=u'Email address')
+
+ def clean_username(self):
+ """ test if username is valid and exist in database """
+ if 'username' in self.cleaned_data:
+ if not username_re.search(self.cleaned_data['username']):
+ raise forms.ValidationError(_('invalid user name'))
+ if self.cleaned_data['username'] in RESERVED_NAMES:
+ raise forms.ValidationError(_('sorry, this name can not be used, please try another'))
+ if len(self.cleaned_data['username']) < 3:
+ raise forms.ValidationError(_('username too short'))
+ try:
+ user = User.objects.get(
+ username__exact = self.cleaned_data['username']
+ )
+ except User.DoesNotExist:
+ return self.cleaned_data['username']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(_('this name is already in use - please try anoter'))
+ raise forms.ValidationError(_('this name is already in use - please try anoter'))
+
+ def clean_email(self):
+ """For security reason one unique email in database"""
+ if 'email' in self.cleaned_data:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(_("This email is already \
+ registered in our database. Please choose another."))
+
+
+class OpenidVerifyForm(forms.Form):
+ """ openid verify form (associate an openid with an account) """
+ next = forms.CharField(max_length=255, widget = forms.HiddenInput(),
+ required=False)
+ username = forms.CharField(max_length=30,
+ widget=forms.widgets.TextInput(attrs=attrs_dict))
+ password = forms.CharField(max_length=128,
+ widget=forms.widgets.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s',
+ prefix=None, initial=None):
+ super(OpenidVerifyForm, self).__init__(data, files, auto_id,
+ prefix, initial)
+ self.user_cache = None
+
+ def clean_username(self):
+ """ validate username """
+ if 'username' in self.cleaned_data:
+ if not username_re.search(self.cleaned_data['username']):
+ raise forms.ValidationError(_("Usernames can only contain \
+ letters, numbers and underscores"))
+ try:
+ user = User.objects.get(
+ username__exact = self.cleaned_data['username']
+ )
+ except User.DoesNotExist:
+ raise forms.ValidationError(_("This username don't exist. \
+ Please choose another."))
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'Somehow, that username is in \
+ use for multiple accounts. Please contact us to get this \
+ problem resolved.')
+ return self.cleaned_data['username']
+
+ def clean_password(self):
+ """ test if password is valid for this user """
+ if 'username' in self.cleaned_data and \
+ 'password' in self.cleaned_data:
+ self.user_cache = authenticate(
+ username = self.cleaned_data['username'],
+ password = self.cleaned_data['password']
+ )
+ if self.user_cache is None:
+ raise forms.ValidationError(_("Please enter a valid \
+ username and password. Note that both fields are \
+ case-sensitive."))
+ elif self.user_cache.is_active == False:
+ raise forms.ValidationError(_("This account is inactive."))
+ return self.cleaned_data['password']
+
+ def get_user(self):
+ """ get authenticated user """
+ return self.user_cache
+
+
+attrs_dict = { 'class': 'required' }
+username_re = re.compile(r'^\w+$')
+
+class RegistrationForm(forms.Form):
+ """ legacy registration form """
+
+ next = forms.CharField(max_length=255, widget=forms.HiddenInput(),
+ required=False)
+ username = forms.CharField(max_length=30,
+ widget=forms.TextInput(attrs=attrs_dict),
+ label=_('choose a username'))
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=200)), label=_('your email address'))
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=_('choose password'))
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict),
+ label=_('retype password'))
+
+ def clean_username(self):
+ """
+ Validates that the username is alphanumeric and is not already
+ in use.
+
+ """
+ if 'username' in self.cleaned_data:
+ if not username_re.search(self.cleaned_data['username']):
+ raise forms.ValidationError(u'Usernames can only contain \
+ letters, numbers and underscores')
+ try:
+ user = User.objects.get(
+ username__exact = self.cleaned_data['username']
+ )
+
+ except User.DoesNotExist:
+ return self.cleaned_data['username']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'Somehow, that username is in \
+ use for multiple accounts. Please contact us to get this \
+ problem resolved.')
+ raise forms.ValidationError(u'This username is already taken. \
+ Please choose another.')
+
+ def clean_email(self):
+ """ validate if email exist in database
+ :return: raise error if it exist """
+ if 'email' in self.cleaned_data:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(u'This email is already registered \
+ in our database. Please choose another.')
+ return self.cleaned_data['email']
+
+ def clean_password2(self):
+ """
+ Validates that the two password inputs match.
+
+ """
+ if 'password1' in self.cleaned_data and \
+ 'password2' in self.cleaned_data and \
+ self.cleaned_data['password1'] == \
+ self.cleaned_data['password2']:
+ return self.cleaned_data['password2']
+ raise forms.ValidationError(u'You must type the same password each \
+ time')
+
+
+class ChangepwForm(forms.Form):
+ """ change password form """
+ oldpw = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, user=None, *args, **kwargs):
+ if user is None:
+ raise TypeError("Keyword argument 'user' must be supplied")
+ super(ChangepwForm, self).__init__(data, *args, **kwargs)
+ self.user = user
+
+ def clean_oldpw(self):
+ """ test old password """
+ if not self.user.check_password(self.cleaned_data['oldpw']):
+ raise forms.ValidationError(_("Old password is incorrect. \
+ Please enter the correct password."))
+ return self.cleaned_data['oldpw']
+
+ def clean_password2(self):
+ """
+ Validates that the two password inputs match.
+ """
+ if 'password1' in self.cleaned_data and \
+ 'password2' in self.cleaned_data and \
+ self.cleaned_data['password1'] == self.cleaned_data['password2']:
+ return self.cleaned_data['password2']
+ raise forms.ValidationError(_("new passwords do not match"))
+
+
+class ChangeemailForm(forms.Form):
+ """ change email form """
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=200)), label=u'Email address')
+ password = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, \
+ initial=None, user=None):
+ if user is None:
+ raise TypeError("Keyword argument 'user' must be supplied")
+ super(ChangeemailForm, self).__init__(data, files, auto_id,
+ prefix, initial)
+ self.test_openid = False
+ self.user = user
+
+
+ def clean_email(self):
+ """ check if email don't exist """
+ if 'email' in self.cleaned_data:
+ if self.user.email != self.cleaned_data['email']:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(u'There is already more than one \
+ account registered with that e-mail address. Please try \
+ another.')
+ raise forms.ValidationError(u'This email is already registered \
+ in our database. Please choose another.')
+ return self.cleaned_data['email']
+
+
+ def clean_password(self):
+ """ check if we have to test a legacy account or not """
+ if 'password' in self.cleaned_data:
+ if not self.user.check_password(self.cleaned_data['password']):
+ self.test_openid = True
+ return self.cleaned_data['password']
+
+class ChangeopenidForm(forms.Form):
+ """ change openid form """
+ openid_url = forms.CharField(max_length=255,
+ widget=forms.TextInput(attrs={'class': "required" }))
+
+ def __init__(self, data=None, user=None, *args, **kwargs):
+ if user is None:
+ raise TypeError("Keyword argument 'user' must be supplied")
+ super(ChangeopenidForm, self).__init__(data, *args, **kwargs)
+ self.user = user
+
+class DeleteForm(forms.Form):
+ """ confirm form to delete an account """
+ confirm = forms.CharField(widget=forms.CheckboxInput(attrs=attrs_dict))
+ password = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s',
+ prefix=None, initial=None, user=None):
+ super(DeleteForm, self).__init__(data, files, auto_id, prefix, initial)
+ self.test_openid = False
+ self.user = user
+
+ def clean_password(self):
+ """ check if we have to test a legacy account or not """
+ if 'password' in self.cleaned_data:
+ if not self.user.check_password(self.cleaned_data['password']):
+ self.test_openid = True
+ return self.cleaned_data['password']
+
+
+class EmailPasswordForm(forms.Form):
+ """ send new password form """
+ username = forms.CharField(max_length=30,
+ widget=forms.TextInput(attrs={'class': "required" }))
+
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
+ initial=None):
+ super(EmailPasswordForm, self).__init__(data, files, auto_id,
+ prefix, initial)
+ self.user_cache = None
+
+
+ def clean_username(self):
+ """ get user for this username """
+ if 'username' in self.cleaned_data:
+ try:
+ self.user_cache = User.objects.get(
+ username = self.cleaned_data['username'])
+ except:
+ raise forms.ValidationError(_("Incorrect username."))
+ return self.cleaned_data['username']
diff --git a/django_authopenid/middleware.py b/django_authopenid/middleware.py
new file mode 100644
index 00000000..c0572c6e
--- /dev/null
+++ b/django_authopenid/middleware.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from django_authopenid import mimeparse
+from django.http import HttpResponseRedirect
+from django.core.urlresolvers import reverse
+
+__all__ = ["OpenIDMiddleware"]
+
+class OpenIDMiddleware(object):
+ """
+ Populate request.openid. This comes either from cookie or from
+ session, depending on the presence of OPENID_USE_SESSIONS.
+ """
+ def process_request(self, request):
+ request.openid = request.session.get('openid', None)
+
+ def process_response(self, request, response):
+ if response.status_code != 200 or len(response.content) < 200:
+ return response
+ path = request.get_full_path()
+ if path == "/" and request.META.has_key('HTTP_ACCEPT') and \
+ mimeparse.best_match(['text/html', 'application/xrds+xml'],
+ request.META['HTTP_ACCEPT']) == 'application/xrds+xml':
+ return HttpResponseRedirect(reverse('yadis_xrdf'))
+ return response \ No newline at end of file
diff --git a/django_authopenid/mimeparse.py b/django_authopenid/mimeparse.py
new file mode 100644
index 00000000..ab02eab0
--- /dev/null
+++ b/django_authopenid/mimeparse.py
@@ -0,0 +1,160 @@
+"""MIME-Type Parser
+
+This module provides basic functions for handling mime-types. It can handle
+matching mime-types against a list of media-ranges. See section 14.1 of
+the HTTP specification [RFC 2616] for a complete explaination.
+
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
+
+Contents:
+ - parse_mime_type(): Parses a mime-type into it's component parts.
+ - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
+ - quality(): Determines the quality ('q') of a mime-type when compared against a list of media-ranges.
+ - quality_parsed(): Just like quality() except the second parameter must be pre-parsed.
+ - best_match(): Choose the mime-type with the highest quality ('q') from a list of candidates.
+"""
+
+__version__ = "0.1.1"
+__author__ = 'Joe Gregorio'
+__email__ = "joe@bitworking.org"
+__credits__ = ""
+
+def parse_mime_type(mime_type):
+ """Carves up a mime_type and returns a tuple of the
+ (type, subtype, params) where 'params' is a dictionary
+ of all the parameters for the media range.
+ For example, the media range 'application/xhtml;q=0.5' would
+ get parsed into:
+
+ ('application', 'xhtml', {'q', '0.5'})
+ """
+ parts = mime_type.split(";")
+ params = dict([tuple([s.strip() for s in param.split("=")])\
+ for param in parts[1:] ])
+ (type, subtype) = parts[0].split("/")
+ return (type.strip(), subtype.strip(), params)
+
+def parse_media_range(range):
+ """Carves up a media range and returns a tuple of the
+ (type, subtype, params) where 'params' is a dictionary
+ of all the parameters for the media range.
+ For example, the media range 'application/*;q=0.5' would
+ get parsed into:
+
+ ('application', '*', {'q', '0.5'})
+
+ In addition this function also guarantees that there
+ is a value for 'q' in the params dictionary, filling it
+ in with a proper default if necessary.
+ """
+ (type, subtype, params) = parse_mime_type(range)
+ if not params.has_key('q') or not params['q'] or \
+ not float(params['q']) or float(params['q']) > 1\
+ or float(params['q']) < 0:
+ params['q'] = '1'
+ return (type, subtype, params)
+
+def quality_parsed(mime_type, parsed_ranges):
+ """Find the best match for a given mime_type against
+ a list of media_ranges that have already been
+ parsed by parse_media_range(). Returns the
+ 'q' quality parameter of the best match, 0 if no
+ match was found. This function bahaves the same as quality()
+ except that 'parsed_ranges' must be a list of
+ parsed media ranges. """
+ best_fitness = -1
+ best_match = ""
+ best_fit_q = 0
+ (target_type, target_subtype, target_params) =\
+ parse_media_range(mime_type)
+ for (type, subtype, params) in parsed_ranges:
+ param_matches = reduce(lambda x, y: x+y, [1 for (key, value) in \
+ target_params.iteritems() if key != 'q' and \
+ params.has_key(key) and value == params[key]], 0)
+ if (type == target_type or type == '*' or target_type == '*') and \
+ (subtype == target_subtype or subtype == '*' or target_subtype == '*'):
+ fitness = (type == target_type) and 100 or 0
+ fitness += (subtype == target_subtype) and 10 or 0
+ fitness += param_matches
+ if fitness > best_fitness:
+ best_fitness = fitness
+ best_fit_q = params['q']
+
+ return float(best_fit_q)
+
+def quality(mime_type, ranges):
+ """Returns the quality 'q' of a mime_type when compared
+ against the media-ranges in ranges. For example:
+
+ >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
+ 0.7
+
+ """
+ parsed_ranges = [parse_media_range(r) for r in ranges.split(",")]
+ return quality_parsed(mime_type, parsed_ranges)
+
+def best_match(supported, header):
+ """Takes a list of supported mime-types and finds the best
+ match for all the media-ranges listed in header. The value of
+ header must be a string that conforms to the format of the
+ HTTP Accept: header. The value of 'supported' is a list of
+ mime-types.
+
+ >>> best_match(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1')
+ 'text/xml'
+ """
+ parsed_header = [parse_media_range(r) for r in header.split(",")]
+ weighted_matches = [(quality_parsed(mime_type, parsed_header), mime_type)\
+ for mime_type in supported]
+ weighted_matches.sort()
+ return weighted_matches[-1][0] and weighted_matches[-1][1] or ''
+
+if __name__ == "__main__":
+ import unittest
+
+ class TestMimeParsing(unittest.TestCase):
+
+ def test_parse_media_range(self):
+ self.assert_(('application', 'xml', {'q': '1'}) == parse_media_range('application/xml;q=1'))
+ self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml'))
+ self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml;q='))
+ self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml ; q='))
+ self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=1;b=other'))
+ self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=2;b=other'))
+
+ def test_rfc_2616_example(self):
+ accept = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5"
+ self.assertEqual(1, quality("text/html;level=1", accept))
+ self.assertEqual(0.7, quality("text/html", accept))
+ self.assertEqual(0.3, quality("text/plain", accept))
+ self.assertEqual(0.5, quality("image/jpeg", accept))
+ self.assertEqual(0.4, quality("text/html;level=2", accept))
+ self.assertEqual(0.7, quality("text/html;level=3", accept))
+
+ def test_best_match(self):
+ mime_types_supported = ['application/xbel+xml', 'application/xml']
+ # direct match
+ self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml'), 'application/xbel+xml')
+ # direct match with a q parameter
+ self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml; q=1'), 'application/xbel+xml')
+ # direct match of our second choice with a q parameter
+ self.assertEqual(best_match(mime_types_supported, 'application/xml; q=1'), 'application/xml')
+ # match using a subtype wildcard
+ self.assertEqual(best_match(mime_types_supported, 'application/*; q=1'), 'application/xml')
+ # match using a type wildcard
+ self.assertEqual(best_match(mime_types_supported, '*/*'), 'application/xml')
+
+ mime_types_supported = ['application/xbel+xml', 'text/xml']
+ # match using a type versus a lower weighted subtype
+ self.assertEqual(best_match(mime_types_supported, 'text/*;q=0.5,*/*; q=0.1'), 'text/xml')
+ # fail to match anything
+ self.assertEqual(best_match(mime_types_supported, 'text/html,application/atom+xml; q=0.9'), '')
+
+ def test_support_wildcards(self):
+ mime_types_supported = ['image/*', 'application/xml']
+ # match using a type wildcard
+ self.assertEqual(best_match(mime_types_supported, 'image/png'), 'image/*')
+ # match using a wildcard for both requested and supported
+ self.assertEqual(best_match(mime_types_supported, 'image/*'), 'image/*')
+
+ unittest.main() \ No newline at end of file
diff --git a/django_authopenid/models.py b/django_authopenid/models.py
new file mode 100644
index 00000000..9826c452
--- /dev/null
+++ b/django_authopenid/models.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.db import models
+
+import md5, random, sys, os, time
+
+__all__ = ['Nonce', 'Association', 'UserAssociation',
+ 'UserPasswordQueueManager', 'UserPasswordQueue']
+
+class Nonce(models.Model):
+ """ openid nonce """
+ server_url = models.CharField(max_length=255)
+ timestamp = models.IntegerField()
+ salt = models.CharField(max_length=40)
+
+ def __unicode__(self):
+ return u"Nonce: %s" % self.id
+
+
+class Association(models.Model):
+ """ association openid url and lifetime """
+ server_url = models.TextField(max_length=2047)
+ handle = models.CharField(max_length=255)
+ secret = models.TextField(max_length=255) # Stored base64 encoded
+ issued = models.IntegerField()
+ lifetime = models.IntegerField()
+ assoc_type = models.TextField(max_length=64)
+
+ def __unicode__(self):
+ return u"Association: %s, %s" % (self.server_url, self.handle)
+
+class UserAssociation(models.Model):
+ """
+ model to manage association between openid and user
+ """
+ openid_url = models.CharField(blank=False, max_length=255)
+ user = models.ForeignKey(User, unique=True)
+
+ def __unicode__(self):
+ return "Openid %s with user %s" % (self.openid_url, self.user)
+
+class UserPasswordQueueManager(models.Manager):
+ """ manager for UserPasswordQueue object """
+ def get_new_confirm_key(self):
+ "Returns key that isn't being used."
+ # The random module is seeded when this Apache child is created.
+ # Use SECRET_KEY as added salt.
+ while 1:
+ confirm_key = md5.new("%s%s%s%s" % (
+ random.randint(0, sys.maxint - 1), os.getpid(),
+ time.time(), settings.SECRET_KEY)).hexdigest()
+ try:
+ self.get(confirm_key=confirm_key)
+ except self.model.DoesNotExist:
+ break
+ return confirm_key
+
+
+class UserPasswordQueue(models.Model):
+ """
+ model for new password queue.
+ """
+ user = models.ForeignKey(User, unique=True)
+ new_password = models.CharField(max_length=30)
+ confirm_key = models.CharField(max_length=40)
+
+ objects = UserPasswordQueueManager()
+
+ def __unicode__(self):
+ return self.user.username
diff --git a/django_authopenid/urls.py b/django_authopenid/urls.py
new file mode 100644
index 00000000..1ab65941
--- /dev/null
+++ b/django_authopenid/urls.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+from django.conf.urls.defaults import patterns, url
+from django.utils.translation import ugettext as _
+
+urlpatterns = patterns('django_authopenid.views',
+ # yadis rdf
+ url(r'^yadis.xrdf$', 'xrdf', name='yadis_xrdf'),
+ # manage account registration
+ url(r'^%s$' % _('signin/'), 'signin', name='user_signin'),
+ url(r'^%s$' % _('signout/'), 'signout', name='user_signout'),
+ url(r'^%s%s$' % (_('signin/'), _('complete/')), 'complete_signin',
+ name='user_complete_signin'),
+ url(r'^%s$' % _('register/'), 'register', name='user_register'),
+ url(r'^%s$' % _('signup/'), 'signup', name='user_signup'),
+ #disable current sendpw function
+ url(r'^%s$' % _('sendpw/'), 'signin', name='user_sendpw'),
+ #url(r'^%s$' % _('sendpw/'), 'sendpw', name='user_sendpw'),
+ #url(r'^%s%s$' % (_('password/'), _('confirm/')), 'confirmchangepw',
+ # name='user_confirmchangepw'),
+
+ # manage account settings
+ #url(r'^$', 'account_settings', name='user_account_settings'),
+ #url(r'^%s$' % _('password/'), 'changepw', name='user_changepw'),
+ #url(r'^%s$' % _('email/'), 'changeemail', name='user_changeemail'),
+ #url(r'^%s$' % _('openid/'), 'changeopenid', name='user_changeopenid'),
+ url(r'^%s$' % _('delete/'), 'delete', name='user_delete'),
+)
diff --git a/django_authopenid/util.py b/django_authopenid/util.py
new file mode 100644
index 00000000..841a81c7
--- /dev/null
+++ b/django_authopenid/util.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+from openid.store.interface import OpenIDStore
+from openid.association import Association as OIDAssociation
+from openid.extensions import sreg
+import openid.store
+
+from django.db.models.query import Q
+from django.conf import settings
+from django.http import str_to_unicode
+
+
+# needed for some linux distributions like debian
+try:
+ from openid.yadis import xri
+except:
+ from yadis import xri
+
+import time, base64, md5, operator
+import urllib
+
+from models import Association, Nonce
+
+__all__ = ['OpenID', 'DjangoOpenIDStore', 'from_openid_response', 'clean_next']
+
+DEFAULT_NEXT = getattr(settings, 'OPENID_REDIRECT_NEXT', '/')
+def clean_next(next):
+ if next is None:
+ return DEFAULT_NEXT
+ next = str_to_unicode(urllib.unquote(next), 'utf-8')
+ next = next.strip()
+ if next.startswith('/'):
+ return next
+ return DEFAULT_NEXT
+
+class OpenID:
+ def __init__(self, openid_, issued, attrs=None, sreg_=None):
+ self.openid = openid_
+ self.issued = issued
+ self.attrs = attrs or {}
+ self.sreg = sreg_ or {}
+ self.is_iname = (xri.identifierScheme(openid_) == 'XRI')
+
+ def __repr__(self):
+ return '<OpenID: %s>' % self.openid
+
+ def __str__(self):
+ return self.openid
+
+class DjangoOpenIDStore(OpenIDStore):
+ def __init__(self):
+ self.max_nonce_age = 6 * 60 * 60 # Six hours
+
+ def storeAssociation(self, server_url, association):
+ assoc = Association(
+ server_url = server_url,
+ handle = association.handle,
+ secret = base64.encodestring(association.secret),
+ issued = association.issued,
+ lifetime = association.issued,
+ assoc_type = association.assoc_type
+ )
+ assoc.save()
+
+ def getAssociation(self, server_url, handle=None):
+ assocs = []
+ if handle is not None:
+ assocs = Association.objects.filter(
+ server_url = server_url, handle = handle
+ )
+ else:
+ assocs = Association.objects.filter(
+ server_url = server_url
+ )
+ if not assocs:
+ return None
+ associations = []
+ for assoc in assocs:
+ association = OIDAssociation(
+ assoc.handle, base64.decodestring(assoc.secret), assoc.issued,
+ assoc.lifetime, assoc.assoc_type
+ )
+ if association.getExpiresIn() == 0:
+ self.removeAssociation(server_url, assoc.handle)
+ else:
+ associations.append((association.issued, association))
+ if not associations:
+ return None
+ return associations[-1][1]
+
+ def removeAssociation(self, server_url, handle):
+ assocs = list(Association.objects.filter(
+ server_url = server_url, handle = handle
+ ))
+ assocs_exist = len(assocs) > 0
+ for assoc in assocs:
+ assoc.delete()
+ return assocs_exist
+
+ def useNonce(self, server_url, timestamp, salt):
+ if abs(timestamp - time.time()) > openid.store.nonce.SKEW:
+ return False
+
+ query = [
+ Q(server_url__exact=server_url),
+ Q(timestamp__exact=timestamp),
+ Q(salt__exact=salt),
+ ]
+ try:
+ ononce = Nonce.objects.get(reduce(operator.and_, query))
+ except Nonce.DoesNotExist:
+ ononce = Nonce(
+ server_url=server_url,
+ timestamp=timestamp,
+ salt=salt
+ )
+ ononce.save()
+ return True
+
+ ononce.delete()
+
+ return False
+
+ def cleanupNonce(self):
+ Nonce.objects.filter(timestamp<int(time.time()) - nonce.SKEW).delete()
+
+ def cleanupAssociations(self):
+ Association.objects.extra(where=['issued + lifetimeint<(%s)' % time.time()]).delete()
+
+ def getAuthKey(self):
+ # Use first AUTH_KEY_LEN characters of md5 hash of SECRET_KEY
+ return md5.new(settings.SECRET_KEY).hexdigest()[:self.AUTH_KEY_LEN]
+
+ def isDumb(self):
+ return False
+
+def from_openid_response(openid_response):
+ """ return openid object from response """
+ issued = int(time.time())
+ sreg_resp = sreg.SRegResponse.fromSuccessResponse(openid_response) \
+ or []
+
+ return OpenID(
+ openid_response.identity_url, issued, openid_response.signed_fields,
+ dict(sreg_resp)
+ )
diff --git a/django_authopenid/views.py b/django_authopenid/views.py
new file mode 100644
index 00000000..a9072c12
--- /dev/null
+++ b/django_authopenid/views.py
@@ -0,0 +1,787 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2007, 2008, Benoît Chesneau
+# Copyright (c) 2007 Simon Willison, original work on django-openid
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# * notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# * notice, this list of conditions and the following disclaimer in the
+# * documentation and/or other materials provided with the
+# * distribution. Neither the name of the <ORGANIZATION> nor the names
+# * of its contributors may be used to endorse or promote products
+# * derived from this software without specific prior written
+# * permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from django.http import HttpResponseRedirect, get_host
+from django.shortcuts import render_to_response as render
+from django.template import RequestContext, loader, Context
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.auth import login, logout
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+from django.utils.encoding import smart_unicode
+from django.utils.html import escape
+from django.utils.translation import ugettext as _
+from django.contrib.sites.models import Site
+from django.utils.http import urlquote_plus
+from django.core.mail import send_mail
+
+from openid.consumer.consumer import Consumer, \
+ SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
+from openid.consumer.discover import DiscoveryFailure
+from openid.extensions import sreg
+# needed for some linux distributions like debian
+try:
+ from openid.yadis import xri
+except ImportError:
+ from yadis import xri
+
+import re
+import urllib
+
+
+from django_authopenid.util import OpenID, DjangoOpenIDStore, from_openid_response, clean_next
+from django_authopenid.models import UserAssociation, UserPasswordQueue
+from django_authopenid.forms import OpenidSigninForm, OpenidAuthForm, OpenidRegisterForm, \
+ OpenidVerifyForm, RegistrationForm, ChangepwForm, ChangeemailForm, \
+ ChangeopenidForm, DeleteForm, EmailPasswordForm
+
+def get_url_host(request):
+ if request.is_secure():
+ protocol = 'https'
+ else:
+ protocol = 'http'
+ host = escape(get_host(request))
+ return '%s://%s' % (protocol, host)
+
+def get_full_url(request):
+ return get_url_host(request) + request.get_full_path()
+
+
+
+def ask_openid(request, openid_url, redirect_to, on_failure=None,
+ sreg_request=None):
+ """ basic function to ask openid and return response """
+ request.encoding = 'UTF-8'
+ on_failure = on_failure or signin_failure
+
+ trust_root = getattr(
+ settings, 'OPENID_TRUST_ROOT', get_url_host(request) + '/'
+ )
+ if xri.identifierScheme(openid_url) == 'XRI' and getattr(
+ settings, 'OPENID_DISALLOW_INAMES', False
+ ):
+ msg = _("i-names are not supported")
+ return on_failure(request, msg)
+ consumer = Consumer(request.session, DjangoOpenIDStore())
+ try:
+ auth_request = consumer.begin(openid_url)
+ except DiscoveryFailure:
+ msg = _(u"非法OpenID地址: %s" % openid_url)
+ return on_failure(request, msg)
+
+ if sreg_request:
+ auth_request.addExtension(sreg_request)
+ redirect_url = auth_request.redirectURL(trust_root, redirect_to)
+ return HttpResponseRedirect(redirect_url)
+
+def complete(request, on_success=None, on_failure=None, return_to=None):
+ """ complete openid signin """
+ on_success = on_success or default_on_success
+ on_failure = on_failure or default_on_failure
+
+ consumer = Consumer(request.session, DjangoOpenIDStore())
+ # make sure params are encoded in utf8
+ params = dict((k,smart_unicode(v)) for k, v in request.GET.items())
+ openid_response = consumer.complete(params, return_to)
+
+
+ if openid_response.status == SUCCESS:
+ return on_success(request, openid_response.identity_url,
+ openid_response)
+ elif openid_response.status == CANCEL:
+ return on_failure(request, 'The request was canceled')
+ elif openid_response.status == FAILURE:
+ return on_failure(request, openid_response.message)
+ elif openid_response.status == SETUP_NEEDED:
+ return on_failure(request, 'Setup needed')
+ else:
+ assert False, "Bad openid status: %s" % openid_response.status
+
+def default_on_success(request, identity_url, openid_response):
+ """ default action on openid signin success """
+ request.session['openid'] = from_openid_response(openid_response)
+ return HttpResponseRedirect(clean_next(request.GET.get('next')))
+
+def default_on_failure(request, message):
+ """ default failure action on signin """
+ return render('openid_failure.html', {
+ 'message': message
+ })
+
+
+def not_authenticated(func):
+ """ decorator that redirect user to next page if
+ he is already logged."""
+ def decorated(request, *args, **kwargs):
+ if request.user.is_authenticated():
+ next = request.GET.get("next", "/")
+ return HttpResponseRedirect(next)
+ return func(request, *args, **kwargs)
+ return decorated
+
+@not_authenticated
+def signin(request):
+ """
+ signin page. It manage the legacy authentification (user/password)
+ and authentification with openid.
+
+ url: /signin/
+
+ template : authopenid/signin.htm
+ """
+ request.encoding = 'UTF-8'
+ on_failure = signin_failure
+ next = clean_next(request.GET.get('next'))
+
+ form_signin = OpenidSigninForm(initial={'next':next})
+ form_auth = OpenidAuthForm(initial={'next':next})
+
+ if request.POST:
+
+ if 'bsignin' in request.POST.keys():
+
+ form_signin = OpenidSigninForm(request.POST)
+ if form_signin.is_valid():
+ next = clean_next(form_signin.cleaned_data.get('next'))
+ sreg_req = sreg.SRegRequest(optional=['nickname', 'email'])
+ redirect_to = "%s%s?%s" % (
+ get_url_host(request),
+ reverse('user_complete_signin'),
+ urllib.urlencode({'next':next})
+ )
+
+ return ask_openid(request,
+ form_signin.cleaned_data['openid_url'],
+ redirect_to,
+ on_failure=signin_failure,
+ sreg_request=sreg_req)
+
+ elif 'blogin' in request.POST.keys():
+ # perform normal django authentification
+ form_auth = OpenidAuthForm(request.POST)
+ if form_auth.is_valid():
+ user_ = form_auth.get_user()
+ login(request, user_)
+ next = clean_next(form_auth.cleaned_data.get('next'))
+ return HttpResponseRedirect(next)
+
+
+ return render('authopenid/signin.html', {
+ 'form1': form_auth,
+ 'form2': form_signin,
+ 'msg': request.GET.get('msg',''),
+ 'sendpw_url': reverse('user_sendpw'),
+ }, context_instance=RequestContext(request))
+
+def complete_signin(request):
+ """ in case of complete signin with openid """
+ return complete(request, signin_success, signin_failure,
+ get_url_host(request) + reverse('user_complete_signin'))
+
+
+def signin_success(request, identity_url, openid_response):
+ """
+ openid signin success.
+
+ If the openid is already registered, the user is redirected to
+ url set par next or in settings with OPENID_REDIRECT_NEXT variable.
+ If none of these urls are set user is redirectd to /.
+
+ if openid isn't registered user is redirected to register page.
+ """
+
+ openid_ = from_openid_response(openid_response)
+ request.session['openid'] = openid_
+ try:
+ rel = UserAssociation.objects.get(openid_url__exact = str(openid_))
+ except:
+ # try to register this new user
+ return register(request)
+ user_ = rel.user
+ if user_.is_active:
+ user_.backend = "django.contrib.auth.backends.ModelBackend"
+ login(request, user_)
+
+ next = clean_next(request.GET.get('next'))
+ return HttpResponseRedirect(next)
+
+def is_association_exist(openid_url):
+ """ test if an openid is already in database """
+ is_exist = True
+ try:
+ uassoc = UserAssociation.objects.get(openid_url__exact = openid_url)
+ except:
+ is_exist = False
+ return is_exist
+
+@not_authenticated
+def register(request):
+ """
+ register an openid.
+
+ If user is already a member he can associate its openid with
+ its account.
+
+ A new account could also be created and automaticaly associated
+ to the openid.
+
+ url : /complete/
+
+ template : authopenid/complete.html
+ """
+
+ is_redirect = False
+ next = clean_next(request.GET.get('next'))
+ openid_ = request.session.get('openid', None)
+ if not openid_:
+ return HttpResponseRedirect(reverse('user_signin') + next)
+
+ nickname = openid_.sreg.get('nickname', '')
+ email = openid_.sreg.get('email', '')
+
+ form1 = OpenidRegisterForm(initial={
+ 'next': next,
+ 'username': nickname,
+ 'email': email,
+ })
+ form2 = OpenidVerifyForm(initial={
+ 'next': next,
+ 'username': nickname,
+ })
+
+ if request.POST:
+ just_completed = False
+ if 'bnewaccount' in request.POST.keys():
+ form1 = OpenidRegisterForm(request.POST)
+ if form1.is_valid():
+ next = clean_next(form1.cleaned_data.get('next'))
+ is_redirect = True
+ tmp_pwd = User.objects.make_random_password()
+ user_ = User.objects.create_user(form1.cleaned_data['username'],
+ form1.cleaned_data['email'], tmp_pwd)
+
+ # make association with openid
+ uassoc = UserAssociation(openid_url=str(openid_),
+ user_id=user_.id)
+ uassoc.save()
+
+ # login
+ user_.backend = "django.contrib.auth.backends.ModelBackend"
+ login(request, user_)
+ elif 'bverify' in request.POST.keys():
+ form2 = OpenidVerifyForm(request.POST)
+ if form2.is_valid():
+ is_redirect = True
+ next = clean_next(form2.cleaned_data.get('next'))
+ user_ = form2.get_user()
+
+ uassoc = UserAssociation(openid_url=str(openid_),
+ user_id=user_.id)
+ uassoc.save()
+ login(request, user_)
+
+ # redirect, can redirect only if forms are valid.
+ if is_redirect:
+ return HttpResponseRedirect(next)
+
+ return render('authopenid/complete.html', {
+ 'form1': form1,
+ 'form2': form2,
+ 'nickname': nickname,
+ 'email': email
+ }, context_instance=RequestContext(request))
+
+def signin_failure(request, message):
+ """
+ falure with openid signin. Go back to signin page.
+
+ template : "authopenid/signin.html"
+ """
+ next = clean_next(request.GET.get('next'))
+ form_signin = OpenidSigninForm(initial={'next': next})
+ form_auth = OpenidAuthForm(initial={'next': next})
+
+ return render('authopenid/signin.html', {
+ 'msg': message,
+ 'form1': form_auth,
+ 'form2': form_signin,
+ }, context_instance=RequestContext(request))
+
+@not_authenticated
+def signup(request):
+ """
+ signup page. Create a legacy account
+
+ url : /signup/"
+
+ templates: authopenid/signup.html, authopenid/confirm_email.txt
+ """
+ action_signin = reverse('user_signin')
+ next = clean_next(request.GET.get('next'))
+ form = RegistrationForm(initial={'next':next})
+ form_signin = OpenidSigninForm(initial={'next':next})
+
+ if request.POST:
+ form = RegistrationForm(request.POST)
+ if form.is_valid():
+ next = clean_next(form.cleaned_data.get('next'))
+ user_ = User.objects.create_user( form.cleaned_data['username'],
+ form.cleaned_data['email'], form.cleaned_data['password1'])
+
+ user_.backend = "django.contrib.auth.backends.ModelBackend"
+ login(request, user_)
+
+ # send email
+ current_domain = Site.objects.get_current().domain
+ subject = _("Welcome")
+ message_template = loader.get_template(
+ 'authopenid/confirm_email.txt'
+ )
+ message_context = Context({
+ 'site_url': 'http://%s/' % current_domain,
+ 'username': form.cleaned_data['username'],
+ 'password': form.cleaned_data['password1']
+ })
+ message = message_template.render(message_context)
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
+ [user_.email])
+
+ return HttpResponseRedirect(next)
+
+ return render('authopenid/signup.html', {
+ 'form': form,
+ 'form2': form_signin,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def signout(request):
+ """
+ signout from the website. Remove openid from session and kill it.
+
+ url : /signout/"
+ """
+ try:
+ del request.session['openid']
+ except KeyError:
+ pass
+ next = clean_next(request.GET.get('next'))
+ logout(request)
+
+ return HttpResponseRedirect(next)
+
+def xrdf(request):
+ url_host = get_url_host(request)
+ return_to = [
+ "%s%s" % (url_host, reverse('user_complete_signin'))
+ ]
+ return render('authopenid/yadis.xrdf', {
+ 'return_to': return_to
+ }, context_instance=RequestContext(request))
+
+@login_required
+def account_settings(request):
+ """
+ index pages to changes some basic account settings :
+ - change password
+ - change email
+ - associate a new openid
+ - delete account
+
+ url : /
+
+ template : authopenid/settings.html
+ """
+ msg = request.GET.get('msg', '')
+ is_openid = True
+
+ try:
+ uassoc = UserAssociation.objects.get(
+ user__username__exact=request.user.username
+ )
+ except:
+ is_openid = False
+
+
+ return render('authopenid/settings.html', {
+ 'msg': msg,
+ 'is_openid': is_openid
+ }, context_instance=RequestContext(request))
+
+@login_required
+def changepw(request):
+ """
+ change password view.
+
+ url : /changepw/
+ template: authopenid/changepw.html
+ """
+
+ user_ = request.user
+
+ if request.POST:
+ form = ChangepwForm(request.POST, user=user_)
+ if form.is_valid():
+ user_.set_password(form.cleaned_data['password1'])
+ user_.save()
+ msg = _("Password changed.")
+ redirect = "%s?msg=%s" % (
+ reverse('user_account_settings'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+ else:
+ form = ChangepwForm(user=user_)
+
+ return render('authopenid/changepw.html', {'form': form },
+ context_instance=RequestContext(request))
+
+@login_required
+def changeemail(request):
+ """
+ changeemail view. It require password or openid to allow change.
+
+ url: /changeemail/
+
+ template : authopenid/changeemail.html
+ """
+ msg = request.GET.get('msg', '')
+ extension_args = {}
+ user_ = request.user
+
+ redirect_to = get_url_host(request) + reverse('user_changeemail')
+
+ if request.POST:
+ form = ChangeemailForm(request.POST, user=user_)
+ if form.is_valid():
+ if not form.test_openid:
+ user_.email = form.cleaned_data['email']
+ user_.save()
+ msg = _("Email changed.")
+ redirect = "%s?msg=%s" % (reverse('user_account_settings'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+ else:
+ request.session['new_email'] = form.cleaned_data['email']
+ return ask_openid(request, form.cleaned_data['password'],
+ redirect_to, on_failure=emailopenid_failure)
+ elif not request.POST and 'openid.mode' in request.GET:
+ return complete(request, emailopenid_success,
+ emailopenid_failure, redirect_to)
+ else:
+ form = ChangeemailForm(initial={'email': user_.email},
+ user=user_)
+
+ return render('authopenid/changeemail.html', {
+ 'form': form,
+ 'msg': msg
+ }, context_instance=RequestContext(request))
+
+
+def emailopenid_success(request, identity_url, openid_response):
+ openid_ = from_openid_response(openid_response)
+
+ user_ = request.user
+ try:
+ uassoc = UserAssociation.objects.get(
+ openid_url__exact=identity_url
+ )
+ except:
+ return emailopenid_failure(request,
+ _("No OpenID %s found associated in our database" % identity_url))
+
+ if uassoc.user.username != request.user.username:
+ return emailopenid_failure(request,
+ _("The OpenID %s isn't associated to current user logged in" %
+ identity_url))
+
+ new_email = request.session.get('new_email', '')
+ if new_email:
+ user_.email = new_email
+ user_.save()
+ del request.session['new_email']
+ msg = _("Email Changed.")
+
+ redirect = "%s?msg=%s" % (reverse('user_account_settings'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+
+def emailopenid_failure(request, message):
+ redirect_to = "%s?msg=%s" % (
+ reverse('user_changeemail'), urlquote_plus(message))
+ return HttpResponseRedirect(redirect_to)
+
+@login_required
+def changeopenid(request):
+ """
+ change openid view. Allow user to change openid
+ associated to its username.
+
+ url : /changeopenid/
+
+ template: authopenid/changeopenid.html
+ """
+
+ extension_args = {}
+ openid_url = ''
+ has_openid = True
+ msg = request.GET.get('msg', '')
+
+ user_ = request.user
+
+ try:
+ uopenid = UserAssociation.objects.get(user=user_)
+ openid_url = uopenid.openid_url
+ except:
+ has_openid = False
+
+ redirect_to = get_url_host(request) + reverse('user_changeopenid')
+ if request.POST and has_openid:
+ form = ChangeopenidForm(request.POST, user=user_)
+ if form.is_valid():
+ return ask_openid(request, form.cleaned_data['openid_url'],
+ redirect_to, on_failure=changeopenid_failure)
+ elif not request.POST and has_openid:
+ if 'openid.mode' in request.GET:
+ return complete(request, changeopenid_success,
+ changeopenid_failure, redirect_to)
+
+ form = ChangeopenidForm(initial={'openid_url': openid_url }, user=user_)
+ return render('authopenid/changeopenid.html', {
+ 'form': form,
+ 'has_openid': has_openid,
+ 'msg': msg
+ }, context_instance=RequestContext(request))
+
+def changeopenid_success(request, identity_url, openid_response):
+ openid_ = from_openid_response(openid_response)
+ is_exist = True
+ try:
+ uassoc = UserAssociation.objects.get(openid_url__exact=identity_url)
+ except:
+ is_exist = False
+
+ if not is_exist:
+ try:
+ uassoc = UserAssociation.objects.get(
+ user__username__exact=request.user.username
+ )
+ uassoc.openid_url = identity_url
+ uassoc.save()
+ except:
+ uassoc = UserAssociation(user=request.user,
+ openid_url=identity_url)
+ uassoc.save()
+ elif uassoc.user.username != request.user.username:
+ return changeopenid_failure(request,
+ _('This OpenID is already associated with another account.'))
+
+ request.session['openids'] = []
+ request.session['openids'].append(openid_)
+
+ msg = _("OpenID %s is now associated with your account." % identity_url)
+ redirect = "%s?msg=%s" % (
+ reverse('user_account_settings'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+
+def changeopenid_failure(request, message):
+ redirect_to = "%s?msg=%s" % (
+ reverse('user_changeopenid'),
+ urlquote_plus(message))
+ return HttpResponseRedirect(redirect_to)
+
+@login_required
+def delete(request):
+ """
+ delete view. Allow user to delete its account. Password/openid are required to
+ confirm it. He should also check the confirm checkbox.
+
+ url : /delete
+
+ template : authopenid/delete.html
+ """
+
+ extension_args = {}
+
+ user_ = request.user
+
+ redirect_to = get_url_host(request) + reverse('user_delete')
+ if request.POST:
+ form = DeleteForm(request.POST, user=user_)
+ if form.is_valid():
+ if not form.test_openid:
+ user_.delete()
+ return signout(request)
+ else:
+ return ask_openid(request, form.cleaned_data['password'],
+ redirect_to, on_failure=deleteopenid_failure)
+ elif not request.POST and 'openid.mode' in request.GET:
+ return complete(request, deleteopenid_success, deleteopenid_failure,
+ redirect_to)
+
+ form = DeleteForm(user=user_)
+
+ msg = request.GET.get('msg','')
+ return render('authopenid/delete.html', {
+ 'form': form,
+ 'msg': msg,
+ }, context_instance=RequestContext(request))
+
+def deleteopenid_success(request, identity_url, openid_response):
+ openid_ = from_openid_response(openid_response)
+
+ user_ = request.user
+ try:
+ uassoc = UserAssociation.objects.get(
+ openid_url__exact=identity_url
+ )
+ except:
+ return deleteopenid_failure(request,
+ _("No OpenID %s found associated in our database" % identity_url))
+
+ if uassoc.user.username == user_.username:
+ user_.delete()
+ return signout(request)
+ else:
+ return deleteopenid_failure(request,
+ _("The OpenID %s isn't associated to current user logged in" %
+ identity_url))
+
+ msg = _("Account deleted.")
+ redirect = "/?msg=%s" % (urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+
+def deleteopenid_failure(request, message):
+ redirect_to = "%s?msg=%s" % (reverse('user_delete'), urlquote_plus(message))
+ return HttpResponseRedirect(redirect_to)
+
+
+def sendpw(request):
+ """
+ send a new password to the user. It return a mail with
+ a new pasword and a confirm link in. To activate the
+ new password, the user should click on confirm link.
+
+ url : /sendpw/
+
+ templates : authopenid/sendpw_email.txt, authopenid/sendpw.html
+ """
+
+ msg = request.GET.get('msg','')
+ if request.POST:
+ form = EmailPasswordForm(request.POST)
+ if form.is_valid():
+ new_pw = User.objects.make_random_password()
+ confirm_key = UserPasswordQueue.objects.get_new_confirm_key()
+ try:
+ uqueue = UserPasswordQueue.objects.get(
+ user=form.user_cache
+ )
+ except:
+ uqueue = UserPasswordQueue(
+ user=form.user_cache
+ )
+ uqueue.new_password = new_pw
+ uqueue.confirm_key = confirm_key
+ uqueue.save()
+ # send email
+ current_domain = Site.objects.get_current().domain
+ subject = _("Request for new password")
+ message_template = loader.get_template(
+ 'authopenid/sendpw_email.txt')
+ message_context = Context({
+ 'site_url': 'http://%s' % current_domain,
+ 'confirm_key': confirm_key,
+ 'username': form.user_cache.username,
+ 'password': new_pw,
+ 'url_confirm': reverse('user_confirmchangepw'),
+ })
+ message = message_template.render(message_context)
+ send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
+ [form.user_cache.email])
+ msg = _("A new password has been sent to your email address.")
+ else:
+ form = EmailPasswordForm()
+
+ return render('authopenid/sendpw.html', {
+ 'form': form,
+ 'msg': msg
+ }, context_instance=RequestContext(request))
+
+
+def confirmchangepw(request):
+ """
+ view to set new password when the user click on confirm link
+ in its mail. Basically it check if the confirm key exist, then
+ replace old password with new password and remove confirm
+ ley from the queue. Then it redirect the user to signin
+ page.
+
+ url : /sendpw/confirm/?key
+
+ """
+ confirm_key = request.GET.get('key', '')
+ if not confirm_key:
+ return HttpResponseRedirect('/')
+
+ try:
+ uqueue = UserPasswordQueue.objects.get(
+ confirm_key__exact=confirm_key
+ )
+ except:
+ msg = _("Could not change password. Confirmation key '%s'\
+ is not registered." % confirm_key)
+ redirect = "%s?msg=%s" % (
+ reverse('user_sendpw'), urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+ try:
+ user_ = User.objects.get(id=uqueue.user.id)
+ except:
+ msg = _("Can not change password. User don't exist anymore \
+ in our database.")
+ redirect = "%s?msg=%s" % (reverse('user_sendpw'),
+ urlquote_plus(msg))
+ return HttpResponseRedirect(redirect)
+
+ user_.set_password(uqueue.new_password)
+ user_.save()
+ uqueue.delete()
+ msg = _("Password changed for %s. You may now sign in." %
+ user_.username)
+ redirect = "%s?msg=%s" % (reverse('user_signin'),
+ urlquote_plus(msg))
+
+ return HttpResponseRedirect(redirect)
diff --git a/forum/__init__.py b/forum/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/forum/__init__.py
diff --git a/forum/admin.py b/forum/admin.py
new file mode 100644
index 00000000..438a99e7
--- /dev/null
+++ b/forum/admin.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+from django.contrib import admin
+from models import *
+
+
+class QuestionAdmin(admin.ModelAdmin):
+ """Question admin class"""
+
+class TagAdmin(admin.ModelAdmin):
+ """Tag admin class"""
+
+class Answerdmin(admin.ModelAdmin):
+ """Answer admin class"""
+
+class CommentAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class VoteAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class FlaggedItemAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class FavoriteQuestionAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class QuestionRevisionAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class AnswerRevisionAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class AwardAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class BadgeAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class ReputeAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class ActivityAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class BookAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class BookAuthorInfoAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+class BookAuthorRssAdmin(admin.ModelAdmin):
+ """ admin class"""
+
+
+admin.site.register(Question, QuestionAdmin)
+admin.site.register(Tag, TagAdmin)
+admin.site.register(Answer, Answerdmin)
+admin.site.register(Comment, CommentAdmin)
+admin.site.register(Vote, VoteAdmin)
+admin.site.register(FlaggedItem, FlaggedItemAdmin)
+admin.site.register(FavoriteQuestion, FavoriteQuestionAdmin)
+admin.site.register(QuestionRevision, QuestionRevisionAdmin)
+admin.site.register(AnswerRevision, AnswerRevisionAdmin)
+admin.site.register(Badge, BadgeAdmin)
+admin.site.register(Award, AwardAdmin)
+admin.site.register(Repute, ReputeAdmin)
+admin.site.register(Activity, ActivityAdmin)
+admin.site.register(Book, BookAdmin)
+admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin)
+admin.site.register(BookAuthorRss, BookAuthorRssAdmin) \ No newline at end of file
diff --git a/forum/auth.py b/forum/auth.py
new file mode 100644
index 00000000..0608031a
--- /dev/null
+++ b/forum/auth.py
@@ -0,0 +1,443 @@
+"""
+Authorisation related functions.
+
+The actions a User is authorised to perform are dependent on their reputation
+and superuser status.
+"""
+import datetime
+from django.contrib.contenttypes.models import ContentType
+from django.db import transaction
+from models import Repute
+from models import Question
+from models import Answer
+from const import TYPE_REPUTATION
+question_type = ContentType.objects.get_for_model(Question)
+answer_type = ContentType.objects.get_for_model(Answer)
+
+VOTE_UP = 15
+FLAG_OFFENSIVE = 15
+POST_IMAGES = 15
+LEAVE_COMMENTS = 50
+UPLOAD_FILES = 60
+VOTE_DOWN = 100
+CLOSE_OWN_QUESTIONS = 250
+RETAG_OTHER_QUESTIONS = 500
+REOPEN_OWN_QUESTIONS = 500
+EDIT_COMMUNITY_WIKI_POSTS = 750
+EDIT_OTHER_POSTS = 2000
+DELETE_COMMENTS = 2000
+VIEW_OFFENSIVE_FLAGS = 2000
+DISABLE_URL_NOFOLLOW = 2000
+CLOSE_OTHER_QUESTIONS = 3000
+LOCK_POSTS = 4000
+
+VOTE_RULES = {
+ 'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
+ 'scope_flags_per_user_per_day' : 5, # how many times user can flag posts everyday
+ 'scope_warn_votes_left' : 10, # start when to warn user how many votes left
+ 'scope_deny_unvote_days' : 1, # if 1 days passed, user can't cancel votes.
+ 'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
+ 'scope_flags_delete_post' : 5, # post will be deleted if it has more than 5 offensive flags
+}
+
+REPUTATION_RULES = {
+ 'initial_score' : 1,
+ 'scope_per_day_by_upvotes' : 200,
+ 'gain_by_upvoted' : 10,
+ 'gain_by_answer_accepted' : 15,
+ 'gain_by_accepting_answer' : 2,
+ 'gain_by_downvote_canceled' : 2,
+ 'gain_by_canceling_downvote' : 1,
+ 'lose_by_canceling_accepted_answer' : -2,
+ 'lose_by_accepted_answer_cancled' : -15,
+ 'lose_by_downvoted' : -2,
+ 'lose_by_flagged' : -2,
+ 'lose_by_downvoting' : -1,
+ 'lose_by_flagged_lastrevision_3_times': -30,
+ 'lose_by_flagged_lastrevision_5_times': -100,
+ 'lose_by_upvote_canceled' : -10,
+}
+
+def can_vote_up(user):
+ """Determines if a User can vote Questions and Answers up."""
+ return user.is_authenticated() and (
+ user.reputation >= VOTE_UP or
+ user.is_superuser)
+
+def can_flag_offensive(user):
+ """Determines if a User can flag Questions and Answers as offensive."""
+ return user.is_authenticated() and (
+ user.reputation >= FLAG_OFFENSIVE or
+ user.is_superuser)
+
+def can_add_comments(user):
+ """Determines if a User can add comments to Questions and Answers."""
+ return user.is_authenticated() and (
+ user.reputation >= LEAVE_COMMENTS or
+ user.is_superuser)
+
+def can_vote_down(user):
+ """Determines if a User can vote Questions and Answers down."""
+ return user.is_authenticated() and (
+ user.reputation >= VOTE_DOWN or
+ user.is_superuser)
+
+def can_retag_questions(user):
+ """Determines if a User can retag Questions."""
+ return user.is_authenticated() and (
+ RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
+ user.is_superuser)
+
+def can_edit_post(user, post):
+ """Determines if a User can edit the given Question or Answer."""
+ return user.is_authenticated() and (
+ user.id == post.author_id or
+ (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
+ user.reputation >= EDIT_OTHER_POSTS or
+ user.is_superuser)
+
+def can_delete_comment(user, comment):
+ """Determines if a User can delete the given Comment."""
+ return user.is_authenticated() and (
+ user.id == comment.user_id or
+ user.reputation >= DELETE_COMMENTS or
+ user.is_superuser)
+
+def can_view_offensive_flags(user):
+ """Determines if a User can view offensive flag counts."""
+ return user.is_authenticated() and (
+ user.reputation >= VIEW_OFFENSIVE_FLAGS or
+ user.is_superuser)
+
+def can_close_question(user, question):
+ """Determines if a User can close the given Question."""
+ return user.is_authenticated() and (
+ (user.id == question.author_id and
+ user.reputation >= CLOSE_OWN_QUESTIONS) or
+ user.reputation >= CLOSE_OTHER_QUESTIONS or
+ user.is_superuser)
+
+def can_lock_posts(user):
+ """Determines if a User can lock Questions or Answers."""
+ return user.is_authenticated() and (
+ user.reputation >= LOCK_POSTS or
+ user.is_superuser)
+
+def can_follow_url(user):
+ """Determines if the URL link can be followed by Google search engine."""
+ return user.reputation >= DISABLE_URL_NOFOLLOW
+
+def can_accept_answer(user, question, answer):
+ return (user.is_authenticated() and
+ question.author != answer.author and
+ question.author == user) or user.is_superuser
+
+# now only support to reopen own question except superuser
+def can_reopen_question(user, question):
+ return (user.is_authenticated() and
+ user.id == question.author_id and
+ user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser
+
+def can_delete_post(user, post):
+ return (user.is_authenticated() and
+ user.id == post.author_id) or user.is_superuser
+
+def can_view_deleted_post(user, post):
+ return user.is_superuser
+
+# user preferences view permissions
+def is_user_self(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_votes(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_preferences(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_view_user_edit(request_user, target_user):
+ return (request_user.is_authenticated() and request_user == target_user)
+
+def can_upload_files(request_user):
+ return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
+ request_user.is_superuser
+
+###########################################
+## actions and reputation changes event
+###########################################
+def calculate_reputation(origin, offset):
+ result = int(origin) + int(offset)
+ return result if result > 0 else 1
+
+@transaction.commit_on_success
+def onFlaggedItem(item, post, user):
+
+ item.save()
+ post.offensive_flag_count = post.offensive_flag_count + 1
+ post.save()
+
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged']))
+ post.author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged']),
+ question=question, reputed_at=datetime.datetime.now(),
+ reputation_type=-4,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ #todo: These should be updated to work on same revisions.
+ if post.offensive_flag_count == VOTE_RULES['scope_flags_invisible_main_page'] :
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
+ post.author.save()
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-6,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
+ post.author.reputation = calculate_reputation(post.author.reputation,
+ int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
+ post.author.save()
+
+ reputation = Repute(user=post.author,
+ negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-7,
+ reputation=post.author.reputation)
+ reputation.save()
+
+ post.deleted = True
+ #post.deleted_at = datetime.datetime.now()
+ #post.deleted_by = Admin
+ post.save()
+
+
+@transaction.commit_on_success
+def onAnswerAccept(answer, user):
+ answer.accepted = True
+ answer.accepted_at = datetime.datetime.now()
+ answer.question.answer_accepted = True
+ answer.save()
+ answer.question.save()
+
+ answer.author.reputation = calculate_reputation(answer.author.reputation,
+ int(REPUTATION_RULES['gain_by_answer_accepted']))
+ answer.author.save()
+ reputation = Repute(user=answer.author,
+ positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=2,
+ reputation=answer.author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['gain_by_accepting_answer']))
+ user.save()
+ reputation = Repute(user=user,
+ positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=3,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onAnswerAcceptCanceled(answer, user):
+ answer.accepted = False
+ answer.accepted_at = None
+ answer.question.answer_accepted = False
+ answer.save()
+ answer.question.save()
+
+ answer.author.reputation = calculate_reputation(answer.author.reputation,
+ int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
+ answer.author.save()
+ reputation = Repute(user=answer.author,
+ negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-2,
+ reputation=answer.author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
+ user.save()
+ reputation = Repute(user=user,
+ negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
+ question=answer.question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-1,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onUpVoted(vote, post, user):
+ vote.save()
+
+ post.vote_up_count = int(post.vote_up_count) + 1
+ post.score = int(post.score) + 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ if Repute.objects.get_reputation_by_upvoted_today(author) < int(REPUTATION_RULES['scope_per_day_by_upvotes']):
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['gain_by_upvoted']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ positive=int(REPUTATION_RULES['gain_by_upvoted']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=1,
+ reputation=author.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onUpVotedCanceled(vote, post, user):
+ vote.delete()
+
+ post.vote_up_count = int(post.vote_up_count) - 1
+ if post.vote_up_count < 0:
+ post.vote_up_count = 0
+ post.score = int(post.score) - 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['lose_by_upvote_canceled']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-8,
+ reputation=author.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onDownVoted(vote, post, user):
+ vote.save()
+
+ post.vote_down_count = int(post.vote_down_count) + 1
+ post.score = int(post.score) - 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['lose_by_downvoted']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ negative=int(REPUTATION_RULES['lose_by_downvoted']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-3,
+ reputation=author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['lose_by_downvoting']))
+ user.save()
+
+ reputation = Repute(user=user,
+ negative=int(REPUTATION_RULES['lose_by_downvoting']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=-5,
+ reputation=user.reputation)
+ reputation.save()
+
+@transaction.commit_on_success
+def onDownVotedCanceled(vote, post, user):
+ vote.delete()
+
+ post.vote_down_count = int(post.vote_down_count) - 1
+ if post.vote_down_count < 0:
+ post.vote_down_count = 0
+ post.score = post.score + 1
+ post.save()
+
+ if not post.wiki:
+ author = post.author
+ author.reputation = calculate_reputation(author.reputation,
+ int(REPUTATION_RULES['gain_by_downvote_canceled']))
+ author.save()
+
+ question = post
+ if ContentType.objects.get_for_model(post) == answer_type:
+ question = post.question
+
+ reputation = Repute(user=author,
+ positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=4,
+ reputation=author.reputation)
+ reputation.save()
+
+ user.reputation = calculate_reputation(user.reputation,
+ int(REPUTATION_RULES['gain_by_canceling_downvote']))
+ user.save()
+
+ reputation = Repute(user=user,
+ positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
+ question=question,
+ reputed_at=datetime.datetime.now(),
+ reputation_type=5,
+ reputation=user.reputation)
+ reputation.save()
+
+def onDeleteCanceled(post, user):
+ post.deleted = False
+ post.deleted_by = None
+ post.deleted_at = None
+ post.save()
+ for tag in list(post.tags.all()):
+ if tag.used_count == 1 and tag.deleted:
+ tag.deleted = False
+ tag.deleted_by = None
+ tag.deleted_at = None
+ tag.save()
+
+def onDeleted(post, user):
+ post.deleted = True
+ post.deleted_by = user
+ post.deleted_at = datetime.datetime.now()
+ post.save()
+
+ for tag in list(post.tags.all()):
+ if tag.used_count == 1:
+ tag.deleted = True
+ tag.deleted_by = user
+ tag.deleted_at = datetime.datetime.now()
+ tag.save()
diff --git a/forum/const.py b/forum/const.py
new file mode 100644
index 00000000..f6649cc4
--- /dev/null
+++ b/forum/const.py
@@ -0,0 +1,87 @@
+# encoding:utf-8
+from django.utils.translation import ugettext as _
+"""
+All constants could be used in other modules
+For reasons that models, views can't have unicode text in this project, all unicode text go here.
+"""
+CLOSE_REASONS = (
+ (1, _('duplicate question')),
+ (2, _('question if off-topic or not relevant')),
+ (3, _('too subjective and argumentative')),
+ (4, _('is not an answer to the question')),
+ (5, _('the question is answered, right answer was accepted')),
+ (6, _('problem is not reproducible or outdated')),
+ #(7, u'太局部、本地化的问题',)
+ (7, _('question contains offensive inappropriate, or malicious remarks')),
+ (8, _('spam or advertising')),
+)
+
+TYPE_REPUTATION = (
+ (1, 'gain_by_upvoted'),
+ (2, 'gain_by_answer_accepted'),
+ (3, 'gain_by_accepting_answer'),
+ (4, 'gain_by_downvote_canceled'),
+ (5, 'gain_by_canceling_downvote'),
+ (-1, 'lose_by_canceling_accepted_answer'),
+ (-2, 'lose_by_accepted_answer_cancled'),
+ (-3, 'lose_by_downvoted'),
+ (-4, 'lose_by_flagged'),
+ (-5, 'lose_by_downvoting'),
+ (-6, 'lose_by_flagged_lastrevision_3_times'),
+ (-7, 'lose_by_flagged_lastrevision_5_times'),
+ (-8, 'lose_by_upvote_canceled'),
+)
+
+TYPE_ACTIVITY_ASK_QUESTION=1
+TYPE_ACTIVITY_ANSWER=2
+TYPE_ACTIVITY_COMMENT_QUESTION=3
+TYPE_ACTIVITY_COMMENT_ANSWER=4
+TYPE_ACTIVITY_UPDATE_QUESTION=5
+TYPE_ACTIVITY_UPDATE_ANSWER=6
+TYPE_ACTIVITY_PRIZE=7
+TYPE_ACTIVITY_MARK_ANSWER=8
+TYPE_ACTIVITY_VOTE_UP=9
+TYPE_ACTIVITY_VOTE_DOWN=10
+TYPE_ACTIVITY_CANCEL_VOTE=11
+TYPE_ACTIVITY_DELETE_QUESTION=12
+TYPE_ACTIVITY_DELETE_ANSWER=13
+TYPE_ACTIVITY_MARK_OFFENSIVE=14
+TYPE_ACTIVITY_UPDATE_TAGS=15
+TYPE_ACTIVITY_FAVORITE=16
+TYPE_ACTIVITY_USER_FULL_UPDATED = 17
+#TYPE_ACTIVITY_EDIT_QUESTION=17
+#TYPE_ACTIVITY_EDIT_ANSWER=18
+
+TYPE_ACTIVITY = (
+ (TYPE_ACTIVITY_ASK_QUESTION, _('question')),
+ (TYPE_ACTIVITY_ANSWER, _('answer')),
+ (TYPE_ACTIVITY_COMMENT_QUESTION, _('commented question')),
+ (TYPE_ACTIVITY_COMMENT_ANSWER, _('commented answer')),
+ (TYPE_ACTIVITY_UPDATE_QUESTION, _('edited question')),
+ (TYPE_ACTIVITY_UPDATE_ANSWER, _('edited answer')),
+ (TYPE_ACTIVITY_PRIZE, _('received award')),
+ (TYPE_ACTIVITY_MARK_ANSWER, _('marked best answer')),
+ (TYPE_ACTIVITY_VOTE_UP, _('upvoted')),
+ (TYPE_ACTIVITY_VOTE_DOWN, _('downvoted')),
+ (TYPE_ACTIVITY_CANCEL_VOTE, _('canceled vote')),
+ (TYPE_ACTIVITY_DELETE_QUESTION, _('deleted question')),
+ (TYPE_ACTIVITY_DELETE_ANSWER, _('deleted answer')),
+ (TYPE_ACTIVITY_MARK_OFFENSIVE, _('marked offensive')),
+ (TYPE_ACTIVITY_UPDATE_TAGS, _('updated tags')),
+ (TYPE_ACTIVITY_FAVORITE, _('selected favorite')),
+ (TYPE_ACTIVITY_USER_FULL_UPDATED, _('completed user profile')),
+)
+
+TYPE_RESPONSE = {
+ 'QUESTION_ANSWERED' : 'question_answered',
+ 'QUESTION_COMMENTED': 'question_commented',
+ 'ANSWER_COMMENTED' : 'answer_commented',
+ 'ANSWER_ACCEPTED' : 'answer_accepted',
+}
+
+CONST = {
+ 'closed' : _('[closed]'),
+ 'deleted' : _('[deleted]'),
+ 'default_version' : _('initial version'),
+ 'retagged' : _('retagged'),
+}
diff --git a/forum/diff.py b/forum/diff.py
new file mode 100644
index 00000000..d741d788
--- /dev/null
+++ b/forum/diff.py
@@ -0,0 +1,66 @@
+#!/usr/bin/python2.2
+"""HTML Diff: http://www.aaronsw.com/2002/diff
+Rough code, badly documented. Send me comments and patches."""
+
+__author__ = 'Aaron Swartz <me@aaronsw.com>'
+__copyright__ = '(C) 2003 Aaron Swartz. GNU GPL 2.'
+__version__ = '0.22'
+
+import difflib, string
+
+def isTag(x): return x[0] == "<" and x[-1] == ">"
+
+def textDiff(a, b):
+ """Takes in strings a and b and returns a human-readable HTML diff."""
+
+ out = []
+ a, b = html2list(a), html2list(b)
+ s = difflib.SequenceMatcher(None, a, b)
+ for e in s.get_opcodes():
+ if e[0] == "replace":
+ # @@ need to do something more complicated here
+ # call textDiff but not for html, but for some html... ugh
+ # gonna cop-out for now
+ out.append('<del>'+''.join(a[e[1]:e[2]]) + '</del><ins>'+''.join(b[e[3]:e[4]])+"</ins>")
+ elif e[0] == "delete":
+ out.append('<del >'+ ''.join(a[e[1]:e[2]]) + "</del>")
+ elif e[0] == "insert":
+ out.append('<ins >'+''.join(b[e[3]:e[4]]) + "</ins>")
+ elif e[0] == "equal":
+ out.append(''.join(b[e[3]:e[4]]))
+ else:
+ raise "Um, something's broken. I didn't expect a '" + `e[0]` + "'."
+ return ''.join(out)
+
+def html2list(x, b=0):
+ mode = 'char'
+ cur = ''
+ out = []
+ for c in x:
+ if mode == 'tag':
+ if c == '>':
+ if b: cur += ']'
+ else: cur += c
+ out.append(cur); cur = ''; mode = 'char'
+ else: cur += c
+ elif mode == 'char':
+ if c == '<':
+ out.append(cur)
+ if b: cur = '['
+ else: cur = c
+ mode = 'tag'
+ elif c in string.whitespace: out.append(cur+c); cur = ''
+ else: cur += c
+ out.append(cur)
+ return filter(lambda x: x is not '', out)
+
+if __name__ == '__main__':
+ import sys
+ try:
+ a, b = sys.argv[1:3]
+ except ValueError:
+ print "htmldiff: highlight the differences between two html files"
+ print "usage: " + sys.argv[0] + " a b"
+ sys.exit(1)
+ print textDiff(open(a).read(), open(b).read())
+
diff --git a/forum/feed.py b/forum/feed.py
new file mode 100644
index 00000000..a4218630
--- /dev/null
+++ b/forum/feed.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Syndication feed class for subsribtion
+# Purpose:
+#
+# Author: Mike
+#
+# Created: 29/01/2009
+# Copyright: (c) CNPROG.COM 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+from django.contrib.syndication.feeds import Feed, FeedDoesNotExist
+from django.utils.translation import ugettext as _
+from models import Question
+class RssLastestQuestionsFeed(Feed):
+ title = _('site title') + _(' - ') + _('site slogan') + _(' - ')+ _('latest questions')
+ #EDIT!!!
+ link = 'http://where.com/questions/'
+ description = _('meta site content')
+ #ttl = 10
+ copyright = _('copyright message')
+
+ def item_link(self, item):
+ return '/questions/%s/' % item.id
+
+ def item_author_name(self, item):
+ return item.author.username
+
+ def item_author_link(self, item):
+ return item.author.get_profile_url()
+
+ def item_pubdate(self, item):
+ return item.added_at
+
+ def items(self, item):
+ return Question.objects.filter(deleted=False).order_by('-added_at')[:30]
+
+def main():
+ pass
+
+if __name__ == '__main__':
+ main()
diff --git a/forum/forms.py b/forum/forms.py
new file mode 100644
index 00000000..1b811ad9
--- /dev/null
+++ b/forum/forms.py
@@ -0,0 +1,193 @@
+import re
+from datetime import date
+from django import forms
+from models import *
+from const import *
+from django.utils.translation import ugettext as _
+
+class TitleField(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ super(TitleField, self).__init__(*args, **kwargs)
+ self.required = True
+ self.widget = forms.TextInput(attrs={'size' : 70, 'autocomplete' : 'off'})
+ self.max_length = 255
+ self.label = _('title')
+ self.help_text = _('please enter a descriptive title for your question')
+ self.initial = ''
+
+ def clean(self, value):
+ if len(value) < 10:
+ raise forms.ValidationError(_('title must be > 10 characters'))
+
+ return value
+
+class EditorField(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ super(EditorField, self).__init__(*args, **kwargs)
+ self.required = True
+ self.widget = forms.Textarea(attrs={'id':'editor'})
+ self.label = _('content')
+ self.help_text = u''
+ self.initial = ''
+
+ def clean(self, value):
+ if len(value) < 10:
+ raise forms.ValidationError(_('question content must be > 10 characters'))
+
+ return value
+
+class TagNamesField(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ super(TagNamesField, self).__init__(*args, **kwargs)
+ self.required = True
+ self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
+ self.max_length = 255
+ self.label = _('tags')
+ self.help_text = _('please use space to separate tags (this enables autocomplete feature)')
+ self.initial = ''
+
+ def clean(self, value):
+ value = super(TagNamesField, self).clean(value)
+ data = value.strip()
+ if len(data) < 1:
+ raise forms.ValidationError(_('tags are required'))
+ list = data.split(' ')
+ list_temp = []
+ if len(list) > 5:
+ raise forms.ValidationError(_('please use 5 tags or less'))
+ for tag in list:
+ if len(tag) > 20:
+ raise forms.ValidationError(_('tags must be shorter than 20 characters'))
+ #take tag regex from settings
+ tagname_re = re.compile(r'[a-z0-9]+')
+ if not tagname_re.match(tag):
+ raise forms.ValidationError(_('please use following characters in tags: letters \'a-z\', numbers, and characters \'.-_#\''))
+ # only keep one same tag
+ if tag not in list_temp and len(tag.strip()) > 0:
+ list_temp.append(tag)
+ return u' '.join(list_temp)
+
+class WikiField(forms.BooleanField):
+ def __init__(self, *args, **kwargs):
+ super(WikiField, self).__init__(*args, **kwargs)
+ self.required = False
+ self.label = _('community wiki')
+ self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown')
+
+
+class SummaryField(forms.CharField):
+ def __init__(self, *args, **kwargs):
+ super(SummaryField, self).__init__(*args, **kwargs)
+ self.required = False
+ self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'})
+ self.max_length = 300
+ self.label = _('update summary:')
+ self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)')
+
+class AskForm(forms.Form):
+ title = TitleField()
+ text = EditorField()
+ tags = TagNamesField()
+ wiki = WikiField()
+
+ openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
+ user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+
+
+
+class AnswerForm(forms.Form):
+ text = EditorField()
+ wiki = WikiField()
+ openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'}))
+ user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ def __init__(self, question, *args, **kwargs):
+ super(AnswerForm, self).__init__(*args, **kwargs)
+ if question.wiki:
+ self.fields['wiki'].initial = True
+
+class CloseForm(forms.Form):
+ reason = forms.ChoiceField(choices=CLOSE_REASONS)
+
+class RetagQuestionForm(forms.Form):
+ tags = TagNamesField()
+ # initialize the default values
+ def __init__(self, question, *args, **kwargs):
+ super(RetagQuestionForm, self).__init__(*args, **kwargs)
+ self.fields['tags'].initial = question.tagnames
+
+class RevisionForm(forms.Form):
+ """
+ Lists revisions of a Question or Answer
+ """
+ revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'}))
+
+ def __init__(self, post, latest_revision, *args, **kwargs):
+ super(RevisionForm, self).__init__(*args, **kwargs)
+ revisions = post.revisions.all().values_list(
+ 'revision', 'author__username', 'revised_at', 'summary')
+ date_format = '%c'
+ self.fields['revision'].choices = [
+ (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3]))
+ for r in revisions]
+ self.fields['revision'].initial = latest_revision.revision
+
+class EditQuestionForm(forms.Form):
+ title = TitleField()
+ text = EditorField()
+ tags = TagNamesField()
+ summary = SummaryField()
+
+ def __init__(self, question, revision, *args, **kwargs):
+ super(EditQuestionForm, self).__init__(*args, **kwargs)
+ self.fields['title'].initial = revision.title
+ self.fields['text'].initial = revision.text
+ self.fields['tags'].initial = revision.tagnames
+ # Once wiki mode is enabled, it can't be disabled
+ if not question.wiki:
+ self.fields['wiki'] = WikiField()
+
+class EditAnswerForm(forms.Form):
+ text = EditorField()
+ summary = SummaryField()
+
+ def __init__(self, answer, revision, *args, **kwargs):
+ super(EditAnswerForm, self).__init__(*args, **kwargs)
+ self.fields['text'].initial = revision.text
+
+class EditUserForm(forms.Form):
+ email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ city = forms.CharField(label=_('Location'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
+ birthday = forms.DateField(label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), required=False, widget=forms.TextInput(attrs={'size' : 35}))
+ about = forms.CharField(label=_('Profile'), required=False, widget=forms.Textarea(attrs={'cols' : 60}))
+
+ def __init__(self, user, *args, **kwargs):
+ super(EditUserForm, self).__init__(*args, **kwargs)
+ self.fields['email'].initial = user.email
+ self.fields['realname'].initial = user.real_name
+ self.fields['website'].initial = user.website
+ self.fields['city'].initial = user.location
+
+ if user.date_of_birth is not None:
+ self.fields['birthday'].initial = user.date_of_birth
+ else:
+ self.fields['birthday'].initial = '1990-01-01'
+ self.fields['about'].initial = user.about
+ self.user = user
+
+ def clean_email(self):
+ """For security reason one unique email in database"""
+ if self.user.email != self.cleaned_data['email']:
+ if 'email' in self.cleaned_data:
+ try:
+ user = User.objects.get(email = self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+ except User.MultipleObjectsReturned:
+ raise forms.ValidationError(_('this email has already been registered, please use another one'))
+ raise forms.ValidationError(_('this email has already been registered, please use another one'))
+ else:
+ return self.cleaned_data['email']
diff --git a/forum/management/__init__.py b/forum/management/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/forum/management/__init__.py
diff --git a/forum/management/commands/__init__.py b/forum/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/forum/management/commands/__init__.py
diff --git a/forum/management/commands/base_command.py b/forum/management/commands/base_command.py
new file mode 100644
index 00000000..c073bf7a
--- /dev/null
+++ b/forum/management/commands/base_command.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike, Sailing
+#
+# Created: 22/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+
+from datetime import datetime, date
+from django.core.management.base import NoArgsCommand
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+from forum.const import *
+
+class BaseCommand(NoArgsCommand):
+ def update_activities_auditted(self, cursor, activity_ids):
+ # update processed rows to auditted
+ if len(activity_ids):
+ query = "UPDATE activity SET is_auditted = 1 WHERE id in (%s)"\
+ % ','.join('%s' % item for item in activity_ids)
+ cursor.execute(query)
+
+
+
+
+
diff --git a/forum/management/commands/clean_award_badges.py b/forum/management/commands/clean_award_badges.py
new file mode 100644
index 00000000..df3d2917
--- /dev/null
+++ b/forum/management/commands/clean_award_badges.py
@@ -0,0 +1,58 @@
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike
+#
+# Created: 18/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+#!/usr/bin/env python
+#encoding:utf-8
+from django.core.management.base import NoArgsCommand
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ try:
+ self.clean_awards()
+ except Exception, e:
+ print e
+ finally:
+ connection.close()
+
+ def clean_awards(self):
+ Award.objects.all().delete()
+
+ award_type =ContentType.objects.get_for_model(Award)
+ Activity.objects.filter(content_type=award_type).delete()
+
+ for user in User.objects.all():
+ user.gold = 0
+ user.silver = 0
+ user.bronze = 0
+ user.save()
+
+ for badge in Badge.objects.all():
+ badge.awarded_count = 0
+ badge.save()
+
+ query = "UPDATE activity SET is_auditted = 0"
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ finally:
+ cursor.close()
+ connection.close()
+
+def main():
+ pass
+
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/forum/management/commands/multi_award_badges.py b/forum/management/commands/multi_award_badges.py
new file mode 100644
index 00000000..723a8cec
--- /dev/null
+++ b/forum/management/commands/multi_award_badges.py
@@ -0,0 +1,347 @@
+#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike, Sailing
+#
+# Created: 22/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+
+from datetime import datetime, date
+from django.core.management.base import NoArgsCommand
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+from forum.const import *
+from base_command import BaseCommand
+"""
+(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
+(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
+(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
+(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
+(5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
+(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
+(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
+(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
+(9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
+(10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
+(11, '村长', 3, '村长', '第一次重新标签', 0, 0),
+(12, '学者', 3, '学者', '第一次标记答案', 0, 0),
+(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
+(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
+(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
+(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
+(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
+(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
+(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
+(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
+(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
+(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0),
+(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
+(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
+(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
+(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0),
+(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0),
+(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0),
+(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0),
+(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0),
+(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
+(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0),
+(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0),
+(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
+(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
+(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
+
+
+TYPE_ACTIVITY_ASK_QUESTION=1
+TYPE_ACTIVITY_ANSWER=2
+TYPE_ACTIVITY_COMMENT_QUESTION=3
+TYPE_ACTIVITY_COMMENT_ANSWER=4
+TYPE_ACTIVITY_UPDATE_QUESTION=5
+TYPE_ACTIVITY_UPDATE_ANSWER=6
+TYPE_ACTIVITY_PRIZE=7
+TYPE_ACTIVITY_MARK_ANSWER=8
+TYPE_ACTIVITY_VOTE_UP=9
+TYPE_ACTIVITY_VOTE_DOWN=10
+TYPE_ACTIVITY_CANCEL_VOTE=11
+TYPE_ACTIVITY_DELETE_QUESTION=12
+TYPE_ACTIVITY_DELETE_ANSWER=13
+TYPE_ACTIVITY_MARK_OFFENSIVE=14
+TYPE_ACTIVITY_UPDATE_TAGS=15
+TYPE_ACTIVITY_FAVORITE=16
+TYPE_ACTIVITY_USER_FULL_UPDATED = 17
+"""
+
+class Command(BaseCommand):
+ def handle_noargs(self, **options):
+ try:
+ self.delete_question_be_voted_up_3()
+ self.delete_answer_be_voted_up_3()
+ self.delete_question_be_vote_down_3()
+ self.delete_answer_be_voted_down_3()
+ self.answer_be_voted_up_10()
+ self.question_be_voted_up_10()
+ self.question_view_1000()
+ self.answer_self_question_be_voted_up_3()
+ self.answer_be_voted_up_100()
+ self.question_be_voted_up_100()
+ self.question_be_favorited_100()
+ self.question_view_10000()
+ self.answer_be_voted_up_25()
+ self.question_be_voted_up_25()
+ self.question_be_favorited_25()
+ self.question_view_2500()
+ self.answer_be_accepted_and_voted_up_40()
+ self.question_be_answered_after_60_days_and_be_voted_up_5()
+ self.created_tag_be_used_in_question_50()
+ except Exception, e:
+ print e
+ finally:
+ connection.close()
+
+ def delete_question_be_voted_up_3(self):
+ """
+ (1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM activity act, question q WHERE act.object_id = q.id AND\
+ act.activity_type = %s AND\
+ q.vote_up_count >=3 AND \
+ act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_QUESTION)
+ self.__process_activities_badge(query, 1, Question)
+
+ def delete_answer_be_voted_up_3(self):
+ """
+ (1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM activity act, answer an WHERE act.object_id = an.id AND\
+ act.activity_type = %s AND\
+ an.vote_up_count >=3 AND \
+ act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_ANSWER)
+ self.__process_activities_badge(query, 1, Answer)
+
+ def delete_question_be_vote_down_3(self):
+ """
+ (2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM activity act, question q WHERE act.object_id = q.id AND\
+ act.activity_type = %s AND\
+ q.vote_down_count >=3 AND \
+ act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_QUESTION)
+ content_type = ContentType.objects.get_for_model(Question)
+ self.__process_activities_badge(query, 2, Question)
+
+ def delete_answer_be_voted_down_3(self):
+ """
+ (2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM activity act, answer an WHERE act.object_id = an.id AND\
+ act.activity_type = %s AND\
+ an.vote_down_count >=3 AND \
+ act.is_auditted = 0" % (TYPE_ACTIVITY_DELETE_ANSWER)
+ self.__process_activities_badge(query, 2, Answer)
+
+ def answer_be_voted_up_10(self):
+ """
+ (3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM \
+ activity act, answer a WHERE act.object_id = a.id AND\
+ act.activity_type = %s AND \
+ a.vote_up_count >= 10 AND\
+ act.is_auditted = 0" % (TYPE_ACTIVITY_ANSWER)
+ self.__process_activities_badge(query, 3, Answer)
+
+ def question_be_voted_up_10(self):
+ """
+ (4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM \
+ activity act, question q WHERE act.object_id = q.id AND\
+ act.activity_type = %s AND \
+ q.vote_up_count >= 10 AND\
+ act.is_auditted = 0" % (TYPE_ACTIVITY_ASK_QUESTION)
+ self.__process_activities_badge(query, 4, Question)
+
+ def question_view_1000(self):
+ """
+ (6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM \
+ activity act, question q WHERE act.activity_type = %s AND\
+ act.object_id = q.id AND \
+ q.view_count >= 1000 AND\
+ act.object_id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 6)
+ self.__process_activities_badge(query, 6, Question, False)
+
+ def answer_self_question_be_voted_up_3(self):
+ """
+ (17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
+ """
+ query = "SELECT act.id, act.user_id, act.object_id FROM \
+ activity act, answer an WHERE act.activity_type = %s AND\
+ act.object_id = an.id AND\
+ an.vote_up_count >= 3 AND\
+ act.user_id = (SELECT user_id FROM question q WHERE q.id = an.question_id) AND\
+ act.object_id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 17)
+ self.__process_activities_badge(query, 17, Question, False)
+
+ def answer_be_voted_up_100(self):
+ """
+ (18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
+ """
+ query = "SELECT an.id, an.author_id FROM answer an WHERE an.vote_up_count >= 100 AND an.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (18)
+
+ self.__process_badge(query, 18, Answer)
+
+ def question_be_voted_up_100(self):
+ """
+ (19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.vote_up_count >= 100 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (19)
+
+ self.__process_badge(query, 19, Question)
+
+ def question_be_favorited_100(self):
+ """
+ (20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.favourite_count >= 100 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (20)
+
+ self.__process_badge(query, 20, Question)
+
+ def question_view_10000(self):
+ """
+ (21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.view_count >= 10000 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (21)
+
+ self.__process_badge(query, 21, Question)
+
+ def answer_be_voted_up_25(self):
+ """
+ (23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
+ """
+ query = "SELECT a.id, a.author_id FROM answer a WHERE a.vote_up_count >= 25 AND a.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (23)
+
+ self.__process_badge(query, 23, Answer)
+
+ def question_be_voted_up_25(self):
+ """
+ (24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.vote_up_count >= 25 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (24)
+
+ self.__process_badge(query, 24, Question)
+
+ def question_be_favorited_25(self):
+ """
+ (25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.favourite_count >= 25 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (25)
+
+ self.__process_badge(query, 25, Question)
+
+ def question_view_2500(self):
+ """
+ (31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
+ """
+ query = "SELECT q.id, q.author_id FROM question q WHERE q.view_count >= 2500 AND q.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (31)
+
+ self.__process_badge(query, 31, Question)
+
+ def answer_be_accepted_and_voted_up_40(self):
+ """
+ (34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
+ """
+ query = "SELECT a.id, a.author_id FROM answer a WHERE a.vote_up_count >= 40 AND\
+ a.accepted = 1 AND\
+ a.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (34)
+
+ self.__process_badge(query, 34, Answer)
+
+ def question_be_answered_after_60_days_and_be_voted_up_5(self):
+ """
+ (35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
+ """
+ query = "SELECT a.id, a.author_id FROM question q, answer a WHERE q.id = a.question_id AND\
+ DATEDIFF(a.added_at, q.added_at) >= 60 AND\
+ a.vote_up_count >= 5 AND \
+ a.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (35)
+
+ self.__process_badge(query, 35, Answer)
+
+ def created_tag_be_used_in_question_50(self):
+ """
+ (36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
+ """
+ query = "SELECT t.id, t.created_by_id FROM tag t, auth_user u WHERE t.created_by_id = u.id AND \
+ t. used_count >= 50 AND \
+ t.id NOT IN \
+ (SELECT object_id FROM award WHERE award.badge_id = %s)" % (36)
+
+ self.__process_badge(query, 36, Tag)
+
+ def __process_activities_badge(self, query, badge, content_object, update_auditted=True):
+ content_type = ContentType.objects.get_for_model(content_object)
+
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ if update_auditted:
+ activity_ids = []
+ badge = get_object_or_404(Badge, id=badge)
+ for row in rows:
+ activity_id = row[0]
+ user_id = row[1]
+ object_id = row[2]
+
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=objet_id)
+ award.save()
+
+ if update_auditted:
+ activity_ids.append(activity_id)
+
+ if update_auditted:
+ self.update_activities_auditted(cursor, activity_ids)
+ finally:
+ cursor.close()
+
+ def __process_badge(self, query, badge, content_object):
+ content_type = ContentType.objects.get_for_model(Answer)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ badge = get_object_or_404(Badge, id=badge)
+ for row in rows:
+ object_id = row[0]
+ user_id = row[1]
+
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ finally:
+ cursor.close() \ No newline at end of file
diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py
new file mode 100644
index 00000000..011c28fd
--- /dev/null
+++ b/forum/management/commands/once_award_badges.py
@@ -0,0 +1,327 @@
+#!/usr/bin/env python
+#encoding:utf-8
+#-------------------------------------------------------------------------------
+# Name: Award badges command
+# Purpose: This is a command file croning in background process regularly to
+# query database and award badges for user's special acitivities.
+#
+# Author: Mike, Sailing
+#
+# Created: 18/01/2009
+# Copyright: (c) Mike 2009
+# Licence: GPL V2
+#-------------------------------------------------------------------------------
+
+from datetime import datetime, date
+from django.db import connection
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+
+from forum.models import *
+from forum.const import *
+from base_command import BaseCommand
+"""
+(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
+(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
+(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
+(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
+(5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
+(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
+(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
+(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
+(9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
+(10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
+(11, '村长', 3, '村长', '第一次重新标签', 0, 0),
+(12, '学者', 3, '学者', '第一次标记答案', 0, 0),
+(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
+(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
+(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
+(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
+(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
+(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
+(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
+(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
+(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
+(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0),
+(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
+(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
+(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
+(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0),
+(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0),
+(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0),
+(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0),
+(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0),
+(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
+(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0),
+(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0),
+(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
+(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
+(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
+
+
+TYPE_ACTIVITY_ASK_QUESTION=1
+TYPE_ACTIVITY_ANSWER=2
+TYPE_ACTIVITY_COMMENT_QUESTION=3
+TYPE_ACTIVITY_COMMENT_ANSWER=4
+TYPE_ACTIVITY_UPDATE_QUESTION=5
+TYPE_ACTIVITY_UPDATE_ANSWER=6
+TYPE_ACTIVITY_PRIZE=7
+TYPE_ACTIVITY_MARK_ANSWER=8
+TYPE_ACTIVITY_VOTE_UP=9
+TYPE_ACTIVITY_VOTE_DOWN=10
+TYPE_ACTIVITY_CANCEL_VOTE=11
+TYPE_ACTIVITY_DELETE_QUESTION=12
+TYPE_ACTIVITY_DELETE_ANSWER=13
+TYPE_ACTIVITY_MARK_OFFENSIVE=14
+TYPE_ACTIVITY_UPDATE_TAGS=15
+TYPE_ACTIVITY_FAVORITE=16
+TYPE_ACTIVITY_USER_FULL_UPDATED = 17
+"""
+
+BADGE_AWARD_TYPE_FIRST = {
+ TYPE_ACTIVITY_MARK_OFFENSIVE : 7,
+ TYPE_ACTIVITY_CANCEL_VOTE: 8,
+ TYPE_ACTIVITY_VOTE_DOWN : 9,
+ TYPE_ACTIVITY_UPDATE_QUESTION : 10,
+ TYPE_ACTIVITY_UPDATE_ANSWER : 10,
+ TYPE_ACTIVITY_UPDATE_TAGS : 11,
+ TYPE_ACTIVITY_MARK_ANSWER : 12,
+ TYPE_ACTIVITY_VOTE_UP : 14,
+ TYPE_ACTIVITY_USER_FULL_UPDATED: 16
+
+}
+
+class Command(BaseCommand):
+ def handle_noargs(self, **options):
+ try:
+ self.alpha_user()
+ self.beta_user()
+ self.first_type_award()
+ self.first_ask_be_voted()
+ self.first_answer_be_voted()
+ self.first_answer_be_voted_10()
+ self.vote_count_300()
+ self.edit_count_100()
+ self.comment_count_10()
+ except Exception, e:
+ print e
+ finally:
+ connection.close()
+
+ def alpha_user(self):
+ """
+ Before Jan 25, 2009(Chinese New Year Eve and enter into Beta for CNProg), every registered user
+ will be awarded the "Alpha" badge if he has any activities.
+ """
+ alpha_end_date = date(2009, 1, 25)
+ if date.today() < alpha_end_date:
+ badge = get_object_or_404(Badge, id=22)
+ for user in User.objects.all():
+ award = Award.objects.filter(user=user, badge=badge)
+ if award and not badge.multiple:
+ continue
+ activities = Activity.objects.filter(user=user)
+ if len(activities) > 0:
+ new_award = Award(user=user, badge=badge)
+ new_award.save()
+
+ def beta_user(self):
+ """
+ Before Feb 25, 2009, every registered user
+ will be awarded the "Beta" badge if he has any activities.
+ """
+ beta_end_date = date(2009, 2, 25)
+ if date.today() < beta_end_date:
+ badge = get_object_or_404(Badge, id=33)
+ for user in User.objects.all():
+ award = Award.objects.filter(user=user, badge=badge)
+ if award and not badge.multiple:
+ continue
+ activities = Activity.objects.filter(user=user)
+ if len(activities) > 0:
+ new_award = Award(user=user, badge=badge)
+ new_award.save()
+
+ def first_type_award(self):
+ """
+ This will award below badges for users first behaviors:
+
+ (7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
+ (8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
+ (9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
+ (10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
+ (11, '村长', 3, '村长', '第一次重新标签', 0, 0),
+ (12, '学者', 3, '学者', '第一次标记答案', 0, 0),
+ (14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
+ (16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
+ """
+ activity_types = ','.join('%s' % item for item in BADGE_AWARD_TYPE_FIRST.keys())
+ # ORDER BY user_id, activity_type
+ query = "SELECT id, user_id, activity_type, content_type_id, object_id FROM activity WHERE is_auditted = 0 AND activity_type IN (%s) ORDER BY user_id, activity_type" % activity_types
+
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+ # collect activity_id in current process
+ activity_ids = []
+ last_user_id = 0
+ last_activity_type = 0
+ for row in rows:
+ activity_ids.append(row[0])
+ user_id = row[1]
+ activity_type = row[2]
+ content_type_id = row[3]
+ object_id = row[4]
+
+ # if the user and activity are same as the last, continue
+ if user_id == last_user_id and activity_type == last_activity_type:
+ continue;
+
+ user = get_object_or_404(User, id=user_id)
+ badge = get_object_or_404(Badge, id=BADGE_AWARD_TYPE_FIRST[activity_type])
+ content_type = get_object_or_404(ContentType, id=content_type_id)
+
+ count = Award.objects.filter(user=user, badge=badge).count()
+ if count and not badge.multiple:
+ continue
+ else:
+ # new award
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+
+ # set the current user_id and activity_type to last
+ last_user_id = user_id
+ last_activity_type = activity_type
+
+ # update processed rows to auditted
+ self.update_activities_auditted(cursor, activity_ids)
+ finally:
+ cursor.close()
+
+ def first_ask_be_voted(self):
+ """
+ For user asked question and got first upvote, we award him following badge:
+
+ (13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
+ """
+ query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM activity act, question q WHERE act.activity_type = %s AND act.object_id = q.id AND act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ASK_QUESTION, 13)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ badge = get_object_or_404(Badge, id=13)
+ content_type = ContentType.objects.get_for_model(Question)
+ awarded_users = []
+ for row in rows:
+ user_id = row[0]
+ vote_up_count = row[1]
+ object_id = row[2]
+ if vote_up_count > 0 and user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def first_answer_be_voted(self):
+ """
+ When user answerd questions and got first upvote, we award him following badge:
+
+ (15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
+ """
+ query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM activity act, answer a WHERE act.activity_type = %s AND act.object_id = a.id AND act.user_id NOT IN (SELECT distinct user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 15)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=15)
+ content_type = ContentType.objects.get_for_model(Answer)
+ for row in rows:
+ user_id = row[0]
+ vote_up_count = row[1]
+ object_id = row[2]
+ if vote_up_count > 0 and user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def first_answer_be_voted_10(self):
+ """
+ (32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0)
+ """
+ query = "SELECT act.user_id, act.object_id FROM activity act, answer a WHERE act.object_id = a.id AND act.activity_type = %s AND a.vote_up_count >= 10 AND act.user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s)" % (TYPE_ACTIVITY_ANSWER, 32)
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=32)
+ content_type = ContentType.objects.get_for_model(Answer)
+ for row in rows:
+ user_id = row[0]
+ if user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ object_id = row[1]
+ award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+ def vote_count_300(self):
+ """
+ (26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0)
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE activity_type = %s OR activity_type = %s AND user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) GROUP BY user_id HAVING vote_count >= 300" % (TYPE_ACTIVITY_VOTE_UP, TYPE_ACTIVITY_VOTE_DOWN, 26)
+
+ self.__award_for_count_num(query, 26)
+
+ def edit_count_100(self):
+ """
+ (27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0)
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE activity_type = %s OR activity_type = %s AND user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) GROUP BY user_id HAVING vote_count >= 100" % (TYPE_ACTIVITY_UPDATE_QUESTION, TYPE_ACTIVITY_UPDATE_ANSWER, 27)
+
+ self.__award_for_count_num(query, 27)
+
+ def comment_count_10(self):
+ """
+ (5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
+ """
+ query = "SELECT count(*) vote_count, user_id FROM activity WHERE activity_type = %s OR activity_type = %s AND user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) GROUP BY user_id HAVING vote_count >= 10" % (TYPE_ACTIVITY_COMMENT_QUESTION, TYPE_ACTIVITY_COMMENT_ANSWER, 5)
+ self.__award_for_count_num(query, 5)
+
+ def __award_for_count_num(self, query, badge):
+ cursor = connection.cursor()
+ try:
+ cursor.execute(query)
+ rows = cursor.fetchall()
+
+ awarded_users = []
+ badge = get_object_or_404(Badge, id=badge)
+ for row in rows:
+ vote_count = row[0]
+ user_id = row[1]
+
+ if user_id not in awarded_users:
+ user = get_object_or_404(User, id=user_id)
+ award = Award(user=user, badge=badge)
+ award.save()
+ awarded_users.append(user_id)
+ finally:
+ cursor.close()
+
+def main():
+ pass
+
+if __name__ == '__main__':
+ main()
diff --git a/forum/management/commands/sample_command.py b/forum/management/commands/sample_command.py
new file mode 100644
index 00000000..55e67235
--- /dev/null
+++ b/forum/management/commands/sample_command.py
@@ -0,0 +1,7 @@
+from django.core.management.base import NoArgsCommand
+from forum.models import Comment
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ objs = Comment.objects.all()
+ print objs \ No newline at end of file
diff --git a/forum/managers.py b/forum/managers.py
new file mode 100644
index 00000000..94f58ea7
--- /dev/null
+++ b/forum/managers.py
@@ -0,0 +1,208 @@
+import datetime
+import logging
+from django.contrib.auth.models import User, UserManager
+from django.db import connection, models, transaction
+from django.db.models import Q
+from forum.models import *
+
+class QuestionManager(models.Manager):
+ def update_tags(self, question, tagnames, user):
+ """
+ Updates Tag associations for a question to match the given
+ tagname string.
+
+ Returns ``True`` if tag usage counts were updated as a result,
+ ``False`` otherwise.
+ """
+ from forum.models import Tag
+ current_tags = list(question.tags.all())
+ current_tagnames = set(t.name for t in current_tags)
+ updated_tagnames = set(t for t in tagnames.split(' ') if t)
+ modified_tags = []
+
+ removed_tags = [t for t in current_tags
+ if t.name not in updated_tagnames]
+ if removed_tags:
+ modified_tags.extend(removed_tags)
+ question.tags.remove(*removed_tags)
+
+ added_tagnames = updated_tagnames - current_tagnames
+ if added_tagnames:
+ added_tags = Tag.objects.get_or_create_multiple(added_tagnames,
+ user)
+ modified_tags.extend(added_tags)
+ question.tags.add(*added_tags)
+
+ if modified_tags:
+ Tag.objects.update_use_counts(modified_tags)
+ return True
+
+ return False
+
+ def update_answer_count(self, question):
+ """
+ Executes an UPDATE query to update denormalised data with the
+ number of answers the given question has.
+ """
+
+ # for some reasons, this Answer class failed to be imported,
+ # although we have imported all classes from models on top.
+ from forum.models import Answer
+ self.filter(id=question.id).update(
+ answer_count=Answer.objects.get_answers_from_question(question).count())
+
+ def update_view_count(self, question):
+ """
+ update counter+1 when user browse question page
+ """
+ self.filter(id=question.id).update(view_count = question.view_count + 1)
+
+ def update_favorite_count(self, question):
+ """
+ update favourite_count for given question
+ """
+ from forum.models import FavoriteQuestion
+ self.filter(id=question.id).update(favourite_count = FavoriteQuestion.objects.filter(question=question).count())
+
+ def get_similar_questions(self, question):
+ """
+ Get 10 similar questions for given one.
+ This will search the same tag list for give question(by exactly same string) first.
+ Questions with the individual tags will be added to list if above questions are not full.
+ """
+ #print datetime.datetime.now()
+ from forum.models import Question
+ questions = list(Question.objects.filter(tagnames = question.tagnames).all())
+
+ tags_list = question.tags.all()
+ for tag in tags_list:
+ extend_questions = Question.objects.filter(tags__id = tag.id)[:50]
+ for item in extend_questions:
+ if item not in questions and len(questions) < 10:
+ questions.append(item)
+
+ #print datetime.datetime.now()
+ return questions
+
+class TagManager(models.Manager):
+ UPDATE_USED_COUNTS_QUERY = (
+ 'UPDATE tag '
+ 'SET used_count = ('
+ 'SELECT COUNT(*) FROM question_tags '
+ 'WHERE tag_id = tag.id'
+ ') '
+ 'WHERE id IN (%s)')
+
+ def get_or_create_multiple(self, names, user):
+ """
+ Fetches a list of Tags with the given names, creating any Tags
+ which don't exist when necesssary.
+ """
+ tags = list(self.filter(name__in=names))
+ #Set all these tag visible
+ for tag in tags:
+ if tag.deleted:
+ tag.deleted = False
+ tag.deleted_by = None
+ tag.deleted_at = None
+ tag.save()
+
+ if len(tags) < len(names):
+ existing_names = set(tag.name for tag in tags)
+ new_names = [name for name in names if name not in existing_names]
+ tags.extend([self.create(name=name, created_by=user)
+ for name in new_names if self.filter(name=name).count() == 0 and len(name.strip()) > 0])
+
+ return tags
+
+ def update_use_counts(self, tags):
+ """Updates the given Tags with their current use counts."""
+ if not tags:
+ return
+ cursor = connection.cursor()
+ query = self.UPDATE_USED_COUNTS_QUERY % ','.join(['%s'] * len(tags))
+ cursor.execute(query, [tag.id for tag in tags])
+ transaction.commit_unless_managed()
+
+class AnswerManager(models.Manager):
+ GET_ANSWERS_FROM_USER_QUESTIONS = u'SELECT answer.* FROM answer INNER JOIN question ON answer.question_id = question.id WHERE question.author_id =%s AND answer.author_id <> %s'
+ def get_answers_from_question(self, question, user=None):
+ """
+ Retrieves visibile answers for the given question. Delete answers
+ are only visibile to the person who deleted them.
+ """
+
+ if user is None or not user.is_authenticated():
+ return self.filter(question=question, deleted=False)
+ else:
+ return self.filter(Q(question=question),
+ Q(deleted=False) | Q(deleted_by=user))
+
+ def get_answers_from_questions(self, user_id):
+ """
+ Retrieves visibile answers for the given question. Which are not included own answers
+ """
+ cursor = connection.cursor()
+ cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id])
+ return cursor.fetchall()
+
+class VoteManager(models.Manager):
+ COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1"
+ COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1"
+ COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = DATE(NOW())"
+ def get_up_vote_count_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_UP_VOTE_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+ else:
+ return 0
+
+ def get_down_vote_count_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_DOWN_VOTE_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+ else:
+ return 0
+
+ def get_votes_count_today_from_user(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+
+class FlaggedItemManager(models.Manager):
+ COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = DATE(NOW())"
+ def get_flagged_items_count_today(self, user):
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0
+
+class ReputeManager(models.Manager):
+ COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = DATE(NOW())"
+ def get_reputation_by_upvoted_today(self, user):
+ """
+ For one user in one day, he can only earn rep till certain score (ep. +200)
+ by upvoted(also substracted from upvoted canceled). This is because we need
+ to prohibit gaming system by upvoting/cancel again and again.
+ """
+ if user is not None:
+ cursor = connection.cursor()
+ cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id])
+ row = cursor.fetchone()
+ return row[0]
+
+ else:
+ return 0 \ No newline at end of file
diff --git a/forum/models.py b/forum/models.py
new file mode 100644
index 00000000..aba2bf0b
--- /dev/null
+++ b/forum/models.py
@@ -0,0 +1,653 @@
+# encoding:utf-8
+import datetime
+import hashlib
+from urllib import quote_plus, urlencode
+from django.db import models
+from django.utils.html import strip_tags
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.template.defaultfilters import slugify
+from django.db.models.signals import post_delete, post_save, pre_save
+from django.utils.translation import ugettext as _
+import django.dispatch
+
+from forum.managers import *
+from const import *
+
+class Tag(models.Model):
+ name = models.CharField(max_length=255, unique=True)
+ created_by = models.ForeignKey(User, related_name='created_tags')
+ deleted = models.BooleanField(default=False)
+ deleted_at = models.DateTimeField(null=True, blank=True)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_tags')
+ # Denormalised data
+ used_count = models.PositiveIntegerField(default=0)
+
+ objects = TagManager()
+
+ class Meta:
+ db_table = u'tag'
+ ordering = ('-used_count', 'name')
+
+ def __unicode__(self):
+ return self.name
+
+class Comment(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='comments')
+ comment = models.CharField(max_length=300)
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+
+ class Meta:
+ ordering = ('-added_at',)
+ db_table = u'comment'
+ def __unicode__(self):
+ return self.comment
+
+class Vote(models.Model):
+ VOTE_UP = +1
+ VOTE_DOWN = -1
+ VOTE_CHOICES = (
+ (VOTE_UP, u'Up'),
+ (VOTE_DOWN, u'Down'),
+ )
+
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='votes')
+ vote = models.SmallIntegerField(choices=VOTE_CHOICES)
+ voted_at = models.DateTimeField(default=datetime.datetime.now)
+
+ objects = VoteManager()
+
+ class Meta:
+ unique_together = ('content_type', 'object_id', 'user')
+ db_table = u'vote'
+ def __unicode__(self):
+ return '[%s] voted at %s: %s' %(self.user, self.voted_at, self.vote)
+
+ def is_upvote(self):
+ return self.vote == self.VOTE_UP
+
+ def is_downvote(self):
+ return self.vote == self.VOTE_DOWN
+
+class FlaggedItem(models.Model):
+ """A flag on a Question or Answer indicating offensive content."""
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ user = models.ForeignKey(User, related_name='flagged_items')
+ flagged_at = models.DateTimeField(default=datetime.datetime.now)
+
+ objects = FlaggedItemManager()
+
+ class Meta:
+ unique_together = ('content_type', 'object_id', 'user')
+ db_table = u'flagged_item'
+ def __unicode__(self):
+ return '[%s] flagged at %s' %(self.user, self.flagged_at)
+
+class Question(models.Model):
+ title = models.CharField(max_length=300)
+ author = models.ForeignKey(User, related_name='questions')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ tags = models.ManyToManyField(Tag, related_name='questions')
+ # Status
+ wiki = models.BooleanField(default=False)
+ wikified_at = models.DateTimeField(null=True, blank=True)
+ answer_accepted = models.BooleanField(default=False)
+ closed = models.BooleanField(default=False)
+ closed_by = models.ForeignKey(User, null=True, blank=True, related_name='closed_questions')
+ closed_at = models.DateTimeField(null=True, blank=True)
+ close_reason = models.SmallIntegerField(choices=CLOSE_REASONS, null=True, blank=True)
+ deleted = models.BooleanField(default=False)
+ deleted_at = models.DateTimeField(null=True, blank=True)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_questions')
+ locked = models.BooleanField(default=False)
+ locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_questions')
+ locked_at = models.DateTimeField(null=True, blank=True)
+ # Denormalised data
+ score = models.IntegerField(default=0)
+ vote_up_count = models.IntegerField(default=0)
+ vote_down_count = models.IntegerField(default=0)
+ answer_count = models.PositiveIntegerField(default=0)
+ comment_count = models.PositiveIntegerField(default=0)
+ view_count = models.PositiveIntegerField(default=0)
+ offensive_flag_count = models.SmallIntegerField(default=0)
+ favourite_count = models.PositiveIntegerField(default=0)
+ last_edited_at = models.DateTimeField(null=True, blank=True)
+ last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_questions')
+ last_activity_at = models.DateTimeField(default=datetime.datetime.now)
+ last_activity_by = models.ForeignKey(User, related_name='last_active_in_questions')
+ tagnames = models.CharField(max_length=125)
+ summary = models.CharField(max_length=180)
+ html = models.TextField()
+ comments = generic.GenericRelation(Comment)
+ votes = generic.GenericRelation(Vote)
+ flagged_items = generic.GenericRelation(FlaggedItem)
+
+ objects = QuestionManager()
+
+ def save(self, **kwargs):
+ """
+ Overridden to manually manage addition of tags when the object
+ is first saved.
+
+ This is required as we're using ``tagnames`` as the sole means of
+ adding and editing tags.
+ """
+ initial_addition = (self.id is None)
+ super(Question, self).save(**kwargs)
+ if initial_addition:
+ tags = Tag.objects.get_or_create_multiple(self.tagname_list(),
+ self.author)
+ self.tags.add(*tags)
+ Tag.objects.update_use_counts(tags)
+
+ def tagname_list(self):
+ """Creates a list of Tag names from the ``tagnames`` attribute."""
+ return [name for name in self.tagnames.split(u' ')]
+
+ def get_absolute_url(self):
+ return '%s%s' % (reverse('question', args=[self.id]), self.title)
+
+ def has_favorite_by_user(self, user):
+ if not user.is_authenticated():
+ return False
+ return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0
+
+ def get_answer_count_by_user(self, user_id):
+ query_set = Answer.objects.filter(author__id=user_id)
+ return query_set.filter(question=self).count()
+
+ def get_question_title(self):
+ if self.closed:
+ attr = CONST['closed']
+ elif self.deleted:
+ attr = CONST['deleted']
+ else:
+ attr = None
+ return u'%s %s' % (self.title, attr) if attr is not None else self.title
+
+ def get_revision_url(self):
+ return reverse('question_revisions', args=[self.id])
+
+ def get_latest_revision(self):
+ return self.revisions.all()[0]
+
+ def __unicode__(self):
+ return self.title
+
+ class Meta:
+ db_table = u'question'
+
+class QuestionRevision(models.Model):
+ """A revision of a Question."""
+ question = models.ForeignKey(Question, related_name='revisions')
+ revision = models.PositiveIntegerField(blank=True)
+ title = models.CharField(max_length=300)
+ author = models.ForeignKey(User, related_name='question_revisions')
+ revised_at = models.DateTimeField()
+ tagnames = models.CharField(max_length=125)
+ summary = models.CharField(max_length=300, blank=True)
+ text = models.TextField()
+
+ class Meta:
+ db_table = u'question_revision'
+ ordering = ('-revision',)
+
+ def get_question_title(self):
+ return self.question.title
+
+ def get_absolute_url(self):
+ return '/questions/%s/revisions' % (self.question.id)
+
+ def save(self, **kwargs):
+ """Looks up the next available revision number."""
+ if not self.revision:
+ self.revision = QuestionRevision.objects.filter(
+ question=self.question).values_list('revision',
+ flat=True)[0] + 1
+ super(QuestionRevision, self).save(**kwargs)
+
+ def __unicode__(self):
+ return u'revision %s of %s' % (self.revision, self.title)
+
+class Answer(models.Model):
+ question = models.ForeignKey(Question, related_name='answers')
+ author = models.ForeignKey(User, related_name='answers')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ # Status
+ wiki = models.BooleanField(default=False)
+ wikified_at = models.DateTimeField(null=True, blank=True)
+ accepted = models.BooleanField(default=False)
+ accepted_at = models.DateTimeField(null=True, blank=True)
+ deleted = models.BooleanField(default=False)
+ deleted_by = models.ForeignKey(User, null=True, blank=True, related_name='deleted_answers')
+ locked = models.BooleanField(default=False)
+ locked_by = models.ForeignKey(User, null=True, blank=True, related_name='locked_answers')
+ locked_at = models.DateTimeField(null=True, blank=True)
+ # Denormalised data
+ score = models.IntegerField(default=0)
+ vote_up_count = models.IntegerField(default=0)
+ vote_down_count = models.IntegerField(default=0)
+ comment_count = models.PositiveIntegerField(default=0)
+ offensive_flag_count = models.SmallIntegerField(default=0)
+ last_edited_at = models.DateTimeField(null=True, blank=True)
+ last_edited_by = models.ForeignKey(User, null=True, blank=True, related_name='last_edited_answers')
+ html = models.TextField()
+ comments = generic.GenericRelation(Comment)
+ votes = generic.GenericRelation(Vote)
+ flagged_items = generic.GenericRelation(FlaggedItem)
+
+ objects = AnswerManager()
+
+ def get_user_vote(self, user):
+ votes = self.votes.filter(user=user)
+ if votes.count() > 0:
+ return votes[0]
+ else:
+ return None
+
+ def get_latest_revision(self):
+ return self.revisions.all()[0]
+
+ def get_question_title(self):
+ return self.question.title
+
+ def get_absolute_url(self):
+ return '%s%s#%s' % (reverse('question', args=[self.question.id]), self.question.title, self.id)
+
+ class Meta:
+ db_table = u'answer'
+
+ def __unicode__(self):
+ return self.html
+
+class AnswerRevision(models.Model):
+ """A revision of an Answer."""
+ answer = models.ForeignKey(Answer, related_name='revisions')
+ revision = models.PositiveIntegerField()
+ author = models.ForeignKey(User, related_name='answer_revisions')
+ revised_at = models.DateTimeField()
+ summary = models.CharField(max_length=300, blank=True)
+ text = models.TextField()
+
+ def get_absolute_url(self):
+ return '/answers/%s/revisions' % (self.answer.id)
+
+ def get_question_title(self):
+ return self.answer.question.title
+
+ class Meta:
+ db_table = u'answer_revision'
+ ordering = ('-revision',)
+
+ def save(self, **kwargs):
+ """Looks up the next available revision number if not set."""
+ if not self.revision:
+ self.revision = AnswerRevision.objects.filter(
+ answer=self.answer).values_list('revision',
+ flat=True)[0] + 1
+ super(AnswerRevision, self).save(**kwargs)
+
+class FavoriteQuestion(models.Model):
+ """A favorite Question of a User."""
+ question = models.ForeignKey(Question)
+ user = models.ForeignKey(User, related_name='user_favorite_questions')
+ added_at = models.DateTimeField(default=datetime.datetime.now)
+ class Meta:
+ db_table = u'favorite_question'
+ def __unicode__(self):
+ return '[%s] favorited at %s' %(self.user, self.added_at)
+
+class Badge(models.Model):
+ """Awarded for notable actions performed on the site by Users."""
+ GOLD = 1
+ SILVER = 2
+ BRONZE = 3
+ TYPE_CHOICES = (
+ (GOLD, _('gold')),
+ (SILVER, _('silver')),
+ (BRONZE, _('bronze')),
+ )
+
+ name = models.CharField(max_length=50)
+ type = models.SmallIntegerField(choices=TYPE_CHOICES)
+ slug = models.SlugField(max_length=50, blank=True)
+ description = models.CharField(max_length=300)
+ multiple = models.BooleanField(default=False)
+ # Denormalised data
+ awarded_count = models.PositiveIntegerField(default=0)
+
+ class Meta:
+ db_table = u'badge'
+ ordering = ('name',)
+ unique_together = ('name', 'type')
+
+ def __unicode__(self):
+ return u'%s: %s' % (self.get_type_display(), self.name)
+
+ def save(self, **kwargs):
+ if not self.slug:
+ self.slug = self.name#slugify(self.name)
+ super(Badge, self).save(**kwargs)
+
+ def get_absolute_url(self):
+ return '%s%s/' % (reverse('badge', args=[self.id]), self.slug)
+
+class Award(models.Model):
+ """The awarding of a Badge to a User."""
+ user = models.ForeignKey(User, related_name='award_user')
+ badge = models.ForeignKey(Badge, related_name='award_badge')
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ awarded_at = models.DateTimeField(default=datetime.datetime.now)
+ notified = models.BooleanField(default=False)
+
+ def __unicode__(self):
+ return u'[%s] is awarded a badge [%s] at %s' % (self.user.username, self.badge.name, self.awarded_at)
+
+ class Meta:
+ db_table = u'award'
+
+class Repute(models.Model):
+ """The reputation histories for user"""
+ user = models.ForeignKey(User)
+ positive = models.SmallIntegerField(default=0)
+ negative = models.SmallIntegerField(default=0)
+ question = models.ForeignKey(Question)
+ reputed_at = models.DateTimeField(default=datetime.datetime.now)
+ reputation_type = models.SmallIntegerField(choices=TYPE_REPUTATION)
+ reputation = models.IntegerField(default=1)
+ objects = ReputeManager()
+
+ def __unicode__(self):
+ return u'[%s]\' reputation changed at %s' % (self.user.username, self.reputed_at)
+
+ class Meta:
+ db_table = u'repute'
+
+class Activity(models.Model):
+ """
+ We keep some history data for user activities
+ """
+ user = models.ForeignKey(User)
+ activity_type = models.SmallIntegerField(choices=TYPE_ACTIVITY)
+ active_at = models.DateTimeField(default=datetime.datetime.now)
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+ is_auditted = models.BooleanField(default=False)
+
+ def __unicode__(self):
+ return u'[%s] was active at %s' % (self.user.username, self.active_at)
+
+ class Meta:
+ db_table = u'activity'
+
+class Book(models.Model):
+ """
+ Model for book info
+ """
+ user = models.ForeignKey(User)
+ title = models.CharField(max_length=255)
+ short_name = models.CharField(max_length=255)
+ author = models.CharField(max_length=255)
+ price = models.DecimalField(max_digits=6, decimal_places=2)
+ pages = models.SmallIntegerField()
+ published_at = models.DateTimeField()
+ publication = models.CharField(max_length=255)
+ cover_img = models.CharField(max_length=255)
+ tagnames = models.CharField(max_length=125)
+ added_at = models.DateTimeField()
+ last_edited_at = models.DateTimeField()
+ questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
+
+ def get_absolute_url(self):
+ return '%s' % reverse('book', args=[self.short_name])
+
+ def __unicode__(self):
+ return self.title
+ class Meta:
+ db_table = u'book'
+
+class BookAuthorInfo(models.Model):
+ """
+ Model for book author info
+ """
+ user = models.ForeignKey(User)
+ book = models.ForeignKey(Book)
+ blog_url = models.CharField(max_length=255)
+ added_at = models.DateTimeField()
+ last_edited_at = models.DateTimeField()
+
+ class Meta:
+ db_table = u'book_author_info'
+
+class BookAuthorRss(models.Model):
+ """
+ Model for book author blog rss
+ """
+ user = models.ForeignKey(User)
+ book = models.ForeignKey(Book)
+ title = models.CharField(max_length=255)
+ url = models.CharField(max_length=255)
+ rss_created_at = models.DateTimeField()
+ added_at = models.DateTimeField()
+
+ class Meta:
+ db_table = u'book_author_rss'
+
+# User extend properties
+QUESTIONS_PER_PAGE_CHOICES = (
+ (10, u'10'),
+ (30, u'30'),
+ (50, u'50'),
+)
+
+User.add_to_class('reputation', models.PositiveIntegerField(default=1))
+User.add_to_class('gravatar', models.CharField(max_length=32))
+User.add_to_class('favorite_questions',
+ models.ManyToManyField(Question, through=FavoriteQuestion,
+ related_name='favorited_by'))
+User.add_to_class('badges', models.ManyToManyField(Badge, through=Award,
+ related_name='awarded_to'))
+User.add_to_class('gold', models.SmallIntegerField(default=0))
+User.add_to_class('silver', models.SmallIntegerField(default=0))
+User.add_to_class('bronze', models.SmallIntegerField(default=0))
+User.add_to_class('questions_per_page',
+ models.SmallIntegerField(choices=QUESTIONS_PER_PAGE_CHOICES, default=10))
+User.add_to_class('last_seen',
+ models.DateTimeField(default=datetime.datetime.now))
+User.add_to_class('real_name', models.CharField(max_length=100, blank=True))
+User.add_to_class('website', models.URLField(max_length=200, blank=True))
+User.add_to_class('location', models.CharField(max_length=100, blank=True))
+User.add_to_class('date_of_birth', models.DateField(null=True, blank=True))
+User.add_to_class('about', models.TextField(blank=True))
+
+# custom signal
+tags_updated = django.dispatch.Signal(providing_args=["question"])
+edit_question_or_answer = django.dispatch.Signal(providing_args=["instance", "modified_by"])
+delete_post_or_answer = django.dispatch.Signal(providing_args=["instance", "deleted_by"])
+mark_offensive = django.dispatch.Signal(providing_args=["instance", "mark_by"])
+user_updated = django.dispatch.Signal(providing_args=["instance", "updated_by"])
+def get_messages(self):
+ messages = []
+ for m in self.message_set.all():
+ messages.append(m.message)
+ return messages
+
+def delete_messages(self):
+ self.message_set.all().delete()
+
+def get_profile_url(self):
+ """Returns the URL for this User's profile."""
+ return '%s%s/' % (reverse('user', args=[self.id]), self.username)
+User.add_to_class('get_profile_url', get_profile_url)
+User.add_to_class('get_messages', get_messages)
+User.add_to_class('delete_messages', delete_messages)
+
+def calculate_gravatar_hash(instance, **kwargs):
+ """Calculates a User's gravatar hash from their email address."""
+ if kwargs.get('raw', False):
+ return
+ instance.gravatar = hashlib.md5(instance.email).hexdigest()
+
+def record_ask_event(instance, created, **kwargs):
+ if created:
+ activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ASK_QUESTION)
+ activity.save()
+
+def record_answer_event(instance, created, **kwargs):
+ if created:
+ activity = Activity(user=instance.author, active_at=instance.added_at, content_object=instance, activity_type=TYPE_ACTIVITY_ANSWER)
+ activity.save()
+
+def record_comment_event(instance, created, **kwargs):
+ if created:
+ from django.contrib.contenttypes.models import ContentType
+ question_type = ContentType.objects.get_for_model(Question)
+ question_type_id = question_type.id
+ type = TYPE_ACTIVITY_COMMENT_QUESTION if instance.content_type_id == question_type_id else TYPE_ACTIVITY_COMMENT_ANSWER
+ activity = Activity(user=instance.user, active_at=instance.added_at, content_object=instance, activity_type=type)
+ activity.save()
+
+def record_revision_question_event(instance, created, **kwargs):
+ if created and instance.revision <> 1:
+ activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_QUESTION)
+ activity.save()
+
+def record_revision_answer_event(instance, created, **kwargs):
+ if created and instance.revision <> 1:
+ activity = Activity(user=instance.author, active_at=instance.revised_at, content_object=instance, activity_type=TYPE_ACTIVITY_UPDATE_ANSWER)
+ activity.save()
+
+def record_award_event(instance, created, **kwargs):
+ """
+ After we awarded a badge to user, we need to record this activity and notify user.
+ We also recaculate awarded_count of this badge and user information.
+ """
+ if created:
+ activity = Activity(user=instance.user, active_at=instance.awarded_at, content_object=instance,
+ activity_type=TYPE_ACTIVITY_PRIZE)
+ activity.save()
+
+ instance.badge.awarded_count += 1
+ instance.badge.save()
+
+ if instance.badge.type == Badge.GOLD:
+ instance.user.gold += 1
+ if instance.badge.type == Badge.SILVER:
+ instance.user.silver += 1
+ if instance.badge.type == Badge.BRONZE:
+ instance.user.bronze += 1
+ instance.user.save()
+
+def notify_award_message(instance, created, **kwargs):
+ """
+ Notify users when they have been awarded badges by using Django message.
+ """
+ if created:
+ user = instance.user
+ user.message_set.create(message=u"%s" % instance.badge.name)
+
+def record_answer_accepted(instance, created, **kwargs):
+ """
+ when answer is accepted, we record this for question author - who accepted it.
+ """
+ if not created and instance.accepted:
+ activity = Activity(user=instance.question.author, active_at=datetime.datetime.now(), \
+ content_object=instance, activity_type=TYPE_ACTIVITY_MARK_ANSWER)
+ activity.save()
+
+def update_last_seen(instance, created, **kwargs):
+ """
+ when user has activities, we update 'last_seen' time stamp for him
+ """
+ user = instance.user
+ user.last_seen = datetime.datetime.now()
+ user.save()
+
+def record_vote(instance, created, **kwargs):
+ """
+ when user have voted
+ """
+ if created:
+ if instance.vote == 1:
+ vote_type = TYPE_ACTIVITY_VOTE_UP
+ else:
+ vote_type = TYPE_ACTIVITY_VOTE_DOWN
+
+ activity = Activity(user=instance.user, active_at=instance.voted_at, content_object=instance, activity_type=vote_type)
+ activity.save()
+
+def record_cancel_vote(instance, **kwargs):
+ """
+ when user canceled vote, the vote will be deleted.
+ """
+ activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_CANCEL_VOTE)
+ activity.save()
+
+def record_delete_question(instance, delete_by, **kwargs):
+ """
+ when user deleted the question
+ """
+ if instance.__class__ == "Question":
+ activity_type = TYPE_ACTIVITY_DELETE_QUESTION
+ else:
+ activity_type = TYPE_ACTIVITY_DELETE_ANSWER
+
+ activity = Activity(user=delete_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=activity_type)
+ activity.save()
+
+def record_mark_offensive(instance, mark_by, **kwargs):
+ activity = Activity(user=mark_by, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_MARK_OFFENSIVE)
+ activity.save()
+
+def record_update_tags(question, **kwargs):
+ """
+ when user updated tags of the question
+ """
+ activity = Activity(user=question.author, active_at=datetime.datetime.now(), content_object=question, activity_type=TYPE_ACTIVITY_UPDATE_TAGS)
+ activity.save()
+
+def record_favorite_question(instance, created, **kwargs):
+ """
+ when user add the question in him favorite questions list.
+ """
+ if created:
+ activity = Activity(user=instance.user, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_FAVORITE)
+ activity.save()
+
+def record_user_full_updated(instance, **kwargs):
+ activity = Activity(user=instance, active_at=datetime.datetime.now(), content_object=instance, activity_type=TYPE_ACTIVITY_USER_FULL_UPDATED)
+ activity.save()
+
+#signal for User modle save changes
+pre_save.connect(calculate_gravatar_hash, sender=User)
+post_save.connect(record_ask_event, sender=Question)
+post_save.connect(record_answer_event, sender=Answer)
+post_save.connect(record_comment_event, sender=Comment)
+post_save.connect(record_revision_question_event, sender=QuestionRevision)
+post_save.connect(record_revision_answer_event, sender=AnswerRevision)
+post_save.connect(record_award_event, sender=Award)
+post_save.connect(notify_award_message, sender=Award)
+post_save.connect(record_answer_accepted, sender=Answer)
+post_save.connect(update_last_seen, sender=Activity)
+post_save.connect(record_vote, sender=Vote)
+post_delete.connect(record_cancel_vote, sender=Vote)
+delete_post_or_answer.connect(record_delete_question, sender=Question)
+delete_post_or_answer.connect(record_delete_question, sender=Answer)
+mark_offensive.connect(record_mark_offensive, sender=Question)
+mark_offensive.connect(record_mark_offensive, sender=Answer)
+tags_updated.connect(record_update_tags, sender=Question)
+post_save.connect(record_favorite_question, sender=FavoriteQuestion)
+user_updated.connect(record_user_full_updated, sender=User)
diff --git a/forum/templatetags/__init__.py b/forum/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/forum/templatetags/__init__.py
diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py
new file mode 100644
index 00000000..744fa762
--- /dev/null
+++ b/forum/templatetags/extra_filters.py
@@ -0,0 +1,83 @@
+from django import template
+from forum import auth
+
+register = template.Library()
+
+@register.filter
+def can_vote_up(user):
+ return auth.can_vote_up(user)
+
+@register.filter
+def can_flag_offensive(user):
+ return auth.can_flag_offensive(user)
+
+@register.filter
+def can_add_comments(user):
+ return auth.can_add_comments(user)
+
+@register.filter
+def can_vote_down(user):
+ return auth.can_vote_down(user)
+
+@register.filter
+def can_retag_questions(user):
+ return auth.can_retag_questions(user)
+
+@register.filter
+def can_edit_post(user, post):
+ return auth.can_edit_post(user, post)
+
+@register.filter
+def can_delete_comment(user, comment):
+ return auth.can_delete_comment(user, comment)
+
+@register.filter
+def can_view_offensive_flags(user):
+ return auth.can_view_offensive_flags(user)
+
+@register.filter
+def can_close_question(user, question):
+ return auth.can_close_question(user, question)
+
+@register.filter
+def can_lock_posts(user):
+ return auth.can_lock_posts(user)
+
+@register.filter
+def can_accept_answer(user, question, answer):
+ return auth.can_accept_answer(user, question, answer)
+
+@register.filter
+def can_reopen_question(user, question):
+ return auth.can_reopen_question(user, question)
+
+@register.filter
+def can_delete_post(user, post):
+ return auth.can_delete_post(user, post)
+
+@register.filter
+def can_view_user_edit(request_user, target_user):
+ return auth.can_view_user_edit(request_user, target_user)
+
+@register.filter
+def can_view_user_votes(request_user, target_user):
+ return auth.can_view_user_votes(request_user, target_user)
+
+@register.filter
+def can_view_user_preferences(request_user, target_user):
+ return auth.can_view_user_preferences(request_user, target_user)
+
+@register.filter
+def is_user_self(request_user, target_user):
+ return auth.is_user_self(request_user, target_user)
+
+@register.filter
+def cnprog_intword(number):
+ try:
+ if 1000 <= number < 10000:
+ string = str(number)[0:1]
+ return "<span class=""thousand"">%sk</span>" % string
+ else:
+ return number
+ except:
+ return number \ No newline at end of file
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
new file mode 100644
index 00000000..de853135
--- /dev/null
+++ b/forum/templatetags/extra_tags.py
@@ -0,0 +1,240 @@
+import time
+import datetime
+import math
+import re
+import logging
+from django import template
+from django.utils.encoding import smart_unicode
+from django.utils.safestring import mark_safe
+from django.utils.timesince import timesince
+from forum.const import *
+from django.utils.translation import ugettext as _
+
+register = template.Library()
+
+GRAVATAR_TEMPLATE = ('<img width="%(size)s" height="%(size)s" '
+ 'src="http://www.gravatar.com/avatar/%(gravatar_hash)s'
+ '?s=%(size)s&d=identicon&r=PG">')
+
+@register.simple_tag
+def gravatar(user, size):
+ """
+ Creates an ``<img>`` for a user's Gravatar with a given size.
+
+ This tag can accept a User object, or a dict containing the
+ appropriate values.
+ """
+ try:
+ gravatar = user['gravatar']
+ except (TypeError, AttributeError, KeyError):
+ gravatar = user.gravatar
+ return mark_safe(GRAVATAR_TEMPLATE % {
+ 'size': size,
+ 'gravatar_hash': gravatar,
+ })
+
+MAX_FONTSIZE = 18
+MIN_FONTSIZE = 12
+@register.simple_tag
+def tag_font_size(max_size, min_size, current_size):
+ """
+ do a logarithmic mapping calcuation for a proper size for tagging cloud
+ Algorithm from http://blogs.dekoh.com/dev/2007/10/29/choosing-a-good-font-size-variation-algorithm-for-your-tag-cloud/
+ """
+ #avoid invalid calculation
+ if current_size == 0:
+ current_size = 1
+ try:
+ weight = (math.log10(current_size) - math.log10(min_size)) / (math.log10(max_size) - math.log10(min_size))
+ except:
+ weight = 0
+ return MIN_FONTSIZE + round((MAX_FONTSIZE - MIN_FONTSIZE) * weight)
+
+
+LEADING_PAGE_RANGE_DISPLAYED = TRAILING_PAGE_RANGE_DISPLAYED = 5
+LEADING_PAGE_RANGE = TRAILING_PAGE_RANGE = 4
+NUM_PAGES_OUTSIDE_RANGE = 1
+ADJACENT_PAGES = 2
+@register.inclusion_tag("paginator.html")
+def cnprog_paginator(context):
+ """
+ custom paginator tag
+ Inspired from http://blog.localkinegrinds.com/2007/09/06/digg-style-pagination-in-django/
+ """
+ if (context["is_paginated"]):
+ " Initialize variables "
+ in_leading_range = in_trailing_range = False
+ pages_outside_leading_range = pages_outside_trailing_range = range(0)
+
+ if (context["pages"] <= LEADING_PAGE_RANGE_DISPLAYED):
+ in_leading_range = in_trailing_range = True
+ page_numbers = [n for n in range(1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
+ elif (context["page"] <= LEADING_PAGE_RANGE):
+ in_leading_range = True
+ page_numbers = [n for n in range(1, LEADING_PAGE_RANGE_DISPLAYED + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
+ elif (context["page"] > context["pages"] - TRAILING_PAGE_RANGE):
+ in_trailing_range = True
+ page_numbers = [n for n in range(context["pages"] - TRAILING_PAGE_RANGE_DISPLAYED + 1, context["pages"] + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
+ else:
+ page_numbers = [n for n in range(context["page"] - ADJACENT_PAGES, context["page"] + ADJACENT_PAGES + 1) if n > 0 and n <= context["pages"]]
+ pages_outside_leading_range = [n + context["pages"] for n in range(0, -NUM_PAGES_OUTSIDE_RANGE, -1)]
+ pages_outside_trailing_range = [n + 1 for n in range(0, NUM_PAGES_OUTSIDE_RANGE)]
+
+ extend_url = context.get('extend_url', '')
+ return {
+ "base_url": context["base_url"],
+ "is_paginated": context["is_paginated"],
+ "previous": context["previous"],
+ "has_previous": context["has_previous"],
+ "next": context["next"],
+ "has_next": context["has_next"],
+ "page": context["page"],
+ "pages": context["pages"],
+ "page_numbers": page_numbers,
+ "in_leading_range" : in_leading_range,
+ "in_trailing_range" : in_trailing_range,
+ "pages_outside_leading_range": pages_outside_leading_range,
+ "pages_outside_trailing_range": pages_outside_trailing_range,
+ "extend_url" : extend_url
+ }
+
+@register.inclusion_tag("pagesize.html")
+def cnprog_pagesize(context):
+ """
+ display the pagesize selection boxes for paginator
+ """
+ if (context["is_paginated"]):
+ return {
+ "base_url": context["base_url"],
+ "pagesize" : context["pagesize"],
+ "is_paginated": context["is_paginated"]
+ }
+
+@register.simple_tag
+def get_score_badge(user):
+ BADGE_TEMPLATE = '<span class="score" title="%(reputation)s %(reputationword)s">%(reputation)s</span>'
+ if user.gold > 0 :
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgesword)s">'
+ '<span class="badge1">&#9679;</span>'
+ '<span class="badgecount">%(gold)s</span>'
+ '</span>')
+ if user.silver > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgesword)s">'
+ '<span class="silver">&#9679;</span>'
+ '<span class="badgecount">%(silver)s</span>'
+ '</span>')
+ if user.bronze > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgesword)s">'
+ '<span class="bronze">&#9679;</span>'
+ '<span class="badgecount">%(bronze)s</span>'
+ '</span>')
+ BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
+ return mark_safe(BADGE_TEMPLATE % {
+ 'reputation' : user.reputation,
+ 'gold' : user.gold,
+ 'silver' : user.silver,
+ 'bronze' : user.bronze,
+ 'badgesword' : _('badges'),
+ 'reputationword' : _('reputation points'),
+ })
+
+@register.simple_tag
+def get_score_badge_by_details(rep, gold, silver, bronze):
+ BADGE_TEMPLATE = '<span class="reputation-score" title="%(reputation)s %(repword)s">%(reputation)s</span>'
+ if gold > 0 :
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(gold)s %(badgeword)s">'
+ '<span class="badge1">&#9679;</span>'
+ '<span class="badgecount">%(gold)s</span>'
+ '</span>')
+ if silver > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(silver)s %(badgeword)s">'
+ '<span class="badge2">&#9679;</span>'
+ '<span class="badgecount">%(silver)s</span>'
+ '</span>')
+ if bronze > 0:
+ BADGE_TEMPLATE = '%s%s' % (BADGE_TEMPLATE, '<span title="%(bronze)s %(badgeword)s">'
+ '<span class="badge3">&#9679;</span>'
+ '<span class="badgecount">%(bronze)s</span>'
+ '</span>')
+ BADGE_TEMPLATE = smart_unicode(BADGE_TEMPLATE, encoding='utf-8', strings_only=False, errors='strict')
+ return mark_safe(BADGE_TEMPLATE % {
+ 'reputation' : rep,
+ 'gold' : gold,
+ 'silver' : silver,
+ 'bronze' : bronze,
+ 'repword' : _('reputation points'),
+ 'badgeword' : _('badges'),
+ })
+
+@register.simple_tag
+def get_user_vote_image(dic, key, arrow):
+ if dic.has_key(key):
+ if int(dic[key]) == int(arrow):
+ return '-on'
+ return ''
+
+@register.simple_tag
+def get_age(birthday):
+ current_time = datetime.datetime(*time.localtime()[0:6])
+ year = birthday.year
+ month = birthday.month
+ day = birthday.day
+ diff = current_time - datetime.datetime(year,month,day,0,0,0)
+ return diff.days / 365
+
+@register.simple_tag
+def get_total_count(up_count, down_count):
+ return up_count + down_count
+
+@register.simple_tag
+def format_number(value):
+ strValue = str(value)
+ if len(strValue) <= 3:
+ return strValue
+ result = ''
+ first = ''
+ pattern = re.compile('(-?\d+)(\d{3})')
+ m = re.match(pattern, strValue)
+ while m != None:
+ first = m.group(1)
+ second = m.group(2)
+ result = ',' + second + result
+ strValue = first + ',' + second
+ m = re.match(pattern, strValue)
+ return first + result
+
+@register.simple_tag
+def convert2tagname_list(question):
+ question['tagnames'] = [name for name in question['tagnames'].split(u' ')]
+ return ''
+
+@register.simple_tag
+def diff_date(date, limen=2):
+ current_time = datetime.datetime(*time.localtime()[0:6])
+ diff = current_time - date
+ diff_days = diff.days
+ if diff_days > limen:
+ return date
+ else:
+ return timesince(date) + _(' ago')
+
+@register.simple_tag
+def get_latest_changed_timestamp():
+ try:
+ from time import localtime, strftime
+ from os import path
+ from django.conf import settings
+ root = settings.SITE_SRC_ROOT
+ dir = (
+ root,
+ '%s/forum' % root,
+ '%s/templates' % root,
+ )
+ stamp = (path.getmtime(d) for d in dir)
+ latest = max(stamp)
+ timestr = strftime("%H:%M %b-%d-%Y %Z", localtime(latest))
+ except:
+ timestr = ''
+ return timestr
diff --git a/forum/user.py b/forum/user.py
new file mode 100644
index 00000000..13e9be30
--- /dev/null
+++ b/forum/user.py
@@ -0,0 +1,74 @@
+from django.utils.translation import ugettext as _
+class UserView:
+ def __init__(self, id, tab_title, tab_description, page_title, view_name, template_file, data_size=0):
+ self.id = id
+ self.tab_title = tab_title
+ self.tab_description = tab_description
+ self.page_title = page_title
+ self.view_name = view_name
+ self.template_file = template_file
+ self.data_size = data_size
+
+
+USER_TEMPLATE_VIEWS = (
+ UserView(
+ id = 'stats',
+ tab_title = _('overview'),
+ tab_description = _('user profile'),
+ page_title = _('user profile overview'),
+ view_name = 'user_stats',
+ template_file = 'user_stats.html'
+ ),
+ UserView(
+ id = 'recent',
+ tab_title = _('recent activity'),
+ tab_description = _('recent user activity'),
+ page_title = _('profile - recent activity'),
+ view_name = 'user_recent',
+ template_file = 'user_recent.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'responses',
+ tab_title = _('responses'),
+ tab_description = _('comments and answers to others questions'),
+ page_title = _('profile - responses'),
+ view_name = 'user_responses',
+ template_file = 'user_responses.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'reputation',
+ tab_title = _('reputation'),
+ tab_description = _('user reputation in the community'),
+ page_title = _('profile - user reputation'),
+ view_name = 'user_reputation',
+ template_file = 'user_reputation.html'
+ ),
+ UserView(
+ id = 'favorites',
+ tab_title = _('favorite questions'),
+ tab_description = _('users favorite questions'),
+ page_title = _('profile - favorite questions'),
+ view_name = 'user_favorites',
+ template_file = 'user_favorites.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'votes',
+ tab_title = _('casted votes'),
+ tab_description = _('user vote record'),
+ page_title = _('profile - votes'),
+ view_name = 'user_votes',
+ template_file = 'user_votes.html',
+ data_size = 50
+ ),
+ UserView(
+ id = 'preferences',
+ tab_title = _('preferences'),
+ tab_description = _('user preference settings'),
+ page_title = _('profile - user preferences'),
+ view_name = 'user_preferences',
+ template_file = 'user_preferences.html'
+ )
+)
diff --git a/forum/views.py b/forum/views.py
new file mode 100644
index 00000000..25574e0b
--- /dev/null
+++ b/forum/views.py
@@ -0,0 +1,1962 @@
+# encoding:utf-8
+import os.path
+import time, datetime, calendar, random
+import logging
+from urllib import quote, unquote
+from django.conf import settings
+from django.core.files.storage import default_storage
+from django.shortcuts import render_to_response, get_object_or_404
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponseRedirect, HttpResponse,Http404
+from django.core.paginator import Paginator, EmptyPage, InvalidPage
+from django.template import RequestContext
+from django.utils.html import *
+from django.utils import simplejson
+from django.core import serializers
+from django.db import transaction
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import ugettext as _
+
+from utils.html import sanitize_html
+from markdown2 import Markdown
+#from lxml.html.diff import htmldiff
+from forum.diff import textDiff as htmldiff
+from forum.forms import *
+from forum.models import *
+from forum.auth import *
+from forum.const import *
+from forum.user import *
+from forum import auth
+
+# used in index page
+INDEX_PAGE_SIZE = 20
+INDEX_AWARD_SIZE = 15
+INDEX_TAGS_SIZE = 100
+# used in tags list
+DEFAULT_PAGE_SIZE = 60
+# used in questions
+QUESTIONS_PAGE_SIZE = 10
+# used in users
+USERS_PAGE_SIZE = 35
+# used in answers
+ANSWERS_PAGE_SIZE = 10
+markdowner = Markdown(html4tags=True)
+question_type = ContentType.objects.get_for_model(Question)
+answer_type = ContentType.objects.get_for_model(Answer)
+comment_type = ContentType.objects.get_for_model(Comment)
+question_revision_type = ContentType.objects.get_for_model(QuestionRevision)
+answer_revision_type = ContentType.objects.get_for_model(AnswerRevision)
+repute_type =ContentType.objects.get_for_model(Repute)
+question_type_id = question_type.id
+answer_type_id = answer_type.id
+comment_type_id = comment_type.id
+question_revision_type_id = question_revision_type.id
+answer_revision_type_id = answer_revision_type.id
+repute_type_id = repute_type.id
+def _get_tags_cache_json():
+ tags = Tag.objects.filter(deleted=False).all()
+ tags_list = []
+ for tag in tags:
+ dic = {'n': tag.name, 'c': tag.used_count }
+ tags_list.append(dic)
+ tags = simplejson.dumps(tags_list)
+ return tags
+
+def index(request):
+ view_id = request.GET.get('sort', None)
+ view_dic = {
+ "latest":"-last_activity_at",
+ "hottest":"-answer_count",
+ "mostvoted":"-score",
+ "trans": "-last_activity_at"
+ }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-last_activity_at"
+ # group questions by author_id of 28,29
+ if view_id == 'trans':
+ questions = Question.objects.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:INDEX_PAGE_SIZE]
+ else:
+ questions = Question.objects.filter(deleted=False).order_by(orderby)[:INDEX_PAGE_SIZE]
+ # RISK - inner join queries
+ questions = questions.select_related();
+ tags = Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-id")[:INDEX_TAGS_SIZE]
+
+ awards = Award.objects.extra(
+ select={'badge_id': 'badge.id', 'badge_name':'badge.name',
+ 'badge_description': 'badge.description', 'badge_type': 'badge.type',
+ 'user_id': 'auth_user.id', 'user_name': 'auth_user.username'
+ },
+ tables=['award', 'badge', 'auth_user'],
+ order_by=['-awarded_at'],
+ where=['auth_user.id=award.user_id AND badge_id=badge.id'],
+ ).values('badge_id', 'badge_name', 'badge_description', 'badge_type', 'user_id', 'user_name')
+
+ class testvar:
+ content = 'haha'
+
+ return render_to_response('index.html', {
+ "questions" : questions,
+ 'testvar':testvar,
+ "tab_id" : view_id,
+ "tags" : tags,
+ "awards" : awards[:INDEX_AWARD_SIZE],
+ }, context_instance=RequestContext(request))
+
+def about(request):
+ return render_to_response('about.html', context_instance=RequestContext(request))
+
+def faq(request):
+ return render_to_response('faq.html', context_instance=RequestContext(request))
+
+def privacy(request):
+ return render_to_response('privacy.html', context_instance=RequestContext(request))
+
+def unanswered(request):
+ return questions(request, unanswered=True)
+
+def questions(request, tagname=None, unanswered=False):
+ """
+ List of Questions, Tagged questions, and Unanswered questions.
+ """
+ # template file
+ # "questions.html" or "unanswered.html"
+ template_file = "questions.html"
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ # get new pagesize from UI selection
+ pagesize = int(request.GET.get('pagesize', user_page_size))
+ if pagesize <> user_page_size:
+ pagesize_changed = True
+
+ except ValueError:
+ page = 1
+ pagesize = user_page_size
+
+ # save this pagesize to user database
+ if pagesize_changed:
+ request.session["pagesize"] = pagesize
+ if request.user.is_authenticated():
+ user = request.user
+ user.questions_per_page = pagesize
+ user.save()
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ # check if request is from tagged questions
+ if tagname is not None:
+ #print datetime.datetime.now()
+ objects = Question.objects.filter(deleted=False, tags__name = unquote(tagname)).order_by(orderby)
+ #print datetime.datetime.now()
+ elif unanswered:
+ #check if request is from unanswered questions
+ template_file = "unanswered.html"
+ objects = Question.objects.filter(deleted=False, answer_count=0).order_by(orderby)
+ else:
+ objects = Question.objects.filter(deleted=False).order_by(orderby)
+
+ # RISK - inner join queries
+ objects = objects.select_related();
+ objects_list = Paginator(objects, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ related_tags = []
+ for question in questions.object_list:
+ tags = list(question.tags.all())
+ for tag in tags:
+ if tag not in related_tags:
+ related_tags.append(tag)
+
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "searchtag" : tagname,
+ "is_unanswered" : unanswered,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+#TODO: allow anynomus user to ask question by providing email and username.
+@login_required
+def ask(request):
+ if request.method == "POST":
+ form = AskForm(request.POST)
+ if form.is_valid():
+ added_at = datetime.datetime.now()
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ question = Question(
+ title = strip_tags(form.cleaned_data['title']),
+ author = request.user,
+ added_at = added_at,
+ last_activity_at = added_at,
+ last_activity_by = request.user,
+ wiki = form.cleaned_data['wiki'],
+ tagnames = form.cleaned_data['tags'].strip(),
+ html = html,
+ summary = strip_tags(html)[:120]
+ )
+ if question.wiki:
+ question.last_edited_by = question.author
+ question.last_edited_at = added_at
+ question.wikified_at = added_at
+
+ question.save()
+
+ # create the first revision
+ QuestionRevision.objects.create(
+ question = question,
+ revision = 1,
+ title = question.title,
+ author = request.user,
+ revised_at = added_at,
+ tagnames = question.tagnames,
+ summary = CONST['default_version'],
+ text = form.cleaned_data['text']
+ )
+
+ return HttpResponseRedirect(question.get_absolute_url())
+
+ else:
+ form = AskForm()
+
+ tags = _get_tags_cache_json()
+ return render_to_response('ask.html', {
+ 'form' : form,
+ 'tags' : tags,
+ }, context_instance=RequestContext(request))
+
+def question(request, id):
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+ view_id = request.GET.get('sort', 'votes')
+ view_dic = {"latest":"-added_at", "oldest":"added_at", "votes":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "votes"
+ orderby = "-score"
+
+ question = get_object_or_404(Question, id=id)
+ if question.deleted and not can_view_deleted_post(request.user, question):
+ raise Http404
+ answer_form = AnswerForm(question)
+ answers = Answer.objects.get_answers_from_question(question, request.user)
+ answers = answers.select_related(depth=1)
+
+ favorited = question.has_favorite_by_user(request.user)
+ question_vote = question.votes.select_related().filter(user=request.user)
+ if question_vote is not None and question_vote.count() > 0:
+ question_vote = question_vote[0]
+
+ user_answer_votes = {}
+ for answer in answers:
+ vote = answer.get_user_vote(request.user)
+ if vote is not None and not user_answer_votes.has_key(answer.id):
+ vote_value = -1
+ if vote.is_upvote():
+ vote_value = 1
+ user_answer_votes[answer.id] = vote_value
+
+
+ if answers is not None:
+ answers = answers.order_by("-accepted", orderby)
+ objects_list = Paginator(answers, ANSWERS_PAGE_SIZE)
+ page_objects = objects_list.page(page)
+ # update view count
+ Question.objects.update_view_count(question)
+ return render_to_response('question.html', {
+ "question" : question,
+ "question_vote" : question_vote,
+ "question_comment_count":question.comments.count(),
+ "answer" : answer_form,
+ "answers" : page_objects.object_list,
+ "user_answer_votes": user_answer_votes,
+ "tags" : question.tags.all(),
+ "tab_id" : view_id,
+ "favorited" : favorited,
+ "similar_questions" : Question.objects.get_similar_questions(question),
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': page_objects.has_previous(),
+ 'has_next': page_objects.has_next(),
+ 'previous': page_objects.previous_page_number(),
+ 'next': page_objects.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'extend_url' : "#sort-top"
+ }
+ }, context_instance=RequestContext(request))
+
+@login_required
+def close(request, id):
+ question = get_object_or_404(Question, id=id)
+ if not can_close_question(request.user, question):
+ return HttpResponse('Permission denied.')
+ if request.method == 'POST':
+ form = CloseForm(request.POST)
+ if form.is_valid():
+ reason = form.cleaned_data['reason']
+ question.closed = True
+ question.closed_by = request.user
+ question.closed_at = datetime.datetime.now()
+ question.close_reason = reason
+ question.save()
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = CloseForm()
+ return render_to_response('close.html', {
+ 'form' : form,
+ 'question' : question,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def reopen(request, id):
+ question = get_object_or_404(Question, id=id)
+ # open question
+ if not can_reopen_question(request.user, question):
+ return HttpResponse('Permission denied.')
+ if request.method == 'POST' :
+ Question.objects.filter(id=question.id).update(closed=False,
+ closed_by=None, closed_at=None, close_reason=None)
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ return render_to_response('reopen.html', {
+ 'question' : question,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def edit_question(request, id):
+ question = get_object_or_404(Question, id=id)
+ if question.deleted and not can_view_deleted_post(request.user, question):
+ raise Http404
+ if can_edit_post(request.user, question):
+ return _edit_question(request, question)
+ elif can_retag_questions(request.user):
+ return _retag_question(request, question)
+ else:
+ raise Http404
+
+def _retag_question(request, question):
+ if request.method == 'POST':
+ form = RetagQuestionForm(question, request.POST)
+ if form.is_valid():
+ if form.has_changed():
+ latest_revision = question.get_latest_revision()
+ retagged_at = datetime.datetime.now()
+ # Update the Question itself
+ Question.objects.filter(id=question.id).update(
+ tagnames = form.cleaned_data['tags'],
+ last_edited_at = retagged_at,
+ last_edited_by = request.user,
+ last_activity_at = retagged_at,
+ last_activity_by = request.user
+ )
+ # Update the Question's tag associations
+ tags_updated = Question.objects.update_tags(question,
+ form.cleaned_data['tags'], request.user)
+ # Create a new revision
+ QuestionRevision.objects.create(
+ question = question,
+ title = latest_revision.title,
+ author = request.user,
+ revised_at = retagged_at,
+ tagnames = form.cleaned_data['tags'],
+ summary = CONST['retagged'],
+ text = latest_revision.text
+ )
+ # send tags updated singal
+ tags_updated.send(sender=question.__class__, question=question)
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = RetagQuestionForm(question)
+ return render_to_response('question_retag.html', {
+ 'question': question,
+ 'form' : form,
+ 'tags' : _get_tags_cache_json(),
+ }, context_instance=RequestContext(request))
+
+
+def _edit_question(request, question):
+ latest_revision = question.get_latest_revision()
+ revision_form = None
+ if request.method == 'POST':
+ if 'select_revision' in request.POST:
+ # user has changed revistion number
+ revision_form = RevisionForm(question, latest_revision, request.POST)
+ if revision_form.is_valid():
+ # Replace with those from the selected revision
+ form = EditQuestionForm(question,
+ QuestionRevision.objects.get(question=question,
+ revision=revision_form.cleaned_data['revision']))
+ else:
+ form = EditQuestionForm(question, latest_revision, request.POST)
+ else:
+ # Always check modifications against the latest revision
+ form = EditQuestionForm(question, latest_revision, request.POST)
+ if form.is_valid():
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ if form.has_changed():
+ edited_at = datetime.datetime.now()
+ tags_changed = (latest_revision.tagnames !=
+ form.cleaned_data['tags'])
+ tags_updated = False
+ # Update the Question itself
+ updated_fields = {
+ 'title': form.cleaned_data['title'],
+ 'last_edited_at': edited_at,
+ 'last_edited_by': request.user,
+ 'last_activity_at': edited_at,
+ 'last_activity_by': request.user,
+ 'tagnames': form.cleaned_data['tags'],
+ 'summary': strip_tags(html)[:120],
+ 'html': html,
+ }
+
+ # only save when it's checked
+ # because wiki doesn't allow to be edited if last version has been enabled already
+ # and we make sure this in forms.
+ if ('wiki' in form.cleaned_data and
+ form.cleaned_data['wiki']):
+ updated_fields['wiki'] = True
+ updated_fields['wikified_at'] = edited_at
+
+ Question.objects.filter(
+ id=question.id).update(**updated_fields)
+ # Update the Question's tag associations
+ if tags_changed:
+ tags_updated = Question.objects.update_tags(
+ question, form.cleaned_data['tags'], request.user)
+ # Create a new revision
+ revision = QuestionRevision(
+ question = question,
+ title = form.cleaned_data['title'],
+ author = request.user,
+ revised_at = edited_at,
+ tagnames = form.cleaned_data['tags'],
+ text = form.cleaned_data['text'],
+ )
+ if form.cleaned_data['summary']:
+ revision.summary = form.cleaned_data['summary']
+ else:
+ revision.summary = 'No.%s Revision' % latest_revision.revision
+ revision.save()
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+
+ revision_form = RevisionForm(question, latest_revision)
+ form = EditQuestionForm(question, latest_revision)
+ return render_to_response('question_edit.html', {
+ 'question': question,
+ 'revision_form': revision_form,
+ 'form' : form,
+ 'tags' : _get_tags_cache_json()
+ }, context_instance=RequestContext(request))
+
+
+@login_required
+def edit_answer(request, id):
+ answer = get_object_or_404(Answer, id=id)
+ if answer.deleted and not can_view_deleted_post(request.user, answer):
+ raise Http404
+ elif not can_edit_post(request.user, answer):
+ raise Http404
+ else:
+ latest_revision = answer.get_latest_revision()
+ if request.method == "POST":
+ if 'select_revision' in request.POST:
+ # user has changed revistion number
+ revision_form = RevisionForm(answer, latest_revision, request.POST)
+ if revision_form.is_valid():
+ # Replace with those from the selected revision
+ form = EditAnswerForm(answer,
+ AnswerRevision.objects.get(answer=answer,
+ revision=revision_form.cleaned_data['revision']))
+ else:
+ form = EditAnswerForm(answer, latest_revision, request.POST)
+ else:
+ form = EditAnswerForm(answer, latest_revision, request.POST)
+ if form.is_valid():
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ if form.has_changed():
+ edited_at = datetime.datetime.now()
+ updated_fields = {
+ 'last_edited_at': edited_at,
+ 'last_edited_by': request.user,
+ 'html': html,
+ }
+ Answer.objects.filter(id=answer.id).update(**updated_fields)
+
+ revision = AnswerRevision(
+ answer = answer,
+ author = request.user,
+ revised_at = edited_at,
+ text = form.cleaned_data['text']
+ )
+
+ if form.cleaned_data['summary']:
+ revision.summary = form.cleaned_data['summary']
+ else:
+ revision.summary = 'No.%s Revision' % latest_revision.revision
+ revision.save()
+
+ answer.question.last_activity_at = edited_at
+ answer.question.last_activity_by = request.user
+ answer.question.save()
+
+ return HttpResponseRedirect(answer.get_absolute_url())
+ else:
+ revision_form = RevisionForm(answer, latest_revision)
+ form = EditAnswerForm(answer, latest_revision)
+ return render_to_response('answer_edit.html', {
+ 'answer': answer,
+ 'revision_form': revision_form,
+ 'form' : form,
+ }, context_instance=RequestContext(request))
+
+QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n'
+ '<div class="text">%(html)s</div>\n'
+ '<div class="tags">%(tags)s</div>')
+def question_revisions(request, id):
+ post = get_object_or_404(Question, id=id)
+ revisions = list(post.revisions.all())
+ for i, revision in enumerate(revisions):
+ revision.html = QUESTION_REVISION_TEMPLATE % {
+ 'title': revision.title,
+ 'html': sanitize_html(markdowner.convert(revision.text)),
+ 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
+ for tag in revision.tagnames.split(' ')]),
+ }
+ if i > 0:
+ revisions[i - 1].diff = htmldiff(revision.html,
+ revisions[i - 1].html)
+ else:
+ revisions[i - 1].diff = QUESTION_REVISION_TEMPLATE % {
+ 'title': revisions[0].title,
+ 'html': sanitize_html(markdowner.convert(revisions[0].text)),
+ 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag
+ for tag in revisions[0].tagnames.split(' ')]),
+ }
+ revisions[i - 1].summary = None
+ return render_to_response('revisions_question.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>')
+def answer_revisions(request, id):
+ post = get_object_or_404(Answer, id=id)
+ revisions = list(post.revisions.all())
+ for i, revision in enumerate(revisions):
+ revision.html = ANSWER_REVISION_TEMPLATE % {
+ 'html': sanitize_html(markdowner.convert(revision.text))
+ }
+ if i > 0:
+ revisions[i - 1].diff = htmldiff(revision.html,
+ revisions[i - 1].html)
+ else:
+ revisions[i - 1].diff = revisions[i-1].text
+ revisions[i - 1].summary = None
+ return render_to_response('revisions_answer.html', {
+ 'post': post,
+ 'revisions': revisions,
+ }, context_instance=RequestContext(request))
+
+#TODO: allow anynomus
+@login_required
+def answer(request, id):
+ question = get_object_or_404(Question, id=id)
+ if request.method == "POST":
+ form = AnswerForm(question, request.POST)
+ if form.is_valid():
+ update_time = datetime.datetime.now()
+ answer = Answer(
+ question = question,
+ author = request.user,
+ added_at = update_time,
+ wiki = form.cleaned_data['wiki'],
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text'])),
+ )
+ if answer.wiki:
+ answer.last_edited_by = answer.author
+ answer.last_edited_at = update_time
+ answer.wikified_at = update_time
+
+ answer.save()
+ Question.objects.update_answer_count(question)
+
+ question = get_object_or_404(Question, id=id)
+ question.last_activity_at = update_time
+ question.last_activity_by = request.user
+ question.save()
+
+ AnswerRevision.objects.create(
+ answer = answer,
+ revision = 1,
+ author = request.user,
+ revised_at = update_time,
+ summary = CONST['default_version'],
+ text = form.cleaned_data['text']
+ )
+
+ return HttpResponseRedirect(question.get_absolute_url())
+
+def tags(request):
+ stag = ""
+ is_paginated = True
+ sortby = request.GET.get('sort', 'used')
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ if request.method == "GET":
+ stag = request.GET.get("q", "").strip()
+ if stag is not None:
+ objects_list = Paginator(Tag.objects.filter(deleted=False).exclude(used_count=0).extra(where=['name like %s'], params=['%' + stag + '%']), DEFAULT_PAGE_SIZE)
+ else:
+ if sortby == "name":
+ objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("name"), DEFAULT_PAGE_SIZE)
+ else:
+ objects_list = Paginator(Tag.objects.all().filter(deleted=False).exclude(used_count=0).order_by("-used_count"), DEFAULT_PAGE_SIZE)
+
+ try:
+ tags = objects_list.page(page)
+ except (EmptyPage, InvalidPage):
+ tags = objects_list.page(objects_list.num_pages)
+
+ return render_to_response('tags.html', {
+ "tags" : tags,
+ "stag" : stag,
+ "tab_id" : sortby,
+ "keywords" : stag,
+ "context" : {
+ 'is_paginated' : is_paginated,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': tags.has_previous(),
+ 'has_next': tags.has_next(),
+ 'previous': tags.previous_page_number(),
+ 'next': tags.next_page_number(),
+ 'base_url' : '/tags/?sort=%s&' % sortby
+ }
+
+ }, context_instance=RequestContext(request))
+
+def tag(request, tag):
+ return questions(request, tagname=tag)
+
+def vote(request, id):
+ """
+ vote_type:
+ acceptAnswer : 0,
+ questionUpVote : 1,
+ questionDownVote : 2,
+ favorite : 4,
+ answerUpVote: 5,
+ answerDownVote:6,
+ offensiveQuestion : 7,
+ offensiveAnswer:8,
+ removeQuestion: 9,
+ removeAnswer:10
+
+ accept answer code:
+ response_data['allowed'] = -1, Accept his own answer 0, no allowed - Anonymous 1, Allowed - by default
+ response_data['success'] = 0, failed 1, Success - by default
+ response_data['status'] = 0, By default 1, Answer has been accepted already(Cancel)
+
+ vote code:
+ allowed = -3, Don't have enough votes left
+ -2, Don't have enough reputation score
+ -1, Vote his own post
+ 0, no allowed - Anonymous
+ 1, Allowed - by default
+ status = 0, By default
+ 1, Cancel
+ 2, Vote is too old to be canceled
+
+ offensive code:
+ allowed = -3, Don't have enough flags left
+ -2, Don't have enough reputation score to do this
+ 0, not allowed
+ 1, allowed
+ status = 0, by default
+ 1, can't do it again
+ """
+ response_data = {
+ "allowed": 1,
+ "success": 1,
+ "status" : 0,
+ "count" : 0,
+ "message" : ''
+ }
+
+ def can_vote(vote_score, user):
+ if vote_score == 1:
+ return can_vote_up(request.user)
+ else:
+ return can_vote_down(request.user)
+
+ try:
+ if not request.user.is_authenticated():
+ response_data['allowed'] = 0
+ response_data['success'] = 0
+
+ elif request.is_ajax():
+ question = get_object_or_404(Question, id=id)
+ vote_type = request.POST.get('type')
+
+ #accept answer
+ if vote_type == '0':
+ answer_id = request.POST.get('postId')
+ answer = get_object_or_404(Answer, id=answer_id)
+ # make sure question author is current user
+ if question.author == request.user:
+ # answer user who is also question author is not allow to accept answer
+ if answer.author == question.author:
+ response_data['success'] = 0
+ response_data['allowed'] = -1
+ # check if answer has been accepted already
+ elif answer.accepted:
+ onAnswerAcceptCanceled(answer, request.user)
+ response_data['status'] = 1
+ else:
+ # set other answers in this question not accepted first
+ for answer_of_question in Answer.objects.get_answers_from_question(question, request.user):
+ if answer_of_question != answer and answer_of_question.accepted:
+ onAnswerAcceptCanceled(answer_of_question, request.user)
+
+ #make sure retrieve data again after above author changes, they may have related data
+ answer = get_object_or_404(Answer, id=answer_id)
+ onAnswerAccept(answer, request.user)
+ else:
+ response_data['allowed'] = 0
+ response_data['success'] = 0
+ # favorite
+ elif vote_type == '4':
+ has_favorited = False
+ fav_questions = FavoriteQuestion.objects.filter(question=question)
+ # if the same question has been favorited before, then delete it
+ if fav_questions is not None:
+ for item in fav_questions:
+ if item.user == request.user:
+ item.delete()
+ response_data['status'] = 1
+ response_data['count'] = len(fav_questions) - 1
+ if response_data['count'] < 0:
+ response_data['count'] = 0
+ has_favorited = True
+ # if above deletion has not been executed, just insert a new favorite question
+ if not has_favorited:
+ new_item = FavoriteQuestion(question=question, user=request.user)
+ new_item.save()
+ response_data['count'] = FavoriteQuestion.objects.filter(question=question).count()
+ Question.objects.update_favorite_count(question)
+
+ elif vote_type in ['1', '2', '5', '6']:
+ post_id = id
+ post = question
+ vote_score = 1
+ if vote_type in ['5', '6']:
+ answer_id = request.POST.get('postId')
+ answer = get_object_or_404(Answer, id=answer_id)
+ post_id = answer_id
+ post = answer
+ if vote_type in ['2', '6']:
+ vote_score = -1
+
+ if post.author == request.user:
+ response_data['allowed'] = -1
+ elif not can_vote(vote_score, request.user):
+ response_data['allowed'] = -2
+ elif post.votes.filter(user=request.user).count() > 0:
+ vote = post.votes.filter(user=request.user)[0]
+ # unvote should be less than certain time
+ if (datetime.datetime.now().day - vote.voted_at.day) >= VOTE_RULES['scope_deny_unvote_days']:
+ response_data['status'] = 2
+ else:
+ voted = vote.vote
+ if voted > 0:
+ # cancel upvote
+ onUpVotedCanceled(vote, post, request.user)
+
+ else:
+ # cancel downvote
+ onDownVotedCanceled(vote, post, request.user)
+
+ response_data['status'] = 1
+ response_data['count'] = post.score
+ elif Vote.objects.get_votes_count_today_from_user(request.user) >= VOTE_RULES['scope_votes_per_user_per_day']:
+ response_data['allowed'] = -3
+ else:
+ vote = Vote(user=request.user, content_object=post, vote=vote_score, voted_at=datetime.datetime.now())
+ if vote_score > 0:
+ # upvote
+ onUpVoted(vote, post, request.user)
+ else:
+ # downvote
+ onDownVoted(vote, post, request.user)
+
+ votes_left = VOTE_RULES['scope_votes_per_user_per_day'] - Vote.objects.get_votes_count_today_from_user(request.user)
+ if votes_left <= VOTE_RULES['scope_warn_votes_left']:
+ response_data['message'] = u'%s votes left' % votes_left
+ response_data['count'] = post.score
+ elif vote_type in ['7', '8']:
+ post = question
+ post_id = id
+ if vote_type == '8':
+ post_id = request.POST.get('postId')
+ post = get_object_or_404(Answer, id=post_id)
+
+ if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= VOTE_RULES['scope_flags_per_user_per_day']:
+ response_data['allowed'] = -3
+ elif not can_flag_offensive(request.user):
+ response_data['allowed'] = -2
+ elif post.flagged_items.filter(user=request.user).count() > 0:
+ response_data['status'] = 1
+ else:
+ item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now())
+ onFlaggedItem(item, post, request.user)
+ response_data['count'] = post.offensive_flag_count
+ # send signal when question or answer be marked offensive
+ mark_offensive.send(sender=post.__class__, instance=post, mark_by=request.user)
+ elif vote_type in ['9', '10']:
+ post = question
+ post_id = id
+ if vote_type == '10':
+ post_id = request.POST.get('postId')
+ post = get_object_or_404(Answer, id=post_id)
+
+ if not can_delete_post(request.user, post):
+ response_data['allowed'] = -2
+ elif post.deleted:
+ onDeleteCanceled(post, request.user)
+ response_data['status'] = 1
+ else:
+ onDeleted(post, request.user)
+ delete_post_or_answer.send(sender=post.__class__, instance=post, delete_by=request.user)
+ else:
+ response_data['success'] = 0
+ response_data['message'] = u'Request mode is not supported. Please try again.'
+
+ data = simplejson.dumps(response_data)
+
+ except Exception, e:
+ response_data['message'] = str(e)
+ data = simplejson.dumps(response_data)
+ return HttpResponse(data, mimetype="application/json")
+
+def users(request):
+ is_paginated = True
+ sortby = request.GET.get('sort', 'reputation')
+ suser = request.REQUEST.get('q', "")
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ if suser == "":
+ if sortby == "newest":
+ objects_list = Paginator(User.objects.all().order_by('-date_joined'), USERS_PAGE_SIZE)
+ elif sortby == "last":
+ objects_list = Paginator(User.objects.all().order_by('date_joined'), USERS_PAGE_SIZE)
+ elif sortby == "user":
+ objects_list = Paginator(User.objects.all().order_by('username'), USERS_PAGE_SIZE)
+ # default
+ else:
+ objects_list = Paginator(User.objects.all().order_by('-reputation'), USERS_PAGE_SIZE)
+ base_url = '/users/?sort=%s&' % sortby
+ else:
+ sortby = "reputation"
+ objects_list = Paginator(User.objects.extra(where=['username like %s'], params=['%' + suser + '%']).order_by('-reputation'), USERS_PAGE_SIZE)
+ base_url = '/users/?name=%s&sort=%s&' % (suser, sortby)
+
+ try:
+ users = objects_list.page(page)
+ except (EmptyPage, InvalidPage):
+ users = objects_list.page(objects_list.num_pages)
+
+ return render_to_response('users.html', {
+ "users" : users,
+ "suser" : suser,
+ "keywords" : suser,
+ "tab_id" : sortby,
+ "context" : {
+ 'is_paginated' : is_paginated,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': users.has_previous(),
+ 'has_next': users.has_next(),
+ 'previous': users.previous_page_number(),
+ 'next': users.next_page_number(),
+ 'base_url' : base_url
+ }
+
+ }, context_instance=RequestContext(request))
+
+def user(request, id):
+ sort = request.GET.get('sort', 'stats')
+ user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0])
+ from forum import views
+ func = getattr(views, user_view.view_name)
+ return func(request, id, user_view)
+
+@login_required
+def edit_user(request, id):
+ user = get_object_or_404(User, id=id)
+ if request.user != user:
+ raise Http404
+ if request.method == "POST":
+ form = EditUserForm(user, request.POST)
+ if form.is_valid():
+ user.email = sanitize_html(form.cleaned_data['email'])
+ user.real_name = sanitize_html(form.cleaned_data['realname'])
+ user.website = sanitize_html(form.cleaned_data['website'])
+ user.location = sanitize_html(form.cleaned_data['city'])
+ user.date_of_birth = sanitize_html(form.cleaned_data['birthday'])
+ if len(user.date_of_birth) == 0:
+ user.date_of_birth = '1900-01-01'
+ user.about = sanitize_html(form.cleaned_data['about'])
+
+ user.save()
+ # send user updated singal if full fields have been updated
+ if user.email and user.real_name and user.website and user.location and \
+ user.date_of_birth and user.about:
+ user_updated.send(sender=user.__class__, instance=user, updated_by=user)
+ return HttpResponseRedirect(user.get_profile_url())
+ else:
+ form = EditUserForm(user)
+ return render_to_response('user_edit.html', {
+ 'form' : form,
+ }, context_instance=RequestContext(request))
+
+def user_stats(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ questions = Question.objects.extra(
+ select={
+ 'vote_count' : 'question.score',
+ 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
+ 'la_user_id' : 'auth_user.id',
+ 'la_username' : 'auth_user.username',
+ 'la_user_gold' : 'auth_user.gold',
+ 'la_user_silver' : 'auth_user.silver',
+ 'la_user_bronze' : 'auth_user.bronze',
+ 'la_user_reputation' : 'auth_user.reputation'
+ },
+ select_params=[user_id],
+ tables=['question', 'auth_user'],
+ where=['question.deleted = 0 AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'],
+ params=[user_id],
+ order_by=['-vote_count', '-last_activity_at']
+ ).values('vote_count',
+ 'favorited_myself',
+ 'id',
+ 'title',
+ 'author_id',
+ 'added_at',
+ 'answer_accepted',
+ 'answer_count',
+ 'comment_count',
+ 'view_count',
+ 'favourite_count',
+ 'summary',
+ 'tagnames',
+ 'vote_up_count',
+ 'vote_down_count',
+ 'last_activity_at',
+ 'la_user_id',
+ 'la_username',
+ 'la_user_gold',
+ 'la_user_silver',
+ 'la_user_bronze',
+ 'la_user_reputation')[:100]
+
+ answered_questions = Question.objects.extra(
+ select={
+ 'vote_up_count' : 'answer.vote_up_count',
+ 'vote_down_count' : 'answer.vote_down_count',
+ 'answer_id' : 'answer.id',
+ 'accepted' : 'answer.accepted',
+ 'vote_count' : 'answer.score',
+ 'comment_count' : 'answer.comment_count'
+ },
+ tables=['question', 'answer'],
+ where=['answer.deleted=0 AND answer.author_id=%s AND answer.question_id=question.id'],
+ params=[user_id],
+ order_by=['-vote_count', '-answer_id'],
+ select_params=[user_id]
+ ).distinct().values('comment_count',
+ 'id',
+ 'answer_id',
+ 'title',
+ 'author_id',
+ 'accepted',
+ 'answer_count',
+ 'vote_up_count',
+ 'vote_down_count')[:100]
+ up_votes = Vote.objects.get_up_vote_count_from_user(user)
+ down_votes = Vote.objects.get_down_vote_count_from_user(user)
+ votes_today = Vote.objects.get_votes_count_today_from_user(user)
+ votes_total = VOTE_RULES['scope_votes_per_user_per_day']
+ tags = user.created_tags.all().order_by('-used_count')[:50]
+ awards = Award.objects.extra(
+ select={'id': 'badge.id', 'count': 'count(badge_id)', 'name':'badge.name', 'description': 'badge.description', 'type': 'badge.type'},
+ tables=['award', 'badge'],
+ order_by=['-awarded_at'],
+ where=['user_id=%s AND badge_id=badge.id'],
+ params=[user.id]
+ ).values('id', 'count', 'name', 'description', 'type')
+ total_awards = awards.count()
+ awards.query.group_by = ['badge_id']
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "questions" : questions,
+ "answered_questions" : answered_questions,
+ "up_votes" : up_votes,
+ "down_votes" : down_votes,
+ "total_votes": up_votes + down_votes,
+ "votes_today_left": votes_total-votes_today,
+ "votes_total_per_day": votes_total,
+ "tags" : tags,
+ "awards": awards,
+ "total_awards" : total_awards,
+ }, context_instance=RequestContext(request))
+
+def user_recent(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ def get_type_name(type_id):
+ for item in TYPE_ACTIVITY:
+ if type_id in item:
+ return item[1]
+
+ class Event:
+ def __init__(self, time, type, title, summary, answer_id, question_id):
+ self.time = time
+ self.type = get_type_name(type)
+ self.type_id = type
+ self.title = title
+ self.summary = summary
+ self.title_link = u'/questions/%s/%s#%s' %(question_id, title, answer_id)\
+ if int(answer_id) > 0 else u'/questions/%s/%s' %(question_id, title)
+ class AwardEvent:
+ def __init__(self, time, type, id):
+ self.time = time
+ self.type = get_type_name(type)
+ self.type_id = type
+ self.badge = get_object_or_404(Badge, id=id)
+
+ activities = []
+ # ask questions
+ questions = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'active_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = question.id AND activity.user_id = %s AND activity.activity_type = %s'],
+ params=[question_type_id, user_id, TYPE_ACTIVITY_ASK_QUESTION],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'active_at',
+ 'activity_type'
+ )
+ if len(questions) > 0:
+ questions = [(Event(q['active_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in questions]
+ activities.extend(questions)
+
+ # answers
+ answers = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'active_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'answer', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = answer.id '
+ 'AND answer.question_id=question.id AND activity.user_id=%s AND activity.activity_type=%s'],
+ params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'active_at',
+ 'activity_type'
+ )
+ if len(answers) > 0:
+ answers = [(Event(q['active_at'], q['activity_type'], q['title'], '', q['answer_id'], \
+ q['question_id'])) for q in answers]
+ activities.extend(answers)
+
+ # question comments
+ comments = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'comment.object_id',
+ 'added_at' : 'comment.added_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question', 'comment'],
+
+ where=['activity.content_type_id = %s AND activity.object_id = comment.id AND activity.user_id = comment.user_id AND comment.object_id=question.id AND comment.content_type_id=%s AND activity.user_id = %s AND activity.activity_type=%s'],
+ params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type'
+ )
+
+ if len(comments) > 0:
+ comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in comments]
+ activities.extend(comments)
+
+ # answer comments
+ comments = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'comment.added_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'question', 'answer', 'comment'],
+
+ where=['activity.content_type_id = %s AND activity.object_id = comment.id AND activity.user_id = comment.user_id AND comment.object_id=answer.id AND comment.content_type_id=%s AND question.id = answer.question_id AND activity.user_id = %s AND activity.activity_type=%s'],
+ params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'activity_type'
+ )
+
+ if len(comments) > 0:
+ comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', q['answer_id'], \
+ q['question_id'])) for q in comments]
+ activities.extend(comments)
+
+ # question revisions
+ revisions = Activity.objects.extra(
+ select={
+ 'title' : 'question_revision.title',
+ 'question_id' : 'question_revision.question_id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ 'summary' : 'question_revision.summary'
+ },
+ tables=['activity', 'question_revision'],
+ where=['activity.content_type_id = %s AND activity.object_id = question_revision.id AND activity.user_id = question_revision.author_id AND activity.user_id = %s AND activity.activity_type=%s'],
+ params=[question_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_QUESTION],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type',
+ 'summary'
+ )
+
+ if len(revisions) > 0:
+ revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], '0', \
+ q['question_id'])) for q in revisions]
+ activities.extend(revisions)
+
+ # answer revisions
+ revisions = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ 'summary' : 'answer_revision.summary'
+ },
+ tables=['activity', 'answer_revision', 'question', 'answer'],
+
+ where=['activity.content_type_id = %s AND activity.object_id = answer_revision.id AND activity.user_id = answer_revision.author_id AND activity.user_id = %s AND answer_revision.answer_id=answer.id AND answer.question_id = question.id AND activity.activity_type=%s'],
+ params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'answer_id',
+ 'activity_type',
+ 'summary'
+ )
+
+ if len(revisions) > 0:
+ revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], \
+ q['answer_id'], q['question_id'])) for q in revisions]
+ activities.extend(revisions)
+
+ # accepted answers
+ accept_answers = Activity.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'added_at' : 'activity.active_at',
+ 'activity_type' : 'activity.activity_type',
+ },
+ tables=['activity', 'answer', 'question'],
+ where=['activity.content_type_id = %s AND activity.object_id = answer.id AND activity.user_id = question.author_id AND activity.user_id = %s AND answer.question_id=question.id AND activity.activity_type=%s'],
+ params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER],
+ order_by=['-activity.active_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'activity_type',
+ )
+ if len(accept_answers) > 0:
+ accept_answers = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \
+ q['question_id'])) for q in accept_answers]
+ activities.extend(accept_answers)
+ #award history
+ awards = Activity.objects.extra(
+ select={
+ 'badge_id' : 'badge.id',
+ 'awarded_at': 'award.awarded_at',
+ 'activity_type' : 'activity.activity_type'
+ },
+ tables=['activity', 'award', 'badge'],
+ where=['activity.user_id = award.user_id AND activity.user_id = %s AND award.badge_id=badge.id AND activity.object_id=award.id AND activity.activity_type=%s'],
+ params=[user_id, TYPE_ACTIVITY_PRIZE],
+ order_by=['-activity.active_at']
+ ).values(
+ 'badge_id',
+ 'awarded_at',
+ 'activity_type'
+ )
+ if len(awards) > 0:
+ awards = [(AwardEvent(q['awarded_at'], q['activity_type'], q['badge_id'])) for q in awards]
+ activities.extend(awards)
+
+ activities.sort(lambda x,y: cmp(y.time, x.time))
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "activities" : activities[:user_view.data_size]
+ }, context_instance=RequestContext(request))
+
+def user_responses(request, user_id, user_view):
+ """
+ We list answers for question, comments, and answer accepted by others for this user.
+ """
+ class Response:
+ def __init__(self, type, title, question_id, answer_id, time, username, user_id, content):
+ self.type = type
+ self.title = title
+ self.titlelink = u'/questions/%s/%s#%s' % (question_id, title, answer_id)
+ self.time = time
+ self.userlink = u'/users/%s/%s/' % (user_id, username)
+ self.username = username
+ self.content = u'%s ...' % strip_tags(content)[:300]
+
+ def __unicode__(self):
+ return u'%s %s' % (self.type, self.titlelink)
+
+ user = get_object_or_404(User, id=user_id)
+ responses = []
+ answers = Answer.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'answer.added_at',
+ 'html' : 'answer.html',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ select_params=[user_id],
+ tables=['answer', 'question', 'auth_user'],
+ where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND question.author_id = %s AND answer.author_id <> %s AND answer.author_id=auth_user.id'],
+ params=[user_id, user_id],
+ order_by=['-answer.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'html',
+ 'username',
+ 'user_id'
+ )
+ if len(answers) > 0:
+ answers = [(Response(TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'],
+ a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
+ responses.extend(answers)
+
+
+ # question comments
+ comments = Comment.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'comment.object_id',
+ 'added_at' : 'comment.added_at',
+ 'comment' : 'comment.comment',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ tables=['question', 'auth_user', 'comment'],
+ where=['question.deleted = 0 AND question.author_id = %s AND comment.object_id=question.id AND comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id'],
+ params=[user_id, question_type_id, user_id],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'added_at',
+ 'comment',
+ 'username',
+ 'user_id'
+ )
+
+ if len(comments) > 0:
+ comments = [(Response(TYPE_RESPONSE['QUESTION_COMMENTED'], c['title'], c['question_id'],
+ '', c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
+ responses.extend(comments)
+
+ # answer comments
+ comments = Comment.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'comment.added_at',
+ 'comment' : 'comment.comment',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ tables=['answer', 'auth_user', 'comment', 'question'],
+ where=['answer.deleted = 0 AND answer.author_id = %s AND comment.object_id=answer.id AND comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id AND question.id = answer.question_id'],
+ params=[user_id, answer_type_id, user_id],
+ order_by=['-comment.added_at']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'comment',
+ 'username',
+ 'user_id'
+ )
+
+ if len(comments) > 0:
+ comments = [(Response(TYPE_RESPONSE['ANSWER_COMMENTED'], c['title'], c['question_id'],
+ c['answer_id'], c['added_at'], c['username'], c['user_id'], c['comment'])) for c in comments]
+ responses.extend(comments)
+
+ # answer has been accepted
+ answers = Answer.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'added_at' : 'answer.accepted_at',
+ 'html' : 'answer.html',
+ 'username' : 'auth_user.username',
+ 'user_id' : 'auth_user.id'
+ },
+ select_params=[user_id],
+ tables=['answer', 'question', 'auth_user'],
+ where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND answer.author_id = %s AND answer.accepted=1 AND question.author_id=auth_user.id'],
+ params=[user_id],
+ order_by=['-answer.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'added_at',
+ 'html',
+ 'username',
+ 'user_id'
+ )
+ if len(answers) > 0:
+ answers = [(Response(TYPE_RESPONSE['ANSWER_ACCEPTED'], a['title'], a['question_id'],
+ a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers]
+ responses.extend(answers)
+
+ # sort posts by time
+ responses.sort(lambda x,y: cmp(y.time, x.time))
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "responses" : responses[:user_view.data_size],
+
+ }, context_instance=RequestContext(request))
+
+def user_votes(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ if not can_view_user_votes(request.user, user):
+ raise Http404
+ votes = []
+ question_votes = Vote.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 0,
+ 'voted_at' : 'vote.voted_at',
+ 'vote' : 'vote',
+ },
+ select_params=[user_id],
+ tables=['vote', 'question', 'auth_user'],
+ where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = question.id AND vote.user_id=auth_user.id'],
+ params=[question_type_id, user_id],
+ order_by=['-vote.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'voted_at',
+ 'vote',
+ )
+ if(len(question_votes) > 0):
+ votes.extend(question_votes)
+
+ answer_votes = Vote.objects.extra(
+ select={
+ 'title' : 'question.title',
+ 'question_id' : 'question.id',
+ 'answer_id' : 'answer.id',
+ 'voted_at' : 'vote.voted_at',
+ 'vote' : 'vote',
+ },
+ select_params=[user_id],
+ tables=['vote', 'answer', 'question', 'auth_user'],
+ where=['vote.content_type_id = %s AND vote.user_id = %s AND vote.object_id = answer.id AND answer.question_id = question.id AND vote.user_id=auth_user.id'],
+ params=[answer_type_id, user_id],
+ order_by=['-vote.id']
+ ).values(
+ 'title',
+ 'question_id',
+ 'answer_id',
+ 'voted_at',
+ 'vote',
+ )
+ if(len(answer_votes) > 0):
+ votes.extend(answer_votes)
+ votes.sort(lambda x,y: cmp(y['voted_at'], x['voted_at']))
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "votes" : votes[:user_view.data_size]
+
+ }, context_instance=RequestContext(request))
+
+def user_reputation(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ reputation = Repute.objects.extra(
+ select={'positive': 'sum(positive)', 'negative': 'sum(negative)', 'question_id':'question_id', 'title': 'question.title'},
+ tables=['repute', 'question'],
+ order_by=['-reputed_at'],
+ where=['user_id=%s AND question_id=question.id'],
+ params=[user.id]
+ ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation')
+
+ reputation.query.group_by = ['question_id']
+
+ rep_list = []
+ for rep in Repute.objects.filter(user=user).order_by('reputed_at'):
+ dic = '[%s,%s]' % (calendar.timegm(rep.reputed_at.timetuple()) * 1000, rep.reputation)
+ rep_list.append(dic)
+ reps = ','.join(rep_list)
+ reps = '[%s]' % reps
+
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ "reputation" : reputation,
+ "reps" : reps
+ }, context_instance=RequestContext(request))
+
+def user_favorites(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ questions = Question.objects.extra(
+ select={
+ 'vote_count' : 'question.vote_up_count + question.vote_down_count',
+ 'favorited_myself' : 'SELECT count(*) FROM favorite_question f WHERE f.user_id = %s AND f.question_id = question.id',
+ 'la_user_id' : 'auth_user.id',
+ 'la_username' : 'auth_user.username',
+ 'la_user_gold' : 'auth_user.gold',
+ 'la_user_silver' : 'auth_user.silver',
+ 'la_user_bronze' : 'auth_user.bronze',
+ 'la_user_reputation' : 'auth_user.reputation'
+ },
+ select_params=[user_id],
+ tables=['question', 'auth_user', 'favorite_question'],
+ where=['question.deleted = 0 AND question.last_activity_by_id = auth_user.id AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'],
+ params=[user_id],
+ order_by=['-vote_count', '-question.id']
+ ).values('vote_count',
+ 'favorited_myself',
+ 'id',
+ 'title',
+ 'author_id',
+ 'added_at',
+ 'answer_accepted',
+ 'answer_count',
+ 'comment_count',
+ 'view_count',
+ 'favourite_count',
+ 'summary',
+ 'tagnames',
+ 'vote_up_count',
+ 'vote_down_count',
+ 'last_activity_at',
+ 'la_user_id',
+ 'la_username',
+ 'la_user_gold',
+ 'la_user_silver',
+ 'la_user_bronze',
+ 'la_user_reputation')
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "questions" : questions[:user_view.data_size],
+ "view_user" : user
+ }, context_instance=RequestContext(request))
+
+
+def user_preferences(request, user_id, user_view):
+ user = get_object_or_404(User, id=user_id)
+ return render_to_response(user_view.template_file,{
+ "tab_name" : user_view.id,
+ "tab_description" : user_view.tab_description,
+ "page_title" : user_view.page_title,
+ "view_user" : user,
+ }, context_instance=RequestContext(request))
+
+def question_comments(request, id):
+ question = get_object_or_404(Question, id=id)
+ user = request.user
+ return __comments(request, question, 'question', user)
+
+def answer_comments(request, id):
+ answer = get_object_or_404(Answer, id=id)
+ user = request.user
+ return __comments(request, answer, 'answer', user)
+
+def __comments(request, obj, type, user):
+ # only support get comments by ajax now
+ if request.is_ajax():
+ if request.method == "GET":
+ return __generate_comments_json(obj, type, user)
+ elif request.method == "POST":
+ comment_data = request.POST.get('comment')
+ comment = Comment(content_object=obj, comment=comment_data, user=request.user)
+ comment.save()
+ obj.comment_count = obj.comment_count + 1
+ obj.save()
+ return __generate_comments_json(obj, type, user)
+
+def __generate_comments_json(obj, type, user):
+ comments = obj.comments.all().order_by('-id')
+ # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
+ json_comments = []
+ for comment in comments:
+ comment_user = comment.user
+ delete_url = ""
+ if user != None and auth.can_delete_comment(user, comment):
+ #/posts/392845/comments/219852/delete
+ delete_url = "/" + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id)
+ json_comments.append({"id" : comment.id,
+ "object_id" : obj.id,
+ "add_date" : comment.added_at.strftime('%Y-%m-%d'),
+ "text" : comment.comment,
+ "user_display_name" : comment_user.username,
+ "user_url" : "/users/%s/%s" % (comment_user.id, comment_user.username),
+ "delete_url" : delete_url
+ })
+
+ data = simplejson.dumps(json_comments)
+ return HttpResponse(data, mimetype="application/json")
+
+def delete_question_comment(request, question_id, comment_id):
+ if request.is_ajax():
+ question = get_object_or_404(Question, id=question_id)
+ comment = get_object_or_404(Comment, id=comment_id)
+
+ question.comments.remove(comment)
+ question.comment_count = question.comment_count - 1
+ question.save()
+ user = request.user
+ return __generate_comments_json(question, 'question', user)
+
+def delete_answer_comment(request, answer_id, comment_id):
+ if request.is_ajax():
+ answer = get_object_or_404(Answer, id=answer_id)
+ comment = get_object_or_404(Comment, id=comment_id)
+
+ answer.comments.remove(comment)
+ answer.comment_count = answer.comment_count - 1
+ answer.save()
+ user = request.user
+ return __generate_comments_json(answer, 'answer', user)
+
+def logout(request):
+ url = request.GET.get('next')
+ return render_to_response('logout.html', {
+ 'next' : url,
+ }, context_instance=RequestContext(request))
+
+def badges(request):
+ badges = Badge.objects.all().order_by('type')
+ my_badges = []
+ if request.user.is_authenticated():
+ my_badges = Award.objects.filter(user=request.user)
+ my_badges.query.group_by = ['badge_id']
+
+ return render_to_response('badges.html', {
+ 'badges' : badges,
+ 'mybadges' : my_badges,
+ }, context_instance=RequestContext(request))
+
+def badge(request, id):
+ badge = get_object_or_404(Badge, id=id)
+ awards = Award.objects.extra(
+ select={'id': 'auth_user.id', 'name': 'auth_user.username', 'rep':'auth_user.reputation', 'gold': 'auth_user.gold', 'silver': 'auth_user.silver', 'bronze': 'auth_user.bronze'},
+ tables=['award', 'auth_user'],
+ where=['badge_id=%s AND user_id=auth_user.id'],
+ params=[id]
+ ).values('id').distinct()
+
+ return render_to_response('badge.html', {
+ 'awards' : awards,
+ 'badge' : badge,
+ }, context_instance=RequestContext(request))
+
+def read_message(request):
+ if request.method == "POST":
+ if request.POST['formdata'] == 'required':
+ request.session['message_silent'] = 1
+
+ if request.user.is_authenticated():
+ request.user.delete_messages()
+ return HttpResponse('')
+
+def upload(request):
+ class FileTypeNotAllow(Exception):
+ pass
+ class FileSizeNotAllow(Exception):
+ pass
+ class UploadPermissionNotAuthorized(Exception):
+ pass
+
+ #<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>
+ xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>"
+
+ try:
+ f = request.FILES['file-upload']
+ # check upload permission
+ if not can_upload_files(request.user):
+ raise UploadPermissionNotAuthorized
+
+ # check file type
+ file_name_suffix = os.path.splitext(f.name)[1].lower()
+ if not file_name_suffix in settings.ALLOW_FILE_TYPES:
+ raise FileTypeNotAllow
+
+ # genetate new file name
+ new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix
+ # use default storage to store file
+ default_storage.save(new_file_name, f)
+ # check file size
+ # byte
+ size = default_storage.size(new_file_name)
+ if size > settings.ALLOW_MAX_FILE_SIZE:
+ default_storage.delete(new_file_name)
+ raise FileSizeNotAllow
+
+ result = xml_template % ('Good', '', default_storage.url(new_file_name))
+ except UploadPermissionNotAuthorized:
+ result = xml_template % ('', _('uploading images is limited to users with >60 reputation points'), '')
+ except FileTypeNotAllow:
+ result = xml_template % ('', _("allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"), '')
+ except FileSizeNotAllow:
+ result = xml_template % ('', _("maximum upload file size is %sK") % settings.ALLOW_MAX_FILE_SIZE / 1024, '')
+ except Exception as e:
+ result = xml_template % ('', _('Error uploading file. Please contact the site administrator. Thank you. %s' % e), '')
+
+ return HttpResponse(result, mimetype="application/xml")
+
+def books(request):
+ return HttpResponseRedirect("/books/mysql-zhaoyang")
+
+def book(request, short_name, unanswered=False):
+ """
+ 1. questions list
+ 2. book info
+ 3. author info and blog rss items
+ """
+ """
+ List of Questions, Tagged questions, and Unanswered questions.
+ """
+ books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
+ match_count = len(books)
+ if match_count == 0 :
+ raise Http404
+ else:
+ # the book info
+ book = books[0]
+ # get author info
+ author_info = BookAuthorInfo.objects.get(book=book)
+ # get author rss info
+ author_rss = BookAuthorRss.objects.filter(book=book)
+
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ # check if request is from tagged questions
+ if unanswered:
+ # check if request is from unanswered questions
+ # Article.objects.filter(publications__id__exact=1)
+ objects = Question.objects.filter(book__id__exact=book.id, deleted=False, answer_count=0).order_by(orderby)
+ else:
+ objects = Question.objects.filter(book__id__exact=book.id, deleted=False).order_by(orderby)
+
+ # RISK - inner join queries
+ objects = objects.select_related();
+ objects_list = Paginator(objects, user_page_size)
+ questions = objects_list.page(page)
+
+ return render_to_response('book.html', {
+ "book" : book,
+ "author_info" : author_info,
+ "author_rss" : author_rss,
+ "questions" : questions,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?sort=%s&' % view_id,
+ 'pagesize' : user_page_size
+ }
+ }, context_instance=RequestContext(request))
+
+@login_required
+def ask_book(request, short_name):
+ if request.method == "POST":
+ form = AskForm(request.POST)
+ if form.is_valid():
+ added_at = datetime.datetime.now()
+ html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
+ question = Question(
+ title = strip_tags(form.cleaned_data['title']),
+ author = request.user,
+ added_at = added_at,
+ last_activity_at = added_at,
+ last_activity_by = request.user,
+ wiki = form.cleaned_data['wiki'],
+ tagnames = form.cleaned_data['tags'].strip(),
+ html = html,
+ summary = strip_tags(html)[:120]
+ )
+ if question.wiki:
+ question.last_edited_by = question.author
+ question.last_edited_at = added_at
+ question.wikified_at = added_at
+
+ question.save()
+
+ # create the first revision
+ QuestionRevision.objects.create(
+ question = question,
+ revision = 1,
+ title = question.title,
+ author = request.user,
+ revised_at = added_at,
+ tagnames = question.tagnames,
+ summary = CONST['default_version'],
+ text = form.cleaned_data['text']
+ )
+
+ books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
+ match_count = len(books)
+ if match_count == 1:
+ # the book info
+ book = books[0]
+ book.questions.add(question)
+
+ return HttpResponseRedirect(question.get_absolute_url())
+ else:
+ form = AskForm()
+
+ tags = _get_tags_cache_json()
+ return render_to_response('ask.html', {
+ 'form' : form,
+ 'tags' : tags,
+ }, context_instance=RequestContext(request))
+
+def search(request):
+ """
+ Search by question, user and tag keywords.
+ For questions now we only search keywords in question title.
+ """
+ if request.method == "GET":
+ keywords = request.GET.get("q")
+ search_type = request.GET.get("t")
+ try:
+ page = int(request.GET.get('page', '1'))
+ except ValueError:
+ page = 1
+ if keywords is None:
+ return HttpResponseRedirect('/')
+ if search_type == 'tag':
+ return HttpResponseRedirect('/tags/?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "user":
+ return HttpResponseRedirect('/users/?q=%s&page=%s' % (keywords.strip(), page))
+ elif search_type == "question":
+
+ template_file = "questions.html"
+ # Set flag to False by default. If it is equal to True, then need to be saved.
+ pagesize_changed = False
+ # get pagesize from session, if failed then get default value
+ user_page_size = request.session.get("pagesize", QUESTIONS_PAGE_SIZE)
+ # set pagesize equal to logon user specified value in database
+ if request.user.is_authenticated() and request.user.questions_per_page > 0:
+ user_page_size = request.user.questions_per_page
+
+ try:
+ page = int(request.GET.get('page', '1'))
+ # get new pagesize from UI selection
+ pagesize = int(request.GET.get('pagesize', user_page_size))
+ if pagesize <> user_page_size:
+ pagesize_changed = True
+
+ except ValueError:
+ page = 1
+ pagesize = user_page_size
+
+ # save this pagesize to user database
+ if pagesize_changed:
+ request.session["pagesize"] = pagesize
+ if request.user.is_authenticated():
+ user = request.user
+ user.questions_per_page = pagesize
+ user.save()
+
+ view_id = request.GET.get('sort', None)
+ view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
+ try:
+ orderby = view_dic[view_id]
+ except KeyError:
+ view_id = "latest"
+ orderby = "-added_at"
+
+ objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby)
+
+ # RISK - inner join queries
+ objects = objects.select_related();
+ objects_list = Paginator(objects, pagesize)
+ questions = objects_list.page(page)
+
+ # Get related tags from this page objects
+ related_tags = []
+ for question in questions.object_list:
+ tags = list(question.tags.all())
+ for tag in tags:
+ if tag not in related_tags:
+ related_tags.append(tag)
+
+ return render_to_response(template_file, {
+ "questions" : questions,
+ "tab_id" : view_id,
+ "questions_count" : objects_list.count,
+ "tags" : related_tags,
+ "searchtag" : None,
+ "searchtitle" : keywords,
+ "keywords" : keywords,
+ "is_unanswered" : False,
+ "context" : {
+ 'is_paginated' : True,
+ 'pages': objects_list.num_pages,
+ 'page': page,
+ 'has_previous': questions.has_previous(),
+ 'has_next': questions.has_next(),
+ 'previous': questions.previous_page_number(),
+ 'next': questions.next_page_number(),
+ 'base_url' : request.path + '?t=question&q=%s&sort=%s&' % (keywords, view_id),
+ 'pagesize' : pagesize
+ }}, context_instance=RequestContext(request))
+
+ else:
+ raise Http404
+
diff --git a/junk b/junk
new file mode 100644
index 00000000..3f3226d6
--- /dev/null
+++ b/junk
@@ -0,0 +1,7 @@
+/branches/beta2/forum/feed.py r110 line 17:
+/branches/beta2/forum/feed.py r110 line 20:
+/branches/beta2/forum/forms.py r110 line 63:
+/branches/beta2/templates/question.html r120 line 237:
+/branches/beta2/templates/question.html r120 line 57:
+/branches/beta2/templates/question.html r120 line 456:
+/branches/beta2/forum/views.py r127 line 1088:
diff --git a/lanai.psproj b/lanai.psproj
new file mode 100644
index 00000000..7f44d7ff
--- /dev/null
+++ b/lanai.psproj
@@ -0,0 +1,557 @@
+[PyScripter]
+Version=1.9.9.3
+
+[Project]
+ClassName=TProjectRootNode
+StoreRelativePaths=FALSE
+ShowFileExtensions=FALSE
+
+[Project\ChildNodes]
+Count=2
+
+[Project\ChildNodes\Node0]
+ClassName=TProjectFilesNode
+
+[Project\ChildNodes\Node0\ChildNodes]
+Count=1
+
+[Project\ChildNodes\Node0\ChildNodes\Node0]
+ClassName=TProjectFolderNode
+Name=src
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes]
+Count=10
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0]
+ClassName=TProjectFolderNode
+Name=django_authopenid
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes]
+Count=9
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\__init__.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\admin.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\forms.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\middleware.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node4]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\mimeparse.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node5]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\models.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node6]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\urls.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node7]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\util.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node8]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\django_authopenid\views.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1]
+ClassName=TProjectFolderNode
+Name=forum
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes]
+Count=12
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node0]
+ClassName=TProjectFolderNode
+Name=management
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node0\ChildNodes]
+Count=2
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0]
+ClassName=TProjectFolderNode
+Name=commands
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0\ChildNodes]
+Count=3
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\management\commands\__init__.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\management\commands\mark_offensive_cron.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\management\commands\sample_command.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\management\__init__.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node1]
+ClassName=TProjectFolderNode
+Name=templatetags
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node1\ChildNodes]
+Count=3
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node1\ChildNodes\Node0]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\templatetags\__init__.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node1\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\templatetags\extra_filters.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node1\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\templatetags\extra_tags.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\__init__.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node3]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\admin.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node4]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\auth.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node5]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\const.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node6]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\diff.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node7]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\forms.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node8]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\managers.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node9]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\models.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node10]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\user.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1\ChildNodes\Node11]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\forum\views.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2]
+ClassName=TProjectFolderNode
+Name=sql_scripts
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2\ChildNodes]
+Count=6
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2\ChildNodes\Node0]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\sql_scripts\cnprog_new_install.sql
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\sql_scripts\update_2009_01_13_001.sql
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\sql_scripts\update_2009_01_13_002.sql
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2\ChildNodes\Node3]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\sql_scripts\update_2009_12_24_001.sql
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2\ChildNodes\Node4]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\sql_scripts\update_2009_12_27_001.sql
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2\ChildNodes\Node5]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\sql_scripts\update_2009_12_27_002.sql
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3]
+ClassName=TProjectFolderNode
+Name=templates
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes]
+Count=44
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0]
+ClassName=TProjectFolderNode
+Name=authopenid
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes]
+Count=10
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node0]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\changeemail.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\changeopenid.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\changepw.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node3]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\complete.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node4]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\delete.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node5]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\failure.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node6]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\sendpw.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node7]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\settings.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node8]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\signin.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node0\ChildNodes\Node9]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\authopenid\signup.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1]
+ClassName=TProjectFolderNode
+Name=content
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes]
+Count=2
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0]
+ClassName=TProjectFolderNode
+Name=js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes]
+Count=10
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0]
+ClassName=TProjectFolderNode
+Name=wmd
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0\ChildNodes]
+Count=3
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node0]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\wmd\showdown.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\wmd\wmd-base.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\wmd\wmd-plus.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\com.cnprog.editor.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\com.cnprog.post.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node3]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\com.cnprog.utils.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node4]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\excanvas.pack.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node5]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\jquery.flot.pack.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node6]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\jquery.openid.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node7]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\jquery.validate.pack.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node8]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\jquery-1.2.6.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node0\ChildNodes\Node9]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\js\jquery-1.2.6.min.js
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node1]
+ClassName=TProjectFolderNode
+Name=style
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node1\ChildNodes]
+Count=4
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node1\ChildNodes\Node0]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\style\default.css
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node1\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\style\jquery.autocomplete.css
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node1\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\style\openid.css
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node1\ChildNodes\Node1\ChildNodes\Node3]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\content\style\prettify.css
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\404.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node3]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\500.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node4]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\about.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node5]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\answer_edit.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node6]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\ask.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node7]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\badge.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node8]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\badges.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node9]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\base.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node10]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\base_content.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node11]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\close.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node12]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\faq.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node13]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\footer.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node14]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\header.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node15]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\index.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node16]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\logout.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node17]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\pagesize.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node18]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\paginator.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node19]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\privacy.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node20]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\question.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node21]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\question_edit.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node22]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\question_retag.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node23]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\questions.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node24]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\reopen.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node25]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\revisions_answer.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node26]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\revisions_question.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node27]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\sidebar.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node28]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\tags.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node29]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\unanswered.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node30]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node31]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_edit.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node32]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_favorites.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node33]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_footer.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node34]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_info.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node35]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_preferences.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node36]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_recent.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node37]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_reputation.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node38]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_responses.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node39]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_stats.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node40]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_tabs.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node41]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\user_votes.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node42]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\users.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node3\ChildNodes\Node43]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\templates\users_questions.html
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node4]
+ClassName=TProjectFolderNode
+Name=utils
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node4\ChildNodes]
+Count=4
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node4\ChildNodes\Node0]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\utils\__init__.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node4\ChildNodes\Node1]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\utils\cache.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node4\ChildNodes\Node2]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\utils\html.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node4\ChildNodes\Node3]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\utils\lists.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node5]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\__init__.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node6]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\manage.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node7]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\settings.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node8]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\test.py
+
+[Project\ChildNodes\Node0\ChildNodes\Node0\ChildNodes\Node9]
+ClassName=TProjectFileNode
+FileName=C:\Projects\Lanai\src\urls.py
+
+[Project\ChildNodes\Node1]
+ClassName=TProjectRunConfiguationsNode
+
+[Project\ExtraPythonPath]
+Count=0
+
diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 00000000..c4c1e674
--- /dev/null
+++ b/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,2540 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-06-22 20:40-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: settings.py:32
+msgid "account/"
+msgstr ""
+
+#: settings.py:32 django_authopenid/urls.py:9 django_authopenid/urls.py:11
+msgid "signin/"
+msgstr ""
+
+#: django_authopenid/forms.py:67 django_authopenid/views.py:93
+msgid "i-names are not supported"
+msgstr ""
+
+#: django_authopenid/forms.py:102 django_authopenid/forms.py:207
+msgid ""
+"Usernames can only contain letters, numbers and "
+"underscores"
+msgstr ""
+
+#: django_authopenid/forms.py:109
+msgid ""
+"This username does not exist in our database. Please "
+"choose another."
+msgstr ""
+
+#: django_authopenid/forms.py:126 django_authopenid/forms.py:231
+msgid ""
+"Please enter a valid username and password. Note that "
+"both fields are case-sensitive."
+msgstr ""
+
+#: django_authopenid/forms.py:130 django_authopenid/forms.py:235
+msgid "This account is inactive."
+msgstr ""
+
+#: django_authopenid/forms.py:158
+msgid "invalid user name"
+msgstr ""
+
+#: django_authopenid/forms.py:160
+msgid "sorry, this name can not be used, please try another"
+msgstr ""
+
+#: django_authopenid/forms.py:162
+msgid "username too short"
+msgstr ""
+
+#: django_authopenid/forms.py:170 django_authopenid/forms.py:171
+msgid "this name is already in use - please try anoter"
+msgstr ""
+
+#: django_authopenid/forms.py:184
+msgid ""
+"This email is already registered in our database. Please "
+"choose another."
+msgstr ""
+
+#: django_authopenid/forms.py:214
+msgid ""
+"This username don't exist. Please choose another."
+msgstr ""
+
+#: django_authopenid/forms.py:253
+msgid "choose a username"
+msgstr ""
+
+#: django_authopenid/forms.py:255 templates/authopenid/signup.html:36
+msgid "your email address"
+msgstr ""
+
+#: django_authopenid/forms.py:257 templates/authopenid/signup.html:37
+msgid "choose password"
+msgstr ""
+
+#: django_authopenid/forms.py:259 templates/authopenid/signup.html:38
+msgid "retype password"
+msgstr ""
+
+#: django_authopenid/forms.py:330
+msgid ""
+"Old password is incorrect. Please enter the correct "
+"password."
+msgstr ""
+
+#: django_authopenid/forms.py:342
+msgid "new passwords do not match"
+msgstr ""
+
+#: django_authopenid/forms.py:434
+msgid "Incorrect username."
+msgstr ""
+
+#: django_authopenid/urls.py:10
+msgid "signout/"
+msgstr ""
+
+#: django_authopenid/urls.py:11
+msgid "complete/"
+msgstr ""
+
+#: django_authopenid/urls.py:13
+msgid "register/"
+msgstr ""
+
+#: django_authopenid/urls.py:14
+msgid "signup/"
+msgstr ""
+
+#: django_authopenid/urls.py:16
+msgid "sendpw/"
+msgstr ""
+
+#: django_authopenid/urls.py:26
+msgid "delete/"
+msgstr ""
+
+#: django_authopenid/views.py:99
+#, python-format
+msgid "非法OpenID地址: %s"
+msgstr ""
+
+#: django_authopenid/views.py:366
+msgid "Welcome"
+msgstr ""
+
+#: django_authopenid/views.py:456
+msgid "Password changed."
+msgstr ""
+
+#: django_authopenid/views.py:488
+msgid "Email changed."
+msgstr ""
+
+#: django_authopenid/views.py:519 django_authopenid/views.py:671
+#, python-format
+msgid "No OpenID %s found associated in our database"
+msgstr ""
+
+#: django_authopenid/views.py:523 django_authopenid/views.py:678
+#, python-format
+msgid "The OpenID %s isn't associated to current user logged in"
+msgstr ""
+
+#: django_authopenid/views.py:531
+msgid "Email Changed."
+msgstr ""
+
+#: django_authopenid/views.py:606
+msgid "This OpenID is already associated with another account."
+msgstr ""
+
+#: django_authopenid/views.py:611
+#, python-format
+msgid "OpenID %s is now associated with your account."
+msgstr ""
+
+#: django_authopenid/views.py:681
+msgid "Account deleted."
+msgstr ""
+
+#: django_authopenid/views.py:721
+msgid "Request for new password"
+msgstr ""
+
+#: django_authopenid/views.py:734
+msgid "A new password has been sent to your email address."
+msgstr ""
+
+#: django_authopenid/views.py:764
+#, python-format
+msgid ""
+"Could not change password. Confirmation key '%s' is not "
+"registered."
+msgstr ""
+
+#: django_authopenid/views.py:773
+msgid ""
+"Can not change password. User don't exist anymore in our "
+"database."
+msgstr ""
+
+#: django_authopenid/views.py:782
+#, python-format
+msgid "Password changed for %s. You may now sign in."
+msgstr ""
+
+#: forum/const.py:8
+msgid "duplicate question"
+msgstr ""
+
+#: forum/const.py:9
+msgid "question if off-topic or not relevant"
+msgstr ""
+
+#: forum/const.py:10
+msgid "too subjective and argumentative"
+msgstr ""
+
+#: forum/const.py:11
+msgid "is not an answer to the question"
+msgstr ""
+
+#: forum/const.py:12
+msgid "the question is answered, right answer was accepted"
+msgstr ""
+
+#: forum/const.py:13
+msgid "problem is not reproducible or outdated"
+msgstr ""
+
+#: forum/const.py:15
+msgid "question contains offensive inappropriate, or malicious remarks"
+msgstr ""
+
+#: forum/const.py:16
+msgid "spam or advertising"
+msgstr ""
+
+#: forum/const.py:56
+msgid "question"
+msgstr ""
+
+#: forum/const.py:57 templates/book.html:110 templates/backup/book.html:110
+msgid "answer"
+msgstr ""
+
+#: forum/const.py:58
+msgid "commented question"
+msgstr ""
+
+#: forum/const.py:59
+msgid "commented answer"
+msgstr ""
+
+#: forum/const.py:60
+msgid "edited question"
+msgstr ""
+
+#: forum/const.py:61
+msgid "edited answer"
+msgstr ""
+
+#: forum/const.py:62
+msgid "received award"
+msgstr ""
+
+#: forum/const.py:63
+msgid "marked best answer"
+msgstr ""
+
+#: forum/const.py:64
+msgid "upvoted"
+msgstr ""
+
+#: forum/const.py:65
+msgid "downvoted"
+msgstr ""
+
+#: forum/const.py:66
+msgid "canceled vote"
+msgstr ""
+
+#: forum/const.py:67
+msgid "deleted question"
+msgstr ""
+
+#: forum/const.py:68
+msgid "deleted answer"
+msgstr ""
+
+#: forum/const.py:69
+msgid "marked offensive"
+msgstr ""
+
+#: forum/const.py:70
+msgid "updated tags"
+msgstr ""
+
+#: forum/const.py:71
+msgid "selected favorite"
+msgstr ""
+
+#: forum/const.py:72
+msgid "completed user profile"
+msgstr ""
+
+#: forum/const.py:83
+msgid "[closed]"
+msgstr ""
+
+#: forum/const.py:84
+msgid "[deleted]"
+msgstr ""
+
+#: forum/const.py:85
+msgid "initial version"
+msgstr ""
+
+#: forum/const.py:86
+msgid "retagged"
+msgstr ""
+
+#: forum/feed.py:17 templates/base.html:7 templates/base_content.html:6
+#: templates/faq.html:25 templates/faq.html.py:108
+#: templates/backup/base.html:7 templates/backup/base_content.html:6
+#: templates/backup/faq.html:25 templates/backup/faq.html.py:108
+#: templates/tough/faq.html:23 templates/tough/faq.html.py:106
+#: templates/tough/question_retag.html:89
+msgid "site title"
+msgstr ""
+
+#: forum/feed.py:17
+msgid " - "
+msgstr ""
+
+#: forum/feed.py:17 templates/base.html:7 templates/base_content.html:6
+#: templates/backup/base.html:7 templates/backup/base_content.html:6
+msgid "site slogan"
+msgstr ""
+
+#: forum/feed.py:17
+msgid "latest questions"
+msgstr ""
+
+#: forum/feed.py:19 templates/index.html:8 templates/backup/index.html:8
+msgid "meta site content"
+msgstr ""
+
+#: forum/feed.py:21
+msgid "copyright message"
+msgstr ""
+
+#: forum/forms.py:14 templates/answer_edit_tips.html:34
+#: templates/answer_edit_tips.html.py:39 templates/question_edit_tips.html:31
+#: templates/question_edit_tips.html:36
+#: templates/backup/answer_edit_tips.html:33
+#: templates/backup/answer_edit_tips.html:38
+#: templates/backup/question_edit_tips.html:29
+#: templates/backup/question_edit_tips.html:34
+msgid "title"
+msgstr ""
+
+#: forum/forms.py:15
+msgid "please enter a descriptive title for your question"
+msgstr ""
+
+#: forum/forms.py:20
+msgid "title must be > 10 characters"
+msgstr ""
+
+#: forum/forms.py:29
+msgid "content"
+msgstr ""
+
+#: forum/forms.py:35
+msgid "question content must be > 10 characters"
+msgstr ""
+
+#: forum/forms.py:45 templates/header.html:30 templates/header.html.py:61
+#: templates/backup/header.html:30 templates/backup/header.html.py:59
+msgid "tags"
+msgstr ""
+
+#: forum/forms.py:46
+msgid "please use space to separate tags (this enables autocomplete feature)"
+msgstr ""
+
+#: forum/forms.py:53
+msgid "tags are required"
+msgstr ""
+
+#: forum/forms.py:57
+msgid "please use 5 tags or less"
+msgstr ""
+
+#: forum/forms.py:60
+msgid "tags must be shorter than 20 characters"
+msgstr ""
+
+#: forum/forms.py:64
+msgid ""
+"please use following characters in tags: letters 'a-z', numbers, and "
+"characters '.-_#'"
+msgstr ""
+
+#: forum/forms.py:74 templates/index.html:56 templates/question.html:196
+#: templates/question.html.py:377 templates/questions.html:58
+#: templates/questions.html.py:70 templates/unanswered.html:48
+#: templates/unanswered.html.py:60 templates/backup/index.html:56
+#: templates/backup/question.html:195 templates/backup/question.html.py:367
+#: templates/backup/questions.html:57 templates/backup/questions.html.py:69
+#: templates/backup/unanswered.html:47 templates/backup/unanswered.html:59
+#: templates/tough/unanswered.html:46 templates/tough/unanswered.html.py:58
+msgid "community wiki"
+msgstr ""
+
+#: forum/forms.py:75
+msgid ""
+"if you choose community wiki option, the question and answer do not generate "
+"points and name of author will not be shown"
+msgstr ""
+
+#: forum/forms.py:84
+msgid "update summary:"
+msgstr ""
+
+#: forum/forms.py:85
+msgid ""
+"enter a brief summary of your revision (e.g. fixed spelling, grammar, "
+"improved style, this field is optional)"
+msgstr ""
+
+#: forum/forms.py:160
+msgid "this email does not have to be linked to gravatar"
+msgstr ""
+
+#: forum/forms.py:161
+msgid "Real name"
+msgstr ""
+
+#: forum/forms.py:162
+msgid "Website"
+msgstr ""
+
+#: forum/forms.py:163
+msgid "Location"
+msgstr ""
+
+#: forum/forms.py:164
+msgid "Date of birth"
+msgstr ""
+
+#: forum/forms.py:164
+msgid "will not be shown, used to calculate age, format: YYYY-MM-DD"
+msgstr ""
+
+#: forum/forms.py:165 templates/authopenid/settings.html:20
+msgid "Profile"
+msgstr ""
+
+#: forum/forms.py:190 forum/forms.py:191
+msgid "this email has already been registered, please use another one"
+msgstr ""
+
+#: forum/models.py:316 templates/badges.html:52
+#: templates/backup/badges.html:52
+msgid "gold"
+msgstr ""
+
+#: forum/models.py:317 templates/badges.html:62
+#: templates/backup/badges.html:62
+msgid "silver"
+msgstr ""
+
+#: forum/models.py:318 templates/badges.html:70
+#: templates/backup/badges.html:70
+msgid "bronze"
+msgstr ""
+
+#: forum/user.py:16 templates/user_tabs.html:7
+#: templates/backup/user_tabs.html:6
+msgid "overview"
+msgstr ""
+
+#: forum/user.py:17
+msgid "user profile"
+msgstr ""
+
+#: forum/user.py:18
+msgid "user profile overview"
+msgstr ""
+
+#: forum/user.py:24 templates/user_tabs.html:9
+#: templates/backup/user_tabs.html:8
+msgid "recent activity"
+msgstr ""
+
+#: forum/user.py:25
+msgid "recent user activity"
+msgstr ""
+
+#: forum/user.py:26
+msgid "profile - recent activity"
+msgstr ""
+
+#: forum/user.py:33 templates/user_tabs.html:13
+#: templates/backup/user_tabs.html:12
+msgid "responses"
+msgstr ""
+
+#: forum/user.py:34 templates/user_tabs.html:12
+#: templates/backup/user_tabs.html:11
+msgid "comments and answers to others questions"
+msgstr ""
+
+#: forum/user.py:35
+msgid "profile - responses"
+msgstr ""
+
+#: forum/user.py:42 templates/user_info.html:23 templates/users.html:26
+#: templates/backup/user_info.html:22 templates/backup/users.html:25
+msgid "reputation"
+msgstr ""
+
+#: forum/user.py:43
+msgid "user reputation in the community"
+msgstr ""
+
+#: forum/user.py:44
+msgid "profile - user reputation"
+msgstr ""
+
+#: forum/user.py:50
+msgid "favorite questions"
+msgstr ""
+
+#: forum/user.py:51
+msgid "users favorite questions"
+msgstr ""
+
+#: forum/user.py:52
+msgid "profile - favorite questions"
+msgstr ""
+
+#: forum/user.py:59 templates/user_tabs.html:20
+#: templates/backup/user_tabs.html:19
+msgid "casted votes"
+msgstr "votes"
+
+#: forum/user.py:60 templates/user_tabs.html:20
+#: templates/backup/user_tabs.html:19
+msgid "user vote record"
+msgstr ""
+
+#: forum/user.py:61
+msgid "profile - votes"
+msgstr ""
+
+#: forum/user.py:68
+msgid "preferences"
+msgstr ""
+
+#: forum/user.py:69 templates/user_tabs.html:28
+#: templates/backup/user_tabs.html:27
+msgid "user preference settings"
+msgstr ""
+
+#: forum/user.py:70
+msgid "profile - user preferences"
+msgstr ""
+
+#: forum/views.py:1730
+msgid "uploading images is limited to users with >60 reputation points"
+msgstr ""
+
+#: forum/views.py:1732
+msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"
+msgstr ""
+
+#: forum/views.py:1734
+#, python-format
+msgid "maximum upload file size is %sK"
+msgstr ""
+
+#: forum/views.py:1736
+#, python-format
+msgid ""
+"Error uploading file. Please contact the site administrator. Thank you. %s"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:139 forum/templatetags/extra_tags.py:168
+#: templates/header.html:33 templates/backup/header.html:33
+msgid "badges"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:140 forum/templatetags/extra_tags.py:167
+msgid "reputation points"
+msgstr ""
+
+#: forum/templatetags/extra_tags.py:221
+msgid " ago"
+msgstr ""
+
+#: templates/404.html:24 templates/backup/404.html:24
+msgid "Sorry, could not find the page you requested."
+msgstr ""
+
+#: templates/404.html:26 templates/backup/404.html:26
+msgid "This might have happened for the following reasons:"
+msgstr ""
+
+#: templates/404.html:28 templates/backup/404.html:28
+msgid "this question or answer has been deleted;"
+msgstr ""
+
+#: templates/404.html:29 templates/backup/404.html:29
+msgid "url has error - please check it;"
+msgstr ""
+
+#: templates/404.html:30 templates/backup/404.html:30
+msgid ""
+"the page you tried to visit is protected or you don't have sufficient "
+"points, see"
+msgstr ""
+
+#: templates/404.html:31 templates/backup/404.html:31
+msgid "if you believe this error 404 should not have occured, please"
+msgstr ""
+
+#: templates/404.html:32 templates/backup/404.html:32
+msgid "report this problem"
+msgstr ""
+
+#: templates/404.html:41 templates/500.html:27 templates/backup/404.html:41
+#: templates/backup/500.html:27
+msgid "back to previous page"
+msgstr ""
+
+#: templates/404.html:42 templates/backup/404.html:42
+msgid "see all questions"
+msgstr ""
+
+#: templates/404.html:43 templates/backup/404.html:43
+msgid "see all tags"
+msgstr ""
+
+#: templates/500.html:24 templates/backup/500.html:24
+msgid "system error log is recorded, error will be fixed as soon as possible"
+msgstr ""
+
+#: templates/500.html:25 templates/backup/500.html:25
+msgid "please report the error to the site administrators if you wish"
+msgstr ""
+
+#: templates/500.html:28 templates/backup/500.html:28
+msgid "see latest questions"
+msgstr ""
+
+#: templates/500.html:29 templates/backup/500.html:29
+msgid "see tags"
+msgstr ""
+
+#: templates/about.html:6 templates/about.html.py:11
+#: templates/backup/about.html:6 templates/backup/about.html.py:11
+msgid "About"
+msgstr ""
+
+#: templates/answer_edit.html:4 templates/answer_edit.html.py:47
+#: templates/backup/answer_edit.html:3 templates/backup/answer_edit.html:46
+msgid "Edit answer"
+msgstr ""
+
+#: templates/answer_edit.html:24 templates/answer_edit.html.py:27
+#: templates/ask.html:25 templates/ask.html.py:28 templates/question.html:37
+#: templates/question.html.py:40 templates/question_edit.html:27
+#: templates/backup/answer_edit.html:23 templates/backup/answer_edit.html:26
+#: templates/backup/ask.html:24 templates/backup/ask.html.py:27
+#: templates/backup/question.html:36 templates/backup/question.html.py:39
+#: templates/backup/question_edit.html:25
+msgid "hide preview"
+msgstr ""
+
+#: templates/answer_edit.html:27 templates/ask.html:28
+#: templates/question.html:40 templates/question_edit.html:27
+#: templates/backup/answer_edit.html:26 templates/backup/ask.html:27
+#: templates/backup/question.html:39 templates/backup/question_edit.html:25
+msgid "show preview"
+msgstr ""
+
+#: templates/answer_edit.html:47 templates/question_edit.html:65
+#: templates/revisions_answer.html:36 templates/revisions_question.html:36
+#: templates/backup/answer_edit.html:46 templates/backup/question_edit.html:63
+#: templates/backup/revisions_answer.html:35
+#: templates/backup/revisions_question.html:34
+#: templates/tough/question_retag.html:51
+msgid "back"
+msgstr ""
+
+#: templates/answer_edit.html:52 templates/question_edit.html:70
+#: templates/revisions_answer.html:47 templates/revisions_question.html:47
+#: templates/backup/answer_edit.html:51 templates/backup/question_edit.html:68
+#: templates/backup/revisions_answer.html:46
+#: templates/backup/revisions_question.html:45
+msgid "revision"
+msgstr ""
+
+#: templates/answer_edit.html:55 templates/question_edit.html:74
+#: templates/backup/answer_edit.html:54 templates/backup/question_edit.html:72
+msgid "select revision"
+msgstr ""
+
+#: templates/answer_edit.html:62 templates/ask.html:81
+#: templates/question.html:447 templates/question_edit.html:91
+#: templates/backup/answer_edit.html:61 templates/backup/ask.html:80
+#: templates/backup/question.html:437 templates/backup/question_edit.html:89
+msgid "Toggle the real time Markdown editor preview"
+msgstr ""
+
+#: templates/answer_edit.html:62 templates/ask.html:81
+#: templates/question.html:447 templates/question_edit.html:91
+#: templates/backup/answer_edit.html:61 templates/backup/ask.html:80
+#: templates/backup/question.html:437 templates/backup/question_edit.html:89
+msgid "toggle preview"
+msgstr ""
+
+#: templates/answer_edit.html:73 templates/question_edit.html:119
+#: templates/backup/answer_edit.html:72
+#: templates/backup/question_edit.html:117
+msgid "Save edit"
+msgstr ""
+
+#: templates/answer_edit.html:74 templates/close.html:29
+#: templates/question_edit.html:120 templates/reopen.html:30
+#: templates/user_edit.html:83 templates/backup/answer_edit.html:73
+#: templates/backup/close.html:29 templates/backup/question_edit.html:118
+#: templates/backup/reopen.html:28 templates/backup/user_edit.html:81
+#: templates/tough/question_retag.html:75
+msgid "Cancel"
+msgstr ""
+
+#: templates/answer_edit_tips.html:4
+msgid "answer tips"
+msgstr "Tips"
+
+#: templates/answer_edit_tips.html:7 templates/backup/answer_edit_tips.html:6
+msgid "please make your answer relevant to this community"
+msgstr ""
+
+#: templates/answer_edit_tips.html:10 templates/backup/answer_edit_tips.html:9
+msgid "try to give an answer, rather than engage into a discussion"
+msgstr ""
+
+#: templates/answer_edit_tips.html:13
+#: templates/backup/answer_edit_tips.html:12
+msgid "please try to provide details"
+msgstr ""
+
+#: templates/answer_edit_tips.html:16 templates/question_edit_tips.html:13
+#: templates/backup/answer_edit_tips.html:15
+#: templates/backup/question_edit_tips.html:11
+msgid "be clear and concise"
+msgstr ""
+
+#: templates/answer_edit_tips.html:19 templates/question_edit_tips.html:16
+#: templates/backup/answer_edit_tips.html:18
+#: templates/backup/question_edit_tips.html:14
+msgid "see frequently asked questions"
+msgstr ""
+
+#: templates/answer_edit_tips.html:25 templates/question_edit_tips.html:22
+#: templates/backup/answer_edit_tips.html:24
+#: templates/backup/question_edit_tips.html:20
+msgid "Markdown tips"
+msgstr "Markdown basics"
+
+#: templates/answer_edit_tips.html:28 templates/question_edit_tips.html:25
+#: templates/backup/answer_edit_tips.html:27
+#: templates/backup/question_edit_tips.html:23
+msgid "*italic* or __italic__"
+msgstr ""
+
+#: templates/answer_edit_tips.html:31 templates/question_edit_tips.html:28
+#: templates/backup/answer_edit_tips.html:30
+#: templates/backup/question_edit_tips.html:26
+msgid "**bold** or __bold__"
+msgstr ""
+
+#: templates/answer_edit_tips.html:34 templates/question_edit_tips.html:31
+#: templates/backup/answer_edit_tips.html:33
+#: templates/backup/question_edit_tips.html:29
+msgid "link"
+msgstr ""
+
+#: templates/answer_edit_tips.html:34 templates/answer_edit_tips.html.py:39
+#: templates/question_edit_tips.html:31 templates/question_edit_tips.html:36
+#: templates/backup/answer_edit_tips.html:33
+#: templates/backup/answer_edit_tips.html:38
+#: templates/backup/question_edit_tips.html:29
+#: templates/backup/question_edit_tips.html:34
+msgid "text"
+msgstr ""
+
+#: templates/answer_edit_tips.html:39 templates/question_edit_tips.html:36
+#: templates/backup/answer_edit_tips.html:38
+#: templates/backup/question_edit_tips.html:34
+msgid "image"
+msgstr ""
+
+#: templates/answer_edit_tips.html:43 templates/question_edit_tips.html:40
+#: templates/backup/answer_edit_tips.html:42
+#: templates/backup/question_edit_tips.html:38
+msgid "numbered list:"
+msgstr ""
+
+#: templates/answer_edit_tips.html:48 templates/question_edit_tips.html:45
+#: templates/backup/answer_edit_tips.html:47
+#: templates/backup/question_edit_tips.html:43
+msgid "basic HTML tags are also supported"
+msgstr ""
+
+#: templates/answer_edit_tips.html:51 templates/question_edit_tips.html:48
+#: templates/backup/answer_edit_tips.html:50
+#: templates/backup/question_edit_tips.html:46
+msgid "learn more about Markdown"
+msgstr ""
+
+#: templates/ask.html:4 templates/ask.html.py:60 templates/backup/ask.html:3
+#: templates/backup/ask.html.py:59
+msgid "Ask a question"
+msgstr ""
+
+#: templates/ask.html:106 templates/backup/ask.html:105
+msgid "Use"
+msgstr ""
+
+#: templates/ask.html:106 templates/backup/ask.html:105
+msgid "learn more about OpenID"
+msgstr ""
+
+#: templates/ask.html:106 templates/authopenid/signin.html:34
+#: templates/authopenid/signin.html:60 templates/backup/ask.html:105
+msgid "Login"
+msgstr ""
+
+#: templates/ask.html:109 templates/backup/ask.html:108
+msgid "Get your own "
+msgstr ""
+
+#: templates/ask.html:117 templates/authopenid/sendpw.html:27
+#: templates/backup/ask.html:116
+msgid "User name"
+msgstr ""
+
+#: templates/ask.html:120 templates/backup/ask.html:119
+msgid "Email: (won't be shown to anyone)"
+msgstr ""
+
+#: templates/ask.html:127 templates/backup/ask.html:126
+msgid "Ask your question"
+msgstr ""
+
+#: templates/badge.html:6 templates/badge.html.py:17
+#: templates/backup/badge.html:5 templates/backup/badge.html.py:16
+msgid "Badge"
+msgstr ""
+
+#: templates/badge.html:26 templates/backup/badge.html:25
+msgid "The users have been awarded with badges:"
+msgstr ""
+
+#: templates/badges.html:6 templates/backup/badges.html:6
+msgid "Badges summary"
+msgstr ""
+
+#: templates/badges.html:17 templates/user_stats.html:113
+#: templates/backup/badges.html:17 templates/backup/user_stats.html:112
+msgid "Badges"
+msgstr ""
+
+#: templates/badges.html:21 templates/backup/badges.html:21
+msgid "Community gives you awards for your questions, answers and votes."
+msgstr ""
+
+#: templates/badges.html:22 templates/backup/badges.html:22
+msgid ""
+"Below is the list of available badges and number of times each type of badge "
+"has been awarded."
+msgstr ""
+
+#: templates/badges.html:49 templates/backup/badges.html:49
+msgid "Community badges"
+msgstr ""
+
+#: templates/badges.html:55 templates/backup/badges.html:55
+msgid "Gold badge is very rare."
+msgstr ""
+
+#: templates/badges.html:56 templates/backup/badges.html:56
+msgid ""
+"To obtain it you have to show profound knowledge and ability in addition to "
+"actively participating in the community."
+msgstr ""
+
+#: templates/badges.html:57 templates/backup/badges.html:57
+msgid "Gold badge is the highest award in this community."
+msgstr ""
+
+#: templates/badges.html:65 templates/backup/badges.html:65
+msgid "Obtaining silver badge requires significant patience."
+msgstr ""
+
+#: templates/badges.html:66 templates/backup/badges.html:66
+msgid "If you got one, you've very significantly contributed to this community"
+msgstr ""
+
+#: templates/badges.html:69 templates/backup/badges.html:69
+msgid "bronze badge: often given as a special honor"
+msgstr ""
+
+#: templates/badges.html:73 templates/backup/badges.html:73
+msgid ""
+"If you are active in this community, you will will get this medal - still it "
+"is a special honor."
+msgstr ""
+
+#: templates/base.html:59 templates/base_content.html:60
+#: templates/backup/base.html:59 templates/backup/base_content.html:57
+msgid "congratulations, community gave you a badge"
+msgstr ""
+
+#: templates/base.html:61 templates/base_content.html:62
+#: templates/backup/base.html:61 templates/backup/base_content.html:59
+msgid "profile"
+msgstr ""
+
+#: templates/base_content.html:61 templates/backup/base_content.html:58
+msgid "see"
+msgstr ""
+
+#: templates/book.html:7 templates/backup/book.html:7
+msgid "reading channel"
+msgstr ""
+
+#: templates/book.html:26 templates/backup/book.html:26
+msgid "[author]"
+msgstr ""
+
+#: templates/book.html:30 templates/backup/book.html:30
+msgid "[publisher]"
+msgstr ""
+
+#: templates/book.html:34 templates/backup/book.html:34
+msgid "[publication date]"
+msgstr ""
+
+#: templates/book.html:38 templates/backup/book.html:38
+msgid "[price]"
+msgstr ""
+
+#: templates/book.html:39 templates/backup/book.html:39
+msgid "currency unit"
+msgstr ""
+
+#: templates/book.html:42 templates/backup/book.html:42
+msgid "[pages]"
+msgstr ""
+
+#: templates/book.html:43 templates/backup/book.html:43
+msgid "pages abbreviation"
+msgstr ""
+
+#: templates/book.html:46 templates/backup/book.html:46
+msgid "[tags]"
+msgstr ""
+
+#: templates/book.html:56 templates/backup/book.html:56
+msgid "author blog"
+msgstr ""
+
+#: templates/book.html:62 templates/backup/book.html:62
+msgid "book directory"
+msgstr ""
+
+#: templates/book.html:66 templates/backup/book.html:66
+msgid "buy online"
+msgstr ""
+
+#: templates/book.html:79 templates/backup/book.html:79
+msgid "reader questions"
+msgstr ""
+
+#: templates/book.html:82 templates/backup/book.html:82
+msgid "ask the author"
+msgstr ""
+
+#: templates/book.html:88 templates/book.html.py:93
+#: templates/users_questions.html:17 templates/backup/book.html:88
+#: templates/backup/book.html.py:93 templates/backup/users_questions.html:16
+msgid "this question was selected as favorite"
+msgstr ""
+
+#: templates/book.html:88 templates/book.html.py:93
+#: templates/users_questions.html:11 templates/users_questions.html.py:17
+#: templates/backup/book.html:88 templates/backup/book.html.py:93
+#: templates/backup/users_questions.html:10
+#: templates/backup/users_questions.html:16
+msgid "number of times"
+msgstr ""
+
+#: templates/book.html:105 templates/index.html:47 templates/questions.html:46
+#: templates/unanswered.html:37 templates/users_questions.html:30
+#: templates/backup/book.html:105 templates/backup/index.html:47
+#: templates/backup/questions.html:45 templates/backup/unanswered.html:36
+#: templates/backup/users_questions.html:29 templates/tough/unanswered.html:35
+msgid "votes"
+msgstr ""
+
+#: templates/book.html:108 templates/backup/book.html:108
+msgid "the answer has been accepted to be correct"
+msgstr ""
+
+#: templates/book.html:115 templates/index.html:48 templates/questions.html:47
+#: templates/unanswered.html:38 templates/users_questions.html:40
+#: templates/backup/book.html:115 templates/backup/index.html:48
+#: templates/backup/questions.html:46 templates/backup/unanswered.html:37
+#: templates/backup/users_questions.html:39 templates/tough/unanswered.html:36
+msgid "views"
+msgstr ""
+
+#: templates/book.html:125 templates/index.html:68 templates/question.html:112
+#: templates/question.html.py:479 templates/questions.html:84
+#: templates/questions.html.py:149 templates/tags.html:47
+#: templates/unanswered.html:75 templates/unanswered.html.py:109
+#: templates/users_questions.html:52 templates/backup/book.html:125
+#: templates/backup/index.html:68 templates/backup/index.html.py:93
+#: templates/backup/question.html:111 templates/backup/question.html.py:469
+#: templates/backup/questions.html:83 templates/backup/questions.html:148
+#: templates/backup/tags.html:46 templates/backup/unanswered.html:74
+#: templates/backup/unanswered.html:108
+#: templates/backup/users_questions.html:51 templates/tough/unanswered.html:72
+#: templates/tough/unanswered.html:105
+msgid "using tags"
+msgstr ""
+
+#: templates/book.html:147 templates/backup/book.html:147
+msgid "subscribe to book RSS feed"
+msgstr ""
+
+#: templates/book.html:147 templates/index.html:116
+#: templates/backup/book.html:147 templates/backup/index.html:115
+msgid "subscribe to the questions feed"
+msgstr ""
+
+#: templates/close.html:6 templates/close.html.py:16
+#: templates/backup/close.html:6 templates/backup/close.html.py:16
+msgid "Close question"
+msgstr ""
+
+#: templates/close.html:19 templates/backup/close.html:19
+msgid "Close the question"
+msgstr ""
+
+#: templates/close.html:25 templates/backup/close.html:25
+msgid "Reasons"
+msgstr ""
+
+#: templates/close.html:28 templates/backup/close.html:28
+msgid "OK to close"
+msgstr ""
+
+#: templates/faq.html:11 templates/backup/faq.html:11
+#: templates/tough/faq.html:9
+msgid "Frequently Asked Questions "
+msgstr ""
+
+#: templates/faq.html:15 templates/backup/faq.html:15
+#: templates/tough/faq.html:13
+msgid "What kinds of questions can I ask here?"
+msgstr ""
+
+#: templates/faq.html:16 templates/backup/faq.html:16
+#: templates/tough/faq.html:14
+msgid ""
+"Most importanly - questions should be <strong>relevant</strong> to this "
+"community."
+msgstr ""
+
+#: templates/faq.html:17 templates/backup/faq.html:17
+#: templates/tough/faq.html:15
+msgid ""
+"Before asking the question - please make sure to use search to see whether "
+"your question has alredy been answered."
+msgstr ""
+
+#: templates/faq.html:20 templates/backup/faq.html:20
+#: templates/tough/faq.html:18
+msgid "What questions should I avoid asking?"
+msgstr ""
+
+#: templates/faq.html:21 templates/backup/faq.html:21
+#: templates/tough/faq.html:19
+msgid ""
+"Please avoid asking questions that are not relevant to this community, too "
+"subjective and argumentative."
+msgstr ""
+
+#: templates/faq.html:24 templates/backup/faq.html:24
+#: templates/tough/faq.html:22
+msgid "What should I avoid in my answers?"
+msgstr ""
+
+#: templates/faq.html:25 templates/backup/faq.html:25
+#: templates/tough/faq.html:23
+msgid ""
+"is a Q&A site, not a discussion group. Therefore - please avoid having "
+"discussions in your answers, comment facility allows some space for brief "
+"discussions."
+msgstr ""
+
+#: templates/faq.html:28 templates/backup/faq.html:28
+#: templates/tough/faq.html:26
+msgid "Who moderates this community?"
+msgstr ""
+
+#: templates/faq.html:29 templates/backup/faq.html:29
+#: templates/tough/faq.html:27
+msgid "The short answer is: <strong>you</strong>."
+msgstr ""
+
+#: templates/faq.html:30 templates/backup/faq.html:30
+#: templates/tough/faq.html:28
+msgid "This website is moderated by the users."
+msgstr ""
+
+#: templates/faq.html:31 templates/backup/faq.html:31
+#: templates/tough/faq.html:29
+msgid ""
+"The reputation system allows users earn the authorization to perform a "
+"variety of moderation tasks."
+msgstr ""
+
+#: templates/faq.html:34 templates/backup/faq.html:34
+#: templates/tough/faq.html:32
+msgid "How does reputation system work?"
+msgstr ""
+
+#: templates/faq.html:35 templates/backup/faq.html:35
+#: templates/tough/faq.html:33
+msgid ""
+"Anyone can ask questions and give answers, points are not necessary for that."
+msgstr ""
+
+#: templates/faq.html:36 templates/backup/faq.html:36
+#: templates/tough/faq.html:34
+msgid ""
+"As we've said before, users help running this site. Point system helps "
+"select users who can administer this community."
+msgstr ""
+
+#: templates/faq.html:37 templates/backup/faq.html:37
+#: templates/tough/faq.html:35
+msgid ""
+"Reputation points roughly measure how community trusts you. These points are "
+"given to you directly by other members of the community."
+msgstr ""
+
+#: templates/faq.html:40 templates/backup/faq.html:40
+#: templates/tough/faq.html:38
+msgid ""
+"For example, if you ask an interesting question or give a helpful answer, "
+"your input will be upvoted and you will gain more trust in the community."
+msgstr ""
+
+#: templates/faq.html:41 templates/backup/faq.html:41
+#: templates/tough/faq.html:39
+msgid ""
+"If on the other hand someone gives a misleading answer, the answer will be "
+"voted down and he/she loses some points."
+msgstr ""
+
+#: templates/faq.html:42 templates/backup/faq.html:42
+#: templates/tough/faq.html:40
+msgid ""
+"Each vote in favor will generate <strong>10</strong> points, each vote "
+"against will subtract <strong>2</strong> points."
+msgstr ""
+
+#: templates/faq.html:43 templates/backup/faq.html:43
+#: templates/tough/faq.html:41
+msgid ""
+"Through the votes of other people you can accumulate a maximum of "
+"<strong>200</strong> points."
+msgstr ""
+
+#: templates/faq.html:44 templates/backup/faq.html:44
+msgid "After accumulating certain number of points, you can do more:"
+msgstr ""
+
+#: templates/faq.html:52 templates/user_votes.html:14
+#: templates/backup/faq.html:52 templates/backup/user_votes.html:13
+#: templates/tough/faq.html:50
+msgid "upvote"
+msgstr ""
+
+#: templates/faq.html:56 templates/backup/faq.html:56
+#: templates/tough/faq.html:54
+msgid "use tags"
+msgstr ""
+
+#: templates/faq.html:60 templates/backup/faq.html:60
+#: templates/tough/faq.html:58
+msgid "add comments"
+msgstr ""
+
+#: templates/faq.html:64 templates/user_votes.html:16
+#: templates/backup/faq.html:64 templates/backup/user_votes.html:15
+#: templates/tough/faq.html:62
+msgid "downvote"
+msgstr ""
+
+#: templates/faq.html:71 templates/backup/faq.html:71
+#: templates/tough/faq.html:69
+msgid "retag questions"
+msgstr ""
+
+#: templates/faq.html:75 templates/backup/faq.html:75
+#: templates/tough/faq.html:73
+msgid "edit community wiki questions"
+msgstr ""
+
+#: templates/faq.html:79 templates/backup/faq.html:79
+#: templates/tough/faq.html:77
+msgid "edit any answer"
+msgstr ""
+
+#: templates/faq.html:83 templates/backup/faq.html:83
+#: templates/tough/faq.html:81
+msgid "open any closed question"
+msgstr ""
+
+#: templates/faq.html:87 templates/backup/faq.html:87
+#: templates/tough/faq.html:85
+msgid "delete any comment"
+msgstr ""
+
+#: templates/faq.html:91 templates/backup/faq.html:91
+#: templates/tough/faq.html:89
+msgid "delete any questions and answers and perform other moderation tasks"
+msgstr ""
+
+#: templates/faq.html:98 templates/backup/faq.html:98
+msgid "To register, do I need to create new password?"
+msgstr ""
+
+#: templates/faq.html:99 templates/backup/faq.html:99
+#: templates/tough/faq.html:97
+msgid ""
+"No, you don't have to. You can login through any service that supports "
+"OpenID, e.g. Google, Yahoo, AOL, etc."
+msgstr ""
+
+#: templates/faq.html:100 templates/backup/faq.html:100
+#: templates/tough/faq.html:98
+msgid "Login now!"
+msgstr ""
+
+#: templates/faq.html:103 templates/backup/faq.html:103
+#: templates/tough/faq.html:101
+msgid "Why other people can edit my questions/answers?"
+msgstr ""
+
+#: templates/faq.html:104 templates/backup/faq.html:104
+#: templates/tough/faq.html:102
+msgid "Goal of this site is..."
+msgstr ""
+
+#: templates/faq.html:104 templates/backup/faq.html:104
+#: templates/tough/faq.html:102
+msgid ""
+"So questions and answers can be edited like wiki pages by experienced users "
+"of this site and this improves the overall quality of the knowledge base "
+"content."
+msgstr ""
+
+#: templates/faq.html:105 templates/backup/faq.html:105
+#: templates/tough/faq.html:103
+msgid "If this approach is not for you, we respect your choice."
+msgstr ""
+
+#: templates/faq.html:107 templates/backup/faq.html:107
+#: templates/tough/faq.html:105
+msgid "Still have questions?"
+msgstr ""
+
+#: templates/faq.html:108 templates/backup/faq.html:108
+msgid "Please ask your question, help make our community better!"
+msgstr ""
+
+#: templates/faq.html:108 templates/header.html:29 templates/header.html.py:60
+#: templates/backup/faq.html:108 templates/backup/header.html:29
+#: templates/backup/header.html.py:58 templates/tough/faq.html:106
+msgid "questions"
+msgstr ""
+
+#: templates/faq.html:108 templates/index.html:121
+#: templates/backup/faq.html:108 templates/backup/index.html:120
+#: templates/tough/faq.html:106
+msgid "."
+msgstr ""
+
+#: templates/footer.html:7 templates/header.html:12 templates/index.html:83
+#: templates/backup/footer.html:7 templates/backup/header.html:12
+#: templates/backup/index.html:83
+msgid "about"
+msgstr ""
+
+#: templates/footer.html:8 templates/header.html:13 templates/index.html:84
+#: templates/backup/footer.html:8 templates/backup/header.html:13
+#: templates/backup/index.html:84
+msgid "faq"
+msgstr ""
+
+#: templates/footer.html:9 templates/backup/footer.html:9
+msgid "blog"
+msgstr ""
+
+#: templates/footer.html:10 templates/backup/footer.html:10
+msgid "contact us"
+msgstr ""
+
+#: templates/footer.html:11 templates/backup/footer.html:11
+msgid "privacy policy"
+msgstr ""
+
+#: templates/footer.html:12 templates/backup/footer.html:12
+msgid "give feedback"
+msgstr ""
+
+#: templates/footer.html:18 templates/backup/footer.html:18
+msgid "current revision"
+msgstr ""
+
+#: templates/header.html:8 templates/backup/header.html:8
+msgid "logout"
+msgstr ""
+
+#: templates/header.html:10 templates/authopenid/signup.html:39
+#: templates/backup/header.html:10
+msgid "login"
+msgstr ""
+
+#: templates/header.html:23 templates/backup/header.html:23
+msgid "back to home page"
+msgstr ""
+
+#: templates/header.html:31 templates/header.html.py:62
+#: templates/backup/header.html:31 templates/backup/header.html.py:60
+msgid "users"
+msgstr ""
+
+#: templates/header.html:32 templates/backup/header.html:32
+msgid "books"
+msgstr ""
+
+#: templates/header.html:34 templates/index.html:121
+#: templates/backup/header.html:34 templates/backup/index.html:120
+msgid "unanswered questions"
+msgstr "unanswered"
+
+#: templates/header.html:38 templates/backup/header.html:37
+msgid "my profile"
+msgstr ""
+
+#: templates/header.html:42 templates/backup/header.html:40
+msgid "ask a question"
+msgstr ""
+
+#: templates/header.html:57 templates/backup/header.html:55
+msgid "search"
+msgstr ""
+
+#: templates/index.html:6 templates/backup/index.html:6
+msgid "Home"
+msgstr ""
+
+#: templates/index.html:7 templates/backup/index.html:7
+msgid "meta site keywords, comma separated"
+msgstr ""
+
+#: templates/index.html:21 templates/questions.html:7
+#: templates/backup/index.html:21 templates/backup/questions.html:6
+msgid "Questions"
+msgstr ""
+
+#: templates/index.html:23 templates/backup/index.html:23
+msgid "last updated questions"
+msgstr ""
+
+#: templates/index.html:23 templates/questions.html:25
+#: templates/unanswered.html:20 templates/backup/index.html:23
+#: templates/backup/questions.html:24 templates/backup/unanswered.html:19
+#: templates/tough/unanswered.html:18
+msgid "newest"
+msgstr ""
+
+#: templates/index.html:24 templates/questions.html:27
+#: templates/backup/index.html:24 templates/backup/questions.html:26
+msgid "hottest questions"
+msgstr ""
+
+#: templates/index.html:24 templates/questions.html:27
+#: templates/backup/index.html:24 templates/backup/questions.html:26
+msgid "hottest"
+msgstr ""
+
+#: templates/index.html:25 templates/questions.html:28
+#: templates/backup/index.html:25 templates/backup/questions.html:27
+msgid "most voted questions"
+msgstr ""
+
+#: templates/index.html:25 templates/questions.html:28
+#: templates/backup/index.html:25 templates/backup/questions.html:27
+msgid "most voted"
+msgstr ""
+
+#: templates/index.html:26 templates/backup/index.html:26
+msgid "all questions"
+msgstr ""
+
+#: templates/index.html:46 templates/questions.html:45
+#: templates/unanswered.html:36 templates/users_questions.html:35
+#: templates/backup/index.html:46 templates/backup/questions.html:44
+#: templates/backup/unanswered.html:35
+#: templates/backup/users_questions.html:34 templates/tough/unanswered.html:34
+msgid "answers"
+msgstr ""
+
+#: templates/index.html:68 templates/question.html:112
+#: templates/question.html.py:479 templates/questions.html:84
+#: templates/questions.html.py:149 templates/tags.html:47
+#: templates/unanswered.html:75 templates/unanswered.html.py:109
+#: templates/users_questions.html:52 templates/backup/index.html:68
+#: templates/backup/index.html.py:93 templates/backup/question.html:111
+#: templates/backup/question.html.py:469 templates/backup/questions.html:83
+#: templates/backup/questions.html:148 templates/backup/tags.html:46
+#: templates/backup/unanswered.html:74 templates/backup/unanswered.html:108
+#: templates/backup/users_questions.html:51 templates/tough/unanswered.html:72
+#: templates/tough/unanswered.html:105
+msgid "see questions tagged"
+msgstr ""
+
+#: templates/index.html:79 templates/backup/index.html:79
+msgid "welcome to website"
+msgstr ""
+
+#: templates/index.html:81 templates/backup/index.html:81
+msgid "what is this website"
+msgstr ""
+
+#: templates/index.html:82 templates/backup/index.html:82
+msgid "what can one do on this website"
+msgstr ""
+
+#: templates/index.html:89 templates/backup/index.html:89
+msgid "Recent tags"
+msgstr ""
+
+#: templates/index.html:94
+#, python-format
+msgid "see questions tagged '%(tagname)s'"
+msgstr ""
+
+#: templates/index.html:97 templates/index.html.py:121
+#: templates/backup/index.html:96 templates/backup/index.html.py:120
+msgid "popular tags"
+msgstr ""
+
+#: templates/index.html:101 templates/backup/index.html:100
+msgid "Recent awards"
+msgstr ""
+
+#: templates/index.html:107 templates/backup/index.html:106
+msgid "given to"
+msgstr ""
+
+#: templates/index.html:112 templates/backup/index.html:111
+msgid "all awards"
+msgstr ""
+
+#: templates/index.html:116 templates/backup/index.html:115
+msgid "subscribe to last 30 questions by RSS"
+msgstr ""
+
+#: templates/index.html:121 templates/backup/index.html:120
+msgid "Still looking for more? See"
+msgstr ""
+
+#: templates/index.html:121 templates/backup/index.html:120
+msgid "complete list of quesionts"
+msgstr ""
+
+#: templates/index.html:121 templates/backup/index.html:120
+msgid "or"
+msgstr ""
+
+#: templates/index.html:121 templates/backup/index.html:120
+msgid "Please help us answer"
+msgstr ""
+
+#: templates/logout.html:6 templates/logout.html.py:17
+#: templates/backup/logout.html:6 templates/backup/logout.html.py:17
+msgid "Logout"
+msgstr ""
+
+#: templates/logout.html:20 templates/backup/logout.html:20
+msgid ""
+"As a registered user you can login with your OpenID, log out of the site or "
+"permanently remove your account."
+msgstr ""
+
+#: templates/logout.html:21 templates/backup/logout.html:21
+msgid "Logout now"
+msgstr ""
+
+#: templates/pagesize.html:6 templates/backup/pagesize.html:6
+msgid "posts per page"
+msgstr ""
+
+#: templates/paginator.html:6 templates/paginator.html.py:7
+#: templates/backup/paginator.html:5 templates/backup/paginator.html.py:6
+msgid "previous"
+msgstr ""
+
+#: templates/paginator.html:20 templates/backup/paginator.html:19
+msgid "current page"
+msgstr ""
+
+#: templates/paginator.html:23 templates/paginator.html.py:30
+#: templates/backup/paginator.html:22 templates/backup/paginator.html.py:29
+msgid "page number "
+msgstr ""
+
+#: templates/paginator.html:23 templates/paginator.html.py:30
+#: templates/backup/paginator.html:22 templates/backup/paginator.html.py:29
+msgid "number - make blank in english"
+msgstr ""
+
+#: templates/paginator.html:34 templates/backup/paginator.html:33
+msgid "next page"
+msgstr ""
+
+#: templates/privacy.html:6 templates/privacy.html.py:11
+#: templates/backup/privacy.html:5 templates/backup/privacy.html.py:10
+msgid "Privacy policy"
+msgstr ""
+
+#: templates/privacy.html:15 templates/backup/privacy.html:14
+msgid "general message about privacy"
+msgstr ""
+
+#: templates/privacy.html:18 templates/backup/privacy.html:17
+msgid "Site Visitors"
+msgstr ""
+
+#: templates/privacy.html:20 templates/backup/privacy.html:19
+msgid "what technical information is collected about visitors"
+msgstr ""
+
+#: templates/privacy.html:23 templates/backup/privacy.html:22
+msgid "Personal Information"
+msgstr ""
+
+#: templates/privacy.html:25 templates/backup/privacy.html:24
+msgid "details on personal information policies"
+msgstr ""
+
+#: templates/privacy.html:28 templates/backup/privacy.html:27
+msgid "Other Services"
+msgstr ""
+
+#: templates/privacy.html:30 templates/backup/privacy.html:29
+msgid "details on sharing data with third parties"
+msgstr ""
+
+#: templates/privacy.html:35 templates/backup/privacy.html:34
+msgid "cookie policy details"
+msgstr ""
+
+#: templates/privacy.html:37 templates/backup/privacy.html:36
+msgid "Policy Changes"
+msgstr ""
+
+#: templates/privacy.html:38 templates/backup/privacy.html:37
+msgid "how privacy policies can be changed"
+msgstr ""
+
+#: templates/question.html:66 templates/question.html.py:78
+#: templates/backup/question.html:65 templates/backup/question.html.py:77
+msgid "i like this post (click again to cancel)"
+msgstr ""
+
+#: templates/question.html:68 templates/question.html.py:80
+#: templates/question.html:273 templates/backup/question.html:67
+#: templates/backup/question.html.py:79 templates/backup/question.html:272
+msgid "current number of votes"
+msgstr ""
+
+#: templates/question.html:73 templates/question.html.py:84
+#: templates/backup/question.html:72 templates/backup/question.html.py:83
+msgid "i dont like this post (click again to cancel)"
+msgstr ""
+
+#: templates/question.html:90 templates/backup/question.html:89
+msgid "mark this question as favorite (click again to cancel)"
+msgstr ""
+
+#: templates/question.html:96 templates/backup/question.html:95
+msgid "remove favorite mark from this question (click again to restore mark)"
+msgstr ""
+
+#: templates/question.html:121 templates/question.html.py:304
+#: templates/revisions_answer.html:53 templates/revisions_question.html:53
+#: templates/backup/question.html:120 templates/backup/question.html.py:303
+#: templates/backup/revisions_answer.html:52
+#: templates/backup/revisions_question.html:51
+msgid "edit"
+msgstr ""
+
+#: templates/question.html:125 templates/question.html.py:314
+#: templates/backup/question.html:124 templates/backup/question.html.py:307
+msgid "delete"
+msgstr ""
+
+#: templates/question.html:130 templates/backup/question.html:129
+msgid "reopen"
+msgstr ""
+
+#: templates/question.html:135 templates/backup/question.html:134
+msgid "close"
+msgstr ""
+
+#: templates/question.html:141 templates/question.html.py:327
+#: templates/backup/question.html:140 templates/backup/question.html.py:317
+msgid ""
+"report as offensive (i.e containing spam, advertising, malicious text, etc.)"
+msgstr ""
+
+#: templates/question.html:142 templates/question.html.py:328
+#: templates/backup/question.html:141 templates/backup/question.html.py:318
+msgid "flag offensive"
+msgstr ""
+
+#: templates/question.html:154 templates/question.html.py:337
+#: templates/revisions_answer.html:65 templates/revisions_question.html:65
+#: templates/backup/question.html:153 templates/backup/question.html.py:327
+#: templates/backup/revisions_answer.html:64
+#: templates/backup/revisions_question.html:63
+msgid "updated"
+msgstr ""
+
+#: templates/question.html:203 templates/question.html.py:384
+#: templates/revisions_answer.html:63 templates/revisions_question.html:63
+#: templates/backup/question.html:202 templates/backup/question.html.py:374
+#: templates/backup/revisions_answer.html:62
+#: templates/backup/revisions_question.html:61
+msgid "asked"
+msgstr ""
+
+#: templates/question.html:233 templates/question.html.py:411
+#: templates/backup/question.html:232 templates/backup/question.html.py:401
+msgid "comments"
+msgstr ""
+
+#: templates/question.html:234 templates/question.html.py:412
+#: templates/backup/question.html:233 templates/backup/question.html.py:402
+msgid "add comment"
+msgstr ""
+
+#: templates/question.html:247 templates/backup/question.html:246
+#, python-format
+msgid ""
+"The question has been closed for the following reason \"%(question."
+"get_close_reason_display)s\" by"
+msgstr ""
+
+#: templates/question.html:249 templates/backup/question.html:248
+#, python-format
+msgid "close date %(question.closed_at)s"
+msgstr ""
+
+#: templates/question.html:256 templates/user_stats.html:28
+#: templates/backup/question.html:255 templates/backup/user_stats.html:27
+msgid "Answers"
+msgstr " Answers"
+
+#: templates/question.html:258 templates/backup/question.html:257
+msgid "oldest answers will be shown first"
+msgstr ""
+
+#: templates/question.html:258 templates/backup/question.html:257
+msgid "oldest answers"
+msgstr "oldest"
+
+#: templates/question.html:259 templates/backup/question.html:258
+msgid "newest answers will be shown first"
+msgstr ""
+
+#: templates/question.html:259 templates/backup/question.html:258
+msgid "newest answers"
+msgstr "newest"
+
+#: templates/question.html:260 templates/backup/question.html:259
+msgid "most voted answers will be shown first"
+msgstr ""
+
+#: templates/question.html:260 templates/backup/question.html:259
+msgid "popular answers"
+msgstr "most voted"
+
+#: templates/question.html:272 templates/backup/question.html:271
+msgid "i like this answer (click again to cancel)"
+msgstr ""
+
+#: templates/question.html:278 templates/backup/question.html:277
+msgid "i dont like this answer (click again to cancel)"
+msgstr ""
+
+#: templates/question.html:284 templates/backup/question.html:283
+msgid "mark this answer as favorite (click again to undo)"
+msgstr ""
+
+#: templates/question.html:289 templates/backup/question.html:288
+msgid "the author of the question has selected this answer as correct"
+msgstr ""
+
+#: templates/question.html:311
+msgid "undelete"
+msgstr ""
+
+#: templates/question.html:321 templates/backup/question.html:311
+msgid "answer permanent link"
+msgstr ""
+
+#: templates/question.html:322 templates/backup/question.html:312
+msgid "permanent link"
+msgstr ""
+
+#: templates/question.html:436 templates/backup/question.html:426
+msgid "Your answer"
+msgstr ""
+
+#: templates/question.html:460 templates/backup/question.html:450
+msgid "Answer the question"
+msgstr ""
+
+#: templates/question.html:462 templates/backup/question.html:452
+msgid "Login to answer"
+msgstr ""
+
+#: templates/question.html:474 templates/backup/question.html:464
+msgid "Question tags"
+msgstr "Tags"
+
+#: templates/question.html:484 templates/backup/question.html:474
+msgid "question asked"
+msgstr "Asked"
+
+#: templates/question.html:484 templates/question.html.py:490
+#: templates/user_info.html:51 templates/backup/question.html:474
+#: templates/backup/question.html.py:480 templates/backup/user_info.html:50
+msgid "ago"
+msgstr ""
+
+#: templates/question.html:487 templates/backup/question.html:477
+msgid "question was seen"
+msgstr "Seen"
+
+#: templates/question.html:487 templates/backup/question.html:477
+msgid "times"
+msgstr ""
+
+#: templates/question.html:490 templates/backup/question.html:480
+msgid "last updated"
+msgstr "Last updated"
+
+#: templates/question.html:495 templates/backup/question.html:485
+msgid "Related questions"
+msgstr ""
+
+#: templates/question_edit.html:4 templates/question_edit.html.py:65
+#: templates/backup/question_edit.html:2
+#: templates/backup/question_edit.html:63
+msgid "Edit question"
+msgstr ""
+
+#: templates/question_edit_tips.html:4
+msgid "question tips"
+msgstr "Tips"
+
+#: templates/question_edit_tips.html:7
+#: templates/backup/question_edit_tips.html:5
+msgid "please ask a relevant question"
+msgstr ""
+
+#: templates/question_edit_tips.html:10
+#: templates/backup/question_edit_tips.html:8
+msgid "please try provide enough details"
+msgstr "provide enough details"
+
+#: templates/questions.html:23 templates/backup/questions.html:22
+msgid "Found by tags"
+msgstr "Tagged questions"
+
+#: templates/questions.html:23 templates/backup/questions.html:22
+msgid "Found by title"
+msgstr ""
+
+#: templates/questions.html:23 templates/backup/questions.html:22
+msgid "All questions"
+msgstr ""
+
+#: templates/questions.html:25 templates/unanswered.html:20
+#: templates/backup/questions.html:24 templates/backup/unanswered.html:19
+#: templates/tough/unanswered.html:18
+msgid "most recently asked questions"
+msgstr ""
+
+#: templates/questions.html:26 templates/backup/questions.html:25
+msgid "most recently updated questions"
+msgstr ""
+
+#: templates/questions.html:26 templates/backup/questions.html:25
+msgid "active"
+msgstr ""
+
+#: templates/questions.html:109
+#, python-format
+msgid ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question tagged</p><p><span "
+"class=\"tag\">%(tagname)s</span></p>"
+msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions tagged</"
+"p><p><span class=\"tag\">%(tagname)s</span></p>"
+
+#: templates/questions.html:116
+#, python-format
+msgid ""
+"\n"
+"\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t"
+msgstr[0] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>question with title "
+"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
+"p>"
+msgstr[1] ""
+"\n"
+"<div class=\"questions-count\">%(q_num)s</div><p>questions with title "
+"containing <strong><span class=\"darkred\">%(searchtitle)s</span></strong></"
+"p>"
+
+#: templates/questions.html:124
+msgid "latest questions info"
+msgstr "<strong>Newest</strong> questions are shown first."
+
+#: templates/questions.html:128 templates/backup/questions.html:127
+msgid "Questions are sorted by the <strong>time of last update</strong>."
+msgstr ""
+
+#: templates/questions.html:129 templates/backup/questions.html:128
+msgid "Most recently answered ones are shown first."
+msgstr ""
+
+#: templates/questions.html:133 templates/backup/questions.html:132
+msgid "Questions sorted by <strong>number of responses</strong>."
+msgstr ""
+
+#: templates/questions.html:134 templates/backup/questions.html:133
+msgid "Most answered questions are shown first."
+msgstr ""
+
+#: templates/questions.html:138 templates/backup/questions.html:137
+msgid "Questions are sorted by the <strong>number of votes</strong>."
+msgstr ""
+
+#: templates/questions.html:139 templates/backup/questions.html:138
+msgid "Most voted questions are shown first."
+msgstr ""
+
+#: templates/questions.html:146 templates/unanswered.html:105
+#: templates/backup/questions.html:145 templates/backup/unanswered.html:104
+#: templates/tough/unanswered.html:101
+msgid "Related tags"
+msgstr "Tags"
+
+#: templates/reopen.html:6 templates/reopen.html.py:16
+#: templates/backup/reopen.html:4 templates/backup/reopen.html.py:14
+msgid "Reopen question"
+msgstr ""
+
+#: templates/reopen.html:19 templates/backup/reopen.html:17
+msgid "Open the previously closed question"
+msgstr ""
+
+#: templates/reopen.html:22 templates/backup/reopen.html:20
+msgid "The question was closed for the following reason "
+msgstr ""
+
+#: templates/reopen.html:22 templates/backup/reopen.html:20
+msgid "reason - leave blank in english"
+msgstr ""
+
+#: templates/reopen.html:22 templates/backup/reopen.html:20
+msgid "on "
+msgstr ""
+
+#: templates/reopen.html:22 templates/backup/reopen.html:20
+msgid "date closed"
+msgstr ""
+
+#: templates/reopen.html:29 templates/backup/reopen.html:27
+msgid "Reopen this question"
+msgstr ""
+
+#: templates/revisions_answer.html:7 templates/revisions_answer.html.py:36
+#: templates/revisions_question.html:8 templates/revisions_question.html:36
+#: templates/backup/revisions_answer.html:6
+#: templates/backup/revisions_answer.html:35
+#: templates/backup/revisions_question.html:6
+#: templates/backup/revisions_question.html:34
+msgid "Revision history"
+msgstr ""
+
+#: templates/tags.html:6 templates/tags.html.py:29
+#: templates/backup/tags.html:5 templates/backup/tags.html.py:28
+msgid "Tag list"
+msgstr ""
+
+#: templates/tags.html:31 templates/backup/tags.html:30
+msgid "sorted alphabetically"
+msgstr ""
+
+#: templates/tags.html:31 templates/backup/tags.html:30
+msgid "by name"
+msgstr ""
+
+#: templates/tags.html:32 templates/backup/tags.html:31
+msgid "sorted by frequency of tag use"
+msgstr ""
+
+#: templates/tags.html:32 templates/backup/tags.html:31
+msgid "by popularity"
+msgstr ""
+
+#: templates/tags.html:38 templates/backup/tags.html:37
+msgid "All tags matching query"
+msgstr ""
+
+#: templates/tags.html:38 templates/backup/tags.html:37
+msgid "all tags - make this empty in english"
+msgstr ""
+
+#: templates/tags.html:41 templates/backup/tags.html:40
+msgid "Nothing found"
+msgstr ""
+
+#: templates/unanswered.html:7 templates/unanswered.html.py:18
+#: templates/backup/unanswered.html:6 templates/backup/unanswered.html:17
+#: templates/tough/unanswered.html:5 templates/tough/unanswered.html.py:16
+msgid "Unanswered questions"
+msgstr ""
+
+#: templates/unanswered.html:97
+#, python-format
+msgid "have %(num_q)s unanswered questions"
+msgstr ""
+"<div class=\"questions-count\">%(num_q)s</div><strong>unanswered</strong> "
+"questions"
+
+#: templates/unanswered.html:99 templates/backup/questions.html:107
+#: templates/backup/unanswered.html:97
+msgid "Have a total of"
+msgstr ""
+
+#: templates/user_edit.html:6 templates/backup/user_edit.html:4
+msgid "Edit user profile"
+msgstr ""
+
+#: templates/user_edit.html:19 templates/backup/user_edit.html:17
+msgid "edit profile"
+msgstr ""
+
+#: templates/user_edit.html:31 templates/backup/user_edit.html:29
+msgid "image associated with your email address"
+msgstr ""
+
+#: templates/user_edit.html:31 templates/backup/user_edit.html:29
+msgid "avatar"
+msgstr ""
+
+#: templates/user_edit.html:36 templates/user_info.html:31
+#: templates/backup/user_edit.html:34 templates/backup/user_info.html:30
+msgid "Registered user"
+msgstr ""
+
+#: templates/user_edit.html:82 templates/backup/user_edit.html:80
+msgid "Update"
+msgstr ""
+
+#: templates/user_info.html:34 templates/backup/user_info.html:33
+msgid "update profile"
+msgstr ""
+
+#: templates/user_info.html:40 templates/backup/user_info.html:39
+msgid "real name"
+msgstr ""
+
+#: templates/user_info.html:45
+msgid "member for"
+msgstr ""
+
+#: templates/user_info.html:50 templates/backup/user_info.html:49
+msgid "last seen"
+msgstr ""
+
+#: templates/user_info.html:56 templates/backup/user_info.html:55
+msgid "user website"
+msgstr ""
+
+#: templates/user_info.html:62 templates/backup/user_info.html:61
+msgid "location"
+msgstr ""
+
+#: templates/user_info.html:69 templates/backup/user_info.html:68
+msgid "age"
+msgstr ""
+
+#: templates/user_info.html:70 templates/backup/user_info.html:69
+msgid "age unit"
+msgstr ""
+
+#: templates/user_info.html:75 templates/backup/user_info.html:74
+msgid "todays unused votes"
+msgstr ""
+
+#: templates/user_info.html:76 templates/backup/user_info.html:75
+msgid "votes left"
+msgstr ""
+
+#: templates/user_stats.html:15 templates/backup/user_stats.html:14
+msgid "User questions"
+msgstr ""
+
+#: templates/user_stats.html:37
+#, python-format
+msgid "the answer has been voted for %(vote_count)s times"
+msgstr ""
+
+#: templates/user_stats.html:37 templates/backup/user_stats.html:36
+msgid "this answer has been selected as correct"
+msgstr ""
+
+#: templates/user_stats.html:43 templates/backup/user_stats.html:42
+#, python-format
+msgid "the answer has been commented %(answered_question.comment_count)s times"
+msgstr ""
+
+#: templates/user_stats.html:56 templates/backup/user_stats.html:55
+msgid "votes total"
+msgstr "Votes"
+
+#: templates/user_stats.html:65 templates/backup/user_stats.html:64
+msgid "user has voted up this many times"
+msgstr ""
+
+#: templates/user_stats.html:70 templates/backup/user_stats.html:69
+msgid "user voted down this many times"
+msgstr ""
+
+#: templates/user_stats.html:84 templates/backup/user_stats.html:83
+msgid "Tags"
+msgstr ""
+
+#: templates/user_stats.html:94 templates/backup/user_stats.html:93
+#, python-format
+msgid "see other questions tagged '%(tag)s' "
+msgstr ""
+
+#: templates/user_tabs.html:7 templates/backup/user_tabs.html:6
+msgid "User profile"
+msgstr ""
+
+#: templates/user_tabs.html:16 templates/backup/user_tabs.html:15
+msgid "graph of user reputation"
+msgstr ""
+
+#: templates/user_tabs.html:17 templates/backup/user_tabs.html:16
+msgid "reputation history"
+msgstr ""
+
+#: templates/user_tabs.html:24 templates/backup/user_tabs.html:23
+msgid "favorites"
+msgstr ""
+
+#: templates/user_tabs.html:29 templates/backup/user_tabs.html:28
+msgid "settings"
+msgstr ""
+
+#: templates/users.html:6 templates/users.html.py:24
+#: templates/backup/users.html:5 templates/backup/users.html.py:23
+msgid "Users"
+msgstr ""
+
+#: templates/users.html:27 templates/backup/users.html:26
+msgid "recent"
+msgstr ""
+
+#: templates/users.html:28 templates/backup/users.html:27
+msgid "oldest"
+msgstr ""
+
+#: templates/users.html:29 templates/backup/users.html:28
+msgid "by username"
+msgstr ""
+
+#: templates/users.html:35 templates/backup/users.html:34
+#, python-format
+msgid "users matching query %(suser)s:"
+msgstr ""
+
+#: templates/users.html:39 templates/backup/users.html:38
+msgid "Nothing found."
+msgstr ""
+
+#: templates/users_questions.html:11 templates/backup/users_questions.html:10
+msgid "this questions was selected as favorite"
+msgstr ""
+
+#: templates/users_questions.html:33 templates/backup/users_questions.html:32
+msgid "this answer has been accepted to be correct"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:6
+msgid "Account: change email"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:9
+msgid ""
+"This is where you can change the email address associated with your account. "
+"Please keep this email address up to date so we can send you a password-"
+"reset email if you request one."
+msgstr ""
+
+#: templates/authopenid/changeemail.html:11
+#: templates/authopenid/changeopenid.html:13
+#: templates/authopenid/changepw.html:18 templates/authopenid/delete.html:14
+#: templates/authopenid/delete.html:24
+msgid "Please correct errors below:"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:28
+msgid "Email"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:29
+#: templates/authopenid/signin.html:59
+msgid "Password"
+msgstr ""
+
+#: templates/authopenid/changeemail.html:31
+msgid "Change email"
+msgstr ""
+
+#: templates/authopenid/changeopenid.html:7
+msgid "Account: change OpenID URL"
+msgstr ""
+
+#: templates/authopenid/changeopenid.html:11
+msgid ""
+"This is where you can change your OpenID URL. Make sure you remember it!"
+msgstr ""
+
+#: templates/authopenid/changeopenid.html:28
+msgid "OpenID URL:"
+msgstr ""
+
+#: templates/authopenid/changeopenid.html:29
+msgid "Change OpenID"
+msgstr ""
+
+#: templates/authopenid/changepw.html:13
+msgid "Account: change password"
+msgstr ""
+
+#: templates/authopenid/changepw.html:16
+msgid "This is where you can change your password. Make sure you remember it!"
+msgstr ""
+
+#: templates/authopenid/changepw.html:26
+msgid "Current password"
+msgstr ""
+
+#: templates/authopenid/changepw.html:27
+msgid "New password"
+msgstr ""
+
+#: templates/authopenid/changepw.html:28
+msgid "New password again"
+msgstr ""
+
+#: templates/authopenid/changepw.html:29 templates/authopenid/settings.html:28
+msgid "Change password"
+msgstr ""
+
+#: templates/authopenid/complete.html:4
+msgid "Connect your OpenID with this site"
+msgstr ""
+
+#: templates/authopenid/complete.html:7
+msgid "Connect your OpenID with your account on this site"
+msgstr ""
+
+#: templates/authopenid/complete.html:10
+msgid "Your OpenID is accepted. Please complete this to finish registration."
+msgstr ""
+
+#: templates/authopenid/complete.html:11
+msgid "This account already exists, please use another."
+msgstr ""
+
+#: templates/authopenid/complete.html:16 templates/authopenid/complete.html:29
+#: templates/authopenid/signin.html:42
+msgid "Sorry, looks like we have some errors:"
+msgstr ""
+
+#: templates/authopenid/complete.html:45
+msgid "New account"
+msgstr ""
+
+#: templates/authopenid/complete.html:46
+msgid "User name (<i>will be shown to others, cannot be modified</i>)"
+msgstr ""
+
+#: templates/authopenid/complete.html:47
+msgid "Email (<i>not shared with anyone</i>)"
+msgstr ""
+
+#: templates/authopenid/complete.html:48
+msgid "create account"
+msgstr ""
+
+#: templates/authopenid/complete.html:56
+msgid "Existing account"
+msgstr ""
+
+#: templates/authopenid/complete.html:57
+msgid "user name"
+msgstr ""
+
+#: templates/authopenid/complete.html:58
+msgid "password"
+msgstr ""
+
+#: templates/authopenid/complete.html:61
+msgid "Register"
+msgstr ""
+
+#: templates/authopenid/complete.html:62 templates/authopenid/signin.html:61
+msgid "Forgot your password?"
+msgstr ""
+
+#: templates/authopenid/delete.html:8
+msgid "Account: delete account"
+msgstr ""
+
+#: templates/authopenid/delete.html:12
+msgid ""
+"Note: After deleting your account, anyone will be able to register this "
+"username."
+msgstr ""
+
+#: templates/authopenid/delete.html:16
+msgid "Check confirm box, if you want delete your account."
+msgstr ""
+
+#: templates/authopenid/delete.html:19
+msgid "Password:"
+msgstr ""
+
+#: templates/authopenid/delete.html:31
+msgid "I am sure I want to delete my account."
+msgstr ""
+
+#: templates/authopenid/delete.html:32
+msgid "Password/OpenID URL"
+msgstr ""
+
+#: templates/authopenid/delete.html:32
+msgid "(required for your security)"
+msgstr ""
+
+#: templates/authopenid/delete.html:34
+msgid "Delete account permanently"
+msgstr ""
+
+#: templates/authopenid/sendpw.html:3 templates/authopenid/sendpw.html.py:7
+msgid "Send new password"
+msgstr ""
+
+#: templates/authopenid/sendpw.html:11
+msgid "Lost your password? No problem - here you can reset it."
+msgstr ""
+
+#: templates/authopenid/sendpw.html:12
+msgid ""
+"Please enter your username below and new password will be sent to your "
+"registered e-mail"
+msgstr ""
+
+#: templates/authopenid/sendpw.html:29
+msgid "Reset password"
+msgstr ""
+
+#: templates/authopenid/sendpw.html:29
+msgid "return to login"
+msgstr ""
+
+#: templates/authopenid/sendpw.html:32
+msgid ""
+"Note: your new password will be activated only after you click the "
+"activation link in the email message"
+msgstr ""
+
+#: templates/authopenid/settings.html:29
+msgid "Give your account a new password."
+msgstr ""
+
+#: templates/authopenid/settings.html:30
+msgid "Change email "
+msgstr ""
+
+#: templates/authopenid/settings.html:31
+msgid "Add or update the email address associated with your account."
+msgstr ""
+
+#: templates/authopenid/settings.html:34
+msgid "Change openid associated to your account"
+msgstr ""
+
+#: templates/authopenid/settings.html:37
+msgid "Delete account"
+msgstr ""
+
+#: templates/authopenid/settings.html:38
+msgid "Erase your username and all your data from website"
+msgstr ""
+
+#: templates/authopenid/signin.html:3 templates/authopenid/signin.html:16
+msgid "User login"
+msgstr ""
+
+#: templates/authopenid/signin.html:21
+msgid "we support two login modes"
+msgstr ""
+"You can log in either through one of these services or traditionally - using "
+"local username/password."
+
+#: templates/authopenid/signin.html:26 templates/authopenid/signup.html:49
+msgid "Login with your OpenID"
+msgstr ""
+
+#: templates/authopenid/signin.html:28
+msgid "select openid provider"
+msgstr "1) Please select your id service provider."
+
+#: templates/authopenid/signin.html:32
+msgid "verify openid link and login"
+msgstr ""
+"2) Please verify the OpenID URL (type your login name over {username}, if present)"
+" and then log in."
+
+#: templates/authopenid/signin.html:57
+msgid "Use login name and password"
+msgstr ""
+
+#: templates/authopenid/signin.html:58
+msgid "Login name"
+msgstr ""
+
+#: templates/authopenid/signin.html:62
+msgid "Create new acccount"
+msgstr ""
+
+#: templates/authopenid/signin.html:71
+msgid "Why use OpenID?"
+msgstr ""
+
+#: templates/authopenid/signin.html:75
+msgid "with openid it is easier"
+msgstr "With the OpenID you don't need to create new username and password."
+
+#: templates/authopenid/signin.html:78
+msgid "reuse openid"
+msgstr "You can safely re-use the same login for all OpenID-enabled websites."
+
+#: templates/authopenid/signin.html:81
+msgid "openid is widely adopted"
+msgstr ""
+"There are > 160,000,000 OpenID account in use. Over 10,000 sites are OpenID-"
+"enabled."
+
+#: templates/authopenid/signin.html:84
+msgid "openid is supported open standard"
+msgstr "OpenID is based on an open standard, supported by many organizations."
+
+#: templates/authopenid/signin.html:88
+msgid "Find out more"
+msgstr ""
+
+#: templates/authopenid/signin.html:89
+msgid "Get OpenID"
+msgstr ""
+
+#: templates/authopenid/signup.html:2 templates/authopenid/signup.html.py:6
+msgid "Signup"
+msgstr ""
+
+#: templates/authopenid/signup.html:10
+msgid ""
+"We support two types of user registration: conventional username/password, "
+"and"
+msgstr ""
+
+#: templates/authopenid/signup.html:10
+msgid "the OpenID method"
+msgstr ""
+
+#: templates/authopenid/signup.html:15
+msgid "Sorry, looks like we have some errors"
+msgstr ""
+
+#: templates/authopenid/signup.html:33
+msgid "Conventional registration"
+msgstr ""
+
+#: templates/authopenid/signup.html:34
+msgid "choose a user name"
+msgstr ""
+
+#: templates/authopenid/signup.html:40
+msgid "back to login"
+msgstr ""
+
+#: templates/authopenid/signup.html:46
+msgid "Register with your OpenID"
+msgstr ""
+
+#
+#: templates/backup/answer_edit_tips.html:3
+#: templates/backup/question_edit_tips.html:2
+msgid "editing tips"
+msgstr "Tips"
+
+#: templates/backup/questions.html:108
+msgid "number of questions"
+msgstr ""
+
+#: templates/backup/questions.html:110
+msgid "tagged with"
+msgstr ""
+
+#: templates/backup/questions.html:116
+msgid "whose title contains"
+msgstr ""
+
+#: templates/backup/questions.html:121
+msgid "number of questions end of sentence."
+msgstr ""
+
+#: templates/backup/questions.html:123
+msgid "Questions are sorted by <strong>entry date</strong>."
+msgstr ""
+
+#: templates/backup/questions.html:123
+msgid "Newest questions shown first."
+msgstr ""
+"Questions are sorted by <strong>entry date</strong>.Newest questions shown "
+"first."
+
+#: templates/backup/unanswered.html:98 templates/tough/unanswered.html:95
+msgid "number of <strong>unanswered</strong> questions"
+msgstr ""
+
+#: templates/backup/user_info.html:44
+msgid "member since"
+msgstr ""
+
+#: templates/backup/user_stats.html:36
+#, python-format
+msgid "the answer has been voted for %(answered_question.vote_count)s times"
+msgstr ""
+
+#: templates/tough/question_retag.html:2
+msgid "Revise tags"
+msgstr ""
+
+#: templates/tough/question_retag.html:37
+msgid "tags are requried"
+msgstr ""
+
+#: templates/tough/question_retag.html:38
+msgid "up to 5 tags, less than 20 characters each"
+msgstr ""
+
+#: templates/tough/question_retag.html:51
+msgid "Change tags"
+msgstr ""
+
+#: templates/tough/question_retag.html:74
+msgid "Change now"
+msgstr ""
+
+#: templates/tough/question_retag.html:85
+msgid "Why use and modify tags?"
+msgstr ""
+
+#: templates/tough/question_retag.html:96
+msgid "tag editors receive special awards from the community"
+msgstr ""
diff --git a/locale/zh-cn/LC_MESSAGES/django.mo b/locale/zh-cn/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..8b8f5bb9
--- /dev/null
+++ b/locale/zh-cn/LC_MESSAGES/django.mo
Binary files differ
diff --git a/locale/zh-cn/LC_MESSAGES/django.po b/locale/zh-cn/LC_MESSAGES/django.po
new file mode 100644
index 00000000..5cad5b51
--- /dev/null
+++ b/locale/zh-cn/LC_MESSAGES/django.po
@@ -0,0 +1,1430 @@
+#author Evgeny Fadeev (evgeny.fadeev@gmail.com)
+#site-specific messages
+msgid "site title"
+msgstr "CNProg.com"
+
+msgid "site slogan"
+msgstr "程序员问答社区"
+
+msgid "meta site keywords, comma separated"
+msgstr "技术问答社区,中国程序员,编程技术社区,程序员社区,程序员论坛,程序员wiki,程序员博客"
+
+msgid "meta site content"
+msgstr "中国程序员的编程技术问答社区。我们做专业的、可协作编辑的技术问答社区。"
+
+msgid "copyright message"
+msgstr "Copyright(c)2009.CNPROG.COM"
+
+msgid "Connect your OpenID with this site"
+msgstr "绑定OpenID"
+
+msgid "Connect your OpenID with your account on this site"
+msgstr "绑定OpenID帐号"
+
+msgid "Your OpenID is accepted. Please complete this to finish the registration. "
+msgstr "您的OpenID帐号已经验证通过! 请完成最后一步 - 绑定OpenID到您的帐号。"
+
+#minimal length of user name may be language specific
+msgid "username too short"
+msgstr "用户名太短,请使用三个或三个以上字符"
+
+#translation and user name validation are language-specific
+msgid "invalid user name"
+msgstr "用户名只能包含英文字母、数字和下划线"
+
+#chinese translation is domain specific
+msgid "question if off-topic or not relevant"
+msgstr "不是编程技术问题"
+
+msgid "Login name"
+msgstr "用户名"
+
+msgid "welcome to website!"
+msgstr "CNProg欢迎您!"
+
+msgid "what is this website"
+msgstr "CNProg是一个<strong>面向程序员</strong>的可协作编辑的<strong>开放源代码问答社区</strong>。"
+
+msgid "what can one do on this website"
+msgstr "您可以在这里提问各类<strong>程序技术问题</strong> - 问题不分语言和平台。 同时也希望您对力所能及的问题,给予您的宝贵答案。"
+
+msgid "Goal of this site is..."
+msgstr "CNProg 是为了帮助程序员解决更多问题,更加方便的解决问题。"
+
+msgid "how privacy policy can be changed"
+msgstr "我们可能在事先通知或不通知的情况下随时更改此'隐私政策',我们建议用户时常查看CNProg隐私政策的改动,在任何改动生效后您的继续访问和使用本站,我们假设您已同意了CNProg以上的所有条款。"
+
+msgid "general message about privacy"
+msgstr "CNProg承认用户隐私的重要性。本文件概述在您浏览CNProg过程中所接收和收集的个人信息的种类,以及CNProg所采取的保护信息的一些措施。CNProg希望这将有助于您在知情的情况下,就和我们 共享个人信息的问题作出决定。"
+
+msgid "cookie policy details"
+
+msgstr "访问CNProg时,我们会向您的计算机发送一个或多个专门用于识别您的浏览器的Cookie(包含一个字符串的小文件)。 "
+"使用 Cookie 的目的是通过储存用户偏好、跟踪用户倾向(例如搜索"
+"方法)来提高我们的服务质量。大多数浏览器的初始设置均为接受 Cookie,但也可以将其重置为拒绝所有 Cookie 或在收到 Cookie 时提示。不过,如果禁用 Cookie,某些功能和服务可能无法正常运行。"
+
+msgid "details on sharing data with third parties"
+msgstr "CNProg可能会收集和统计用户访问本站的概况数据。例如,CNProg可能会检测网站最流行的部分功能。CNProg可能会公开显示或者提供给第三方使用该数据。但是,CNProg不会公开您的身份信息。"
+
+msgid "details on personal information policies"
+msgstr "在登录使用CNProg的提问和回答功能时,我们要求使用者提供用户名、密码、电子邮件等信息。CNProg收集这类关于个人身份的信息只是为了登录系统获得使用功能的目的。我们不会向任何其他社区用 户、个人或第三方透露您的密码或者电子邮件信息。用户可以选择性地填写用户资料、个人网站、年龄、城市等信息,我们收集这些内容为了使用户能够更容易和更满意地使用CNProg提供的网页和服务。"
+
+msgid "Community gives you awards for your questions, answers and votes."
+msgstr "提出问题,给予回答,投出你的票 - CNProg 会针对你在社区的表现,授予你各类奖牌。"
+
+msgid "what technical information is collected about visitors"
+msgstr "当您访问本网站或使用我们的某些在线服务时,服务器会自动记录信息,包括但不限于URL、IP地址、浏览器的类型、屏幕分辨率、系统类型和使用的语言以及访问日期和时间。我们的目的是为了向您>提供更好的用户服务,包括可能为您提供定制的在线服务。"
+
+msgid "please make your answer relevant to this community"
+msgstr "您的问题与编程相关吗?"
+
+
+#templates/book.html 78
+msgid "reading channel"
+msgstr "读书频道"
+
+msgid "[author]"
+msgstr "【作者】"
+
+msgid "[publisher]"
+msgstr "【出版社】"
+
+msgid "[publication date]"
+msgstr "【出版日期】"
+
+msgid "[price]"
+msgstr "【价格】"
+
+msgid "[pages]"
+msgstr "【页数】"
+
+msgid "[tags]"
+msgstr "【标签】"
+
+msgid "author blog"
+msgstr "作者博客"
+
+msgid "book directory"
+msgstr "书籍目录"
+
+msgid "buy online"
+msgstr "网上购买"
+
+msgid "book technical Q&A"
+msgstr "图书相关的技术答疑"
+
+msgid "reader questions"
+msgstr "答读者问"
+
+msgid "ask the author"
+msgstr "向作者提问"
+
+msgid "this question was selected as favorite"
+msgstr "这个问题被"
+
+msgid "number of times"
+msgstr "位用户收藏"
+
+msgid "votes"
+msgstr "票"
+
+msgid "the answer has been accepted to be correct"
+msgstr "有答案已被接受为正确答案"
+
+msgid "views"
+msgstr "浏览"
+
+#must have extra space after in english
+msgid "see questions tagged"
+msgstr "查看有关"
+
+#book.html line 123 must be empty in english
+msgid "using tags"
+msgstr "的问题"
+
+#this is how above two are supposed to be
+msgid "see questions tagged '%s'"
+msgstr "查看有关'%s'的问题"
+
+msgid "subscribe to book RSS feed"
+msgstr "RSS订阅该图书最新问题"
+
+msgid "subscribe to the questions feed"
+msgstr "订阅最>新问题"
+
+#base_content.html
+msgid "congratulations, community gave you a badge"
+msgstr "恭喜您,社区给您颁发了奖牌"
+
+msgid "see"
+msgstr "查看"
+
+msgid "profile"
+msgstr "个人资料"
+
+#close.html
+msgid "Close question"
+msgstr "关闭问题"
+
+msgid "Close the question"
+msgstr "由于以下原因,你要关闭这个问题"
+
+msgid "Reasons"
+msgstr "原因"
+
+msgid "OK to close"
+msgstr "确定关闭"
+
+msgid "Cancel"
+msgstr "取消"
+
+#footer.html
+msgid "about"
+msgstr "关于本站"
+
+msgid "faq"
+msgstr "常见问题"
+
+msgid "blog"
+msgstr "Blog"
+
+msgid "contact us"
+msgstr "联系我们"
+
+msgid "privacy policy"
+msgstr "隐私政策"
+
+msgid "give feedback"
+msgstr "问题反馈"
+
+msgid "current revision"
+msgstr "当前版本"
+
+#index.html
+msgid "community wiki"
+msgstr "社区Wiki"
+
+msgid "Home"
+msgstr "首页"
+
+msgid "Questions"
+msgstr "问题列表"
+
+msgid "User questions"
+msgstr "个问题"
+
+msgid "newest"
+msgstr "最新问题"
+
+msgid "latest questions"
+msgstr "最新问题"
+
+msgid " - "
+msgstr "-"
+
+msgid "last updated questions"
+msgstr "最新更新的问题"
+
+msgid "hottest"
+msgstr "热门问题"
+
+msgid "hottest questions"
+msgstr "被回复最多的问题"
+
+msgid "most voted"
+msgstr "最有价值的问题"
+
+msgid "most voted questions"
+msgstr "投票次数最多的问题"
+
+msgid "all questions"
+msgstr "全部问题"
+
+msgid "answers"
+msgstr "回答"
+
+msgid "Answers"
+msgstr "个回答"
+
+msgid "number of votes"
+msgstr "票数"
+
+msgid "Recent tags"
+msgstr "最新标签"
+
+msgid "popular tags"
+msgstr "受欢迎的标签"
+
+msgid "Recent awards"
+msgstr "最新奖牌"
+
+msgid "given to"
+msgstr "授予"
+
+msgid "all awards"
+msgstr "所有奖牌"
+
+msgid "subscribe to last 30 questions by RSS"
+msgstr "RSS订阅最新30个问题"
+
+msgid "Still looking for more? See"
+msgstr "在寻找更多问题吗?请查阅"
+
+msgid "complete list of questions"
+msgstr "全部问题列表"
+
+msgid "or"
+msgstr "或者"
+
+msgid "."
+msgstr "。"
+
+msgid "Please help us answer"
+msgstr "请帮助我们回答"
+
+msgid "unanswered questions"
+msgstr "没有回答的问题"
+
+msgid "Unanswered questions"
+msgstr "没有回答的问题"
+
+#revisions_answer.html
+msgid "Revision history"
+msgstr "版本历史"
+
+msgid "back"
+msgstr "返回"
+
+msgid "revision"
+msgstr "版本"
+
+msgid "edit"
+msgstr "编辑"
+
+msgid "asked"
+msgstr "提问于"
+
+msgid "updated"
+msgstr "更新于"
+
+msgid "Tag list"
+msgstr "标签列表"
+
+msgid "by name"
+msgstr "按名称排序"
+
+msgid "sorted alphabetically"
+msgstr "按名称的字母先后顺序排序"
+
+msgid "by polularity"
+msgstr "按流行程度排序"
+
+msgid "sorted by frequency of tag use"
+msgstr "按标签被使用的次数排序"
+
+msgid "All tags matching query"
+msgstr "匹配查询"
+
+msgid "all tags - make this empty in english"
+msgstr "的所有标签"
+
+msgid "Nothing found."
+msgstr "没有找到相关数据。"
+
+#paginator.html
+msgid "previous"
+msgstr "上一页"
+
+msgid "current page"
+msgstr "当前页"
+
+msgid "next page"
+msgstr "下一页"
+
+msgid "page number "
+msgstr "第"
+
+msgid "number - make blank in english"
+msgstr "页"
+
+msgid "Change tags"
+msgstr "修改问题标签"
+
+msgid "tags are required"
+msgstr "标签不能为空。"
+
+msgid "please use 5 tags or less"
+msgstr "最多只能有5个标签"
+
+#todo: remove magic numbers from this file
+msgid "up to 5 tags, less than 20 characters each"
+msgstr "最多5个标签,每个标签长度小于20个字符。"
+
+msgid "Change now"
+msgstr "现在修改"
+
+#synonym of above in Edit question
+msgid "Save edit"
+msgstr "现在修改"
+
+msgid "uses tags for the classification of questions"
+msgstr "用标签来分类系统的信息"
+
+msgid "tag editors receive special awards from the community"
+msgstr "修改标签的用户将授予特殊的社区奖牌"
+
+msgid "Why use and modify tags?"
+msgstr "为什么我只能修改问题标签?"
+
+msgid "Found by tag"
+msgstr "标签问题"
+
+msgid "Found by title"
+msgstr "查询结果"
+
+msgid "All questions"
+msgstr "所有问题"
+
+msgid "active"
+msgstr "活跃问题"
+
+msgid "most recently asked questions"
+msgstr "最新加入系统的问题"
+
+msgid "most recently updated questions"
+msgstr "最近被更新的问题"
+
+msgid "latest questions info"
+msgstr ""
+"问题按<strong>提问时间</strong>显示排序。"
+"新加入的问题将显示在最前面。"
+
+#: templates/questions.html:109
+#, python-format
+msgid ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\thave total %(q_num)s questions tagged %(tagname)s\n"
+"\t\t\t"
+msgstr[0] ""
+"\n"
+"您正在浏览所有"
+"<div class=\"questions-count\">%(q_num)s</div>"
+"个标记为<span class=\"tag\">%(tagname)s</span></p>"
+msgstr[1] ""
+"\n"
+"您正在浏览所有"
+"<div class=\"questions-count\">%(q_num)s</div>"
+"个标记为<span class=\"tag\">%(tagname)s</span></p>"
+
+#: templates/questions.html:109
+#, python-format
+msgid ""
+"\n"
+"\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t"
+msgid_plural ""
+"\n"
+"\t\t\thave total %(q_num)s questions containing %(searchtitle)s\n"
+"\t\t\t"
+msgstr[0] ""
+"\n"
+"您正在浏览所有"
+"<div class=\"questions-count\">%(q_num)s</div>"
+"个标题含有<span class=\"tag\">%(searchtitle)s</span></p>"
+msgstr[1] ""
+"\n"
+"您正在浏览所有"
+"<div class=\"questions-count\">%(q_num)s</div>"
+"个标题含有<span class=\"tag\">%(searchtitle)s</span></p>"
+
+#in unanswered.html and somewhere else
+msgid "Have a total of"
+msgstr "您正在浏览所有"
+
+msgid "number of questions"
+msgstr "个"
+
+msgid "number of <strong>unanswered</strong> questions"
+msgstr "个 <span class=\"darkred\"><strong>没有回答的</strong></span> 问题。"
+
+msgid "tagged with"
+msgstr "标记为"
+
+msgid "whose title contains"
+msgstr "标题含有"
+
+msgid "number of questions end of sentence"
+msgstr "的问题。"
+
+msgid "Questions are sorted by the <strong>time of last update</strong>."
+msgstr "问题按<strong>最后更新时间</strong>显示排序。"
+
+msgid "Most recently answered ones are shown first."
+msgstr "最后被回答或者>更新的问题将显示在最前面。"
+
+msgid "Questions sorted by <strong>number of responses</strong>."
+msgstr "问题按<strong>回复数量</strong>显示排序。"
+
+msgid "Most answered questions are shown first"
+msgstr "回复最多的问题将显示在最前面。"
+
+msgid "Questions are sorted by the <strong>number of votes</strong>."
+msgstr "问题按<strong>投票数量</strong>显示排序。"
+
+msgid "Most voted questions are shown first"
+msgstr "投票最多的问题将显示在最前面。"
+
+msgid "Related tags"
+msgstr "相关标签"
+
+msgid "Reopen question"
+msgstr "重设问题"
+
+msgid "Open the previously closed question"
+msgstr "你将打开这个已经被关闭的问题"
+
+msgid "The question was closed for the following reason"
+msgstr "问题曾以"
+
+msgid "reason - leave blank in english"
+msgstr "的原因被"
+
+msgid "on"
+msgstr "于"
+
+msgid "date closed"
+msgstr "关闭"
+
+msgid "reopen this question"
+msgstr "确定打开这个问题"
+
+msgid "Edit user profile"
+msgstr "修改个人资料"
+
+msgid "edit profile"
+msgstr "修改资料"
+
+msgid "image associated with your email address"
+msgstr "和您的邮件地址是绑定的"
+
+msgid "avatar"
+msgstr "修改头像"
+
+msgid "Registered user"
+msgstr "注册用户"
+
+msgid "Update"
+msgstr "更新"
+
+msgid "User profile"
+msgstr "用户概览"
+
+msgid "overview"
+msgstr "概览"
+
+msgid "recent activity"
+msgstr "最近活动"
+
+msgid "comments and answers to others questions"
+msgstr "其他用户的回复和评论"
+
+msgid "responses"
+msgstr "回应"
+
+msgid "reputation history"
+msgstr "积分"
+
+msgid "reputation"
+msgstr "积分"
+
+msgid "graph of user reputation"
+msgstr "用户的社区积分历史"
+
+msgid "user vote record"
+msgstr "用户所有投票"
+
+msgid "casted votes"
+msgstr "投票"
+
+msgid "questions that user selected as his/her favorite"
+msgstr "用户收藏的问题"
+
+msgid "users favorite questions"
+msgstr "用户收藏的问题"
+
+msgid "favorites"
+msgstr "收藏"
+
+msgid "favorite-questions"
+msgstr "收藏"
+
+msgid "user preference settings"
+msgstr "用户参数的设置"
+
+msgid "settings"
+msgstr "设置"
+
+msgid "preferences"
+msgstr "设置"
+
+msgid "About"
+msgstr "关于本站"
+
+msgid "logout"
+msgstr "退出登录"
+
+msgid "Logout"
+msgstr "退出登录"
+
+msgid "As a registered user you can login with your OpenID, log out of the site or permanently remove your account."
+msgstr "您是系统的<strong class=\"darkred\">注册</strong>用户,可以随时使用OpenID帐号登录系统或者注销登录。"
+
+msgid "Logout now"
+msgstr "点击退出登录"
+
+msgid "Frequently Asked Questions "
+msgstr "常见问题"
+
+msgid "What kinds of questions can I ask here?"
+msgstr "我可以在这里提问什么样的问题?"
+
+msgid "What questions should I avoid asking?"
+msgstr "什么样的问题我不该在这里提问?"
+
+msgid "Most importanly - questions should be <strong>relevant</strong> to this community."
+msgstr "毫无疑问,首先必须是<span class=\"yellowbg\">技术编程问题!</span>"
+
+msgid "Before asking the question - please make sure to use search to see whether your question has alredy been answered."
+msgstr "提问之前,充分利用系统的自动查找、标签和搜索,看看是否已经有一样的问题并有了答案。"
+
+msgid "What should I avoid in my answers?"
+msgstr "什么样的回答是不受欢迎的?"
+
+msgid "Who moderates this community?"
+msgstr "谁是社区的管理员?"
+
+msgid "Please avoid asking questions that are not relevant to this community, too subjective and argumentative."
+msgstr "<span class=\"yellowbg\">与程序员或技术无关的,引起争吵或太过于主观性等违背社区宗旨的内容。</span>本站建立是为了帮助大众程序员解决实际技术问题,我们需要实际的问题!"
+
+msgid "is a Q&A site, not a discussion group. Therefore - please avoid having discussions in your answers, comment facility allows some space for brief discussions."
+msgstr "希望用户提供针对提问的技术回答,可以是进一步了解问题实质,给予参考方案,或完全解决问题的回答。我们希望通过问答的形式解决用户的实际问题。因此,<span class=\"yellowbg\">我们不>欢迎在回答中出现不是回答问题的内容,包括针对他人回答的讨论,和其他无意义的浪费网络资源行为</span>。CNProg建议您使用<span class=\"yellowbg\">评论</span>功能来讨论你的意见和想法。"
+
+msgid "The short answer is: <strong>you</strong>."
+msgstr "答案是:<span class=\"yellowbg\">每个用户。</span>"
+
+msgid "The reputation system allows users earn the authorization to perform a variety of moderation tasks."
+msgstr "通过积分运作,<span class=\"yellowbg\">每个用户都有权限创建标签,进行对所有问题、回答的投票、编辑、关闭等操作。</span>"
+
+msgid "This website is moderated by the users."
+msgstr "社区没有严格意义上的管理员身份"
+
+msgid "How does reputation system work?"
+msgstr "什么是社区积分?"
+
+msgid "Anyone can ask questions and give answers, points are not necessary for that."
+msgstr "对于正常使用社区进行提问、回答而言,积分不是必须的。"
+
+msgid "As we've said before, users help running this site. Point system helps select users who can administer this community."
+msgstr "我们一再声明,CNProg由你来运行和维护。如果你想帮助我们来运作CNProg,你需要一定的积分等级。"
+
+msgid "Reputation points roughly measure how community trusts you. These points are given to you directly by other members of the community."
+msgstr "<span class=\"yellowbg\">积分是一种用来粗略衡量社区对你有多信任的数据。</span>积分不是有谁来支付或直接给予你的,而是你通过获得其他用户的支持和信任“赚得”的。"
+
+msgid "For example, if you ask an interesting question or give a helpful answer, your input will be upvoted and you will gain more trust in the community."
+msgstr "举例来说,如果你提了一个非常有帮助的问题或者做了很有用的回答,你将会被其他用户投赞成票。"
+
+msgid "If on the other hand someone gives a misleading answer, the answer will be voted down and he/she loses some points."
+msgstr "相反,你提了不受欢迎的问题,或者误导用户的回答,你将可能被其他用户投反对票。每个赞成"
+
+msgid "Each vote in favor will generate <strong>10</strong> points, each vote against will subtract <strong>2</strong> points."
+msgstr "票会帮你产生<strong>10</strong>个社区积分,每个反对票会相应扣除你<strong>2</strong>个积分。"
+
+msgid "Through the votes of other people you can accumulate a maximum of <strong>200</strong> points."
+msgstr "每天通过别人投赞成票,你最多只能产生<strong>200</strong>个积分,这是上限。"
+
+msgid "After accumulating certain number of points, you can do more:"
+msgstr "当你累计到一定>积分,你可以在社区做更多的事情:"
+
+msgid "upvote"
+msgstr "投赞成票"
+
+msgid "use tags"
+msgstr "标记垃圾帖"
+
+msgid "add comment"
+msgstr "添加评论"
+
+#todo - check if it's indeed plural
+msgid "add comments"
+msgstr "添加评论"
+
+msgid "comments"
+msgstr "评论"
+
+msgid "downvote"
+msgstr "投反对票"
+
+msgid "retag questions"
+msgstr "给任何问题整理标签"
+
+msgid "edit community wiki questions"
+msgstr "编辑wiki类问题"
+
+msgid "edit any answer"
+msgstr "编辑任何问题或答案"
+
+msgid "reopen any closed questions"
+msgstr "打开关闭任何人的问题"
+
+msgid "delete any comment"
+msgstr "删除任何一个评论"
+
+msgid "delete any questions and answers and perform other moderation tasks"
+msgstr "删除任何一个问题或答案,及其他管理功能"
+
+msgid "To register, do I need to create new password?"
+msgstr "我需要注册一个新用户吗?"
+
+msgid "Why other people can edit my questions/answers?"
+msgstr "为什么其他人可以修改我的问题/回答?"
+
+msgid "Still have questions?"
+msgstr "还有其他问题?"
+
+msgid "Please ask your question, help make our community better!"
+msgstr "如果您对社区还有其他疑问,请一起来完善我们的"
+
+msgid "No, you don't have to. You can login through any service that supports OpenID, e.g. Google, Yahoo, AOL, etc."
+msgstr "不需要。社区提供了OpenID的登录支持,你要用Google、Yahoo等任何支持OpenID登录的帐号就可以使用系统。"
+
+msgid "Login now!"
+msgstr "马上登录"
+
+msgid "Login"
+msgstr "登录"
+
+msgid "So questions and answers can be edited like wiki pages by experienced users of this site and this improves the overall quality of the knowledge base content."
+msgstr "所以问题和答案都是如Wiki一样可编辑的,我们希望社区能帮助用户沉淀、积累更多有用的知识和经验。"
+
+msgid "If this approach is not for you, we respect your choice."
+msgstr "如果您不喜欢这种方式,我们尊重你的选择。"
+
+msgid "Privacy policy"
+msgstr "隐私政策"
+
+msgid "Site Visitors"
+msgstr "网站访问者"
+
+msgid "Personal Information"
+msgstr "个人身份信息"
+
+msgid "Other Services"
+msgstr "其他服务"
+
+msgid "Policy Changes"
+msgstr "政策更改"
+
+msgid "Sorry, could not find the page you requested."
+msgstr "对不起,没有找到您请求的页面!"
+
+msgid "This might have happened for the following reasons:"
+msgstr "有可能是以下原因导致:"
+
+msgid "this question or answer has been deleted;"
+msgstr "你正在查看的问题或者回答已经被删除;"
+
+msgid "url has error - please check it;"
+msgstr "请求的地址有误 - 请核实原始URL地址;"
+
+msgid "the page you tried to visit is protected or you don't have sufficient points, see"
+msgstr "访问的页面被保护或你的积分不够,参见"
+
+msgid "if you believe this error 404 should not have occured, please"
+msgstr "如果你确信不该出现404错误,请"
+
+msgid "report this problem"
+msgstr "报告这个问题"
+
+msgid "back to previous page"
+msgstr "返回前页"
+
+msgid "see all questions"
+msgstr "查看最新问题"
+
+msgid "see all tags"
+msgstr "查看标签列表"
+
+msgid "Edit answer"
+msgstr "修改回答"
+
+msgid "hide preview"
+msgstr "禁用预览"
+
+msgid "show preview"
+msgstr "启用预览"
+
+msgid "select revision"
+msgstr "选择版本"
+
+msgid "Toggle the real time Markdown editor preview"
+msgstr "打开或者关闭Markdown编辑器的实时预览"
+
+msgid "toggle preview"
+msgstr "预览开关"
+
+msgid "question tips"
+msgstr "受欢迎的提问"
+
+msgid "answer tips"
+msgstr "受欢迎的提问"
+
+msgid "try to give an answer, rather than engage into a discussion"
+msgstr "建议您提的问题是可以被答复的,而不仅仅是可以讨论。"
+
+msgid "please try to provide details"
+msgstr "请详细描述您的问题。"
+
+msgid "be clear and concise"
+msgstr "我们推荐您使用中文描述问题,这样可以得到更多的答复机会。"
+
+msgid "see frequently asked questions"
+msgstr "查看常见问题"
+
+msgid "learn more about Markdown"
+msgstr "有关Markdown详细说明"
+
+msgid "basic HTML tags are also supported"
+msgstr "基本的HTML标签也是支持的"
+
+msgid "numbered list:"
+msgstr "列表:"
+
+msgid "image"
+msgstr "图片"
+
+msgid "text"
+msgstr "文本"
+
+msgid "title"
+msgstr "标题"
+
+msgid "Markdown tips"
+msgstr "Markdown快速参考"
+
+msgid "*italic* or _italic_"
+msgstr "*斜体* 或者 _斜体_"
+
+msgid "**bold** or __bold__"
+msgstr "**加粗** 或者 __加粗__ "
+
+msgid "link"
+msgstr "链接"
+
+msgid "Badge"
+msgstr "奖牌"
+
+msgid "The users have been awarded with badges:"
+msgstr "用户已被授予该奖牌:"
+
+msgid "posts per page"
+msgstr "每页显示"
+
+msgid "i like this post (click again to cancel)"
+msgstr "这篇帖子有价值(再次点击取消操作)"
+
+msgid "i like this answer (click again to cancel)"
+msgstr "这篇帖子有价值(再次点击取消操作)"
+
+msgid "current number of votes"
+msgstr "当前总票数"
+
+msgid "i dont like this post (click again to cancel)"
+msgstr "这篇帖子没有价值(再次点击取消操作)"
+
+msgid "i dont like this answer (click again to cancel)"
+msgstr "这篇帖子没有价值(再次点击取消操作)"
+
+msgid "mark this question as favorite (click again to cancel)"
+msgstr "我要收藏这个问题(再次点击取消操作)"
+
+msgid "remove favorite mark from this question (click again to restore mark)"
+msgstr "我要收藏这个问题(再次点击取消操作)"
+
+msgid "delete"
+msgstr "删除"
+
+#todo please check this in chinese
+msgid "undelete"
+msgstr "取消"
+
+#: templates/unanswered.html:98
+#, python-format
+msgid "have %(num_q)s unanswered questions"
+msgstr ""
+"您正在浏览所有<br>"
+"<div class=\"questions-count\">%(num_q)s</div>"
+"个<p>问题按 <strong>问题创建时间</strong> 排序。最新加入的问题将显示在最前面。</p>"
+
+msgid "Change password"
+msgstr "修改密码"
+
+msgid "Change email"
+msgstr "更换电子邮件"
+
+msgid "Change OpenID"
+msgstr "更换OpenID地址"
+
+msgid "Delete account"
+msgstr "删除帐号"
+
+msgid "User login"
+msgstr "用户登录"
+
+msgid "we support two login modes"
+msgstr "CNProg支持<b>两种</b>登录模式。您可以使用帐号、密码登录,或者使用OpenID登录。"
+
+msgid "Login with your OpenID"
+msgstr "使用OpenID登录"
+
+msgid "select openid provider"
+msgstr "1)请选择您的帐号类:"
+
+msgid "verify openid link and login"
+msgstr "2)型并完成正确的OpenID地址(如:替换“{username}”为您的对应帐号):"
+
+msgid "reopen"
+msgstr "打开"
+
+msgid "close"
+msgstr "关闭"
+
+msgid "report as offensive (i.e containing spam, advertising, malicious text, etc.)"
+msgstr "检举该帖为垃“水帖”(含广告、人身攻击、恶意言论等)"
+
+msgid "flag offensive"
+msgstr "垃圾帖?"
+
+msgid "login"
+msgstr "登录"
+
+msgid "back to home page"
+msgstr "回到首页"
+
+msgid "questions"
+msgstr "问题"
+
+msgid "tags"
+msgstr "标签"
+
+msgid "users"
+msgstr "用户"
+
+msgid "books"
+msgstr "读书"
+
+msgid "badges"
+msgstr "奖牌榜"
+
+msgid "my profile"
+msgstr "我的资料"
+
+msgid "ask a question"
+msgstr "我要提问"
+
+msgid "Ask a question"
+msgstr "我要提问"
+
+msgid "search"
+msgstr "搜索"
+
+msgid "update profile"
+msgstr "更新我的资料"
+
+msgid "real name"
+msgstr "姓名"
+
+msgid "member for"
+msgstr "已加入"
+
+msgid "ago"
+msgstr "前"
+
+msgid "last seen"
+msgstr "上次活动时间"
+
+msgid "user website"
+msgstr "个人网站"
+
+msgid "location"
+msgstr "城市"
+
+#user_info.html
+msgid "age"
+msgstr "年龄"
+
+msgid "age unit"
+msgstr "岁"
+
+msgid "todays unused votes"
+msgstr "今日剩余投票数"
+
+msgid "votes left"
+msgstr "票"
+
+msgid "this answer has been selected as correct"
+msgstr "该回答已被设为最佳答案"
+
+msgid "the answer has been voted for %d times"
+msgstr "该回答总共有%d个投票"
+
+msgid "the answer has been commented %d times"
+msgstr "该回答有%d条评论"
+
+msgid "votes total"
+msgstr "个投票"
+
+msgid "user has voted up this many times"
+msgstr "该用户投的赞成票总数"
+
+msgid "user has voted down this many times"
+msgstr "用户投的反对票总数"
+
+msgid "Tags"
+msgstr "个标签"
+
+msgid "see other questions tagged '%s'"
+msgstr "查看有关'%s'的问题"
+
+msgid "Badges"
+msgstr "枚奖牌"
+
+msgid "Badge summary"
+msgstr "奖牌列表"
+
+msgid "Users"
+msgstr "用户列表"
+
+msgid "recent"
+msgstr "最新加入"
+
+msgid "oldest"
+msgstr "最先加入"
+
+msgid "by username"
+msgstr "用户名"
+
+msgid "user name"
+msgstr "用户名"
+
+msgid "users matching query %s:"
+msgstr "匹配查询 '<span class=\"darkred\"><strong>%s</strong></span>' 的所有用户名:"
+
+msgid "Below is the list of available badges and number of times each type of badge has been awarded."
+msgstr "这里列出社区所有的奖牌,以及到目前为此,每个奖牌被授予的用户人数。"
+
+msgid "Community badges"
+msgstr "社区奖牌"
+
+msgid "gold badge: the highest honor and is very rare"
+msgstr "金牌:十分罕见之最高荣耀"
+
+msgid "silver badge: occasionally awarded for the very high quality contributions"
+msgstr "银牌:偶尔颁发之优质奖章"
+
+msgid "gold"
+msgstr "金牌"
+
+msgid "Gold badge is very rare."
+msgstr "金牌是十分罕见的。"
+
+msgid "To obtain it you have to show profound knowledge and ability in addition to actively participating in the community."
+msgstr "你不仅要参与社区的提问、回答、投票等活动,而且需要有高深的知识和能力才能获得。"
+
+msgid "Gold badge is the highest award in this community."
+msgstr "获得金牌意味着你在某个层次上已经达到了顶峰。"
+
+msgid "silver"
+msgstr "银牌"
+
+msgid "Obtaining silver badge requires significant patience."
+msgstr "银牌需要经过长时间的奋斗才能获得。"
+
+msgid "If you got one, you have very significantly contributed to this community"
+msgstr "它是不同寻常的荣誉,只要你付出足够的努力就会得到。"
+
+msgid "bronze badge: often given as a special honor"
+msgstr "铜牌:时常授予之特殊荣誉"
+
+msgid "If you are active in this community, you will will get this medal - still it is a special honor."
+msgstr "铜牌会在你活跃于社区时产生,它相对容易获得,但也是一种特殊的荣誉。"
+
+msgid "Use"
+msgstr "使用"
+
+msgid "learn more about OpenID"
+msgstr "了解更多有关OpenID的信息"
+
+msgid "Get your own"
+msgstr "获取您自己的"
+
+msgid "User name"
+msgstr "您的大名"
+
+msgid "Email: (won't be shown to anyone)"
+msgstr "电子邮件:(不会公开显示)"
+
+msgid "Ask your question"
+msgstr "现在提问"
+
+#page title
+msgid "Edit question"
+msgstr "修改问题"
+
+msgid "The question has been closed for the following reason \"%s\" by"
+msgstr "问题以“%s”的原因已被"
+
+msgid "%s ago"
+msgstr "于%s<font class=\"darkred\">关闭</font>"
+
+msgid "oldest answers"
+msgstr "最先回答"
+
+msgid "newest answers"
+msgstr "最近回答"
+
+msgid "popular answers"
+msgstr "投票最多"
+
+msgid "oldest answers will be shown first"
+msgstr "最先回答显示在最前面"
+
+msgid "newest answers will be shown first"
+msgstr "最晚回答显示在最前面"
+
+msgid "most voted answers will be shown first"
+msgstr "投票次数最多的显示在最前面"
+
+msgid "mark this answer as favorite (click again to undo)"
+msgstr "最佳答案(再次点击取消操作)"
+
+msgid "the author of the question has selected this answer as correct"
+msgstr "这个答案已经被提问作者标记为最佳答案"
+
+msgid "answer permanent link"
+msgstr "该回答的链接地址"
+
+msgid "permanent link"
+msgstr "永久链接"
+
+msgid "Your answer"
+msgstr "您的回答"
+
+msgid "Answer the question"
+msgstr "回答该问题"
+
+msgid "Login to answer"
+msgstr "登录并回答该问题"
+
+msgid "Question tags"
+msgstr "您正在浏览的问题含有以下标签"
+
+msgid "Question asked"
+msgstr "提问时间"
+
+msgid "question was seen"
+msgstr "目前浏览数量"
+
+msgid "times"
+msgstr "次"
+
+msgid "last updated"
+msgstr "最后更新时间"
+
+msgid "related questions"
+msgstr "相似的问题"
+
+msgid "profile - responses"
+msgstr "回应 - 用户资料"
+
+msgid "user reputation in the community"
+msgstr "用户社区积分"
+
+msgid "profile - user reputation"
+msgstr "积分 - 用户资料"
+
+msgid "profile - favorite questions"
+msgstr "收藏 - 用户资料"
+
+msgid "profile - votes"
+msgstr "投票 - 用户资料"
+
+msgid "profile - user preferences"
+msgstr "设置 - 用户资料"
+
+msgid "uploading images is limited to users with >60 reputation points"
+msgstr "上传图片只限于积分+60以上注册用户!"
+
+#todo take these out of settings
+msgid "allowed file types are 'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'"
+msgstr "只允许上传'jpg', 'jpeg', 'gif', 'bmp', 'png', 'tiff'类型的文件!"
+
+msgid "maximum upload file size is %sK"
+msgstr "只允许上传%sK大小的文件!"
+
+msgid "Error uploading file. Please contact the site administrator. Thank you."
+msgstr "在文件上传过程中产生了错误,请联系管理员,谢谢^_^"
+
+msgid "please enter a descriptive title for your question"
+msgstr "请输入对问题具有描述性质的标题 - “帮忙!紧急求助!”不是建议的提问方式。"
+
+msgid "title must be > 10 characters"
+msgstr "标题的长度必须大于10"
+
+msgid "content"
+msgstr "内容"
+
+msgid "question content must be > 10 characters"
+msgstr "内容至少要10个字符"
+
+msgid "please use space to separate tags (this enables autocomplete feature)"
+msgstr "多个标签请用空格间隔-最多5个标签。(优先使用自动匹配的英文标签。)"
+
+msgid "tags must be shorter than 20 characters"
+msgstr "每个标签的长度不超过20"
+
+msgid "please use following characters in tags: letters 'a-z', numbers, and characters '.-_#'"
+msgstr "标签请使用英文字母,中文或者数字字符串(. - _ # 也可以)"
+
+msgid "if you choose community wiki option, the question and answer do not generate points and name of author will not be shown"
+msgstr "选择社区wiki模式,问答不计算积分,签名也不显示作者信息"
+
+msgid "update summary:"
+msgstr "更新概要:"
+
+msgid "enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)"
+msgstr "输入本次修改的简单概述(如:修改了别字,修正了语法,改进了样式等。非必填项。)"
+
+msgid "this email does not have to be linked to gravatar"
+msgstr "不会公开,用于头像显示服务"
+
+msgid "Real name"
+msgstr "真实姓名"
+
+msgid "Website"
+msgstr "个人网站"
+
+msgid "Location"
+msgstr "城市"
+
+msgid "Date of birth"
+msgstr "生日"
+
+msgid "will not be shown, used to calculate age, format: YYYY-MM-DD"
+msgstr "不会公开,只会显示您的年龄,格式为:YYYY-MM-DD"
+
+msgid "Profile"
+msgstr "个人简介"
+
+msgid "this email has already been registered, please use another one"
+msgstr "该电子邮件已被注册,请选择另一个再试。"
+
+msgid "duplicate question"
+msgstr "完全重复的问题"
+
+msgid "too subjective and argumentative"
+msgstr "太主观性、引起争吵的问题"
+
+msgid "is not an answer to the question"
+msgstr "不是一个可以回答的“问题”"
+
+msgid "the question is answered, right answer was accepted"
+msgstr "问题已经解决,已得到正确答案"
+
+msgid "problem is not reproducible or outdated"
+msgstr "已经过时、不可重现的问题"
+
+msgid "question contains offensive inappropriate, or malicious remarks"
+msgstr "恶意言论"
+
+msgid "spam or advertising"
+msgstr "垃圾广告"
+
+msgid "question"
+msgstr "提问"
+
+msgid "answer"
+msgstr "回答"
+
+msgid "commented question"
+msgstr "评论问题"
+
+msgid "edited question"
+msgstr "修改问题"
+
+msgid "edited answer"
+msgstr "修改回答"
+
+msgid "received award"
+msgstr "获奖"
+
+msgid "marked best answer"
+msgstr "标记最佳答案"
+
+msgid "upvoted"
+msgstr "投赞成票"
+
+msgid "downvoted"
+msgstr "投反对票"
+
+msgid "canceled vote"
+msgstr "撤销投票"
+
+msgid "deleted question"
+msgstr "删除问题"
+
+msgid "deleted answer"
+msgstr "删除回答"
+
+msgid "marked offensive"
+msgstr "标记垃圾帖"
+
+msgid "updated tags"
+msgstr "更新标签"
+
+msgid "selected favorite"
+msgstr "收藏"
+
+msgid "completed user profile"
+msgstr "完成个人所有资料"
+
+msgid "[closed]"
+msgstr "[已关闭]"
+
+msgid "[deleted]"
+msgstr "[已删除]"
+
+msgid "initial version"
+msgstr "初始版本"
+
+msgid "retagged"
+msgstr "更新了标签"
+
+#todo: review this message may be confusing user
+msgid "This account already exists, please use another."
+msgstr "输入您的新帐号或者指定已经存在的帐号。"
+
+msgid "Sorry, looks like we have some errors:"
+msgstr "请注意以下错误:"
+
+msgid "New account"
+msgstr "新帐号"
+
+msgid "Use login name and password"
+msgstr "使用帐号密码登录"
+
+msgid "User name (<i>will be shown to others, cannot be modified</i>)"
+msgstr "用户名(<i>在社区显示友好名称,不可更改</i>)"
+
+msgid "Email (<i>not shared with anyone</i>)"
+msgstr "电子邮件(<i>用于头像显示服务</i>)"
+
+msgid "create account"
+msgstr "创建帐号"
+
+msgid "Existing account"
+msgstr "已经存在的用户"
+
+msgid "password"
+msgstr "密码"
+
+msgid "Password"
+msgstr "密码"
+
+msgid "Register"
+msgstr "确认"
+
+msgid "Forgot your password?"
+msgstr "忘记密码?"
+
+msgid "Create new account"
+msgstr "注册新帐号"
+
+msgid "Send new password"
+msgstr "发送新密码"
+
+msgid "Lost your password? No problem - here you can reset it."
+msgstr "丢失了您的密码? 你可以在这里重设密码。"
+
+msgid "Please enter your username below and new password will be sent to your registered e-mail"
+msgstr "请输入用户名,新的密码会发送到你注册时候填写的电子邮件。"
+
+msgid "Reset password"
+msgstr "重设密码"
+
+msgid "return to login"
+msgstr "返回登录"
+
+msgid "back to login"
+msgstr "返回登录"
+
+#todo - check translation or see if it's indeed true
+msgid "Note: your new password will be activated only after you click the activation link in the email message"
+msgstr "注意: 新的密码只有您在激活邮件中的链接后才会被激活。"
+
+msgid "Signup"
+msgstr "注册帐号"
+
+msgid "We support two types of user registration: conventional username/password, and"
+msgstr "我们支持两种注册方式,你可以使用常规的用户名、密码方式注册,或者"
+
+msgid "the OpenID method"
+msgstr "使用OpenID帐号注册"
+
+msgid "Conventional registration"
+msgstr "请注意以下错误:"
+
+msgid "choose a user name"
+msgstr "选择一个用户名"
+
+msgid "your email address"
+msgstr "您的电子邮件地址"
+
+msgid "choose password"
+msgstr "密码"
+
+msgid "retype password"
+msgstr "确认密码"
+
+msgid "Register with your OpenID"
+msgstr "使用OpenID注册"
+
+msgid "sorry, this name can not be used, please try another"
+msgstr "对不起,您不能注册该用户名,请换一个试试"
+
+msgid "this name is already in use - please try anoter"
+msgstr "该用户名已被注册,请换一个试试"
+
+msgid "Why use OpenID?"
+msgstr "为什么需要OpenID登录?"
+
+msgid "with openid it is easier"
+msgstr "构建在OpenID网络认证上的本系统,不需要你注册新的帐号,即可使用我们系统的所有功能"
+
+msgid "openid is supported open standard"
+msgstr "OpenID是有开放标准,并且有相关的基金组织提供支持"
+
+msgid "Find out more"
+msgstr "查看更多"
+
+msgid "Get OpenID"
+msgstr "获取OpenID"
+
+msgid "openid is widely adopted"
+msgstr "全世界有1.6亿OpenID帐号,和10,000个支持OpenID的站点"
+
+msgid "reuse openid"
+msgstr "用同一个帐号可登录互联网所有激活OpenID的网站"
diff --git a/log/cnprog.log b/log/cnprog.log
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/log/cnprog.log
diff --git a/manage.py b/manage.py
new file mode 100644
index 00000000..b8c4be8e
--- /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(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/settings.py b/settings.py
new file mode 100644
index 00000000..7ad8524a
--- /dev/null
+++ b/settings.py
@@ -0,0 +1,146 @@
+# Django settings for lanai project.
+import os.path
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+SITE_SRC_ROOT = '/var/www/vhosts/cnprog'
+#David Cramer debug toolbar
+INTERNAL_IPS = ('127.0.0.1',)
+DEBUG_TOOLBAR_PANELS = (
+ 'debug_toolbar.panels.sql.SQLDebugPanel',
+ 'debug_toolbar.panels.headers.HeaderDebugPanel',
+ 'debug_toolbar.panels.cache.CacheDebugPanel',
+ 'debug_toolbar.panels.profiler.ProfilerDebugPanel',
+ 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
+ 'debug_toolbar.panels.templates.TemplatesDebugPanel',
+ # If you are using the profiler panel you don't need the timer
+ # 'debug_toolbar.panels.timer.TimerDebugPanel',
+)
+
+DEBUG_TOOLBAR_CONFIG = {
+ "INTERCEPT_REDIRECTS":False
+}
+
+#for logging
+import logging
+LOG_FILENAME = '/var/www/vhosts/cnprog/log/cnprog.log'
+logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)
+
+#for OpenID auth
+ugettext = lambda s: s
+LOGIN_URL = '/%s%s' % (ugettext('account/'), ugettext('signin/'))
+
+#system will send admins email about error stacktrace if DEBUG=False
+ADMINS = (
+ ('you', 'you@where.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+#DATABASE_NAME = 'cnprog' # Or path to database file if using sqlite3.
+#DATABASE_USER = 'root' # Not used with sqlite3.
+#DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+DATABASE_NAME = 'dbname' # Or path to database file if using sqlite3.
+DATABASE_USER = 'dbuser' # Not used with sqlite3.
+DATABASE_PASSWORD = 'dbpass' # Not used with sqlite3.
+
+SERVER_EMAIL = 'server@where.com'
+DEFAULT_FROM_EMAIL = 'from@where.com'
+EMAIL_HOST_USER = ''
+EMAIL_HOST_PASSWORD = ''
+EMAIL_SUBJECT_PREFIX = '[cnprog.com]'
+EMAIL_HOST='smtp.gmail.com'
+EMAIL_PORT='587'
+EMAIL_USE_TLS=True
+
+
+
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+#LANGUAGE_CODE = 'en'
+LANGUAGE_CODE = 'zh-cn'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = '/var/www/vhosts/cnprog/templates/upfiles/'
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = 'http://where.com/upfiles/'
+
+# 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/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '$oo^&_m&qwbib=ffljk_4m_n*zn-d=g#s0he5fx9xonnym#8p6yigm'
+
+# 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.middleware.gzip.GZipMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.middleware.transaction.TransactionMiddleware',
+ 'debug_toolbar.middleware.DebugToolbarMiddleware',
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ 'django.core.context_processors.request',
+ 'django.core.context_processors.auth',
+)
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_DIRS = (
+ os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
+)
+
+FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\\','/')
+FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.MemoryFileUploadHandler",
+ "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
+DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
+# for user upload
+ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff')
+# unit byte
+ALLOW_MAX_FILE_SIZE = 1024 * 1024
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.admin',
+ 'django.contrib.humanize',
+ 'forum',
+ 'django_authopenid',
+ 'debug_toolbar' ,
+)
diff --git a/sql_scripts/cnprog.xml b/sql_scripts/cnprog.xml
new file mode 100644
index 00000000..95f9b362
--- /dev/null
+++ b/sql_scripts/cnprog.xml
@@ -0,0 +1,1498 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Db flnm="cnprog" nm="cnprog" ver="1.3.4">
+<VerLbl></VerLbl>
+<VerNotes></VerNotes>
+<DefTblOpts></DefTblOpts>
+<DocFolder>/Users/sailing/Development/cnprog_beta2/sql_scripts</DocFolder>
+<Sch Cm="" nm="schemaA">
+<Tbl UsSo="1" nm="activity">
+<TblOpts>ENGINE=MyISAM AUTO_INCREMENT=103 DEFAULT CHARSET=latin1</TblOpts>
+<Pk ClNs="id" nm="pkactivity"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="activity_type" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="active_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="content_type_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="object_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="0" nm="is_auditted" nu="1">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_activity_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="activity" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="activity_content_type_id" unq="0">
+<ClNs>content_type_id</ClNs>
+</Idx>
+<Idx clu="0" nm="activity_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="answer">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkanswer"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="question_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="author_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="added_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="wiki" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="wikified_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="accepted" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="deleted" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="deleted_by_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="locked" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="locked_by_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="locked_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="score" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="comment_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="offensive_flag_count" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_edited_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_edited_by_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="html" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="vote_up_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="vote_down_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="accepted_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="deleted_by_id_refs_id_192b0170" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="1"/>
+<CdTb mn="1" nm="answer" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="deleted_by_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_answer_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="answer" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="author_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_answer_question" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="question" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="answer" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="question_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="last_edited_by_id_refs_id_192b0170" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="1"/>
+<CdTb mn="1" nm="answer" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="last_edited_by_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="locked_by_id_refs_id_192b0170" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="1"/>
+<CdTb mn="1" nm="answer" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="locked_by_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="answer_author_id" unq="0">
+<ClNs>author_id</ClNs>
+</Idx>
+<Idx clu="0" nm="answer_deleted_by_id" unq="0">
+<ClNs>deleted_by_id</ClNs>
+</Idx>
+<Idx clu="0" nm="answer_last_edited_by_id" unq="0">
+<ClNs>last_edited_by_id</ClNs>
+</Idx>
+<Idx clu="0" nm="answer_locked_by_id" unq="0">
+<ClNs>locked_by_id</ClNs>
+</Idx>
+<Idx clu="0" nm="answer_question_id" unq="0">
+<ClNs>question_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="answer_revision">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci</TblOpts>
+<Pk ClNs="id" nm="pkanswer_revision"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="answer_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="revision" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="author_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="revised_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="summary" nu="0">
+<DT ds="VarChar" en="" id="12" ln="300" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="text" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_answer_revision_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="answer_revision" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="author_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="answer_revision_answer_id" unq="0">
+<ClNs>answer_id</ClNs>
+</Idx>
+<Idx clu="0" nm="answer_revision_author_id" unq="0">
+<ClNs>author_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="auth_group">
+<TblOpts>ENGINE=InnoDB DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkauth_group"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="80" sc="null" sg="1"/>
+</Cl>
+<Idx clu="0" nm="name" unq="1">
+<ClNs>name</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="auth_group_permissions">
+<TblOpts>ENGINE=InnoDB DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkauth_group_permissions"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="group_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="permission_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_auth_group_permissions_auth_group" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_group" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="auth_group_permissions" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="group_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_auth_group_permissions_auth_permission" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_permission" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="auth_group_permissions" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="permission_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="group_id" unq="1">
+<ClNs>group_id, permission_id</ClNs>
+</Idx>
+<Idx clu="0" nm="permission_id_refs_id_5886d21f" unq="0">
+<ClNs>permission_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="auth_message">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkauth_message"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="message" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_auth_message_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="auth_message" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="auth_message_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="auth_permission">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkauth_permission"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="50" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="content_type_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="codename" nu="0">
+<DT ds="VarChar" en="" id="12" ln="100" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_auth_permission_django_content_type" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="django_content_type" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="auth_permission" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="content_type_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="auth_permission_content_type_id" unq="0">
+<ClNs>content_type_id</ClNs>
+</Idx>
+<Idx clu="0" nm="content_type_id" unq="1">
+<ClNs>content_type_id, codename</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="auth_user">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkauth_user"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="username" nu="0">
+<DT ds="VarChar" en="" id="12" ln="30" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="first_name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="30" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="30" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="email" nu="0">
+<DT ds="VarChar" en="" id="12" ln="75" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="password" nu="0">
+<DT ds="VarChar" en="" id="12" ln="128" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="is_staff" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="is_active" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="is_superuser" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_login" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="date_joined" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="0" nm="gold" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="0" nm="silver" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="0" nm="bronze" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="1" nm="reputation" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="gravatar" nu="1">
+<DT ds="VarChar" en="" id="12" ln="128" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="10" nm="questions_per_page" nu="1">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="last_seen" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="real_name" nu="1">
+<DT ds="VarChar" en="" id="12" ln="100" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="website" nu="1">
+<DT ds="VarChar" en="" id="12" ln="200" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="location" nu="1">
+<DT ds="VarChar" en="" id="12" ln="100" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="date_of_birth" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="about" nu="1">
+<DT ds="Text" en="" id="703" ln="null" sc="null" sg="1"/>
+</Cl>
+<Idx clu="0" nm="username" unq="1">
+<ClNs>username</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="auth_user_groups">
+<TblOpts>ENGINE=InnoDB DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkauth_user_groups"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="group_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_auth_user_groups_auth_group" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_group" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="auth_user_groups" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="group_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_auth_user_groups_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="auth_user_groups" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="group_id_refs_id_f116770" unq="0">
+<ClNs>group_id</ClNs>
+</Idx>
+<Idx clu="0" nm="user_id" unq="1">
+<ClNs>user_id, group_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="auth_user_user_permissions">
+<TblOpts>ENGINE=InnoDB DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkauth_user_user_permissions"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="permission_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_auth_user_user_permissions_auth_permission" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_permission" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="auth_user_user_permissions" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="permission_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_auth_user_user_permissions_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="auth_user_user_permissions" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="permission_id_refs_id_67e79cb" unq="0">
+<ClNs>permission_id</ClNs>
+</Idx>
+<Idx clu="0" nm="user_id" unq="1">
+<ClNs>user_id, permission_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="award">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkaward"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="badge_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="awarded_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="notified" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="content_type_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="object_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_award_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="award" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_award_badge" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="badge" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="award" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="badge_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="award_badge_id" unq="0">
+<ClNs>badge_id</ClNs>
+</Idx>
+<Idx clu="0" nm="award_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="badge">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkbadge"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="50" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="type" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="slug" nu="0">
+<DT ds="VarChar" en="" id="12" ln="50" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="description" nu="0">
+<DT ds="VarChar" en="" id="12" ln="300" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="multiple" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="awarded_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Idx clu="0" nm="badge_slug" unq="0">
+<ClNs>slug</ClNs>
+</Idx>
+<Idx clu="0" nm="name" unq="1">
+<ClNs>name, type</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="book">
+<TblOpts></TblOpts>
+<Pk ClNs="id" nm="pkbook"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="title" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="short_name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="author" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="price" nu="1">
+<DT ds="Decimal" en="" id="3" ln="10" sc="2" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="pages" nu="1">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="published_at" nu="0">
+<DT ds="Date" en="" id="91" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="publication" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="cover_img" nu="1">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="tagnames" nu="1">
+<DT ds="VarChar" en="" id="12" ln="125" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="added_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_edited_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_books_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="book" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="book_short_name_Idx" unq="1">
+<ClNs>short_name</ClNs>
+</Idx>
+<Idx clu="0" nm="fk_books_auth_user" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="book_author_info">
+<TblOpts></TblOpts>
+<Pk ClNs="id" nm="pkbook_author_info"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="blog_url" nu="1">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="added_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_edited_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="book_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_book_author_info_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="book_author_info" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_book_author_info_book" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="book" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="book_author_info" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="book_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="fk_book_author_info_auth_user" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<Idx clu="0" nm="fk_book_author_info_book" unq="0">
+<ClNs>book_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="book_author_rss">
+<TblOpts></TblOpts>
+<Pk ClNs="id" nm="pkbook_author_rss"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="title" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="url" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="rss_created_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="added_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="book_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_book_author_rss_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="book_author_rss" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_book_author_rss_book" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="book" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="book_author_rss" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="book_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="fk_book_author_rss_auth_user" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<Idx clu="0" nm="fk_book_author_rss_book" unq="0">
+<ClNs>book_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="book_question">
+<TblOpts></TblOpts>
+<Pk ClNs="id" nm="pkbook_question"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="book_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="question_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_book_question_book" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="book" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="book_question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="book_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_book_question_question" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="question" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="book_question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="question_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="fk_book_question_book" unq="0">
+<ClNs>book_id</ClNs>
+</Idx>
+<Idx clu="0" nm="fk_book_question_question" unq="0">
+<ClNs>question_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="comment">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkcomment"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="content_type_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="object_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="comment" nu="0">
+<DT ds="VarChar" en="" id="12" ln="300" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="added_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_comment_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="comment" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_comment_django_content_type" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="django_content_type" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="comment" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="content_type_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="comment_content_type_id" unq="0">
+<ClNs>content_type_id</ClNs>
+</Idx>
+<Idx clu="0" nm="comment_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<Idx clu="0" nm="content_type_id" unq="0">
+<ClNs>content_type_id, object_id, user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="django_admin_log">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkdjango_admin_log"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="action_time" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="content_type_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="object_id" nu="1">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="object_repr" nu="0">
+<DT ds="VarChar" en="" id="12" ln="200" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="action_flag" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="change_message" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_django_admin_log_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="django_admin_log" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_django_admin_log_django_content_type" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="django_content_type" oe="1" sch="schemaA" zr="1"/>
+<CdTb mn="1" nm="django_admin_log" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="content_type_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="django_admin_log_content_type_id" unq="0">
+<ClNs>content_type_id</ClNs>
+</Idx>
+<Idx clu="0" nm="django_admin_log_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="django_authopenid_association">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkdjango_authopenid_association"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="server_url" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="handle" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="secret" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="issued" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="lifetime" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="assoc_type" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="django_authopenid_nonce">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkdjango_authopenid_nonce"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="server_url" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="timestamp" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="salt" nu="0">
+<DT ds="VarChar" en="" id="12" ln="40" sc="null" sg="1"/>
+</Cl>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="django_authopenid_userassociation">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkdjango_authopenid_userassociation"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="openid_url" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_django_authopenid_userassociation_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="django_authopenid_userassociation" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="user_id" unq="1">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="django_authopenid_userpasswordqueue">
+<TblOpts>ENGINE=InnoDB DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkdjango_authopenid_userpasswordqueue"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="new_password" nu="0">
+<DT ds="VarChar" en="" id="12" ln="30" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="confirm_key" nu="0">
+<DT ds="VarChar" en="" id="12" ln="40" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_django_authopenid_userpasswordqueue_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="django_authopenid_userpasswordqueue" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="user_id" unq="1">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="django_content_type">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkdjango_content_type"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="100" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="app_label" nu="0">
+<DT ds="VarChar" en="" id="12" ln="100" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="model" nu="0">
+<DT ds="VarChar" en="" id="12" ln="100" sc="null" sg="1"/>
+</Cl>
+<Idx clu="0" nm="app_label" unq="1">
+<ClNs>app_label, model</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="django_session">
+<TblOpts>ENGINE=InnoDB DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="session_key" nm="pkdjango_session"/>
+<Cl au="0" df="" nm="session_key" nu="0">
+<DT ds="VarChar" en="" id="12" ln="40" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="session_data" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="expire_date" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="django_site">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkdjango_site"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="domain" nu="0">
+<DT ds="VarChar" en="" id="12" ln="100" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="50" sc="null" sg="1"/>
+</Cl>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="favorite_question">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkfavorite_question"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="question_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="added_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_favorite_question_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="favorite_question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_favorite_question_question" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="question" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="favorite_question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="question_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="favorite_question_question_id" unq="0">
+<ClNs>question_id</ClNs>
+</Idx>
+<Idx clu="0" nm="favorite_question_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="flagged_item">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkflagged_item"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="content_type_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="object_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="flagged_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_flagged_item_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="flagged_item" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_flagged_item_django_content_type" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="django_content_type" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="flagged_item" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="content_type_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="content_type_id" unq="1">
+<ClNs>content_type_id, object_id, user_id</ClNs>
+</Idx>
+<Idx clu="0" nm="flagged_item_content_type_id" unq="0">
+<ClNs>content_type_id</ClNs>
+</Idx>
+<Idx clu="0" nm="flagged_item_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="question">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkquestion"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="title" nu="0">
+<DT ds="VarChar" en="" id="12" ln="300" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="author_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="added_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="wiki" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="wikified_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="answer_accepted" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="closed" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="closed_by_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="closed_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="close_reason" nu="1">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="deleted" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="deleted_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="deleted_by_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="locked" nu="0">
+<DT ds="TinyInt" en="" id="-6" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="locked_by_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="locked_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="score" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="answer_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="comment_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="view_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="offensive_flag_count" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="favourite_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="last_edited_at" nu="1">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_edited_by_id" nu="1">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_activity_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="last_activity_by_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="tagnames" nu="0">
+<DT ds="VarChar" en="" id="12" ln="125" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="summary" nu="0">
+<DT ds="VarChar" en="" id="12" ln="180" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="html" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="vote_up_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="vote_down_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="closed_by_id_refs_id_56e9d00c" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="1"/>
+<CdTb mn="1" nm="question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="closed_by_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="deleted_by_id_refs_id_56e9d00c" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="1"/>
+<CdTb mn="1" nm="question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="deleted_by_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_question_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="author_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="last_activity_by_id_refs_id_56e9d00c" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="last_activity_by_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="last_edited_by_id_refs_id_56e9d00c" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="1"/>
+<CdTb mn="1" nm="question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="last_edited_by_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="locked_by_id_refs_id_56e9d00c" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="1"/>
+<CdTb mn="1" nm="question" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="locked_by_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="question_author_id" unq="0">
+<ClNs>author_id</ClNs>
+</Idx>
+<Idx clu="0" nm="question_closed_by_id" unq="0">
+<ClNs>closed_by_id</ClNs>
+</Idx>
+<Idx clu="0" nm="question_deleted_by_id" unq="0">
+<ClNs>deleted_by_id</ClNs>
+</Idx>
+<Idx clu="0" nm="question_last_activity_by_id" unq="0">
+<ClNs>last_activity_by_id</ClNs>
+</Idx>
+<Idx clu="0" nm="question_last_edited_by_id" unq="0">
+<ClNs>last_edited_by_id</ClNs>
+</Idx>
+<Idx clu="0" nm="question_locked_by_id" unq="0">
+<ClNs>locked_by_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="question_revision">
+<TblOpts>ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1</TblOpts>
+<Pk ClNs="id" nm="pkquestion_revision"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="question_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="revision" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="title" nu="0">
+<DT ds="VarChar" en="" id="12" ln="300" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="author_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="revised_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="tagnames" nu="0">
+<DT ds="VarChar" en="" id="12" ln="125" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="summary" nu="0">
+<DT ds="VarChar" en="" id="12" ln="300" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="text" nu="0">
+<DT ds="LongText" en="" id="712" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_question_revision_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="question_revision" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="author_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_question_revision_question" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="question" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="question_revision" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="question_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="question_revision_author_id" unq="0">
+<ClNs>author_id</ClNs>
+</Idx>
+<Idx clu="0" nm="question_revision_question_id" unq="0">
+<ClNs>question_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="question_tags">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkquestion_tags"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="question_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="tag_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_question_tags_question" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="question" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="question_tags" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="question_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_question_tags_tag" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="tag" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="question_tags" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="tag_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="question_id" unq="1">
+<ClNs>question_id, tag_id</ClNs>
+</Idx>
+<Idx clu="0" nm="tag_id_refs_id_43fcb953" unq="0">
+<ClNs>tag_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="repute">
+<TblOpts>ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1</TblOpts>
+<Pk ClNs="id" nm="pkrepute"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="positive" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="negative" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="question_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="reputed_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="reputation_type" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="reputation" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_repute_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="repute" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_repute_question" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="question" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="repute" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="question_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="repute_question_id" unq="0">
+<ClNs>question_id</ClNs>
+</Idx>
+<Idx clu="0" nm="repute_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="tag">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pktag"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="name" nu="0">
+<DT ds="VarChar" en="" id="12" ln="255" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="created_by_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="used_count" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Fk deAc="3" nm="fk_tag_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="tag" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="created_by_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="name" unq="1">
+<ClNs>name</ClNs>
+</Idx>
+<Idx clu="0" nm="tag_created_by_id" unq="0">
+<ClNs>created_by_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="user_badge">
+<TblOpts>ENGINE=InnoDB DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkuser_badge"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="badge_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_user_badge_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="user_badge" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_user_badge_badge" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="badge" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="user_badge" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="badge_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="fk_user_badge_auth_user" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<Idx clu="0" nm="fk_user_badge_badge" unq="0">
+<ClNs>badge_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="user_favorite_questions">
+<TblOpts>ENGINE=InnoDB DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkuser_favorite_questions"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="question_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_user_favorite_questions_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="user_favorite_questions" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_user_favorite_questions_question" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="question" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="user_favorite_questions" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="question_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="fk_user_favorite_questions_auth_user" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<Idx clu="0" nm="fk_user_favorite_questions_question" unq="0">
+<ClNs>question_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+<Tbl UsSo="1" nm="vote">
+<TblOpts>ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8</TblOpts>
+<Pk ClNs="id" nm="pkvote"/>
+<Cl au="1" df="" nm="id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="content_type_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="object_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="0"/>
+</Cl>
+<Cl au="0" df="" nm="user_id" nu="0">
+<DT ds="Integer" en="" id="4" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="vote" nu="0">
+<DT ds="SmallInt" en="" id="5" ln="null" sc="null" sg="1"/>
+</Cl>
+<Cl au="0" df="" nm="voted_at" nu="0">
+<DT ds="DateTime" en="" id="700" ln="null" sc="null" sg="1"/>
+</Cl>
+<Fk deAc="3" nm="fk_vote_auth_user" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="auth_user" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="vote" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="user_id" prCl="id"/>
+</Fk>
+<Fk deAc="3" nm="fk_vote_django_content_type" prLkCl="id" upAc="3">
+<PrTb mn="0" nm="django_content_type" oe="1" sch="schemaA" zr="0"/>
+<CdTb mn="1" nm="vote" oe="1" sch="schemaA" zr="1"/>
+<ClPr cdCl="content_type_id" prCl="id"/>
+</Fk>
+<Idx clu="0" nm="content_type_id" unq="1">
+<ClNs>content_type_id, object_id, user_id</ClNs>
+</Idx>
+<Idx clu="0" nm="vote_content_type_id" unq="0">
+<ClNs>content_type_id</ClNs>
+</Idx>
+<Idx clu="0" nm="vote_user_id" unq="0">
+<ClNs>user_id</ClNs>
+</Idx>
+<SchTrHis/>
+</Tbl>
+</Sch>
+<Dgm nm="diagramA">
+<RnCf FtSz="12" lkStgy="OffsetDirect" zm="1.0">
+<VbCfg>
+<Fg ky="Auto Number" vl="0"/>
+<Fg ky="Check" vl="0"/>
+<Fg ky="Comment" vl="0"/>
+<Fg ky="Data Type" vl="1"/>
+<Fg ky="Default" vl="0"/>
+<Fg ky="ENUM Values" vl="0"/>
+<Fg ky="Length" vl="1"/>
+<Fg ky="Name" vl="1"/>
+<Fg ky="Nullable" vl="0"/>
+<Fg ky="Schema Name" vl="0"/>
+<Fg ky="Signed" vl="0"/>
+</VbCfg>
+</RnCf>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="activity" x="811" y="284"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="answer" x="413" y="472"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="answer_revision" x="1067" y="41"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="auth_group" x="593" y="703"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="auth_group_permissions" x="765" y="703"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="auth_message" x="323" y="1441"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="auth_permission" x="636" y="794"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="auth_user" x="50" y="50"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="auth_user_groups" x="403" y="703"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="auth_user_user_permissions" x="393" y="794"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="award" x="373" y="971"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="badge" x="606" y="971"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="comment" x="383" y="880"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="django_admin_log" x="363" y="1087"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="django_authopenid_association" x="1543" y="50"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="django_authopenid_nonce" x="1781" y="50"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="django_authopenid_userassociation" x="313" y="1502"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="django_authopenid_userpasswordqueue" x="303" y="1563"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="django_content_type" x="636" y="880"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="django_session" x="1088" y="222"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="django_site" x="1312" y="152"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="favorite_question" x="433" y="50"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="flagged_item" x="353" y="1198"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="question" x="423" y="121"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="question_revision" x="1380" y="241"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="question_tags" x="736" y="121"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="repute" x="1661" y="180"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="tag" x="343" y="1279"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="user_badge" x="916" y="200"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="user_favorite_questions" x="1070" y="366"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="vote" x="93" y="1341"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="book" x="962" y="1063"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="book_question" x="702" y="1160"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="book_author_info" x="931" y="1436"/>
+<TbGl bkCl="ffffffe6" sch="schemaA" tbl="book_author_rss" x="619" y="1321"/>
+<FkGl bkCl="ff000000" nm="schemaA.activity.fk_activity_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.answer.deleted_by_id_refs_id_192b0170"/>
+<FkGl bkCl="ff000000" nm="schemaA.answer.fk_answer_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.answer.fk_answer_question"/>
+<FkGl bkCl="ff000000" nm="schemaA.answer.last_edited_by_id_refs_id_192b0170"/>
+<FkGl bkCl="ff000000" nm="schemaA.answer.locked_by_id_refs_id_192b0170"/>
+<FkGl bkCl="ff000000" nm="schemaA.answer_revision.fk_answer_revision_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.auth_group_permissions.fk_auth_group_permissions_auth_group"/>
+<FkGl bkCl="ff000000" nm="schemaA.auth_group_permissions.fk_auth_group_permissions_auth_permission"/>
+<FkGl bkCl="ff000000" nm="schemaA.auth_message.fk_auth_message_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.auth_permission.fk_auth_permission_django_content_type"/>
+<FkGl bkCl="ff000000" nm="schemaA.auth_user_groups.fk_auth_user_groups_auth_group"/>
+<FkGl bkCl="ff000000" nm="schemaA.auth_user_groups.fk_auth_user_groups_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.auth_user_user_permissions.fk_auth_user_user_permissions_auth_permission"/>
+<FkGl bkCl="ff000000" nm="schemaA.auth_user_user_permissions.fk_auth_user_user_permissions_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.award.fk_award_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.award.fk_award_badge"/>
+<FkGl bkCl="ff000000" nm="schemaA.book.fk_books_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.book_author_info.fk_book_author_info_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.book_author_info.fk_book_author_info_book"/>
+<FkGl bkCl="ff000000" nm="schemaA.book_author_rss.fk_book_author_rss_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.book_author_rss.fk_book_author_rss_book"/>
+<FkGl bkCl="ff000000" nm="schemaA.book_question.fk_book_question_book"/>
+<FkGl bkCl="ff000000" nm="schemaA.book_question.fk_book_question_question"/>
+<FkGl bkCl="ff000000" nm="schemaA.comment.fk_comment_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.comment.fk_comment_django_content_type"/>
+<FkGl bkCl="ff000000" nm="schemaA.django_admin_log.fk_django_admin_log_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.django_admin_log.fk_django_admin_log_django_content_type"/>
+<FkGl bkCl="ff000000" nm="schemaA.django_authopenid_userassociation.fk_django_authopenid_userassociation_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.django_authopenid_userpasswordqueue.fk_django_authopenid_userpasswordqueue_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.favorite_question.fk_favorite_question_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.favorite_question.fk_favorite_question_question"/>
+<FkGl bkCl="ff000000" nm="schemaA.flagged_item.fk_flagged_item_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.flagged_item.fk_flagged_item_django_content_type"/>
+<FkGl bkCl="ff000000" nm="schemaA.question.closed_by_id_refs_id_56e9d00c"/>
+<FkGl bkCl="ff000000" nm="schemaA.question.deleted_by_id_refs_id_56e9d00c"/>
+<FkGl bkCl="ff000000" nm="schemaA.question.fk_question_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.question.last_activity_by_id_refs_id_56e9d00c"/>
+<FkGl bkCl="ff000000" nm="schemaA.question.last_edited_by_id_refs_id_56e9d00c"/>
+<FkGl bkCl="ff000000" nm="schemaA.question.locked_by_id_refs_id_56e9d00c"/>
+<FkGl bkCl="ff000000" nm="schemaA.question_revision.fk_question_revision_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.question_revision.fk_question_revision_question"/>
+<FkGl bkCl="ff000000" nm="schemaA.question_tags.fk_question_tags_question"/>
+<FkGl bkCl="ff000000" nm="schemaA.question_tags.fk_question_tags_tag"/>
+<FkGl bkCl="ff000000" nm="schemaA.repute.fk_repute_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.repute.fk_repute_question"/>
+<FkGl bkCl="ff000000" nm="schemaA.tag.fk_tag_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.user_badge.fk_user_badge_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.user_badge.fk_user_badge_badge"/>
+<FkGl bkCl="ff000000" nm="schemaA.user_favorite_questions.fk_user_favorite_questions_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.user_favorite_questions.fk_user_favorite_questions_question"/>
+<FkGl bkCl="ff000000" nm="schemaA.vote.fk_vote_auth_user"/>
+<FkGl bkCl="ff000000" nm="schemaA.vote.fk_vote_django_content_type"/>
+</Dgm>
+<RnmMgr NxRnmId="6">
+<RnmCh ObjCls="Table" ParCls="Schema" ParNme="schemaA" SupCls="" SupNme="">
+<Rnm id="1" nNm="book" oNm="books"/>
+</RnmCh>
+<RnmCh ObjCls="Table" ParCls="Schema" ParNme="schemaA" SupCls="" SupNme="">
+<Rnm id="2" nNm="book_question" oNm="books_questions"/>
+</RnmCh>
+<RnmCh ObjCls="Column" ParCls="Table" ParNme="book_question" SupCls="Schema" SupNme="schemaA">
+<Rnm id="3" nNm="id" oNm="books_questions_id"/>
+</RnmCh>
+<RnmCh ObjCls="Column" ParCls="Table" ParNme="book_author_rss" SupCls="Schema" SupNme="schemaA">
+<Rnm id="4" nNm="user_id" oNm="book_author_id"/>
+</RnmCh>
+<RnmCh ObjCls="Column" ParCls="Table" ParNme="book" SupCls="Schema" SupNme="schemaA">
+<Rnm id="5" nNm="short_name" oNm="shot_name"/>
+</RnmCh>
+</RnmMgr>
+<DbDocOptionMgr>
+<BasicOptionMgr>
+<Name>db.doc.option.mgr</Name>
+<BoolOpt lbl="Diagrams" nm="doc.diagrams" on="1" spl="0"/>
+<BoolOpt lbl="Foreign Keys" nm="doc.fks" on="1" spl="0"/>
+<BoolOpt lbl="Indexes" nm="doc.indexes" on="1" spl="0"/>
+<BoolOpt lbl="Overwrite CSS File" nm="doc.overwrite.css" on="1" spl="0"/>
+<BoolOpt lbl="Procedures" nm="doc.procs" on="1" spl="0"/>
+<BoolOpt lbl="Schemas" nm="doc.schemas" on="1" spl="0"/>
+<BoolOpt lbl="Sequences" nm="doc.sequences" on="1" spl="0"/>
+<BoolOpt lbl="Tables" nm="doc.tables" on="1" spl="0"/>
+<BoolOpt lbl="Triggers" nm="doc.triggers" on="1" spl="0"/>
+<BoolOpt lbl="Views" nm="doc.views" on="1" spl="0"/>
+</BasicOptionMgr>
+</DbDocOptionMgr>
+</Db>
diff --git a/sql_scripts/cnprog_new_install.sql b/sql_scripts/cnprog_new_install.sql
new file mode 100644
index 00000000..ac33a6ba
--- /dev/null
+++ b/sql_scripts/cnprog_new_install.sql
@@ -0,0 +1,811 @@
+-- MySQL Administrator dump 1.4
+--
+-- ------------------------------------------------------
+-- Server version 5.0.67
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+
+
+--
+-- Create schema cnprog
+--
+
+CREATE DATABASE IF NOT EXISTS cnprog;
+USE cnprog;
+
+--
+-- Definition of table `cnprog`.`answer`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`answer`;
+CREATE TABLE `cnprog`.`answer` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` tinyint(1) NOT NULL,
+ `wikified_at` datetime default NULL,
+ `accepted` tinyint(1) NOT NULL,
+ `deleted` tinyint(1) NOT NULL,
+ `deleted_by_id` int(11) default NULL,
+ `locked` tinyint(1) NOT NULL,
+ `locked_by_id` int(11) default NULL,
+ `locked_at` datetime default NULL,
+ `score` int(11) NOT NULL,
+ `vote_up_count` int(11) NOT NULL,
+ `vote_down_count` int(11) NOT NULL,
+ `comment_count` int(10) unsigned NOT NULL,
+ `offensive_flag_count` smallint(6) NOT NULL,
+ `last_edited_at` datetime default NULL,
+ `last_edited_by_id` int(11) default NULL,
+ `html` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `answer_question_id` (`question_id`),
+ KEY `answer_author_id` (`author_id`),
+ KEY `answer_deleted_by_id` (`deleted_by_id`),
+ KEY `answer_locked_by_id` (`locked_by_id`),
+ KEY `answer_last_edited_by_id` (`last_edited_by_id`),
+ CONSTRAINT `author_id_refs_id_192b0170` FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `deleted_by_id_refs_id_192b0170` FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_edited_by_id_refs_id_192b0170` FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `locked_by_id_refs_id_192b0170` FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `question_id_refs_id_7d6550c9` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`auth_group`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_group`;
+CREATE TABLE `cnprog`.`auth_group` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(80) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_group`
+--
+
+--
+-- Definition of table `cnprog`.`auth_group_permissions`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_group_permissions`;
+CREATE TABLE `cnprog`.`auth_group_permissions` (
+ `id` int(11) NOT NULL auto_increment,
+ `group_id` int(11) NOT NULL,
+ `permission_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `group_id` (`group_id`,`permission_id`),
+ KEY `permission_id_refs_id_5886d21f` (`permission_id`),
+ CONSTRAINT `group_id_refs_id_3cea63fe` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
+ CONSTRAINT `permission_id_refs_id_5886d21f` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_group_permissions`
+--
+
+--
+-- Definition of table `cnprog`.`auth_message`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_message`;
+CREATE TABLE `cnprog`.`auth_message` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `message` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `auth_message_user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_650f49a6` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_message`
+--
+
+--
+-- Definition of table `cnprog`.`auth_permission`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_permission`;
+CREATE TABLE `cnprog`.`auth_permission` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(50) NOT NULL,
+ `content_type_id` int(11) NOT NULL,
+ `codename` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
+ KEY `auth_permission_content_type_id` (`content_type_id`),
+ CONSTRAINT `content_type_id_refs_id_728de91f` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_permission`
+--
+INSERT INTO `cnprog`.`auth_permission` VALUES (1,'Can add permission',1,'add_permission'),
+ (2,'Can change permission',1,'change_permission'),
+ (3,'Can delete permission',1,'delete_permission'),
+ (4,'Can add group',2,'add_group'),
+ (5,'Can change group',2,'change_group'),
+ (6,'Can delete group',2,'delete_group'),
+ (7,'Can add user',3,'add_user'),
+ (8,'Can change user',3,'change_user'),
+ (9,'Can delete user',3,'delete_user'),
+ (10,'Can add message',4,'add_message'),
+ (11,'Can change message',4,'change_message'),
+ (12,'Can delete message',4,'delete_message'),
+ (13,'Can add content type',5,'add_contenttype'),
+ (14,'Can change content type',5,'change_contenttype'),
+ (15,'Can delete content type',5,'delete_contenttype'),
+ (16,'Can add session',6,'add_session'),
+ (17,'Can change session',6,'change_session'),
+ (18,'Can delete session',6,'delete_session'),
+ (19,'Can add site',7,'add_site'),
+ (20,'Can change site',7,'change_site'),
+ (21,'Can delete site',7,'delete_site'),
+ (25,'Can add answer',9,'add_answer'),
+ (26,'Can change answer',9,'change_answer'),
+ (27,'Can delete answer',9,'delete_answer'),
+ (28,'Can add comment',10,'add_comment'),
+ (29,'Can change comment',10,'change_comment'),
+ (30,'Can delete comment',10,'delete_comment'),
+ (31,'Can add tag',11,'add_tag'),
+ (32,'Can change tag',11,'change_tag'),
+ (33,'Can delete tag',11,'delete_tag'),
+ (37,'Can add nonce',13,'add_nonce'),
+ (38,'Can change nonce',13,'change_nonce'),
+ (39,'Can delete nonce',13,'delete_nonce'),
+ (40,'Can add association',14,'add_association'),
+ (41,'Can change association',14,'change_association'),
+ (42,'Can delete association',14,'delete_association'),
+ (43,'Can add nonce',15,'add_nonce'),
+ (44,'Can change nonce',15,'change_nonce'),
+ (45,'Can delete nonce',15,'delete_nonce'),
+ (46,'Can add association',16,'add_association'),
+ (47,'Can change association',16,'change_association'),
+ (48,'Can delete association',16,'delete_association'),
+ (49,'Can add user association',17,'add_userassociation'),
+ (50,'Can change user association',17,'change_userassociation'),
+ (51,'Can delete user association',17,'delete_userassociation'),
+ (52,'Can add user password queue',18,'add_userpasswordqueue'),
+ (53,'Can change user password queue',18,'change_userpasswordqueue'),
+ (54,'Can delete user password queue',18,'delete_userpasswordqueue'),
+ (55,'Can add log entry',19,'add_logentry'),
+ (56,'Can change log entry',19,'change_logentry'),
+ (57,'Can delete log entry',19,'delete_logentry'),
+ (58,'Can add question',20,'add_question'),
+ (59,'Can change question',20,'change_question'),
+ (60,'Can delete question',20,'delete_question'),
+ (61,'Can add vote',21,'add_vote'),
+ (62,'Can change vote',21,'change_vote'),
+ (63,'Can delete vote',21,'delete_vote'),
+ (64,'Can add flagged item',22,'add_flaggeditem'),
+ (65,'Can change flagged item',22,'change_flaggeditem'),
+ (66,'Can delete flagged item',22,'delete_flaggeditem'),
+ (67,'Can add favorite question',23,'add_favoritequestion'),
+ (68,'Can change favorite question',23,'change_favoritequestion'),
+ (69,'Can delete favorite question',23,'delete_favoritequestion'),
+ (70,'Can add badge',24,'add_badge'),
+ (71,'Can change badge',24,'change_badge'),
+ (72,'Can delete badge',24,'delete_badge'),
+ (73,'Can add award',25,'add_award'),
+ (74,'Can change award',25,'change_award'),
+ (75,'Can delete award',25,'delete_award');
+
+--
+-- Definition of table `cnprog`.`auth_user`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_user`;
+CREATE TABLE `cnprog`.`auth_user` (
+ `id` int(11) NOT NULL auto_increment,
+ `username` varchar(30) NOT NULL,
+ `first_name` varchar(30) NOT NULL,
+ `last_name` varchar(30) NOT NULL,
+ `email` varchar(75) NOT NULL,
+ `password` varchar(128) NOT NULL,
+ `is_staff` tinyint(1) NOT NULL,
+ `is_active` tinyint(1) NOT NULL,
+ `is_superuser` tinyint(1) NOT NULL,
+ `last_login` datetime NOT NULL,
+ `date_joined` datetime NOT NULL,
+ `gold` smallint(6) NOT NULL default '0',
+ `silver` smallint(5) unsigned NOT NULL default '0',
+ `bronze` smallint(5) unsigned NOT NULL default '0',
+ `reputation` int(10) unsigned default '1',
+ `gravatar` varchar(128) default NULL,
+ `questions_per_page` smallint(5) unsigned default '10',
+ `last_seen` datetime default NULL,
+ `real_name` varchar(100) default NULL,
+ `website` varchar(200) default NULL,
+ `location` varchar(100) default NULL,
+ `date_of_birth` datetime default NULL,
+ `about` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `username` (`username`)
+) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_user`
+--
+INSERT INTO `cnprog`.`auth_user` VALUES (2,'chagel','','','chagel@gmail.com','sha1$6a2fb$0d2ffe90bcba542fc962f57967a88e507799cc74',1,1,1,'2008-12-16 15:35:17','2008-12-11 20:12:53',0,0,0,1,'8c1efc4f4618aa68b18c88f2bcaa5564',10,NULL,NULL,NULL,NULL,NULL,NULL),
+ (3,'mike','','','ichagel@yahoo.com','sha1$f7ef5$1015ae6b2c8a2774a028419d3c57e13145b83284',0,1,0,'2008-12-15 12:56:23','2008-12-15 12:56:23',0,0,0,1,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL),
+ (4,'sailingcai','','','sailingcai@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-23 06:14:45','2008-12-20 15:19:21',1,2,3,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','',NULL,''),
+ (5,'sailingcai1','','','1@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21',NULL,NULL,NULL,NULL,NULL),
+ (6,'sailing2','','','2@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (7,'sailing3','','','3@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (8,'sailing4','','','4@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (9,'sailing5','','','5@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (10,'sailing6','','','6@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (11,'sailing7','','','7@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (12,'sailing8','','','8@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (13,'sailing9','','','9@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (14,'sailing10','','','10@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (15,'sailing11','','','11@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (16,'sailing12','','','12@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (17,'sailing13','','','13@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (18,'sailing14','','','14@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (19,'sailing15','','','15@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (20,'sailing16','','','16@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (21,'sailing17','','','17@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (22,'sailing18','','','18@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (23,'sailing19','','','19@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (24,'sailing20','','','20@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (25,'sailing21','','','21@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (26,'sailing22','','','22@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (27,'sailing23','','','23@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (28,'sailing24','','','24@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (29,'sailing25','','','25@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (30,'sailing26','','','26@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (31,'sailing27','','','27@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (32,'sailing28','','','28@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (33,'sailing29','','','29@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (34,'sailing30','','','30@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (35,'sailing31','','','31@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (36,'sailing32','','','32@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (37,'sailing33','','','33@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (38,'sailing34','','','34@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (39,'sailing35','','','35@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (40,'sailing36','','','36@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (41,'sailing37','','','37@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (42,'sailing38','','','38@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (43,'sailing39','','','39@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (44,'sailing40','','','40@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (45,'sailing41','','','41@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (46,'sailing42','','','42@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (47,'sailing43','','','43@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (48,'sailing44','','','44@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (49,'sailing45','','','45@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (50,'sailing46','','','46@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (51,'sailing47','','','47@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (52,'sailing48','','','48@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (53,'sailing49','','','49@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (54,'sailing50','','','50@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (55,'sailing51','','','51@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (56,'sailing52','','','52@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (57,'sailing53','','','53@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (58,'sailing54','','','54@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (59,'sailing55','','','55@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (60,'sailing56','','','56@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (61,'sailing57','','','57@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (62,'sailing58','','','58@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (63,'sailing59','','','59@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (64,'sailing60','','','60@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (65,'sailing61','','','61@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (66,'sailing62','','','62@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (67,'sailing63','','','63@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (68,'sailing64','','','64@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (69,'sailing65','','','65@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (70,'sailing66','','','66@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (71,'sailing67','','','67@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (72,'sailing68','','','68@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (73,'sailing69','','','69@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (74,'sailing70','','','70@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (75,'sailing71','','','71@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (76,'sailing72','','','72@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (77,'sailing73','','','73@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (78,'sailing74','','','74@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (79,'sailing75','','','75@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (80,'sailing76','','','76@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (81,'sailing77','','','77@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (82,'sailing78','','','78@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (83,'sailing79','','','79@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (84,'sailing80','','','80@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (85,'sailing81','','','81@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (86,'sailing82','','','82@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (87,'sailing83','','','83@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (88,'sailing84','','','84@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (89,'sailing85','','','85@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (90,'sailing86','','','86@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (91,'sailing87','','','87@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (92,'sailing88','','','88@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (93,'sailing89','','','89@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (94,'sailing90','','','90@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (95,'sailing91','','','91@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (96,'sailing92','','','92@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (97,'sailing93','','','93@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (98,'sailing94','','','94@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (99,'sailing95','','','95@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (100,'sailing96','','','96@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (101,'sailing97','','','97@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (102,'sailing98','','','98@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00',''),
+ (103,'sailing99','','','99@gmail.com','sha1$a417c$ca7d9f2ad55666bf98068cc392b6f62450b216e0',0,1,0,'2008-12-20 15:19:21','2008-12-20 15:19:21',0,0,0,1,'a1cb9864605a32760518b90a4f9a0e73',10,'2008-12-20 15:19:21','','','','0000-00-00 00:00:00','');
+
+--
+-- Definition of table `cnprog`.`auth_user_groups`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_user_groups`;
+CREATE TABLE `cnprog`.`auth_user_groups` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `group_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`,`group_id`),
+ KEY `group_id_refs_id_f116770` (`group_id`),
+ CONSTRAINT `group_id_refs_id_f116770` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
+ CONSTRAINT `user_id_refs_id_7ceef80f` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_user_groups`
+--
+
+--
+-- Definition of table `cnprog`.`auth_user_user_permissions`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`auth_user_user_permissions`;
+CREATE TABLE `cnprog`.`auth_user_user_permissions` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `permission_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`,`permission_id`),
+ KEY `permission_id_refs_id_67e79cb` (`permission_id`),
+ CONSTRAINT `permission_id_refs_id_67e79cb` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`),
+ CONSTRAINT `user_id_refs_id_dfbab7d` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`auth_user_user_permissions`
+--
+
+--
+-- Definition of table `cnprog`.`award`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`award`;
+CREATE TABLE `cnprog`.`award` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `badge_id` int(11) NOT NULL,
+ `awarded_at` datetime NOT NULL,
+ `notified` tinyint(1) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `award_user_id` (`user_id`),
+ KEY `award_badge_id` (`badge_id`),
+ CONSTRAINT `badge_id_refs_id_651af0e1` FOREIGN KEY (`badge_id`) REFERENCES `badge` (`id`),
+ CONSTRAINT `user_id_refs_id_2d83e9b6` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`award`
+--
+
+--
+-- Definition of table `cnprog`.`badge`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`badge`;
+CREATE TABLE `cnprog`.`badge` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(50) NOT NULL,
+ `type` smallint(6) NOT NULL,
+ `slug` varchar(50) NOT NULL,
+ `description` varchar(300) NOT NULL,
+ `multiple` tinyint(1) NOT NULL,
+ `awarded_count` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`,`type`),
+ KEY `badge_slug` (`slug`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`badge`
+--
+
+--
+-- Definition of table `cnprog`.`comment`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`comment`;
+CREATE TABLE `cnprog`.`comment` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `comment` varchar(300) NOT NULL,
+ `added_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `comment_content_type_id` (`content_type_id`),
+ KEY `comment_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_13a5866c` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_6be725e8` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`comment`
+--
+
+--
+-- Definition of table `cnprog`.`django_admin_log`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_admin_log`;
+CREATE TABLE `cnprog`.`django_admin_log` (
+ `id` int(11) NOT NULL auto_increment,
+ `action_time` datetime NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `content_type_id` int(11) default NULL,
+ `object_id` longtext,
+ `object_repr` varchar(200) NOT NULL,
+ `action_flag` smallint(5) unsigned NOT NULL,
+ `change_message` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `django_admin_log_user_id` (`user_id`),
+ KEY `django_admin_log_content_type_id` (`content_type_id`),
+ CONSTRAINT `content_type_id_refs_id_288599e6` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_c8665aa` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_admin_log`
+--
+INSERT INTO `cnprog`.`django_admin_log` VALUES (1,'2008-12-18 23:41:41',2,7,'1','cnprog.com',2,'已修改 domain 和 name 。');
+
+--
+-- Definition of table `cnprog`.`django_authopenid_association`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_authopenid_association`;
+CREATE TABLE `cnprog`.`django_authopenid_association` (
+ `id` int(11) NOT NULL auto_increment,
+ `server_url` longtext NOT NULL,
+ `handle` varchar(255) NOT NULL,
+ `secret` longtext NOT NULL,
+ `issued` int(11) NOT NULL,
+ `lifetime` int(11) NOT NULL,
+ `assoc_type` longtext NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_authopenid_association`
+--
+INSERT INTO `cnprog`.`django_authopenid_association` VALUES (2,'https://www.google.com/accounts/o8/ud','AOQobUfcCH4sgjsBGGscrzxIa5UM4clofAB6nixx8Qq_NWco4ynn_Kc4','u5cva43abzdwF8CJOFZfkzfk7x8=\n',1229022261,1229022261,'HMAC-SHA1'),
+ (3,'https://api.screenname.aol.com/auth/openidServer','diAyLjAgayAwIGJhT2VvYkdDZ21RSHJ4QldzQnhTdjIxV3BVbz0%3D-j5HRXRB1VbPyg48jGKE1Q70dfv76lGHEPwd9071%2FJ7f6SSw5YhakrwWpsVXtr34T6iHwPDdo6RU%3D','EmQL3+5oR6mFKIaeBNy6hXyUJ/w=\n',1229282202,1229282202,'HMAC-SHA1'),
+ (4,'https://open.login.yahooapis.com/openid/op/auth','JcBeY.uWXu2YjzbuCQiqFzAb0MIc7ATeKiPO4eAp3vluPMqZp_NCxepvMLGrJjxxDKTaNnr06wepMos8ap6SQYZiTi51tZ05lMWnpZAiOA1hsq_WMlEL7G9YE66GEA9A','QXiuN6B7E8nP5QhyHI3IB26t4SA=\n',1229282256,1229282256,'HMAC-SHA1'),
+ (5,'http://openid.claimid.com/server','{HMAC-SHA1}{494575fd}{uLEbxQ==}','GvPbkgMHh0QVPH7mStCGuWb2AKY=\n',1229288957,1229288957,'HMAC-SHA1'),
+ (6,'http://www.blogger.com/openid-server.g','oida-1229424484019-158830626','8gaU4aKnIFCLKIkHdxZQp7ZGNck=\n',1229424478,1229424478,'HMAC-SHA1');
+
+--
+-- Definition of table `cnprog`.`django_authopenid_nonce`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_authopenid_nonce`;
+CREATE TABLE `cnprog`.`django_authopenid_nonce` (
+ `id` int(11) NOT NULL auto_increment,
+ `server_url` varchar(255) NOT NULL,
+ `timestamp` int(11) NOT NULL,
+ `salt` varchar(40) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`django_authopenid_userassociation`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_authopenid_userassociation`;
+CREATE TABLE `cnprog`.`django_authopenid_userassociation` (
+ `id` int(11) NOT NULL auto_increment,
+ `openid_url` varchar(255) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_163d208d` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_authopenid_userassociation`
+--
+INSERT INTO `cnprog`.`django_authopenid_userassociation` VALUES (2,'https://www.google.com/accounts/o8/id?id=AItOawl7CVVHl4DWtteqj4dd_A23zKRwPZgOOjw',2),
+ (3,'https://me.yahoo.com/a/f8f2zXF91okYL4iN2Zh4P542a5s-#f4af2',3),
+ (4,'https://me.yahoo.com/sailingcai#6fa4e',4);
+
+--
+-- Definition of table `cnprog`.`django_authopenid_userpasswordqueue`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_authopenid_userpasswordqueue`;
+CREATE TABLE `cnprog`.`django_authopenid_userpasswordqueue` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `new_password` varchar(30) NOT NULL,
+ `confirm_key` varchar(40) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_76bcaaa4` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_authopenid_userpasswordqueue`
+--
+
+--
+-- Definition of table `cnprog`.`django_content_type`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_content_type`;
+CREATE TABLE `cnprog`.`django_content_type` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(100) NOT NULL,
+ `app_label` varchar(100) NOT NULL,
+ `model` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `app_label` (`app_label`,`model`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_content_type`
+--
+INSERT INTO `cnprog`.`django_content_type` VALUES (1,'permission','auth','permission'),
+ (2,'group','auth','group'),
+ (3,'user','auth','user'),
+ (4,'message','auth','message'),
+ (5,'content type','contenttypes','contenttype'),
+ (6,'session','sessions','session'),
+ (7,'site','sites','site'),
+ (9,'answer','forum','answer'),
+ (10,'comment','forum','comment'),
+ (11,'tag','forum','tag'),
+ (13,'nonce','django_openidconsumer','nonce'),
+ (14,'association','django_openidconsumer','association'),
+ (15,'nonce','django_authopenid','nonce'),
+ (16,'association','django_authopenid','association'),
+ (17,'user association','django_authopenid','userassociation'),
+ (18,'user password queue','django_authopenid','userpasswordqueue'),
+ (19,'log entry','admin','logentry'),
+ (20,'question','forum','question'),
+ (21,'vote','forum','vote'),
+ (22,'flagged item','forum','flaggeditem'),
+ (23,'favorite question','forum','favoritequestion'),
+ (24,'badge','forum','badge'),
+ (25,'award','forum','award');
+
+--
+-- Definition of table `cnprog`.`django_session`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_session`;
+CREATE TABLE `cnprog`.`django_session` (
+ `session_key` varchar(40) NOT NULL,
+ `session_data` longtext NOT NULL,
+ `expire_date` datetime NOT NULL,
+ PRIMARY KEY (`session_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`django_site`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`django_site`;
+CREATE TABLE `cnprog`.`django_site` (
+ `id` int(11) NOT NULL auto_increment,
+ `domain` varchar(100) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`django_site`
+--
+INSERT INTO `cnprog`.`django_site` VALUES (1,'cnprog.com','CNProg.com');
+
+--
+-- Definition of table `cnprog`.`favorite_question`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`favorite_question`;
+CREATE TABLE `cnprog`.`favorite_question` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `favorite_question_question_id` (`question_id`),
+ KEY `favorite_question_user_id` (`user_id`),
+ CONSTRAINT `question_id_refs_id_1ebe1cc3` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
+ CONSTRAINT `user_id_refs_id_52853822` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`favorite_question`
+--
+
+--
+-- Definition of table `cnprog`.`flagged_item`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`flagged_item`;
+CREATE TABLE `cnprog`.`flagged_item` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `flagged_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `flagged_item_content_type_id` (`content_type_id`),
+ KEY `flagged_item_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_76e44d74` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_35e3c608` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`flagged_item`
+--
+
+--
+-- Definition of table `cnprog`.`question`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`question`;
+CREATE TABLE `cnprog`.`question` (
+ `id` int(11) NOT NULL auto_increment,
+ `title` varchar(300) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` tinyint(1) NOT NULL,
+ `wikified_at` datetime default NULL,
+ `answer_accepted` tinyint(1) NOT NULL,
+ `closed` tinyint(1) NOT NULL,
+ `closed_by_id` int(11) default NULL,
+ `closed_at` datetime default NULL,
+ `close_reason` smallint(6) default NULL,
+ `deleted` tinyint(1) NOT NULL,
+ `deleted_at` datetime default NULL,
+ `deleted_by_id` int(11) default NULL,
+ `locked` tinyint(1) NOT NULL,
+ `locked_by_id` int(11) default NULL,
+ `locked_at` datetime default NULL,
+ `vote_up_count` int(11) NOT NULL,
+ `vote_down_count` int(11) NOT NULL,
+ `score` int(11) NOT NULL,
+ `answer_count` int(10) unsigned NOT NULL,
+ `comment_count` int(10) unsigned NOT NULL,
+ `view_count` int(10) unsigned NOT NULL,
+ `offensive_flag_count` smallint(6) NOT NULL,
+ `favourite_count` int(10) unsigned NOT NULL,
+ `last_edited_at` datetime default NULL,
+ `last_edited_by_id` int(11) default NULL,
+ `last_activity_at` datetime NOT NULL,
+ `last_activity_by_id` int(11) NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `summary` varchar(180) NOT NULL,
+ `html` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `question_author_id` (`author_id`),
+ KEY `question_closed_by_id` (`closed_by_id`),
+ KEY `question_deleted_by_id` (`deleted_by_id`),
+ KEY `question_locked_by_id` (`locked_by_id`),
+ KEY `question_last_edited_by_id` (`last_edited_by_id`),
+ KEY `question_last_activity_by_id` (`last_activity_by_id`),
+ CONSTRAINT `author_id_refs_id_56e9d00c` FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `closed_by_id_refs_id_56e9d00c` FOREIGN KEY (`closed_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `deleted_by_id_refs_id_56e9d00c` FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_activity_by_id_refs_id_56e9d00c` FOREIGN KEY (`last_activity_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_edited_by_id_refs_id_56e9d00c` FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `locked_by_id_refs_id_56e9d00c` FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`question_tags`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`question_tags`;
+CREATE TABLE `cnprog`.`question_tags` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `tag_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `question_id` (`question_id`,`tag_id`),
+ KEY `tag_id_refs_id_43fcb953` (`tag_id`),
+ CONSTRAINT `question_id_refs_id_266147c6` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
+ CONSTRAINT `tag_id_refs_id_43fcb953` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`tag`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`tag`;
+CREATE TABLE `cnprog`.`tag` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(255) NOT NULL,
+ `created_by_id` int(11) NOT NULL,
+ `used_count` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`),
+ KEY `tag_created_by_id` (`created_by_id`),
+ CONSTRAINT `created_by_id_refs_id_47205d6d` FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`user_badge`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`user_badge`;
+CREATE TABLE `cnprog`.`user_badge` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `user_id` int(10) unsigned NOT NULL,
+ `badge_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Definition of table `cnprog`.`user_favorite_questions`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`user_favorite_questions`;
+CREATE TABLE `cnprog`.`user_favorite_questions` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `user_id` int(10) unsigned NOT NULL,
+ `question_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`user_favorite_questions`
+--
+
+DROP TABLE IF EXISTS `cnprog`.`vote`;
+CREATE TABLE `cnprog`.`vote` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `vote` smallint(6) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `vote_content_type_id` (`content_type_id`),
+ KEY `vote_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_50124414` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_760a4df0` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Dumping data for table `cnprog`.`vote`
+--
+
+
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
diff --git a/sql_scripts/cnprog_new_install_2009_02_28.sql b/sql_scripts/cnprog_new_install_2009_02_28.sql
new file mode 100644
index 00000000..80b9fced
--- /dev/null
+++ b/sql_scripts/cnprog_new_install_2009_02_28.sql
@@ -0,0 +1,456 @@
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE `activity` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `activity_type` smallint(6) NOT NULL,
+ `active_at` datetime NOT NULL,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `is_auditted` tinyint(1) default '0',
+ PRIMARY KEY (`id`),
+ KEY `activity_user_id` (`user_id`),
+ KEY `activity_content_type_id` (`content_type_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=103 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `answer` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` tinyint(1) NOT NULL,
+ `wikified_at` datetime default NULL,
+ `accepted` tinyint(1) NOT NULL,
+ `deleted` tinyint(1) NOT NULL,
+ `deleted_by_id` int(11) default NULL,
+ `locked` tinyint(1) NOT NULL,
+ `locked_by_id` int(11) default NULL,
+ `locked_at` datetime default NULL,
+ `score` int(11) NOT NULL,
+ `comment_count` int(10) unsigned NOT NULL,
+ `offensive_flag_count` smallint(6) NOT NULL,
+ `last_edited_at` datetime default NULL,
+ `last_edited_by_id` int(11) default NULL,
+ `html` longtext NOT NULL,
+ `vote_up_count` int(11) NOT NULL,
+ `vote_down_count` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `answer_question_id` (`question_id`),
+ KEY `answer_author_id` (`author_id`),
+ KEY `answer_deleted_by_id` (`deleted_by_id`),
+ KEY `answer_locked_by_id` (`locked_by_id`),
+ KEY `answer_last_edited_by_id` (`last_edited_by_id`),
+ CONSTRAINT `author_id_refs_id_192b0170` FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `deleted_by_id_refs_id_192b0170` FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_edited_by_id_refs_id_192b0170` FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `locked_by_id_refs_id_192b0170` FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `question_id_refs_id_7d6550c9` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `answer_revision` (
+ `id` int(11) NOT NULL auto_increment,
+ `answer_id` int(11) NOT NULL,
+ `revision` int(10) unsigned NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `revised_at` datetime NOT NULL,
+ `summary` varchar(300) collate utf8_unicode_ci NOT NULL,
+ `text` longtext collate utf8_unicode_ci NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `answer_revision_answer_id` (`answer_id`),
+ KEY `answer_revision_author_id` (`author_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_group` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(80) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_group_permissions` (
+ `id` int(11) NOT NULL auto_increment,
+ `group_id` int(11) NOT NULL,
+ `permission_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `group_id` (`group_id`,`permission_id`),
+ KEY `permission_id_refs_id_5886d21f` (`permission_id`),
+ CONSTRAINT `group_id_refs_id_3cea63fe` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
+ CONSTRAINT `permission_id_refs_id_5886d21f` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_message` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `message` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `auth_message_user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_650f49a6` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_permission` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(50) NOT NULL,
+ `content_type_id` int(11) NOT NULL,
+ `codename` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`codename`),
+ KEY `auth_permission_content_type_id` (`content_type_id`),
+ CONSTRAINT `content_type_id_refs_id_728de91f` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_user` (
+ `id` int(11) NOT NULL auto_increment,
+ `username` varchar(30) NOT NULL,
+ `first_name` varchar(30) NOT NULL,
+ `last_name` varchar(30) NOT NULL,
+ `email` varchar(75) NOT NULL,
+ `password` varchar(128) NOT NULL,
+ `is_staff` tinyint(1) NOT NULL,
+ `is_active` tinyint(1) NOT NULL,
+ `is_superuser` tinyint(1) NOT NULL,
+ `last_login` datetime NOT NULL,
+ `date_joined` datetime NOT NULL,
+ `gold` smallint(6) NOT NULL default '0',
+ `silver` smallint(5) unsigned NOT NULL default '0',
+ `bronze` smallint(5) unsigned NOT NULL default '0',
+ `reputation` int(10) unsigned default '1',
+ `gravatar` varchar(128) default NULL,
+ `questions_per_page` smallint(5) unsigned default '10',
+ `last_seen` datetime default NULL,
+ `real_name` varchar(100) default NULL,
+ `website` varchar(200) default NULL,
+ `location` varchar(100) default NULL,
+ `date_of_birth` datetime default NULL,
+ `about` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `username` (`username`)
+) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_user_groups` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `group_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`,`group_id`),
+ KEY `group_id_refs_id_f116770` (`group_id`),
+ CONSTRAINT `group_id_refs_id_f116770` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`),
+ CONSTRAINT `user_id_refs_id_7ceef80f` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `auth_user_user_permissions` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `permission_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`,`permission_id`),
+ KEY `permission_id_refs_id_67e79cb` (`permission_id`),
+ CONSTRAINT `permission_id_refs_id_67e79cb` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`),
+ CONSTRAINT `user_id_refs_id_dfbab7d` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `award` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `badge_id` int(11) NOT NULL,
+ `awarded_at` datetime NOT NULL,
+ `notified` tinyint(1) NOT NULL,
+ `content_type_id` int(11) default NULL,
+ `object_id` int(10) default NULL,
+ PRIMARY KEY (`id`),
+ KEY `award_user_id` (`user_id`),
+ KEY `award_badge_id` (`badge_id`),
+ CONSTRAINT `badge_id_refs_id_651af0e1` FOREIGN KEY (`badge_id`) REFERENCES `badge` (`id`),
+ CONSTRAINT `user_id_refs_id_2d83e9b6` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `badge` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(50) NOT NULL,
+ `type` smallint(6) NOT NULL,
+ `slug` varchar(50) NOT NULL,
+ `description` varchar(300) NOT NULL,
+ `multiple` tinyint(1) NOT NULL,
+ `awarded_count` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`,`type`),
+ KEY `badge_slug` (`slug`)
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `comment` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `comment` varchar(300) NOT NULL,
+ `added_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `comment_content_type_id` (`content_type_id`),
+ KEY `comment_user_id` (`user_id`),
+ KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ CONSTRAINT `content_type_id_refs_id_13a5866c` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_6be725e8` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_admin_log` (
+ `id` int(11) NOT NULL auto_increment,
+ `action_time` datetime NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `content_type_id` int(11) default NULL,
+ `object_id` longtext,
+ `object_repr` varchar(200) NOT NULL,
+ `action_flag` smallint(5) unsigned NOT NULL,
+ `change_message` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `django_admin_log_user_id` (`user_id`),
+ KEY `django_admin_log_content_type_id` (`content_type_id`),
+ CONSTRAINT `content_type_id_refs_id_288599e6` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_c8665aa` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_authopenid_association` (
+ `id` int(11) NOT NULL auto_increment,
+ `server_url` longtext NOT NULL,
+ `handle` varchar(255) NOT NULL,
+ `secret` longtext NOT NULL,
+ `issued` int(11) NOT NULL,
+ `lifetime` int(11) NOT NULL,
+ `assoc_type` longtext NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_authopenid_nonce` (
+ `id` int(11) NOT NULL auto_increment,
+ `server_url` varchar(255) NOT NULL,
+ `timestamp` int(11) NOT NULL,
+ `salt` varchar(40) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_authopenid_userassociation` (
+ `id` int(11) NOT NULL auto_increment,
+ `openid_url` varchar(255) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_163d208d` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_authopenid_userpasswordqueue` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `new_password` varchar(30) NOT NULL,
+ `confirm_key` varchar(40) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `user_id` (`user_id`),
+ CONSTRAINT `user_id_refs_id_76bcaaa4` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_content_type` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(100) NOT NULL,
+ `app_label` varchar(100) NOT NULL,
+ `model` varchar(100) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `app_label` (`app_label`,`model`)
+) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_session` (
+ `session_key` varchar(40) NOT NULL,
+ `session_data` longtext NOT NULL,
+ `expire_date` datetime NOT NULL,
+ PRIMARY KEY (`session_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `django_site` (
+ `id` int(11) NOT NULL auto_increment,
+ `domain` varchar(100) NOT NULL,
+ `name` varchar(50) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `favorite_question` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `favorite_question_question_id` (`question_id`),
+ KEY `favorite_question_user_id` (`user_id`),
+ CONSTRAINT `question_id_refs_id_1ebe1cc3` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
+ CONSTRAINT `user_id_refs_id_52853822` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `flagged_item` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `flagged_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `flagged_item_content_type_id` (`content_type_id`),
+ KEY `flagged_item_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_76e44d74` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_35e3c608` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `question` (
+ `id` int(11) NOT NULL auto_increment,
+ `title` varchar(300) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `added_at` datetime NOT NULL,
+ `wiki` tinyint(1) NOT NULL,
+ `wikified_at` datetime default NULL,
+ `answer_accepted` tinyint(1) NOT NULL,
+ `closed` tinyint(1) NOT NULL,
+ `closed_by_id` int(11) default NULL,
+ `closed_at` datetime default NULL,
+ `close_reason` smallint(6) default NULL,
+ `deleted` tinyint(1) NOT NULL,
+ `deleted_at` datetime default NULL,
+ `deleted_by_id` int(11) default NULL,
+ `locked` tinyint(1) NOT NULL,
+ `locked_by_id` int(11) default NULL,
+ `locked_at` datetime default NULL,
+ `score` int(11) NOT NULL,
+ `answer_count` int(10) unsigned NOT NULL,
+ `comment_count` int(10) unsigned NOT NULL,
+ `view_count` int(10) unsigned NOT NULL,
+ `offensive_flag_count` smallint(6) NOT NULL,
+ `favourite_count` int(10) unsigned NOT NULL,
+ `last_edited_at` datetime default NULL,
+ `last_edited_by_id` int(11) default NULL,
+ `last_activity_at` datetime NOT NULL,
+ `last_activity_by_id` int(11) NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `summary` varchar(180) NOT NULL,
+ `html` longtext NOT NULL,
+ `vote_up_count` int(11) NOT NULL,
+ `vote_down_count` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `question_author_id` (`author_id`),
+ KEY `question_closed_by_id` (`closed_by_id`),
+ KEY `question_deleted_by_id` (`deleted_by_id`),
+ KEY `question_locked_by_id` (`locked_by_id`),
+ KEY `question_last_edited_by_id` (`last_edited_by_id`),
+ KEY `question_last_activity_by_id` (`last_activity_by_id`),
+ CONSTRAINT `author_id_refs_id_56e9d00c` FOREIGN KEY (`author_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `closed_by_id_refs_id_56e9d00c` FOREIGN KEY (`closed_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `deleted_by_id_refs_id_56e9d00c` FOREIGN KEY (`deleted_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_activity_by_id_refs_id_56e9d00c` FOREIGN KEY (`last_activity_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `last_edited_by_id_refs_id_56e9d00c` FOREIGN KEY (`last_edited_by_id`) REFERENCES `auth_user` (`id`),
+ CONSTRAINT `locked_by_id_refs_id_56e9d00c` FOREIGN KEY (`locked_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `question_revision` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `revision` int(10) unsigned NOT NULL,
+ `title` varchar(300) NOT NULL,
+ `author_id` int(11) NOT NULL,
+ `revised_at` datetime NOT NULL,
+ `tagnames` varchar(125) NOT NULL,
+ `summary` varchar(300) NOT NULL,
+ `text` longtext NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `question_revision_question_id` (`question_id`),
+ KEY `question_revision_author_id` (`author_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
+
+
+CREATE TABLE `question_tags` (
+ `id` int(11) NOT NULL auto_increment,
+ `question_id` int(11) NOT NULL,
+ `tag_id` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `question_id` (`question_id`,`tag_id`),
+ KEY `tag_id_refs_id_43fcb953` (`tag_id`),
+ CONSTRAINT `question_id_refs_id_266147c6` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`),
+ CONSTRAINT `tag_id_refs_id_43fcb953` FOREIGN KEY (`tag_id`) REFERENCES `tag` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `repute` (
+ `id` int(11) NOT NULL auto_increment,
+ `user_id` int(11) NOT NULL,
+ `positive` smallint(6) NOT NULL,
+ `negative` smallint(6) NOT NULL,
+ `question_id` int(11) NOT NULL,
+ `reputed_at` datetime NOT NULL,
+ `reputation_type` smallint(6) NOT NULL,
+ `reputation` int(11) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `repute_user_id` (`user_id`),
+ KEY `repute_question_id` (`question_id`)
+) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `tag` (
+ `id` int(11) NOT NULL auto_increment,
+ `name` varchar(255) NOT NULL,
+ `created_by_id` int(11) NOT NULL,
+ `used_count` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`),
+ KEY `tag_created_by_id` (`created_by_id`),
+ CONSTRAINT `created_by_id_refs_id_47205d6d` FOREIGN KEY (`created_by_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `user_badge` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `user_id` int(10) unsigned NOT NULL,
+ `badge_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `user_favorite_questions` (
+ `id` int(10) unsigned NOT NULL auto_increment,
+ `user_id` int(10) unsigned NOT NULL,
+ `question_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE `vote` (
+ `id` int(11) NOT NULL auto_increment,
+ `content_type_id` int(11) NOT NULL,
+ `object_id` int(10) unsigned NOT NULL,
+ `user_id` int(11) NOT NULL,
+ `vote` smallint(6) NOT NULL,
+ `voted_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `content_type_id` (`content_type_id`,`object_id`,`user_id`),
+ KEY `vote_content_type_id` (`content_type_id`),
+ KEY `vote_user_id` (`user_id`),
+ CONSTRAINT `content_type_id_refs_id_50124414` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`),
+ CONSTRAINT `user_id_refs_id_760a4df0` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+
+
+SET FOREIGN_KEY_CHECKS = 1;
diff --git a/sql_scripts/cnprog_new_install_2009_03_31.sql b/sql_scripts/cnprog_new_install_2009_03_31.sql
new file mode 100644
index 00000000..c2c69f36
--- /dev/null
+++ b/sql_scripts/cnprog_new_install_2009_03_31.sql
@@ -0,0 +1,891 @@
+USE cnprog;
+
+
+/************ Update: Tables ***************/
+
+/******************** Add Table: activity ************************/
+
+/* Build Table Structure */
+CREATE TABLE activity
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ activity_type SMALLINT NOT NULL,
+ active_at DATETIME NOT NULL,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ is_auditted TINYINT NULL DEFAULT 0
+) ENGINE=MyISAM AUTO_INCREMENT=103 DEFAULT CHARSET=latin1;
+
+/* Table Items: activity */
+
+/* Add Indexes for: activity */
+CREATE INDEX activity_content_type_id ON activity (content_type_id);
+CREATE INDEX activity_user_id ON activity (user_id);
+
+/******************** Add Table: answer ************************/
+
+/* Build Table Structure */
+CREATE TABLE answer
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ author_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ wiki TINYINT NOT NULL,
+ wikified_at DATETIME NULL,
+ accepted TINYINT NOT NULL,
+ deleted TINYINT NOT NULL,
+ deleted_by_id INTEGER NULL,
+ locked TINYINT NOT NULL,
+ locked_by_id INTEGER NULL,
+ locked_at DATETIME NULL,
+ score INTEGER NOT NULL,
+ comment_count INTEGER UNSIGNED NOT NULL,
+ offensive_flag_count SMALLINT NOT NULL,
+ last_edited_at DATETIME NULL,
+ last_edited_by_id INTEGER NULL,
+ html LONGTEXT NOT NULL,
+ vote_up_count INTEGER NOT NULL,
+ vote_down_count INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+/* Table Items: answer */
+
+/* Add Indexes for: answer */
+CREATE INDEX answer_author_id ON answer (author_id);
+CREATE INDEX answer_deleted_by_id ON answer (deleted_by_id);
+CREATE INDEX answer_last_edited_by_id ON answer (last_edited_by_id);
+CREATE INDEX answer_locked_by_id ON answer (locked_by_id);
+CREATE INDEX answer_question_id ON answer (question_id);
+
+/******************** Add Table: answer_revision ************************/
+
+/* Build Table Structure */
+CREATE TABLE answer_revision
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ answer_id INTEGER NOT NULL,
+ revision INTEGER UNSIGNED NOT NULL,
+ author_id INTEGER NOT NULL,
+ revised_at DATETIME NOT NULL,
+ summary TEXT NOT NULL,
+ `text` LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+/* Table Items: answer_revision */
+
+/* Add Indexes for: answer_revision */
+CREATE INDEX answer_revision_answer_id ON answer_revision (answer_id);
+CREATE INDEX answer_revision_author_id ON answer_revision (author_id);
+
+/******************** Add Table: auth_group ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_group
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(80) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_group */
+
+/* Add Indexes for: auth_group */
+CREATE UNIQUE INDEX name ON auth_group (name);
+
+/******************** Add Table: auth_group_permissions ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_group_permissions
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ group_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_group_permissions */
+
+/* Add Indexes for: auth_group_permissions */
+CREATE UNIQUE INDEX group_id ON auth_group_permissions (group_id, permission_id);
+CREATE INDEX permission_id_refs_id_5886d21f ON auth_group_permissions (permission_id);
+
+/******************** Add Table: auth_message ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_message
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ message LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_message */
+
+/* Add Indexes for: auth_message */
+CREATE INDEX auth_message_user_id ON auth_message (user_id);
+
+/******************** Add Table: auth_permission ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_permission
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ content_type_id INTEGER NOT NULL,
+ codename VARCHAR(100) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_permission */
+
+/* Add Indexes for: auth_permission */
+CREATE INDEX auth_permission_content_type_id ON auth_permission (content_type_id);
+CREATE UNIQUE INDEX content_type_id ON auth_permission (content_type_id, codename);
+
+/******************** Add Table: auth_user ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(30) NOT NULL,
+ first_name VARCHAR(30) NOT NULL,
+ last_name VARCHAR(30) NOT NULL,
+ email VARCHAR(75) NOT NULL,
+ password VARCHAR(128) NOT NULL,
+ is_staff TINYINT NOT NULL,
+ is_active TINYINT NOT NULL,
+ is_superuser TINYINT NOT NULL,
+ last_login DATETIME NOT NULL,
+ date_joined DATETIME NOT NULL,
+ gold SMALLINT NOT NULL DEFAULT 0,
+ silver SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ bronze SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ reputation INTEGER UNSIGNED NULL DEFAULT 1,
+ gravatar VARCHAR(128) NULL,
+ questions_per_page SMALLINT UNSIGNED NULL DEFAULT 10,
+ last_seen DATETIME NULL,
+ real_name VARCHAR(100) NULL,
+ website VARCHAR(200) NULL,
+ location VARCHAR(100) NULL,
+ date_of_birth DATETIME NULL,
+ about TEXT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user */
+
+/* Add Indexes for: auth_user */
+CREATE UNIQUE INDEX username ON auth_user (username);
+
+/******************** Add Table: auth_user_groups ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user_groups
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ group_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user_groups */
+
+/* Add Indexes for: auth_user_groups */
+CREATE INDEX group_id_refs_id_f116770 ON auth_user_groups (group_id);
+CREATE UNIQUE INDEX user_id ON auth_user_groups (user_id, group_id);
+
+/******************** Add Table: auth_user_user_permissions ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user_user_permissions
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user_user_permissions */
+
+/* Add Indexes for: auth_user_user_permissions */
+CREATE INDEX permission_id_refs_id_67e79cb ON auth_user_user_permissions (permission_id);
+CREATE UNIQUE INDEX user_id ON auth_user_user_permissions (user_id, permission_id);
+
+/******************** Add Table: award ************************/
+
+/* Build Table Structure */
+CREATE TABLE award
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ badge_id INTEGER NOT NULL,
+ awarded_at DATETIME NOT NULL,
+ notified TINYINT NOT NULL,
+ content_type_id INTEGER NULL,
+ object_id INTEGER NULL
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+
+/* Table Items: award */
+
+/* Add Indexes for: award */
+CREATE INDEX award_badge_id ON award (badge_id);
+CREATE INDEX award_user_id ON award (user_id);
+
+/******************** Add Table: badge ************************/
+
+/* Build Table Structure */
+CREATE TABLE badge
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ `type` SMALLINT NOT NULL,
+ slug VARCHAR(50) NOT NULL,
+ description TEXT NOT NULL,
+ multiple TINYINT NOT NULL,
+ awarded_count INTEGER UNSIGNED NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+/* Table Items: badge */
+
+/* Add Indexes for: badge */
+CREATE INDEX badge_slug ON badge (slug);
+CREATE UNIQUE INDEX name ON badge (name, `type`);
+
+/******************** Add Table: book ************************/
+
+/* Build Table Structure */
+CREATE TABLE book
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ short_name VARCHAR(255) NOT NULL,
+ author VARCHAR(255) NOT NULL,
+ user_id INTEGER NULL,
+ price DECIMAL(10, 2) NULL,
+ pages SMALLINT NULL,
+ published_at DATE NOT NULL,
+ publication VARCHAR(255) NOT NULL,
+ cover_img VARCHAR(255) NULL,
+ tagnames VARCHAR(125) NULL,
+ added_at DATETIME NOT NULL,
+ last_edited_at DATETIME NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book */
+
+/* Add Indexes for: book */
+CREATE UNIQUE INDEX book_short_name_Idx ON book (short_name);
+CREATE INDEX fk_books_auth_user ON book (user_id);
+
+/******************** Add Table: book_author_info ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_author_info
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ blog_url VARCHAR(255) NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ last_edited_at DATETIME NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_author_info */
+
+/* Add Indexes for: book_author_info */
+CREATE INDEX fk_book_author_info_auth_user ON book_author_info (user_id);
+
+/******************** Add Table: book_author_rss ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_author_rss
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ url VARCHAR(255) NOT NULL,
+ rss_created_at DATETIME NOT NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_author_rss */
+
+/* Add Indexes for: book_author_rss */
+CREATE INDEX fk_book_author_rss_auth_user ON book_author_rss (user_id);
+
+/******************** Add Table: book_question ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ book_id INTEGER NOT NULL,
+ question_id INTEGER NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_question */
+
+/* Add Indexes for: book_question */
+CREATE INDEX fk_book_question_book ON book_question (book_id);
+CREATE INDEX fk_book_question_question ON book_question (question_id);
+
+/******************** Add Table: `comment` ************************/
+
+/* Build Table Structure */
+CREATE TABLE `comment`
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ `comment` TEXT NOT NULL,
+ added_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+/* Table Items: `comment` */
+
+/* Add Indexes for: comment */
+CREATE INDEX comment_content_type_id ON `comment` (content_type_id);
+CREATE INDEX comment_user_id ON `comment` (user_id);
+CREATE INDEX content_type_id ON `comment` (content_type_id, object_id, user_id);
+
+/******************** Add Table: django_admin_log ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_admin_log
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ action_time DATETIME NOT NULL,
+ user_id INTEGER NOT NULL,
+ content_type_id INTEGER NULL,
+ object_id LONGTEXT NULL,
+ object_repr VARCHAR(200) NOT NULL,
+ action_flag SMALLINT UNSIGNED NOT NULL,
+ change_message LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_admin_log */
+
+/* Add Indexes for: django_admin_log */
+CREATE INDEX django_admin_log_content_type_id ON django_admin_log (content_type_id);
+CREATE INDEX django_admin_log_user_id ON django_admin_log (user_id);
+
+/******************** Add Table: django_authopenid_association ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_association
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ server_url LONGTEXT NOT NULL,
+ handle VARCHAR(255) NOT NULL,
+ secret LONGTEXT NOT NULL,
+ issued INTEGER NOT NULL,
+ lifetime INTEGER NOT NULL,
+ assoc_type LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: django_authopenid_nonce ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_nonce
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ server_url VARCHAR(255) NOT NULL,
+ `timestamp` INTEGER NOT NULL,
+ salt VARCHAR(40) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: django_authopenid_userassociation ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_userassociation
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ openid_url VARCHAR(255) NOT NULL,
+ user_id INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_authopenid_userassociation */
+
+/* Add Indexes for: django_authopenid_userassociation */
+CREATE UNIQUE INDEX user_id ON django_authopenid_userassociation (user_id);
+
+/******************** Add Table: django_authopenid_userpasswordqueue ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_userpasswordqueue
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ new_password VARCHAR(30) NOT NULL,
+ confirm_key VARCHAR(40) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: django_authopenid_userpasswordqueue */
+
+/* Add Indexes for: django_authopenid_userpasswordqueue */
+CREATE UNIQUE INDEX user_id ON django_authopenid_userpasswordqueue (user_id);
+
+/******************** Add Table: django_content_type ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_content_type
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ app_label VARCHAR(100) NOT NULL,
+ model VARCHAR(100) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_content_type */
+
+/* Add Indexes for: django_content_type */
+CREATE UNIQUE INDEX app_label ON django_content_type (app_label, model);
+
+/******************** Add Table: django_session ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_session
+(
+ session_key VARCHAR(40) NOT NULL,
+ session_data LONGTEXT NOT NULL,
+ expire_date DATETIME NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: django_session */
+ALTER TABLE django_session ADD CONSTRAINT pkdjango_session
+ PRIMARY KEY (session_key);
+
+/******************** Add Table: django_site ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_site
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ domain VARCHAR(100) NOT NULL,
+ name VARCHAR(50) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: favorite_question ************************/
+
+/* Build Table Structure */
+CREATE TABLE favorite_question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+/* Table Items: favorite_question */
+
+/* Add Indexes for: favorite_question */
+CREATE INDEX favorite_question_question_id ON favorite_question (question_id);
+CREATE INDEX favorite_question_user_id ON favorite_question (user_id);
+
+/******************** Add Table: flagged_item ************************/
+
+/* Build Table Structure */
+CREATE TABLE flagged_item
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ flagged_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+/* Table Items: flagged_item */
+
+/* Add Indexes for: flagged_item */
+CREATE UNIQUE INDEX content_type_id ON flagged_item (content_type_id, object_id, user_id);
+CREATE INDEX flagged_item_content_type_id ON flagged_item (content_type_id);
+CREATE INDEX flagged_item_user_id ON flagged_item (user_id);
+
+/******************** Add Table: question ************************/
+
+/* Build Table Structure */
+CREATE TABLE question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title TEXT NOT NULL,
+ author_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ wiki TINYINT NOT NULL,
+ wikified_at DATETIME NULL,
+ answer_accepted TINYINT NOT NULL,
+ closed TINYINT NOT NULL,
+ closed_by_id INTEGER NULL,
+ closed_at DATETIME NULL,
+ close_reason SMALLINT NULL,
+ deleted TINYINT NOT NULL,
+ deleted_at DATETIME NULL,
+ deleted_by_id INTEGER NULL,
+ locked TINYINT NOT NULL,
+ locked_by_id INTEGER NULL,
+ locked_at DATETIME NULL,
+ score INTEGER NOT NULL,
+ answer_count INTEGER UNSIGNED NOT NULL,
+ comment_count INTEGER UNSIGNED NOT NULL,
+ view_count INTEGER UNSIGNED NOT NULL,
+ offensive_flag_count SMALLINT NOT NULL,
+ favourite_count INTEGER UNSIGNED NOT NULL,
+ last_edited_at DATETIME NULL,
+ last_edited_by_id INTEGER NULL,
+ last_activity_at DATETIME NOT NULL,
+ last_activity_by_id INTEGER NOT NULL,
+ tagnames VARCHAR(125) NOT NULL,
+ summary VARCHAR(180) NOT NULL,
+ html LONGTEXT NOT NULL,
+ vote_up_count INTEGER NOT NULL,
+ vote_down_count INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+
+/* Table Items: question */
+
+/* Add Indexes for: question */
+CREATE INDEX question_author_id ON question (author_id);
+CREATE INDEX question_closed_by_id ON question (closed_by_id);
+CREATE INDEX question_deleted_by_id ON question (deleted_by_id);
+CREATE INDEX question_last_activity_by_id ON question (last_activity_by_id);
+CREATE INDEX question_last_edited_by_id ON question (last_edited_by_id);
+CREATE INDEX question_locked_by_id ON question (locked_by_id);
+
+/******************** Add Table: question_revision ************************/
+
+/* Build Table Structure */
+CREATE TABLE question_revision
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ revision INTEGER UNSIGNED NOT NULL,
+ title TEXT NOT NULL,
+ author_id INTEGER NOT NULL,
+ revised_at DATETIME NOT NULL,
+ tagnames VARCHAR(125) NOT NULL,
+ summary TEXT NOT NULL,
+ `text` LONGTEXT NOT NULL
+) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
+
+/* Table Items: question_revision */
+
+/* Add Indexes for: question_revision */
+CREATE INDEX question_revision_author_id ON question_revision (author_id);
+CREATE INDEX question_revision_question_id ON question_revision (question_id);
+
+/******************** Add Table: question_tags ************************/
+
+/* Build Table Structure */
+CREATE TABLE question_tags
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ tag_id INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
+
+/* Table Items: question_tags */
+
+/* Add Indexes for: question_tags */
+CREATE UNIQUE INDEX question_id ON question_tags (question_id, tag_id);
+CREATE INDEX tag_id_refs_id_43fcb953 ON question_tags (tag_id);
+
+/******************** Add Table: repute ************************/
+
+/* Build Table Structure */
+CREATE TABLE repute
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ positive SMALLINT NOT NULL,
+ negative SMALLINT NOT NULL,
+ question_id INTEGER NOT NULL,
+ reputed_at DATETIME NOT NULL,
+ reputation_type SMALLINT NOT NULL,
+ reputation INTEGER NOT NULL
+) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1;
+
+/* Table Items: repute */
+
+/* Add Indexes for: repute */
+CREATE INDEX repute_question_id ON repute (question_id);
+CREATE INDEX repute_user_id ON repute (user_id);
+
+/******************** Add Table: tag ************************/
+
+/* Build Table Structure */
+CREATE TABLE tag
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ created_by_id INTEGER NOT NULL,
+ used_count INTEGER UNSIGNED NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+/* Table Items: tag */
+
+/* Add Indexes for: tag */
+CREATE UNIQUE INDEX name ON tag (name);
+CREATE INDEX tag_created_by_id ON tag (created_by_id);
+
+/******************** Add Table: user_badge ************************/
+
+/* Build Table Structure */
+CREATE TABLE user_badge
+(
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ badge_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: user_badge */
+
+/* Add Indexes for: user_badge */
+CREATE INDEX fk_user_badge_auth_user ON user_badge (user_id);
+CREATE INDEX fk_user_badge_badge ON user_badge (badge_id);
+
+/******************** Add Table: user_favorite_questions ************************/
+
+/* Build Table Structure */
+CREATE TABLE user_favorite_questions
+(
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ question_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: user_favorite_questions */
+
+/* Add Indexes for: user_favorite_questions */
+CREATE INDEX fk_user_favorite_questions_auth_user ON user_favorite_questions (user_id);
+CREATE INDEX fk_user_favorite_questions_question ON user_favorite_questions (question_id);
+
+/******************** Add Table: vote ************************/
+
+/* Build Table Structure */
+CREATE TABLE vote
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ vote SMALLINT NOT NULL,
+ voted_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+
+/* Table Items: vote */
+
+/* Add Indexes for: vote */
+CREATE UNIQUE INDEX content_type_id ON vote (content_type_id, object_id, user_id);
+CREATE INDEX vote_content_type_id ON vote (content_type_id);
+CREATE INDEX vote_user_id ON vote (user_id);
+
+
+/************ Add Foreign Keys to Database ***************/
+/*-----------------------------------------------------------
+Warning: Versions of MySQL prior to 4.1.2 require indexes on all columns involved in a foreign key. The following indexes may be required:
+fk_auth_group_permissions_auth_group may require an index on table: auth_group_permissions, column: group_id
+fk_auth_user_groups_auth_user may require an index on table: auth_user_groups, column: user_id
+fk_auth_user_user_permissions_auth_user may require an index on table: auth_user_user_permissions, column: user_id
+fk_question_tags_question may require an index on table: question_tags, column: question_id
+-----------------------------------------------------------
+*/
+
+/************ Foreign Key: fk_activity_auth_user ***************/
+ALTER TABLE activity ADD CONSTRAINT fk_activity_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: deleted_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT deleted_by_id_refs_id_192b0170
+ FOREIGN KEY (deleted_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_auth_user ***************/
+ALTER TABLE answer ADD CONSTRAINT fk_answer_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_question ***************/
+ALTER TABLE answer ADD CONSTRAINT fk_answer_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_edited_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT last_edited_by_id_refs_id_192b0170
+ FOREIGN KEY (last_edited_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: locked_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT locked_by_id_refs_id_192b0170
+ FOREIGN KEY (locked_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_revision_auth_user ***************/
+ALTER TABLE answer_revision ADD CONSTRAINT fk_answer_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_group_permissions_auth_group ***************/
+ALTER TABLE auth_group_permissions ADD CONSTRAINT fk_auth_group_permissions_auth_group
+ FOREIGN KEY (group_id) REFERENCES auth_group (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_group_permissions_auth_permission ***************/
+ALTER TABLE auth_group_permissions ADD CONSTRAINT fk_auth_group_permissions_auth_permission
+ FOREIGN KEY (permission_id) REFERENCES auth_permission (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_message_auth_user ***************/
+ALTER TABLE auth_message ADD CONSTRAINT fk_auth_message_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_permission_django_content_type ***************/
+ALTER TABLE auth_permission ADD CONSTRAINT fk_auth_permission_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_groups_auth_group ***************/
+ALTER TABLE auth_user_groups ADD CONSTRAINT fk_auth_user_groups_auth_group
+ FOREIGN KEY (group_id) REFERENCES auth_group (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_groups_auth_user ***************/
+ALTER TABLE auth_user_groups ADD CONSTRAINT fk_auth_user_groups_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_user_permissions_auth_permission ***************/
+ALTER TABLE auth_user_user_permissions ADD CONSTRAINT fk_auth_user_user_permissions_auth_permission
+ FOREIGN KEY (permission_id) REFERENCES auth_permission (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_user_permissions_auth_user ***************/
+ALTER TABLE auth_user_user_permissions ADD CONSTRAINT fk_auth_user_user_permissions_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_award_auth_user ***************/
+ALTER TABLE award ADD CONSTRAINT fk_award_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_award_badge ***************/
+ALTER TABLE award ADD CONSTRAINT fk_award_badge
+ FOREIGN KEY (badge_id) REFERENCES badge (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_books_auth_user ***************/
+ALTER TABLE book ADD CONSTRAINT fk_books_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_info_auth_user ***************/
+ALTER TABLE book_author_info ADD CONSTRAINT fk_book_author_info_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_rss_auth_user ***************/
+ALTER TABLE book_author_rss ADD CONSTRAINT fk_book_author_rss_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_question_book ***************/
+ALTER TABLE book_question ADD CONSTRAINT fk_book_question_book
+ FOREIGN KEY (book_id) REFERENCES book (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_question_question ***************/
+ALTER TABLE book_question ADD CONSTRAINT fk_book_question_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_comment_auth_user ***************/
+ALTER TABLE `comment` ADD CONSTRAINT fk_comment_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_comment_django_content_type ***************/
+ALTER TABLE `comment` ADD CONSTRAINT fk_comment_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_admin_log_auth_user ***************/
+ALTER TABLE django_admin_log ADD CONSTRAINT fk_django_admin_log_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_admin_log_django_content_type ***************/
+ALTER TABLE django_admin_log ADD CONSTRAINT fk_django_admin_log_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_authopenid_userassociation_auth_user ***************/
+ALTER TABLE django_authopenid_userassociation ADD CONSTRAINT fk_django_authopenid_userassociation_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_authopenid_userpasswordqueue_auth_user ***************/
+ALTER TABLE django_authopenid_userpasswordqueue ADD CONSTRAINT fk_django_authopenid_userpasswordqueue_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_favorite_question_auth_user ***************/
+ALTER TABLE favorite_question ADD CONSTRAINT fk_favorite_question_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_favorite_question_question ***************/
+ALTER TABLE favorite_question ADD CONSTRAINT fk_favorite_question_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_flagged_item_auth_user ***************/
+ALTER TABLE flagged_item ADD CONSTRAINT fk_flagged_item_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_flagged_item_django_content_type ***************/
+ALTER TABLE flagged_item ADD CONSTRAINT fk_flagged_item_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: closed_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT closed_by_id_refs_id_56e9d00c
+ FOREIGN KEY (closed_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: deleted_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT deleted_by_id_refs_id_56e9d00c
+ FOREIGN KEY (deleted_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_auth_user ***************/
+ALTER TABLE question ADD CONSTRAINT fk_question_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_activity_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT last_activity_by_id_refs_id_56e9d00c
+ FOREIGN KEY (last_activity_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_edited_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT last_edited_by_id_refs_id_56e9d00c
+ FOREIGN KEY (last_edited_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: locked_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT locked_by_id_refs_id_56e9d00c
+ FOREIGN KEY (locked_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_auth_user ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_question ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_tags_question ***************/
+ALTER TABLE question_tags ADD CONSTRAINT fk_question_tags_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_tags_tag ***************/
+ALTER TABLE question_tags ADD CONSTRAINT fk_question_tags_tag
+ FOREIGN KEY (tag_id) REFERENCES tag (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_auth_user ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_question ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_tag_auth_user ***************/
+ALTER TABLE tag ADD CONSTRAINT fk_tag_auth_user
+ FOREIGN KEY (created_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_badge_auth_user ***************/
+ALTER TABLE user_badge ADD CONSTRAINT fk_user_badge_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_badge_badge ***************/
+ALTER TABLE user_badge ADD CONSTRAINT fk_user_badge_badge
+ FOREIGN KEY (badge_id) REFERENCES badge (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_favorite_questions_auth_user ***************/
+ALTER TABLE user_favorite_questions ADD CONSTRAINT fk_user_favorite_questions_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_favorite_questions_question ***************/
+ALTER TABLE user_favorite_questions ADD CONSTRAINT fk_user_favorite_questions_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_vote_auth_user ***************/
+ALTER TABLE vote ADD CONSTRAINT fk_vote_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_vote_django_content_type ***************/
+ALTER TABLE vote ADD CONSTRAINT fk_vote_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION; \ No newline at end of file
diff --git a/sql_scripts/cnprog_new_install_2009_04_07.sql b/sql_scripts/cnprog_new_install_2009_04_07.sql
new file mode 100644
index 00000000..ff9016fa
--- /dev/null
+++ b/sql_scripts/cnprog_new_install_2009_04_07.sql
@@ -0,0 +1,24 @@
+USE cnprog;
+
+
+/************ Add Foreign Keys to Database ***************/
+
+/************ Foreign Key: fk_activity_auth_user ***************/
+ALTER TABLE activity ADD CONSTRAINT fk_activity_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_auth_user ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_question ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_auth_user ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_question ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION; \ No newline at end of file
diff --git a/sql_scripts/cnprog_new_install_2009_04_09.sql b/sql_scripts/cnprog_new_install_2009_04_09.sql
new file mode 100644
index 00000000..f4424852
--- /dev/null
+++ b/sql_scripts/cnprog_new_install_2009_04_09.sql
@@ -0,0 +1,904 @@
+USE cnprog;
+
+
+/************ Update: Tables ***************/
+
+/******************** Add Table: activity ************************/
+
+/* Build Table Structure */
+CREATE TABLE activity
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ activity_type SMALLINT NOT NULL,
+ active_at DATETIME NOT NULL,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ is_auditted TINYINT NULL DEFAULT 0
+) ENGINE=MyISAM AUTO_INCREMENT=103 DEFAULT CHARSET=latin1;
+
+/* Table Items: activity */
+
+/* Add Indexes for: activity */
+CREATE INDEX activity_content_type_id ON activity (content_type_id);
+CREATE INDEX activity_user_id ON activity (user_id);
+
+/******************** Add Table: answer ************************/
+
+/* Build Table Structure */
+CREATE TABLE answer
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ author_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ wiki TINYINT NOT NULL,
+ wikified_at DATETIME NULL,
+ accepted TINYINT NOT NULL,
+ deleted TINYINT NOT NULL,
+ deleted_by_id INTEGER NULL,
+ locked TINYINT NOT NULL,
+ locked_by_id INTEGER NULL,
+ locked_at DATETIME NULL,
+ score INTEGER NOT NULL,
+ comment_count INTEGER UNSIGNED NOT NULL,
+ offensive_flag_count SMALLINT NOT NULL,
+ last_edited_at DATETIME NULL,
+ last_edited_by_id INTEGER NULL,
+ html LONGTEXT NOT NULL,
+ vote_up_count INTEGER NOT NULL,
+ vote_down_count INTEGER NOT NULL,
+ accepted_at DATETIME NULL
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+/* Table Items: answer */
+
+/* Add Indexes for: answer */
+CREATE INDEX answer_author_id ON answer (author_id);
+CREATE INDEX answer_deleted_by_id ON answer (deleted_by_id);
+CREATE INDEX answer_last_edited_by_id ON answer (last_edited_by_id);
+CREATE INDEX answer_locked_by_id ON answer (locked_by_id);
+CREATE INDEX answer_question_id ON answer (question_id);
+
+/******************** Add Table: answer_revision ************************/
+
+/* Build Table Structure */
+CREATE TABLE answer_revision
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ answer_id INTEGER NOT NULL,
+ revision INTEGER UNSIGNED NOT NULL,
+ author_id INTEGER NOT NULL,
+ revised_at DATETIME NOT NULL,
+ summary TEXT NOT NULL,
+ `text` LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+/* Table Items: answer_revision */
+
+/* Add Indexes for: answer_revision */
+CREATE INDEX answer_revision_answer_id ON answer_revision (answer_id);
+CREATE INDEX answer_revision_author_id ON answer_revision (author_id);
+
+/******************** Add Table: auth_group ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_group
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(80) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_group */
+
+/* Add Indexes for: auth_group */
+CREATE UNIQUE INDEX name ON auth_group (name);
+
+/******************** Add Table: auth_group_permissions ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_group_permissions
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ group_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_group_permissions */
+
+/* Add Indexes for: auth_group_permissions */
+CREATE UNIQUE INDEX group_id ON auth_group_permissions (group_id, permission_id);
+CREATE INDEX permission_id_refs_id_5886d21f ON auth_group_permissions (permission_id);
+
+/******************** Add Table: auth_message ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_message
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ message LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_message */
+
+/* Add Indexes for: auth_message */
+CREATE INDEX auth_message_user_id ON auth_message (user_id);
+
+/******************** Add Table: auth_permission ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_permission
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ content_type_id INTEGER NOT NULL,
+ codename VARCHAR(100) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_permission */
+
+/* Add Indexes for: auth_permission */
+CREATE INDEX auth_permission_content_type_id ON auth_permission (content_type_id);
+CREATE UNIQUE INDEX content_type_id ON auth_permission (content_type_id, codename);
+
+/******************** Add Table: auth_user ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ username VARCHAR(30) NOT NULL,
+ first_name VARCHAR(30) NOT NULL,
+ last_name VARCHAR(30) NOT NULL,
+ email VARCHAR(75) NOT NULL,
+ password VARCHAR(128) NOT NULL,
+ is_staff TINYINT NOT NULL,
+ is_active TINYINT NOT NULL,
+ is_superuser TINYINT NOT NULL,
+ last_login DATETIME NOT NULL,
+ date_joined DATETIME NOT NULL,
+ gold SMALLINT NOT NULL DEFAULT 0,
+ silver SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ bronze SMALLINT UNSIGNED NOT NULL DEFAULT 0,
+ reputation INTEGER UNSIGNED NULL DEFAULT 1,
+ gravatar VARCHAR(128) NULL,
+ questions_per_page SMALLINT UNSIGNED NULL DEFAULT 10,
+ last_seen DATETIME NULL,
+ real_name VARCHAR(100) NULL,
+ website VARCHAR(200) NULL,
+ location VARCHAR(100) NULL,
+ date_of_birth DATETIME NULL,
+ about TEXT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user */
+
+/* Add Indexes for: auth_user */
+CREATE UNIQUE INDEX username ON auth_user (username);
+
+/******************** Add Table: auth_user_groups ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user_groups
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ group_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user_groups */
+
+/* Add Indexes for: auth_user_groups */
+CREATE INDEX group_id_refs_id_f116770 ON auth_user_groups (group_id);
+CREATE UNIQUE INDEX user_id ON auth_user_groups (user_id, group_id);
+
+/******************** Add Table: auth_user_user_permissions ************************/
+
+/* Build Table Structure */
+CREATE TABLE auth_user_user_permissions
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ permission_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: auth_user_user_permissions */
+
+/* Add Indexes for: auth_user_user_permissions */
+CREATE INDEX permission_id_refs_id_67e79cb ON auth_user_user_permissions (permission_id);
+CREATE UNIQUE INDEX user_id ON auth_user_user_permissions (user_id, permission_id);
+
+/******************** Add Table: award ************************/
+
+/* Build Table Structure */
+CREATE TABLE award
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ badge_id INTEGER NOT NULL,
+ awarded_at DATETIME NOT NULL,
+ notified TINYINT NOT NULL,
+ content_type_id INTEGER NULL,
+ object_id INTEGER NULL
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+
+/* Table Items: award */
+
+/* Add Indexes for: award */
+CREATE INDEX award_badge_id ON award (badge_id);
+CREATE INDEX award_user_id ON award (user_id);
+
+/******************** Add Table: badge ************************/
+
+/* Build Table Structure */
+CREATE TABLE badge
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ `type` SMALLINT NOT NULL,
+ slug VARCHAR(50) NOT NULL,
+ description TEXT NOT NULL,
+ multiple TINYINT NOT NULL,
+ awarded_count INTEGER UNSIGNED NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;
+
+/* Table Items: badge */
+
+/* Add Indexes for: badge */
+CREATE INDEX badge_slug ON badge (slug);
+CREATE UNIQUE INDEX name ON badge (name, `type`);
+
+/******************** Add Table: book ************************/
+
+/* Build Table Structure */
+CREATE TABLE book
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ short_name VARCHAR(255) NOT NULL,
+ author VARCHAR(255) NOT NULL,
+ user_id INTEGER NULL,
+ price DECIMAL(10, 2) NULL,
+ pages SMALLINT NULL,
+ published_at DATE NOT NULL,
+ publication VARCHAR(255) NOT NULL,
+ cover_img VARCHAR(255) NULL,
+ tagnames VARCHAR(125) NULL,
+ added_at DATETIME NOT NULL,
+ last_edited_at DATETIME NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book */
+
+/* Add Indexes for: book */
+CREATE UNIQUE INDEX book_short_name_Idx ON book (short_name);
+CREATE INDEX fk_books_auth_user ON book (user_id);
+
+/******************** Add Table: book_author_info ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_author_info
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ blog_url VARCHAR(255) NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ last_edited_at DATETIME NOT NULL,
+ book_id INTEGER NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_author_info */
+
+/* Add Indexes for: book_author_info */
+CREATE INDEX fk_book_author_info_auth_user ON book_author_info (user_id);
+CREATE INDEX fk_book_author_info_book ON book_author_info (book_id);
+
+/******************** Add Table: book_author_rss ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_author_rss
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ url VARCHAR(255) NOT NULL,
+ rss_created_at DATETIME NOT NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ book_id INTEGER NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_author_rss */
+
+/* Add Indexes for: book_author_rss */
+CREATE INDEX fk_book_author_rss_auth_user ON book_author_rss (user_id);
+CREATE INDEX fk_book_author_rss_book ON book_author_rss (book_id);
+
+/******************** Add Table: book_question ************************/
+
+/* Build Table Structure */
+CREATE TABLE book_question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ book_id INTEGER NOT NULL,
+ question_id INTEGER NOT NULL
+) TYPE=InnoDB;
+
+/* Table Items: book_question */
+
+/* Add Indexes for: book_question */
+CREATE INDEX fk_book_question_book ON book_question (book_id);
+CREATE INDEX fk_book_question_question ON book_question (question_id);
+
+/******************** Add Table: `comment` ************************/
+
+/* Build Table Structure */
+CREATE TABLE `comment`
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ `comment` TEXT NOT NULL,
+ added_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8;
+
+/* Table Items: `comment` */
+
+/* Add Indexes for: comment */
+CREATE INDEX comment_content_type_id ON `comment` (content_type_id);
+CREATE INDEX comment_user_id ON `comment` (user_id);
+CREATE INDEX content_type_id ON `comment` (content_type_id, object_id, user_id);
+
+/******************** Add Table: django_admin_log ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_admin_log
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ action_time DATETIME NOT NULL,
+ user_id INTEGER NOT NULL,
+ content_type_id INTEGER NULL,
+ object_id LONGTEXT NULL,
+ object_repr VARCHAR(200) NOT NULL,
+ action_flag SMALLINT UNSIGNED NOT NULL,
+ change_message LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_admin_log */
+
+/* Add Indexes for: django_admin_log */
+CREATE INDEX django_admin_log_content_type_id ON django_admin_log (content_type_id);
+CREATE INDEX django_admin_log_user_id ON django_admin_log (user_id);
+
+/******************** Add Table: django_authopenid_association ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_association
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ server_url LONGTEXT NOT NULL,
+ handle VARCHAR(255) NOT NULL,
+ secret LONGTEXT NOT NULL,
+ issued INTEGER NOT NULL,
+ lifetime INTEGER NOT NULL,
+ assoc_type LONGTEXT NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: django_authopenid_nonce ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_nonce
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ server_url VARCHAR(255) NOT NULL,
+ `timestamp` INTEGER NOT NULL,
+ salt VARCHAR(40) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: django_authopenid_userassociation ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_userassociation
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ openid_url VARCHAR(255) NOT NULL,
+ user_id INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_authopenid_userassociation */
+
+/* Add Indexes for: django_authopenid_userassociation */
+CREATE UNIQUE INDEX user_id ON django_authopenid_userassociation (user_id);
+
+/******************** Add Table: django_authopenid_userpasswordqueue ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_authopenid_userpasswordqueue
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ new_password VARCHAR(30) NOT NULL,
+ confirm_key VARCHAR(40) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: django_authopenid_userpasswordqueue */
+
+/* Add Indexes for: django_authopenid_userpasswordqueue */
+CREATE UNIQUE INDEX user_id ON django_authopenid_userpasswordqueue (user_id);
+
+/******************** Add Table: django_content_type ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_content_type
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ app_label VARCHAR(100) NOT NULL,
+ model VARCHAR(100) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
+
+/* Table Items: django_content_type */
+
+/* Add Indexes for: django_content_type */
+CREATE UNIQUE INDEX app_label ON django_content_type (app_label, model);
+
+/******************** Add Table: django_session ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_session
+(
+ session_key VARCHAR(40) NOT NULL,
+ session_data LONGTEXT NOT NULL,
+ expire_date DATETIME NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: django_session */
+ALTER TABLE django_session ADD CONSTRAINT pkdjango_session
+ PRIMARY KEY (session_key);
+
+/******************** Add Table: django_site ************************/
+
+/* Build Table Structure */
+CREATE TABLE django_site
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ domain VARCHAR(100) NOT NULL,
+ name VARCHAR(50) NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
+
+/******************** Add Table: favorite_question ************************/
+
+/* Build Table Structure */
+CREATE TABLE favorite_question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
+
+/* Table Items: favorite_question */
+
+/* Add Indexes for: favorite_question */
+CREATE INDEX favorite_question_question_id ON favorite_question (question_id);
+CREATE INDEX favorite_question_user_id ON favorite_question (user_id);
+
+/******************** Add Table: flagged_item ************************/
+
+/* Build Table Structure */
+CREATE TABLE flagged_item
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ flagged_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
+
+/* Table Items: flagged_item */
+
+/* Add Indexes for: flagged_item */
+CREATE UNIQUE INDEX content_type_id ON flagged_item (content_type_id, object_id, user_id);
+CREATE INDEX flagged_item_content_type_id ON flagged_item (content_type_id);
+CREATE INDEX flagged_item_user_id ON flagged_item (user_id);
+
+/******************** Add Table: question ************************/
+
+/* Build Table Structure */
+CREATE TABLE question
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ title TEXT NOT NULL,
+ author_id INTEGER NOT NULL,
+ added_at DATETIME NOT NULL,
+ wiki TINYINT NOT NULL,
+ wikified_at DATETIME NULL,
+ answer_accepted TINYINT NOT NULL,
+ closed TINYINT NOT NULL,
+ closed_by_id INTEGER NULL,
+ closed_at DATETIME NULL,
+ close_reason SMALLINT NULL,
+ deleted TINYINT NOT NULL,
+ deleted_at DATETIME NULL,
+ deleted_by_id INTEGER NULL,
+ locked TINYINT NOT NULL,
+ locked_by_id INTEGER NULL,
+ locked_at DATETIME NULL,
+ score INTEGER NOT NULL,
+ answer_count INTEGER UNSIGNED NOT NULL,
+ comment_count INTEGER UNSIGNED NOT NULL,
+ view_count INTEGER UNSIGNED NOT NULL,
+ offensive_flag_count SMALLINT NOT NULL,
+ favourite_count INTEGER UNSIGNED NOT NULL,
+ last_edited_at DATETIME NULL,
+ last_edited_by_id INTEGER NULL,
+ last_activity_at DATETIME NOT NULL,
+ last_activity_by_id INTEGER NOT NULL,
+ tagnames VARCHAR(125) NOT NULL,
+ summary VARCHAR(180) NOT NULL,
+ html LONGTEXT NOT NULL,
+ vote_up_count INTEGER NOT NULL,
+ vote_down_count INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
+
+/* Table Items: question */
+
+/* Add Indexes for: question */
+CREATE INDEX question_author_id ON question (author_id);
+CREATE INDEX question_closed_by_id ON question (closed_by_id);
+CREATE INDEX question_deleted_by_id ON question (deleted_by_id);
+CREATE INDEX question_last_activity_by_id ON question (last_activity_by_id);
+CREATE INDEX question_last_edited_by_id ON question (last_edited_by_id);
+CREATE INDEX question_locked_by_id ON question (locked_by_id);
+
+/******************** Add Table: question_revision ************************/
+
+/* Build Table Structure */
+CREATE TABLE question_revision
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ revision INTEGER UNSIGNED NOT NULL,
+ title TEXT NOT NULL,
+ author_id INTEGER NOT NULL,
+ revised_at DATETIME NOT NULL,
+ tagnames VARCHAR(125) NOT NULL,
+ summary TEXT NOT NULL,
+ `text` LONGTEXT NOT NULL
+) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
+
+/* Table Items: question_revision */
+
+/* Add Indexes for: question_revision */
+CREATE INDEX question_revision_author_id ON question_revision (author_id);
+CREATE INDEX question_revision_question_id ON question_revision (question_id);
+
+/******************** Add Table: question_tags ************************/
+
+/* Build Table Structure */
+CREATE TABLE question_tags
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ question_id INTEGER NOT NULL,
+ tag_id INTEGER NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
+
+/* Table Items: question_tags */
+
+/* Add Indexes for: question_tags */
+CREATE UNIQUE INDEX question_id ON question_tags (question_id, tag_id);
+CREATE INDEX tag_id_refs_id_43fcb953 ON question_tags (tag_id);
+
+/******************** Add Table: repute ************************/
+
+/* Build Table Structure */
+CREATE TABLE repute
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ positive SMALLINT NOT NULL,
+ negative SMALLINT NOT NULL,
+ question_id INTEGER NOT NULL,
+ reputed_at DATETIME NOT NULL,
+ reputation_type SMALLINT NOT NULL,
+ reputation INTEGER NOT NULL
+) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1;
+
+/* Table Items: repute */
+
+/* Add Indexes for: repute */
+CREATE INDEX repute_question_id ON repute (question_id);
+CREATE INDEX repute_user_id ON repute (user_id);
+
+/******************** Add Table: tag ************************/
+
+/* Build Table Structure */
+CREATE TABLE tag
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ created_by_id INTEGER NOT NULL,
+ used_count INTEGER UNSIGNED NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
+
+/* Table Items: tag */
+
+/* Add Indexes for: tag */
+CREATE UNIQUE INDEX name ON tag (name);
+CREATE INDEX tag_created_by_id ON tag (created_by_id);
+
+/******************** Add Table: user_badge ************************/
+
+/* Build Table Structure */
+CREATE TABLE user_badge
+(
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ badge_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: user_badge */
+
+/* Add Indexes for: user_badge */
+CREATE INDEX fk_user_badge_auth_user ON user_badge (user_id);
+CREATE INDEX fk_user_badge_badge ON user_badge (badge_id);
+
+/******************** Add Table: user_favorite_questions ************************/
+
+/* Build Table Structure */
+CREATE TABLE user_favorite_questions
+(
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ question_id INTEGER NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+/* Table Items: user_favorite_questions */
+
+/* Add Indexes for: user_favorite_questions */
+CREATE INDEX fk_user_favorite_questions_auth_user ON user_favorite_questions (user_id);
+CREATE INDEX fk_user_favorite_questions_question ON user_favorite_questions (question_id);
+
+/******************** Add Table: vote ************************/
+
+/* Build Table Structure */
+CREATE TABLE vote
+(
+ id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ content_type_id INTEGER NOT NULL,
+ object_id INTEGER UNSIGNED NOT NULL,
+ user_id INTEGER NOT NULL,
+ vote SMALLINT NOT NULL,
+ voted_at DATETIME NOT NULL
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
+
+/* Table Items: vote */
+
+/* Add Indexes for: vote */
+CREATE UNIQUE INDEX content_type_id ON vote (content_type_id, object_id, user_id);
+CREATE INDEX vote_content_type_id ON vote (content_type_id);
+CREATE INDEX vote_user_id ON vote (user_id);
+
+
+/************ Add Foreign Keys to Database ***************/
+/*-----------------------------------------------------------
+Warning: Versions of MySQL prior to 4.1.2 require indexes on all columns involved in a foreign key. The following indexes may be required:
+fk_auth_group_permissions_auth_group may require an index on table: auth_group_permissions, column: group_id
+fk_auth_user_groups_auth_user may require an index on table: auth_user_groups, column: user_id
+fk_auth_user_user_permissions_auth_user may require an index on table: auth_user_user_permissions, column: user_id
+fk_question_tags_question may require an index on table: question_tags, column: question_id
+-----------------------------------------------------------
+*/
+
+/************ Foreign Key: fk_activity_auth_user ***************/
+ALTER TABLE activity ADD CONSTRAINT fk_activity_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: deleted_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT deleted_by_id_refs_id_192b0170
+ FOREIGN KEY (deleted_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_auth_user ***************/
+ALTER TABLE answer ADD CONSTRAINT fk_answer_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_question ***************/
+ALTER TABLE answer ADD CONSTRAINT fk_answer_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_edited_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT last_edited_by_id_refs_id_192b0170
+ FOREIGN KEY (last_edited_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: locked_by_id_refs_id_192b0170 ***************/
+ALTER TABLE answer ADD CONSTRAINT locked_by_id_refs_id_192b0170
+ FOREIGN KEY (locked_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_answer_revision_auth_user ***************/
+ALTER TABLE answer_revision ADD CONSTRAINT fk_answer_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_group_permissions_auth_group ***************/
+ALTER TABLE auth_group_permissions ADD CONSTRAINT fk_auth_group_permissions_auth_group
+ FOREIGN KEY (group_id) REFERENCES auth_group (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_group_permissions_auth_permission ***************/
+ALTER TABLE auth_group_permissions ADD CONSTRAINT fk_auth_group_permissions_auth_permission
+ FOREIGN KEY (permission_id) REFERENCES auth_permission (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_message_auth_user ***************/
+ALTER TABLE auth_message ADD CONSTRAINT fk_auth_message_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_permission_django_content_type ***************/
+ALTER TABLE auth_permission ADD CONSTRAINT fk_auth_permission_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_groups_auth_group ***************/
+ALTER TABLE auth_user_groups ADD CONSTRAINT fk_auth_user_groups_auth_group
+ FOREIGN KEY (group_id) REFERENCES auth_group (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_groups_auth_user ***************/
+ALTER TABLE auth_user_groups ADD CONSTRAINT fk_auth_user_groups_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_user_permissions_auth_permission ***************/
+ALTER TABLE auth_user_user_permissions ADD CONSTRAINT fk_auth_user_user_permissions_auth_permission
+ FOREIGN KEY (permission_id) REFERENCES auth_permission (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_auth_user_user_permissions_auth_user ***************/
+ALTER TABLE auth_user_user_permissions ADD CONSTRAINT fk_auth_user_user_permissions_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_award_auth_user ***************/
+ALTER TABLE award ADD CONSTRAINT fk_award_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_award_badge ***************/
+ALTER TABLE award ADD CONSTRAINT fk_award_badge
+ FOREIGN KEY (badge_id) REFERENCES badge (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_books_auth_user ***************/
+ALTER TABLE book ADD CONSTRAINT fk_books_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_info_auth_user ***************/
+ALTER TABLE book_author_info ADD CONSTRAINT fk_book_author_info_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_info_book ***************/
+ALTER TABLE book_author_info ADD CONSTRAINT fk_book_author_info_book
+ FOREIGN KEY (book_id) REFERENCES book (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_rss_auth_user ***************/
+ALTER TABLE book_author_rss ADD CONSTRAINT fk_book_author_rss_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_author_rss_book ***************/
+ALTER TABLE book_author_rss ADD CONSTRAINT fk_book_author_rss_book
+ FOREIGN KEY (book_id) REFERENCES book (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_question_book ***************/
+ALTER TABLE book_question ADD CONSTRAINT fk_book_question_book
+ FOREIGN KEY (book_id) REFERENCES book (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_book_question_question ***************/
+ALTER TABLE book_question ADD CONSTRAINT fk_book_question_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_comment_auth_user ***************/
+ALTER TABLE `comment` ADD CONSTRAINT fk_comment_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_comment_django_content_type ***************/
+ALTER TABLE `comment` ADD CONSTRAINT fk_comment_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_admin_log_auth_user ***************/
+ALTER TABLE django_admin_log ADD CONSTRAINT fk_django_admin_log_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_admin_log_django_content_type ***************/
+ALTER TABLE django_admin_log ADD CONSTRAINT fk_django_admin_log_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_authopenid_userassociation_auth_user ***************/
+ALTER TABLE django_authopenid_userassociation ADD CONSTRAINT fk_django_authopenid_userassociation_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_django_authopenid_userpasswordqueue_auth_user ***************/
+ALTER TABLE django_authopenid_userpasswordqueue ADD CONSTRAINT fk_django_authopenid_userpasswordqueue_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_favorite_question_auth_user ***************/
+ALTER TABLE favorite_question ADD CONSTRAINT fk_favorite_question_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_favorite_question_question ***************/
+ALTER TABLE favorite_question ADD CONSTRAINT fk_favorite_question_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_flagged_item_auth_user ***************/
+ALTER TABLE flagged_item ADD CONSTRAINT fk_flagged_item_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_flagged_item_django_content_type ***************/
+ALTER TABLE flagged_item ADD CONSTRAINT fk_flagged_item_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: closed_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT closed_by_id_refs_id_56e9d00c
+ FOREIGN KEY (closed_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: deleted_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT deleted_by_id_refs_id_56e9d00c
+ FOREIGN KEY (deleted_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_auth_user ***************/
+ALTER TABLE question ADD CONSTRAINT fk_question_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_activity_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT last_activity_by_id_refs_id_56e9d00c
+ FOREIGN KEY (last_activity_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: last_edited_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT last_edited_by_id_refs_id_56e9d00c
+ FOREIGN KEY (last_edited_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: locked_by_id_refs_id_56e9d00c ***************/
+ALTER TABLE question ADD CONSTRAINT locked_by_id_refs_id_56e9d00c
+ FOREIGN KEY (locked_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_auth_user ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_auth_user
+ FOREIGN KEY (author_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_revision_question ***************/
+ALTER TABLE question_revision ADD CONSTRAINT fk_question_revision_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_tags_question ***************/
+ALTER TABLE question_tags ADD CONSTRAINT fk_question_tags_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_question_tags_tag ***************/
+ALTER TABLE question_tags ADD CONSTRAINT fk_question_tags_tag
+ FOREIGN KEY (tag_id) REFERENCES tag (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_auth_user ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_repute_question ***************/
+ALTER TABLE repute ADD CONSTRAINT fk_repute_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_tag_auth_user ***************/
+ALTER TABLE tag ADD CONSTRAINT fk_tag_auth_user
+ FOREIGN KEY (created_by_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_badge_auth_user ***************/
+ALTER TABLE user_badge ADD CONSTRAINT fk_user_badge_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_badge_badge ***************/
+ALTER TABLE user_badge ADD CONSTRAINT fk_user_badge_badge
+ FOREIGN KEY (badge_id) REFERENCES badge (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_favorite_questions_auth_user ***************/
+ALTER TABLE user_favorite_questions ADD CONSTRAINT fk_user_favorite_questions_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_user_favorite_questions_question ***************/
+ALTER TABLE user_favorite_questions ADD CONSTRAINT fk_user_favorite_questions_question
+ FOREIGN KEY (question_id) REFERENCES question (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_vote_auth_user ***************/
+ALTER TABLE vote ADD CONSTRAINT fk_vote_auth_user
+ FOREIGN KEY (user_id) REFERENCES auth_user (id) ON UPDATE NO ACTION ON DELETE NO ACTION;
+
+/************ Foreign Key: fk_vote_django_content_type ***************/
+ALTER TABLE vote ADD CONSTRAINT fk_vote_django_content_type
+ FOREIGN KEY (content_type_id) REFERENCES django_content_type (id) ON UPDATE NO ACTION ON DELETE NO ACTION; \ No newline at end of file
diff --git a/sql_scripts/update_2009_01_13_001.sql b/sql_scripts/update_2009_01_13_001.sql
new file mode 100644
index 00000000..165d1125
--- /dev/null
+++ b/sql_scripts/update_2009_01_13_001.sql
@@ -0,0 +1,62 @@
+-- phpMyAdmin SQL Dump
+-- version 3.0.0-beta
+-- http://www.phpmyadmin.net
+--
+-- Host: localhost
+-- Generation Time: Jan 12, 2009 at 08:55 PM
+-- Server version: 5.0.67
+-- PHP Version: 5.2.6
+
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+--
+-- Database: `twogeekt_lanai`
+--
+
+--
+-- Dumping data for table `badge`
+--
+
+INSERT INTO `badge` (`id`, `name`, `type`, `slug`, `description`, `multiple`, `awarded_count`) VALUES
+(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
+(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
+(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
+(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
+(5, '评论家', 3, '评论家', '评论10次以上', 1, 0),
+(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
+(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 1, 0),
+(8, '清洁工', 3, '清洁工', '第一次撤销投票', 1, 0),
+(9, '批评家', 3, '批评家', '第一次反对票', 1, 0),
+(10, '小编', 3, '小编', '第一次编辑更新', 1, 0),
+(11, '村长', 3, '村长', '第一次重新标签', 1, 0),
+(12, '学者', 3, '学者', '第一次标记答案', 1, 0),
+(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 1, 0),
+(14, '支持者', 3, '支持者', '第一次赞成票', 1, 0),
+(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 1, 0),
+(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 1, 0),
+(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
+(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
+(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
+(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
+(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
+(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 1, 0),
+(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
+(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
+(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
+(26, '优秀市民', 2, '优秀市民', '投票300次以上', 1, 0),
+(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 1, 0),
+(28, '通才', 2, '通才', '在多个标签领域活跃', 1, 0),
+(29, '专家', 2, '专家', '在一个标签领域活跃出众', 1, 0),
+(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 1, 0),
+(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
+(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 1, 0),
+(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 1, 0),
+(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
+(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
+(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
diff --git a/sql_scripts/update_2009_01_13_002.sql b/sql_scripts/update_2009_01_13_002.sql
new file mode 100644
index 00000000..c223cb8c
--- /dev/null
+++ b/sql_scripts/update_2009_01_13_002.sql
@@ -0,0 +1 @@
+ALTER TABLE activity ADD COLUMN is_auditted tinyint(1) DEFAULT 0 \ No newline at end of file
diff --git a/sql_scripts/update_2009_01_18_001.sql b/sql_scripts/update_2009_01_18_001.sql
new file mode 100644
index 00000000..6f29fa32
--- /dev/null
+++ b/sql_scripts/update_2009_01_18_001.sql
@@ -0,0 +1,62 @@
+-- phpMyAdmin SQL Dump
+-- version 3.0.0-beta
+-- http://www.phpmyadmin.net
+--
+-- Host: localhost
+-- Generation Time: Jan 12, 2009 at 08:55 PM
+-- Server version: 5.0.67
+-- PHP Version: 5.2.6
+
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+--
+-- Database: `twogeekt_lanai`
+--
+
+--
+-- Dumping data for table `badge`
+--
+
+INSERT INTO `badge` (`id`, `name`, `type`, `slug`, `description`, `multiple`, `awarded_count`) VALUES
+(1, '炼狱法师', 3, '炼狱法师', '删除自己有3个以上赞成票的帖子', 1, 0),
+(2, '压力白领', 3, '压力白领', '删除自己有3个以上反对票的帖子', 1, 0),
+(3, '优秀回答', 3, '优秀回答', '回答好评10次以上', 1, 0),
+(4, '优秀问题', 3, '优秀问题', '问题好评10次以上', 1, 0),
+(5, '评论家', 3, '评论家', '评论10次以上', 0, 0),
+(6, '流行问题', 3, '流行问题', '问题的浏览量超过1000人次', 1, 0),
+(7, '巡逻兵', 3, '巡逻兵', '第一次标记垃圾帖子', 0, 0),
+(8, '清洁工', 3, '清洁工', '第一次撤销投票', 0, 0),
+(9, '批评家', 3, '批评家', '第一次反对票', 0, 0),
+(10, '小编', 3, '小编', '第一次编辑更新', 0, 0),
+(11, '村长', 3, '村长', '第一次重新标签', 0, 0),
+(12, '学者', 3, '学者', '第一次标记答案', 0, 0),
+(13, '学生', 3, '学生', '第一次提问并且有一次以上赞成票', 0, 0),
+(14, '支持者', 3, '支持者', '第一次赞成票', 0, 0),
+(15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0),
+(16, '自传作者', 3, '自传作者', '完整填写用户资料所有选项', 0, 0),
+(17, '自学成才', 3, '自学成才', '回答自己的问题并且有3个以上赞成票', 1, 0),
+(18, '最有价值回答', 1, '最有价值回答', '回答超过100次赞成票', 1, 0),
+(19, '最有价值问题', 1, '最有价值问题', '问题超过100次赞成票', 1, 0),
+(20, '万人迷', 1, '万人迷', '问题被100人以上收藏', 1, 0),
+(21, '著名问题', 1, '著名问题', '问题的浏览量超过10000人次', 1, 0),
+(22, 'alpha用户', 2, 'alpha用户', '内测期间的活跃用户', 0, 0),
+(23, '极好回答', 2, '极好回答', '回答超过25次赞成票', 1, 0),
+(24, '极好问题', 2, '极好问题', '问题超过25次赞成票', 1, 0),
+(25, '受欢迎问题', 2, '受欢迎问题', '问题被25人以上收藏', 1, 0),
+(26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0),
+(27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0),
+(28, '通才', 2, '通才', '在多个标签领域活跃', 0, 0),
+(29, '专家', 2, '专家', '在一个标签领域活跃出众', 0, 0),
+(30, '老鸟', 2, '老鸟', '活跃超过一年的用户', 0, 0),
+(31, '最受关注问题', 2, '最受关注问题', '问题的浏览量超过2500人次', 1, 0),
+(32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0),
+(33, 'beta用户', 2, 'beta用户', 'beta期间活跃参与', 0, 0),
+(34, '导师', 2, '导师', '被指定为最佳答案并且赞成票40以上', 1, 0),
+(35, '巫师', 2, '巫师', '在提问60天之后回答并且赞成票5次以上', 1, 0),
+(36, '分类专家', 2, '分类专家', '创建的标签被50个以上问题使用', 1, 0);
diff --git a/sql_scripts/update_2009_01_24.sql b/sql_scripts/update_2009_01_24.sql
new file mode 100644
index 00000000..45b83935
--- /dev/null
+++ b/sql_scripts/update_2009_01_24.sql
@@ -0,0 +1,2 @@
+ALTER TABLE award ADD COLUMN `content_type_id` int(11);
+ALTER TABLE award ADD COLUMN `object_id` int(10); \ No newline at end of file
diff --git a/sql_scripts/update_2009_01_25_001.sql b/sql_scripts/update_2009_01_25_001.sql
new file mode 100644
index 00000000..1f1942e3
--- /dev/null
+++ b/sql_scripts/update_2009_01_25_001.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `award` ADD `content_type_id` INT NULL
+ALTER TABLE `award` ADD `object_id` INT NULL \ No newline at end of file
diff --git a/sql_scripts/update_2009_02_26_001.sql b/sql_scripts/update_2009_02_26_001.sql
new file mode 100644
index 00000000..9cc80974
--- /dev/null
+++ b/sql_scripts/update_2009_02_26_001.sql
@@ -0,0 +1,19 @@
+ALTER TABLE answer ADD COLUMN `accepted_at` datetime default null;
+
+/* Update accepted_at column with answer added datetime for existing data */
+UPDATE answer
+SET accepted_at = added_at
+WHERE accepted = 1 AND accepted_at IS NULL;
+
+/* workround for c# url problem on bluehost server */
+UPDATE tag
+SET name = 'csharp'
+WHERE name = 'c#'
+
+UPDATE question
+SET tagnames = replace(tagnames, 'c#', 'csharp')
+WHERE tagnames like '%c#%'
+
+UPDATE question_revision
+SET tagnames = replace(tagnames, 'c#', 'csharp')
+WHERE tagnames like '%c#%' \ No newline at end of file
diff --git a/sql_scripts/update_2009_04_10_001.sql b/sql_scripts/update_2009_04_10_001.sql
new file mode 100644
index 00000000..b0d05ac7
--- /dev/null
+++ b/sql_scripts/update_2009_04_10_001.sql
@@ -0,0 +1,3 @@
+ALTER TABLE Tag ADD COLUMN deleted_at datetime default null;
+ALTER TABLE Tag ADD COLUMN deleted_by_id INTEGER NULL;
+ALTER TABLE Tag ADD COLUMN deleted TINYINT NOT NULL; \ No newline at end of file
diff --git a/sql_scripts/update_2009_12_24_001.sql b/sql_scripts/update_2009_12_24_001.sql
new file mode 100644
index 00000000..3d082c2f
--- /dev/null
+++ b/sql_scripts/update_2009_12_24_001.sql
@@ -0,0 +1,5 @@
+alter table question add column `vote_up_count` int(11) NOT NULL;
+alter table question add column `vote_down_count` int(11) NOT NULL;
+
+alter table answer add column `vote_up_count` int(11) NOT NULL;
+alter table answer add column `vote_down_count` int(11) NOT NULL; \ No newline at end of file
diff --git a/sql_scripts/update_2009_12_27_001.sql b/sql_scripts/update_2009_12_27_001.sql
new file mode 100644
index 00000000..e2da7d4d
--- /dev/null
+++ b/sql_scripts/update_2009_12_27_001.sql
@@ -0,0 +1,3 @@
+ALTER TABLE comment DROP INDEX content_type_id;
+
+ALTER TABLE comment ADD INDEX `content_type_id` (`content_type_id`,`object_id`,`user_id`); \ No newline at end of file
diff --git a/sql_scripts/update_2009_12_27_002.sql b/sql_scripts/update_2009_12_27_002.sql
new file mode 100644
index 00000000..a36470bf
--- /dev/null
+++ b/sql_scripts/update_2009_12_27_002.sql
@@ -0,0 +1 @@
+ALTER TABLE `vote` ADD `voted_at` DATETIME NOT NULL \ No newline at end of file
diff --git a/templates/404.html b/templates/404.html
new file mode 100644
index 00000000..d24edaf0
--- /dev/null
+++ b/templates/404.html
@@ -0,0 +1,49 @@
+<!-- template 404.html -->
+{% extends "base_content.html" %}
+{% load i18n %}
+{% block title %}{% spaceless %}404 Error{% endspaceless %}{% endblock %}
+{% block forestyle%}
+ <style type="text/css">
+ form input { margin-right: 5px; }
+ </style>
+{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#linkPrevious").bind("click", back=function(){history.go(-1);})
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<div id="main-bar" class="headNormal">
+ 404 Not Found
+</div>
+<div id="main-body" class="">
+ <div style="padding:5px 0px 10px 0;line-height:25px;">
+ <h3>{% trans "Sorry, could not find the page you requested." %}</h3>
+ <div style="margin-top:5px">
+ {% trans "This might have happened for the following reasons:" %}<br>
+ <ul>
+ <li>{% trans "this question or answer has been deleted;" %}</li>
+ <li>{% trans "url has error - please check it;" %}</li>
+ <li>{% trans "the page you tried to visit is protected or you don't have sufficient points, see" %} <a href="/faq"> faq</a>;</li>
+ <li>{% trans "if you believe this error 404 should not have occured, please" %}
+ <a href="{{feedback_site_url}}" target="_blank">{% trans "report this problem" %}</a></li>
+ </u>
+ </div>
+ <script type="text/javascript">
+ var GOOG_FIXURL_LANG = 'zh-cn';
+ var GOOG_FIXURL_SITE = '{{site_url}}';
+ </script>
+ <script type="text/javascript" src="http://linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script>
+ <ul>
+ <li><a href="#" id="linkPrevious">{% trans "back to previous page" %} » </li>
+ <li><a href="/questions">{% trans "see all questions" %} » </a></li>
+ <li><a href="/tags/">{% trans "see all tags" %} » </a></li>
+ </u>
+ </div>
+
+</div>
+{% endblock %}
+<!-- end template 404.html -->
diff --git a/templates/500.html b/templates/500.html
new file mode 100644
index 00000000..c99774b3
--- /dev/null
+++ b/templates/500.html
@@ -0,0 +1,35 @@
+<!-- template 500.html -->
+{% extends "base_content.html" %}
+{% load i18n %}
+{% block title %}{% spaceless %}500 Error{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#linkPrevious").bind("click", back=function(){history.go(-1);})
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<div id="main-bar" class="">
+ <h3>
+ 500 Server Error
+ </h3>
+
+</div>
+<div id="main-body" class="headNormal">
+ <div style="padding:5px 0px 10px 0;line-height:25px">
+ <h3>{% trans "sorry, system error"</h3>
+ <br>
+ {% trans "system error log is recorded, error will be fixed as soon as possible" %}<br>
+ {% trans "please report the error to the site administrators if you wish" %}
+ <ul>
+ <li><a href="#" id="linkPrevious">{% trans "back to previous page" %}</li>
+ <li><a href="/questions">{% trans "see latest questions" %}</a></li>
+ <li><a href="/tags/">{% trans "see tags" %}</a></li>
+ </u>
+ </div>
+
+</div>
+{% endblock %}
+<!-- end template 500.html -->
diff --git a/templates/about.html b/templates/about.html
new file mode 100644
index 00000000..4655a641
--- /dev/null
+++ b/templates/about.html
@@ -0,0 +1,21 @@
+<!-- template about.html -->
+{% extends "base_content.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+{% block title %}{% spaceless %}{% trans "About" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+{% endblock %}
+{% block content %}
+<div class="headNormal">
+{% trans "About" %}
+</div>
+
+<div class="content">
+ <p>edit file templates/about.html. Below are just suggestions of what can go here</p>
+ <p>what is your site for?</p>
+ <p>how does it work? what are roles of members?</p>
+ <p>is there a place to find out more about this website?</p>
+</div>
+{% endblock %}
+<!-- end template about.html -->
diff --git a/templates/allfiles b/templates/allfiles
new file mode 100644
index 00000000..3ca0191e
--- /dev/null
+++ b/templates/allfiles
@@ -0,0 +1,30 @@
+404.html
+500.html
+about.html
+ask.html
+badges.html
+base_content.html
+base.html
+book.html
+close.html
+faq.html
+footer.html
+header.html
+index.html
+logout.html
+pagesize.html
+paginator.html
+privacy.html
+question_edit_tips.html
+question.html
+questions.html
+revisions_answer.html
+tags.html
+unanswered.html
+user_edit.html
+user_info.html
+users.html
+users_questions.html
+user_stats.html
+user_tabs.html
+user_votes.html
diff --git a/templates/answer_edit.html b/templates/answer_edit.html
new file mode 100644
index 00000000..008d9f78
--- /dev/null
+++ b/templates/answer_edit.html
@@ -0,0 +1,88 @@
+<!-- template answer_edit.html -->
+{% extends "base.html" %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Edit answer" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
+ <script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
+ <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type='text/javascript' src='/content/js/wmd/showdown.js'></script>
+ <script type='text/javascript' src='/content/js/wmd/wmd.js'></script>
+ <link rel="stylesheet" type="text/css" href="/content/js/wmd/wmd.css" />
+ <script type="text/javascript">
+
+ $().ready(function(){
+ $("#nav_questions").attr('className',"on");
+ $('#editor').TextAreaResizer();
+
+ //highlight code synctax when editor has new text
+ $("#editor").typeWatch({highlight: false, wait: 3000,
+ captureLength: 5, callback: lanai.highlightSyntax});
+
+ //toggle preview of editor
+ var display = true;
+ var txt = "{% trans "hide preview" %}";
+ $('#pre-collapse').text(txt);
+ $('#pre-collapse').bind('click', function(){
+ txt = display ? "{% trans "show preview" %}" : "{% trans "hide preview" %}";
+ display = !display;
+ $('#previewer').toggle();
+ $('#pre-collapse').text(txt);
+ });
+
+ setupFormValidation("#fmedit", CPValidator.getQuestionFormRules(), CPValidator.getQuestionFormMessages());
+
+ $('#id_revision').unbind().change(function(){
+ $("#select_revision").click();
+ });
+
+ lanai.highlightSyntax();
+
+ });
+ </script>
+{% endblock %}
+
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Edit answer" %} [<a href="{{ answer.question.get_absolute_url }}#{{ answer.id }}">{% trans "back" %}</a>]
+</div>
+<div id="main-body" class="ask-body">
+ <div id="askform">
+ <form id="fmedit" action="{% url edit_answer answer.id %}" method="post" >
+ <label for="id_revision" ><strong>{% trans "revision" %}:</strong></label> <br>
+ {% if revision_form.revision.errors %}{{ revision_form.revision.errors.as_ul }}{% endif %}
+ <div style="vertical-align:middle">
+ {{ revision_form.revision }} <input type="submit" style="display:none" id="select_revision" name="select_revision" value="{% trans "select revision" %}">
+ </div><br>
+ <div class="form-item">
+ <div id="wmd-button-bar" class="wmd-panel"></div>
+ {{ form.text }}
+ <span class="form-error"></span>
+ <div class="preview-toggle"><span id="pre-collapse"
+ title="{% trans "Toggle the real time Markdown editor preview" %}打">{% trans "toggle preview" %}</span></div>
+ <div id="previewer" class="wmd-preview"></div>
+ <br>
+ </div>
+
+ <strong>{{ form.summary.label_tag }}</strong> <br>
+ {{ form.summary }} {{ form.summary.errors }}
+ <div class="title-desc">
+ {{ form.summary.help_text }}
+ </div>
+ <br>
+ <input type="submit" value="{% trans "Save edit" %}" class="submit" />
+ <input type="button" value="{% trans "Cancel" %}" class="submit" onclick="history.back(-1);" />
+ <br>
+ <br>
+ </form>
+ </div>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+{% include "answer_edit_tips.html" %}
+{% endblock %}
+
+{% block endjs %}
+{% endblock %}
+<!-- end template answer_edit.html -->
diff --git a/templates/answer_edit_tips.html b/templates/answer_edit_tips.html
new file mode 100644
index 00000000..08d33bc9
--- /dev/null
+++ b/templates/answer_edit_tips.html
@@ -0,0 +1,54 @@
+<!-- template answer_edit_tips.html -->
+{% load i18n %}
+<div class="boxC">
+ <p class="subtitle darkred">{% trans "answer tips" %}</p>
+ <div>
+ <ul class="list-item">
+ <li> <b>{% trans "please make your answer relevant to this community" %}</b>
+ </li>
+ <li>
+ {% trans "try to give an answer, rather than engage into a discussion" %}
+ </li>
+ <li>
+ {% trans "please try to provide details" %}
+ </li>
+ <li>
+ {% trans "be clear and concise" %}
+ </li>
+ </ul>
+ <a href="/faq/" target="_blank" title="{% trans "see frequently asked questions" %}" style="float:right;position:relative">faq »</a>
+ <br>
+ </div>
+</div>
+
+<div class="boxC">
+ <p class="subtitle">{% trans "Markdown tips" %}</p>
+ <ul class="list-item">
+ <li>
+ {% trans "*italic* or __italic__" %}
+ </li>
+ <li>
+ {% trans "**bold** or __bold__" %}
+ </li>
+ <li>
+ <b>{% trans "link" %}</b>:[{% trans "text" %}](http://url.com/ "{% trans "title" %}")
+
+ </li>
+
+ <li>
+ <b>{% trans "image" %}</b>:![alt {% trans "text" %}](/path/img.jpg "{% trans "title" %}")
+
+ </li>
+ <li>
+ {% trans "numbered list:" %}
+ 1. Foo
+ 2. Bar
+ </li>
+ <li>
+ {% trans "basic HTML tags are also supported" %}
+ </li>
+ </ul>
+ <a href="http://en.wikipedia.org/wiki/Markdown" target="_blank" style="float:right;position:relative">{% trans "learn more about Markdown" %} »</a>
+ <br>
+</div>
+<!-- end template answer_edit_tips.html -->
diff --git a/templates/ask.html b/templates/ask.html
new file mode 100644
index 00000000..1b00a701
--- /dev/null
+++ b/templates/ask.html
@@ -0,0 +1,140 @@
+<!-- template ask.html -->
+{% extends "base.html" %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Ask a question" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
+ <script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
+ <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type='text/javascript' src='/content/js/wmd/showdown.js'></script>
+ <script type='text/javascript' src='/content/js/wmd/wmd.js'></script>
+ <link rel="stylesheet" type="text/css" href="/content/js/wmd/wmd.css" />
+ <script type="text/javascript">
+ $().ready(function(){
+ //set current module button style
+ $("#nav_ask").attr('className',"on");
+ $('#editor').TextAreaResizer();
+
+ //highlight code synctax when editor has new text
+ $("#editor").typeWatch({highlight: false, wait: 3000,
+ captureLength: 5, callback: lanai.highlightSyntax});
+
+ //toggle preview of editor
+ //todo remove copy-paste
+ var display = true;
+ var txt = "[{% trans "hide preview" %}]";
+ $('#pre-collapse').text(txt);
+ $('#pre-collapse').bind('click', function(){
+ txt = display ? "[{% trans "show preview" %}]" : "[{% trans "hide preview" %}]";
+ display = !display;
+ $('#previewer').toggle();
+ $('#pre-collapse').text(txt);
+ });
+
+ //Tags autocomplete action
+ var tags = {{ tags|safe }};
+ $("#id_tags").autocomplete(tags, {
+ minChars: 1,
+ matchContains: true,
+ max: 20,
+ multiple: true,
+ multipleSeparator: " ",
+ formatItem: function(row, i, max) {
+ return row.n + " ("+ row.c +")";
+ },
+ formatResult: function(row, i, max){
+ return row.n;
+ }
+
+ });
+
+ setupFormValidation("#fmask", CPValidator.getQuestionFormRules(), CPValidator.getQuestionFormMessages());
+ lanai.highlightSyntax();
+
+ });
+ </script>
+{% endblock %}
+
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Ask a question" %}
+</div>
+<div id="main-body" class="ask-body">
+ <div id="askform">
+ <form id="fmask" action="" method="post" >
+ <p class="form-item">
+ <label for="id_title" ><strong>{{ form.title.label_tag }}:</strong></label> <span class="form-error"></span><br>
+ {{ form.title }} {{ form.title.errors }}
+ <div class="title-desc">
+ {{ form.title.help_text }}
+ </div>
+ </p>
+
+ <p class="form-item">
+ <div id="wmd-button-bar" class="wmd-panel"></div>
+ {{ form.text }}
+
+ <div class="preview-toggle">
+ <table width="100%">
+ <tr>
+ <td>
+ <span id="pre-collapse" title="{% trans "Toggle the real time Markdown editor preview" %}">{% trans "toggle preview" %}</span>
+ </td>
+ <td style="text-align:right;">
+ {{ form.wiki }} <span style="font-weight:normal;cursor:help" title="{{form.wiki.help_text}}">{{ form.wiki.label_tag }} </span>
+ </td>
+ </tr>
+
+ </table>
+ </div>
+ <div id="previewer" class="wmd-preview"></div>
+ <span class="form-error"></span>
+ <br>
+ </p>
+ <p class="form-item">
+ <strong>{{ form.tags.label_tag }}:</strong> <span class="form-error"></span><br>
+ {{ form.tags }} {{ form.tags.errors }}
+ <div class="title-desc">
+ {{ form.tags.help_text }}
+ </div>
+ </p>
+ <br>
+ {% if not request.user.is_authenticated %}
+ <table id="login-box">
+ <tr>
+ <td style="vertical-align:middle;">
+ <strong>{% trans "Use" %} <a href="http://openid.net/" title="{% trans "learn more about OpenID" %}">OpenID</a> {% trans "Login" %}:</strong><br>
+ {{ form.openid }}
+ <div class="title-desc">
+ {% trans "Get your own "%} <a href="https://www.myopenid.com/" target=="_blank">OpenID</a>。
+ </div>
+ </td>
+ <td style="vertical-align:middle; padding: 0px 40px 0px 40px">
+ <div style="position: absolute; margin-top:40px; background-color:white; margin-left:-10px; padding:5px;">或</div>
+ <div style="width:1px; border-left:solid 1px #999; height:8em; margin:auto;"></div>
+ </td>
+ <td style="vertical-align:middle;">
+ <strong>{% trans "User name" %}:</strong><br>
+ {{ form.user }}
+ <p>
+ <strong>{% trans "Email: (won't be shown to anyone)" %}:</strong><br>
+ {{ form.email }}
+ </p>
+ </td>
+ </tr>
+ </table>
+ {% endif %}
+ <input type="submit" value="{% trans "Ask your question" %}" class="submit" />
+ <br><br>
+ </form>
+ </div>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+{% include "question_edit_tips.html" %}
+{% endblock %}
+
+{% block endjs %}
+{% endblock %}
+<!-- end template ask.html -->
diff --git a/templates/authopenid/changeemail.html b/templates/authopenid/changeemail.html
new file mode 100644
index 00000000..99984b3f
--- /dev/null
+++ b/templates/authopenid/changeemail.html
@@ -0,0 +1,35 @@
+{% extends "base_content.html" %}
+{% load i18n %}
+{% block content %}
+<div id="main-bar" class="">
+ <h3>
+ {% trans "Account: change email" %}
+ </h3>
+</div>
+<p class="settings-descr">{% blocktrans %}This is where you can change the email address associated with your account. Please keep this email address up to date so we can send you a password-reset email if you request one.{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+ {% if form.email.errors %}
+ <span class="error">{{ form.email.errors|join:", " }}</span>
+ {% endif %}
+ {% if form.password.errors %}
+ <span class="error">{{ form.password.errors|join:", " }}</span>
+ {% endif %}
+</p>
+{% endif %}
+
+{% if msg %}
+<p class="errors">{{ msg }}</p>
+{% endif %}
+
+<div class="aligned">
+ <form action="." method="post" accept-charset="utf-8">
+
+ <div id="form-row"><label for="id_email">{% trans "Email" %}</label>{{ form.email }}</div>
+ <div id="form-row"><label for="id_password">{% trans "Password" %}</label>{{ form.password }}</div>
+
+ <p><input type="submit" value="{% trans "Change email" %}"></p>
+
+ </form>
+ </div>
+{% endblock %}
diff --git a/templates/authopenid/changeopenid.html b/templates/authopenid/changeopenid.html
new file mode 100644
index 00000000..c1f3d180
--- /dev/null
+++ b/templates/authopenid/changeopenid.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block content %}
+<div id="main-bar" class="">
+ <h3>
+ {% trans "Account: change OpenID URL" %}
+ </h3>
+</div>
+
+<p>{% blocktrans %}This is where you can change your OpenID URL. Make sure you remember it!{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+ {% if form.openid_url.errors %}
+ <span class="error">{{ form.openid_url.errors|join:", " }}</span>
+ {% endif %}
+
+
+</p>
+{% endif %}
+{% if msg %}
+ <p class="errors">{{ msg }}</p>
+{% endif %}
+
+<div class="aligned">
+ <form action="." method="post" accept-charset="utf-8">
+
+ <div id="form-row"><label for="id_openid_url">{% trans "OpenID URL:" %}</label>{{ form.openid_url }}</div>
+ <p><input type="submit" value="{% trans "Change OpenID" %}"></p>
+
+ </form>
+ </div>
+{% endblock %}
diff --git a/templates/authopenid/changepw.html b/templates/authopenid/changepw.html
new file mode 100644
index 00000000..f3cf4be0
--- /dev/null
+++ b/templates/authopenid/changepw.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block head %}
+
+{% endblock %}
+
+
+
+{% block content %}
+<div id="main-bar" class="">
+ <h3>
+ {% trans "Account: change password" %}
+ </h3>
+</div>
+<p>{% blocktrans %}This is where you can change your password. Make sure you remember it!{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+{{ form.errors }}
+</p>
+{% endif %}
+
+<div class="aligned">
+ <form action="." method="post" accept-charset="utf-8">
+
+ <div id="form-row"><label for="id_oldpw">{% trans "Current password" %}</label>{{ form.oldpw }}</div>
+ <div id="form-row"><label for="id_password1">{% trans "New password" %}</label>{{ form.password1 }}</div>
+ <div id="form-row"><label for="id_password2">{% trans "New password again" %}</label>{{ form.password2 }}</div>
+ <p><input type="submit" value="{% trans "Change password" %}"></p>
+
+ </form>
+ </div>
+{% endblock %}
diff --git a/templates/authopenid/complete.html b/templates/authopenid/complete.html
new file mode 100644
index 00000000..28c38a04
--- /dev/null
+++ b/templates/authopenid/complete.html
@@ -0,0 +1,67 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block head %}{% endblock %}
+{% block title %}{% spaceless %}{% trans "Connect your OpenID with this site" %}{% endspaceless %}{% endblock %}
+{% block content %}
+ <div id="main-bar" class="headNormal">
+ {% trans "Connect your OpenID with your account on this site" %}
+ </div>
+ <p id="completetxt" >
+ <h3>{% trans "Your OpenID is accepted. Please complete this to finish registration." %}</h3>
+ <p style="display:none">{% trans "This account already exists, please use another." %}</p>
+ </p>
+
+ {% if form1.errors %}
+ <p class="errors">
+ <span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br>
+ <ul class="error-list">
+ {% if form1.username.errors %}
+ <li><span class="error">{{ form1.username.errors|join:", " }}</span></li>
+ {% endif %}
+ {% if form1.email.errors %}
+ <li><span class="error">{{ form1.email.errors|join:", " }}</span></li>
+ {% endif %}
+ </ul>
+ </p>
+ {% endif %}
+ {% if form2.errors %}
+ <p class="errors">
+ <span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br>
+ <ul class="error-list">
+ {% if form2.username.errors %}
+ <li><span class="error">{{ form2.username.errors|join:", " }}</span></li>
+ {% endif %}
+ {% if form2.password.errors %}
+ <li><span class="error">{{ form2.password.errors|join:", " }}</span></li>
+ {% endif %}
+ </ul>
+ </p>
+ {% endif %}
+
+ <div class="login">
+ <form name="fregister" action="{% url user_register %}" method="POST">
+ {{ form.next }}
+ <fieldset style="padding:10px">
+ <legend class="big">{% trans "New account" %}</legend>
+ <div class="form-row"><label for="id_username">{% trans "User name (<i>will be shown to others, cannot be modified</i>)" %}</label><br />{{ form1.username }}</div>
+ <div class="form-row"><label for="id_email">{% trans "Email (<i>not shared with anyone</i>)" %}</label><br />{{ form1.email }}</div>
+ <div class="submit-row"><input type="submit" class="submit" name="bnewaccount" value="{% trans "create account" %}"></div>
+ </fieldset>
+ </form>
+ </div>
+ <div class="login" style="display:none">
+ <form name="fverify" action="{% url user_register %}" method="POST">
+ {{ form.next }}
+ <fieldset style="padding:10px">
+ <legend class="big">{% trans "Existing account" %}</legend>
+ <div class="form-row"><label for="id_username">{% trans "user name" %}</label><br />{{ form2.username }}</div>
+ <div class="form-row"><label for="id_passwordl">{% trans "password" %}</label><br />{{ form2.password }}</div>
+ <!--todo double check translation from chinese 确认 = "Register" -->
+ <div class="submit-row">
+ <input type="submit" class="submit" name="bverify" value="{% trans "Register" %}">
+ <a href="">{% trans "Forgot your password?" %}</a>
+ </div>
+ </fieldset>
+ </form>
+ </div>
+{% endblock %}
diff --git a/templates/authopenid/confirm_email.txt b/templates/authopenid/confirm_email.txt
new file mode 100644
index 00000000..9af177ed
--- /dev/null
+++ b/templates/authopenid/confirm_email.txt
@@ -0,0 +1,12 @@
+Thank you for registering.
+
+Your account details are:
+
+Username: {{ username }}
+Password: {{ password }}
+
+
+You may sign in here:
+{{ site_url }}signin/
+
+
diff --git a/templates/authopenid/delete.html b/templates/authopenid/delete.html
new file mode 100644
index 00000000..19e0884a
--- /dev/null
+++ b/templates/authopenid/delete.html
@@ -0,0 +1,38 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+
+{% block content %}
+<div id="main-bar" class="">
+ <h3>
+ {% trans "Account: delete account" %}
+ </h3>
+</div>
+
+<p class="settings-descr">{% blocktrans %}Note: After deleting your account, anyone will be able to register this username.{% endblocktrans %}</p>
+{% if form.errors %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+ {% if form.confirm.errors %}
+ <span class="error">{% trans "Check confirm box, if you want delete your account." %}</span><br />
+ {% endif %}
+ {% if form.password.errors %}
+ <span class="error">{% trans "Password:" %} {{ form.password.errors|join:", " }}</span>
+ {% endif %}
+</p>
+{% endif %}
+{% if msg %}
+<p class="errors">{% trans "Please correct errors below:" %}<br />
+ <span class="error">{{ msg }}</span>
+ </p>
+{% endif %}
+<div class="aligned">
+ <form action="." method="post" accept-charset="utf-8">
+
+ <div id="form-row"> {{ form.confirm }} {% trans "I am sure I want to delete my account." %}</div>
+ <div id="form-row"><label for="id_password">{% trans "Password/OpenID URL" %}</label>{{ form.password }} {% trans "(required for your security)" %}</div>
+
+ <p><input type="submit" value="{% trans "Delete account permanently" %}"></p>
+
+ </form>
+ </div>
+{% endblock %}
diff --git a/templates/authopenid/failure.html b/templates/authopenid/failure.html
new file mode 100644
index 00000000..87839ab2
--- /dev/null
+++ b/templates/authopenid/failure.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <title>OpenID failed</title>
+</head>
+<body>
+<h1>OpenID failed</h1>
+
+<p>{{ message|escape }}</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/templates/authopenid/htmlfiles b/templates/authopenid/htmlfiles
new file mode 100644
index 00000000..1b9dccd0
--- /dev/null
+++ b/templates/authopenid/htmlfiles
@@ -0,0 +1,8 @@
+changeemail.html
+changeopenid.html
+changepw.html
+delete.html
+failure.html
+sendpw.html
+settings.html
+signup.html
diff --git a/templates/authopenid/sendpw.html b/templates/authopenid/sendpw.html
new file mode 100644
index 00000000..a9488c4c
--- /dev/null
+++ b/templates/authopenid/sendpw.html
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Send new password" %}{% endspaceless %}{% endblock %}
+
+{% block content %}
+<div id="main-bar" class="">
+ <h3>{% trans "Send new password" %}</h3>
+
+</div>
+<div class="paragraph">
+{% trans "Lost your password? No problem - here you can reset it." %}<br>
+{% trans "Please enter your username below and new password will be sent to your registered e-mail" %}
+</div>
+{% if form.errors %}
+<p class="errors"><span class="big">{% "Sorry, looks like we have some errors:" %}</span><br>
+ {% if form.username.errors %}
+ <span class="error">{{ form.username.errors|join:", " }}</span>
+ {% endif %}
+</p>
+{% endif %}
+{% if msg %}
+ <div class="paragraph error">{{ msg }}</div>
+{% endif %}
+
+<div class="aligned">
+ <form action="." method="post" accept-charset="utf-8">
+ <div id="form-row"><label for="id_username">{% trans "User name" %}:</label>{{ form.username }}</div>
+
+ <p style="padding-top:10px"><input type="submit" value="{% trans "Reset password" %}"> <a href="{% url user_signin %}">{% trans "return to login" %}</a></p>
+
+ </form>
+ <span class="darkred">{% trans "Note: your new password will be activated only after you click the activation link in the email message" %}</span>
+</div>
+{% endblock %}
diff --git a/templates/authopenid/sendpw_email.txt b/templates/authopenid/sendpw_email.txt
new file mode 100644
index 00000000..dec062a8
--- /dev/null
+++ b/templates/authopenid/sendpw_email.txt
@@ -0,0 +1,14 @@
+Someone has requested to reset your password on {{ site_url }}.
+If this is not you, it is safe to ignore this email.
+
+Your new account details are:
+
+Username: {{ username }}
+New password: {{ password }}
+
+To confirm reset of your password go to this address:
+{{ site_url }}{{ url_confirm }}?key={{ confirm_key }}
+
+Regards,
+
+
diff --git a/templates/authopenid/settings.html b/templates/authopenid/settings.html
new file mode 100644
index 00000000..ffd5dd8f
--- /dev/null
+++ b/templates/authopenid/settings.html
@@ -0,0 +1,41 @@
+{% extends "base_content.html" %}
+{% load i18n %}
+
+{% block head %}
+<style type="text/css" media="screen">
+ h4 {font-size:12pt;}
+ dt, dd { padding:0 0 0.35em 0; }
+ dt { float: left; width: 21ex; }
+ dd { margin-left: 23ex; }
+
+ #settings-options, #settings-intro { padding: 4em 1.5em;}
+ #settings-options { min-height: 300px; border-left: 1px solid #333;}
+
+ #settings-options h5 { font-weight: bold;}
+</style>
+{% endblock %}
+
+{% block content %}
+<div id="main-bar">
+ <h3><strong>{{ request.user.username }} {% trans "Profile" %}</strong></h3>
+</div>
+<div id="settings-options">
+ {% if msg %}
+ <p class="error">{{ msg }}</p>
+ {% endif %}
+
+ <dl class="list-item">
+ <dt>» <a href="{% url user_changepw %}">{% trans "Change password" %}</a></dt>
+ <dd>{% trans "Give your account a new password." %}</dd>
+ <dt>» <a href="{% url user_changeemail %}">{% trans "Change email " %}</a></dt>
+ <dd>{% trans "Add or update the email address associated with your account." %}</dd>
+ {% if is_openid %}
+ <dt>» <a href="{% url user_changeopenid %}">{% trans "Change OpenID %}</a></dt>
+ <dd>{% trans "Change openid associated to your account" %}</dd>
+ {% endif %}
+
+ <dt>» <a href="{% url user_delete %}">{% trans "Delete account" %}</a></dt>
+ <dd>{% trans "Erase your username and all your data from website" %}</dd>
+ </dl>
+</div>
+{% endblock %}
diff --git a/templates/authopenid/signin.html b/templates/authopenid/signin.html
new file mode 100644
index 00000000..aff2f06f
--- /dev/null
+++ b/templates/authopenid/signin.html
@@ -0,0 +1,95 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "User login" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript" src="/content/js/jquery.openid.js?"></script>
+ <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type="text/javascript">
+ $().ready(function(){
+ openid.init('id_openid_url');
+ setupFormValidation("#openid_form", {bsignin:{required: true}});
+ });
+ </script>
+{% endblock %}
+{% block content %}
+<div class="headNormal">
+ {% trans "User login" %}
+</div>
+<div class="login">
+ <form name="openid_form" action="{% url user_signin %}" method="post">
+ {{ form2.next }}
+ <p style="display:none">{% trans "we support two login modes" %}</p>
+ {% if msg %}
+ <p class="warning">{{ msg }}</p>
+ {% endif %}
+ <fieldset class="fieldset">
+ <legend ><strong>{% trans "Login with your OpenID" %}</strong></legend>
+ <div id="openid_choice">
+ <p>{% trans "select openid provider" %}</p>
+ <div id="openid_btns">
+ </div>
+ <br><br><br>
+ <p>{% trans "verify openid link and login" %}</p>
+ </div>
+ <p>
+ {{ form2.openid_url }} <input id="bsignin" name="bsignin" type="submit" value="{% trans "Login" %}" class="openid-login-submit" />
+ </p>
+ </fieldset>
+ </form>
+ <div style="display:none">
+ <br>
+ {% if form1.errors %}
+ <p class="errors">
+ <span class="big">{% trans "Sorry, looks like we have some errors:" %}</span><br>
+ <ul class="error-list">
+ {% if form1.username.errors %}
+ <li><span class="error">{{ form1.username.errors|join:", " }}</span></li>
+ {% endif %}
+ {% if form1.password.errors %}
+ <li><span class="error">{{ form1.password.errors|join:", " }}</span></li>
+ {% endif %}
+ </ul>
+ </p>
+ {% endif %}
+ <br>
+ <form name="fauth" action="{% url user_signin %}" method="post">
+ {{ form1.next }}
+ <fieldset class="fieldset" >
+ <legend class="big">{% trans "Use login name and password" %}</legend>
+ <div class="form-row"><label for="id_username">{% trans "Login name" %}:</label><br />{{ form1.username }}</div>
+ <div class="form-row"><label for="id_password">{% trans "Password" %}:</label><br />{{ form1.password }}</div>
+ <div class="submit-row"><input type="submit" class="submit" name="blogin" value="{% trans "Login" %}">
+ <a href="">{% trans "Forgot your password?" %}</a>
+ <a href="">{% trans "Create new acccount" %}</a></div>
+ </fieldset>
+ </form>
+ </div>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Why use OpenID?" %}</h3>
+ <p>
+ <ul class="list-item">
+ <li>
+ {% trans "with openid it is easier" %}
+ </li>
+ <li>
+ {% trans "reuse openid" %}
+ </li>
+ <li>
+ {% trans "openid is widely adopted" %}
+ </li>
+ <li>
+ {% trans "openid is supported open standard" %}
+ </li>
+
+ </ul>
+ <a href="http://openid.net/what/" target="_blank" style="float:right;position:relative">{% trans "Find out more" %} »</a><br>
+ <a href="http://openid.net/get/" target="_blank" style="float:right;position:relative">{% trans "Get OpenID" %} »</a>
+ </p>
+ <br>
+</div>
+{% endblock%}
+
diff --git a/templates/authopenid/signup.html b/templates/authopenid/signup.html
new file mode 100644
index 00000000..a4460aa3
--- /dev/null
+++ b/templates/authopenid/signup.html
@@ -0,0 +1,52 @@
+{% extends "base.html" %}
+{% block title %}{% spaceless %}{% trans "Signup" %}{% endspaceless %}{% endblock %}
+
+{% block content %}
+<div id="main-bar" class="">
+ <h3 >{% trans "Signup" %}</h3>
+
+</div>
+<div class="jointxt">
+ <p>{% trans "We support two types of user registration: conventional username/password, and" %} <a href="{% url user_signin %}">{% trans "the OpenID method" %}</a>.</p>
+
+ {% if form.errors %}
+
+ <p class="errors">
+ <span class="big">{% trans "Sorry, looks like we have some errors" %}</span><br>
+ <ul class="error-list">
+ {% if form.username.errors %}
+ <li><span class="error">{{ form.username.errors|join:", " }}</span></li>
+ {% endif %}
+ {% if form.email.errors %}
+ <li><span class="error">{{ form.email.errors|join:", " }}</span></li>
+ {% endif %}
+ {% if form.password2.errors %}
+ <li><span class="error">{{ form.password2.errors|join:", " }}
+ </span></li>
+ {% endif %}
+ </ul>
+ </p>
+ {% endif %}
+</div>
+ <form action="{% url user_signup %}" method="post" accept-charset="utf-8">
+ <fieldset class="fieldset">
+ <legend class="big">{% trans "Conventional registration" %}</legend>
+ <div class="form-row"><label for="id_username">{% trans "choose a user name" %}:</label><br>{{ form.username }}</div>
+
+ <div class="form-row"><label for="id_email">{% trans "your email address" %}:</label><br>{{ form.email }}</div>
+ <div class="form-row"><label for="id_password1">{% trans "choose password" %}:</label><br />{{ form.password1 }}</div>
+ <div class="form-row"><label for="id_password2">{% trans "retype password" %}:</label><br>{{ form.password2 }}</div>
+ <div class="submit-row"><input type="submit" class="submit" value="{% trans "login" %}" >
+ <a href="{% url user_signin %}">{% trans "back to login" %}</a></div>
+ </fieldset>
+ <br >
+ </form>
+ <br >
+ <div style="display:none">
+ <h2 class="signup">{% trans "Register with your OpenID" %}</h2>
+ <form name="fopenid" action="{% url user_signin %}" method="post">
+ <div class="form-row">{{ form2.openid_url }}</div>
+ <div class="submit-row "><input name="bsignin" class="submit" type="submit" value="{% trans "Login with your OpenID" %}"></div>
+ </form>
+ </div>
+{% endblock %}
diff --git a/templates/authopenid/yadis.xrdf b/templates/authopenid/yadis.xrdf
new file mode 100644
index 00000000..a9ed44fe
--- /dev/null
+++ b/templates/authopenid/yadis.xrdf
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<xrds:XRDS
+ xmlns:xrds='xri://$xrds'
+ xmlns:openid='http://openid.net/xmlns/1.0'
+ xmlns='xri://$xrd*($v*2.0)'>
+ <XRD>
+ <Service>
+ <Type>http://specs.openid.net/auth/2.0/return_to</Type>
+ {% for uri in return_to %}
+ <URI>{{ uri }}</URI>
+ {% endfor %}
+ </Service>
+ </XRD>
+</xrds:XRDS> \ No newline at end of file
diff --git a/templates/badge.html b/templates/badge.html
new file mode 100644
index 00000000..d0906918
--- /dev/null
+++ b/templates/badge.html
@@ -0,0 +1,37 @@
+<!-- template badge.html -->
+{% extends "base_content.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+{% block title %}{% spaceless %}{{ badge.name }} - {% trans "Badge" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#nav_badges").attr('className',"on");
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Badge" %}
+</div>
+<div id="main-body" style="width:100%;margin-bottom:20px">
+ <p>
+ <a href="{{badge.get_absolute_url}}" title="{{ badge.get_type_display }} : {{ badge.description }}" class="medal"><span class="badge{{ badge.type }}">&#9679;</span>&nbsp;{{ badge.name }}</a> {{ badge.description }}
+ </p>
+ <div>
+ {% if badge.awarded_count %}
+ <p style="float:left"><span class="count">{{ awards|length|intcomma }}</span>
+ <strong>{% trans "The users have been awarded with badges:" %}</strong></p>
+ {% endif %}
+ </div>
+ <div id="award-list" style="clear:both;margin-left:20px;line-height:25px;">
+ {% for award in awards %}
+ <p style="width:185px;float:left"><a href="/users/{{ award.id }}/{{ award.name }}">{{ award.name }}</a> {% get_score_badge_by_details award.rep award.gold award.silver award.bronze %}</p>
+ {% endfor %}
+ </div>
+
+</div>
+{% endblock %}
+<!-- end template badge.html -->
diff --git a/templates/badges.html b/templates/badges.html
new file mode 100644
index 00000000..8a3b7eab
--- /dev/null
+++ b/templates/badges.html
@@ -0,0 +1,78 @@
+<!-- template badges.html -->
+{% extends "base.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Badges summary" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#nav_badges").attr('className',"on");
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<div class="headlineA">
+ <span class="headMedals">{% trans "Badges" %}</span>
+</div>
+<div id="main-body" style="width:100%">
+ <p>
+ {% trans "Community gives you awards for your questions, answers and votes." %}<br>
+ {% trans "Below is the list of available badges and number of times each type of badge has been awarded." %}
+ </p>
+ <br>
+ <div id="medalList">
+ {% for badge in badges %}
+ <div style="clear:both;line-height:30px">
+ <div style="float:left;min-width:30px;text-align:right;height:30px">
+ {% for a in mybadges %}
+ {% ifequal a.badge_id badge.id %}
+ <span style="font-size:175%; padding-right:5px; color:#5B9058;">&#10004;</span>
+ {% endifequal %}
+ {% endfor %}
+ </div>
+ <div style="float:left;width:180px;">
+ <a href="{{badge.get_absolute_url}}" title="{{ badge.get_type_display }} : {{ badge.description }}" class="medal"><span class="badge{{ badge.type }}">&#9679;</span>&nbsp;{{ badge.name }}</a><strong> × {{ badge.awarded_count|intcomma }}</strong>
+ </div>
+ <p style="float:left;width:350px;">
+ {{ badge.description }}
+ </p>
+ </div>
+ {% endfor %}
+ </div>
+</div>
+{% endblock %}
+{% block sidebar %}
+
+<div class="boxB">
+ <h3>{% trans "Community badges" %}</h3>
+ <div class="body">
+ <p>
+ <a style="cursor:default;" title="gold badge: the highest honor and is very rare" class="medal"><span class="badge1">&#9679;</span>&nbsp;{% trans "gold" %}</a>
+ </p>
+ <p>
+ {% trans "Gold badge is very rare." %}
+ {% trans "To obtain it you have to show profound knowledge and ability in addition to actively participating in the community." %}
+ {% trans "Gold badge is the highest award in this community." %}
+ </p>
+ <p>
+ <a style="cursor:default;"
+ title="silver badge: occasionally awarded for the very high quality contributions"
+ class="medal"><span class="badge2">&#9679;</span>&nbsp;{% trans "silver" %}</a>
+ </p>
+ <p>
+ {% trans "Obtaining silver badge requires significant patience." %}
+ {% trans "If you got one, you've very significantly contributed to this community" %}
+ </p>
+ <p>
+ <a style="cursor:default;" title="{% trans "bronze badge: often given as a special honor" %}" class="medal">
+ <span class="badge3">&#9679;</span>&nbsp;{% trans "bronze" %}</a>
+ </p>
+ <p>
+ {% trans "If you are active in this community, you will will get this medal - still it is a special honor." %}
+ </p>
+ </div>
+</div>
+{% endblock %}
+<!-- end template badges.html -->
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 00000000..dca312e3
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!-- template base.html -->
+{% load extra_filters %}
+{% load i18n %}
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>{% block title %}{% endblock %} - {% trans "site title" %} - {% trans "site slogan" %}</title>
+ {% spaceless %}
+ {% block meta %}{% endblock %}
+ {% endspaceless %}
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta name="verify-v1" content="{{verify_v1_code}}" />
+ <link rel="shortcut icon" href="/content/images/favicon.ico" >
+ <link href="/content/style/style.css" rel="stylesheet" type="text/css" />
+ <script src="http://www.google.com/jsapi"></script>
+ <script>google.load("jquery", "1.2.6");</script>
+ <script type="text/javascript">
+ var i18nLang = 'en';
+ </script>
+ <script type='text/javascript' src='/content/js/com.cnprog.i18n.js'></script>
+ <script type='text/javascript' src='/content/js/jquery.i18n.js'></script>
+ <script type='text/javascript' src='/content/js/com.cnprog.utils.js'></script>
+ <!--<script type="text/javascript">
+ var uservoiceJsHost = ("https:" == document.location.protocol) ? "https://uservoice.com" : "http://cdn.uservoice.com";
+ document.write(unescape("%3Cscript src='" + uservoiceJsHost + "/javascripts/widgets/tab.js' type='text/javascript'%3E%3C/script%3E"))
+ </script>
+ <script type="text/javascript">
+ UserVoice.Tab.show({
+ //EDIT!!!
+ key: 'key',
+ host: 'where.uservoice.com',
+ forum: 'general',
+ alignment: 'left', /* 'left', 'right' */
+ background_color:'#777',
+ text_color: 'white', /* 'white', 'black' */
+ hover_color: '#06C',
+ lang: 'en' /* 'en', 'de', 'nl', 'es', 'fr' */
+ })
+ </script>-->
+ <!-- todo move this to settings -->
+ {% with request.user.get_messages as messages%}
+ {% if messages %}
+ <style type="text/css">
+ body { margin-top:2.4em; }
+ </style>
+ <script type="text/javascript">
+ $().ready(function() {
+ notify.show();
+ });
+ </script>
+ {% endif %}
+ {% endwith %}
+
+ {% block forejs %}
+ {% endblock %}
+ </head>
+ <body>
+ <div class="notify" style="display:none">
+ <span>{% if request.user.get_messages %}
+ {% trans "congratulations, community gave you a badge" %}: {% for message in request.user.get_messages %}
+ <font class="darkred">{{ message }}</font>, {% endfor %}查看
+ <a href="{{ request.user.get_profile_url }}">{% trans "profile" %}</a>{% endif %}</span>
+ <a class="close-notify" onclick="notify.close(true)">&times;</a>
+ </div>
+ {% include "header.html" %}
+ <div id="wrapper">
+ <div id="room">
+ <div id="CALeft">
+ {% block content%}
+ {% endblock%}
+
+ </div>
+
+ <div id="CARight">
+ {% block sidebar%}
+ {% endblock%}
+
+ </div>
+ <div id="tail" style="clear:both;">
+ {% block tail %}
+ {% endblock %}
+ </div>
+ </div>
+ <div class="spacer3"></div>
+ </div>
+ {% include "footer.html" %}
+ {% block endjs %}
+ {% endblock %}
+ </body>
+</html>
+<!-- end template base.html -->
diff --git a/templates/base_content.html b/templates/base_content.html
new file mode 100644
index 00000000..98a09150
--- /dev/null
+++ b/templates/base_content.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!-- template base_content.html -->
+{% load i18n %}
+<html>
+ <head>
+ <title>{% block title %}{% endblock %} - {% trans "site title" %} - {% trans "site slogan" %}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta name="verify-v1" content="55uGNnQVJW8p1bbXeF/Xbh9I7nZBM/wLhRz6N/I1kkA=" />
+ <link rel="shortcut icon" href="/content/images/favicon.ico" >
+ <link href="/content/style/style.css" rel="stylesheet" type="text/css" />
+ {% spaceless %}
+ {% block forestyle %}{% endblock %}
+ {% endspaceless %}
+ <script src="http://www.google.com/jsapi"></script>
+ <script>google.load("jquery", "1.2.6");</script>
+ <script type="text/javascript">
+ var i18nLang = 'en';
+ </script>
+ <script type='text/javascript' src='/content/js/com.cnprog.i18n.js'></script>
+ <script type='text/javascript' src='/content/js/jquery.i18n.js'></script>
+ <script type='text/javascript' src='/content/js/com.cnprog.utils.js'></script>
+
+ <!-- <script type="text/javascript">
+ var uservoiceJsHost = ("https:" == document.location.protocol) ? "https://uservoice.com" : "http://cdn.uservoice.com";
+ document.write(unescape("%3Cscript src='" + uservoiceJsHost + "/javascripts/widgets/tab.js' type='text/javascript'%3E%3C/script%3E"))
+ </script>
+ <script type="text/javascript">
+ UserVoice.Tab.show({
+ //EDIT!!!
+ key: 'uservoicekey',
+ host: 'where.uservoice.com',
+ forum: 'general',
+ alignment: 'left', /* 'left', 'right' */
+ background_color:'#777',
+ text_color: 'white', /* 'white', 'black' */
+ hover_color: '#06C',
+ lang: 'en' /* 'en', 'de', 'nl', 'es', 'fr' */
+ })
+ </script>-->
+ <!-- todo move this to settings-->
+
+ {% with request.user.get_messages as messages%}
+ {% if messages %}
+ <style type="text/css">
+ body { margin-top:2.4em; }
+ </style>
+ <script type="text/javascript">
+ $().ready(function() {
+ notify.show();
+ });
+ </script>
+ {% endif %}
+ {% endwith %}
+
+ {% block forejs %}
+ {% endblock %}
+ </head>
+ <body>
+ <div class="notify" style="display:none">
+ <span>{% if request.user.get_messages %}
+ {% trans "congratulations, community gave you a badge" %}:{% for message in request.user.get_messages %}
+ <font class="darkred">{{ message }}</font>, {% endfor %}{% trans "see" %}
+ <a href="{{ request.user.get_profile_url }}">{% trans "profile" %}</a>{% endif %}</span>
+ <a class="close-notify" onclick="notify.close(true)">&times;</a>
+ </div>
+ {% include "header.html" %}
+ <div id="wrapper">
+ <div id="room">
+ <div id="CAFull">
+ {% block content%}
+ {% endblock%}
+
+ </div>
+ <div id="tail" style="clear:both;">
+ {% block tail %}
+ {% endblock %}
+ </div>
+ </div>
+ <div class="spacer3"></div>
+ </div>
+ {% include "footer.html" %}
+ {% block endjs %}
+ {% endblock %}
+ </body>
+</html>
+<!-- end template base_content.html -->
diff --git a/templates/book.html b/templates/book.html
new file mode 100644
index 00000000..a58a09f2
--- /dev/null
+++ b/templates/book.html
@@ -0,0 +1,152 @@
+<!-- template book.html -->
+{% extends "base_content.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load extra_filters %}
+{% load humanize %}
+{% block title %}{% spaceless %}{{ book.title }}-{% trans "reading channel" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#nav_books").attr('className',"on");
+ //$("#nav_ask").hide();
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+ <div class="headNormal"><a href="{{ book.get_absolute_url }}">《{{ book.title }}》</a></div>
+ <div class="bookInfo">
+ <div class="bookCover">
+ <img src="{{ book.cover_img }}" >
+ </div>
+ <div class="bookSummary">
+ <table>
+ <tr>
+ <td>{% trans "[author]" %}</td>
+ <td><b><a href="{% url user book.user.id %}" rel="nofollow" >{{ book.author }}</a></b></td>
+ </tr>
+ <tr>
+ <td>{% trans "[publisher]" %}</td>
+ <td>{{ book.publication }}</td>
+ </tr>
+ <tr>
+ <td>{% trans "[publication date]" %}</td>
+ <td>{{ book.published_at|date:"Y-m" }}</td>
+ </tr>
+ <tr>
+ <td>{% trans "[price]" %}</td>
+ <td>{{ book.price }} {% trans "currency unit" %}</td>
+ </tr>
+ <tr>
+ <td>{% trans "[pages]" %}</td>
+ <td>{{ book.pages }} {% trans "pages abbreviation" %}</td>
+ </tr>
+ <tr>
+ <td>{% trans "[tags]" %}</td>
+ <td>{{ book.tagnames }}</td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td> </td>
+ </tr>
+ {% if author_info.blog_url %}
+ <tr>
+ <td></td>
+ <td><a href="{{ author_info.blog_url }}" rel="nofollow" >{% trans "author blog" %} »</a></td>
+ </tr>
+
+ {% endif %}
+ <tr>
+ <td> </td>
+ <td><a href="#" rel="nofollow">{% trans "book directory" %} »</a></td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td><a href="#" rel="nofollow">{% trans "buy online" %} »</a></td>
+ </tr>
+ </table>
+ </div>
+ <div class="blogRss">
+
+ </div>
+ </div>
+
+ <div class="bookQuestions">
+
+ <div class="tabBar">
+ <div class="tabsB">
+ <a id="qa" class="on" title="{% trans "book technical Q&A %}" href="#">{% trans "reader questions" %}</a>
+ </div>
+ </div>
+ <div class="bookAsk"><a href="{% url ask_book book.short_name %}">{% trans "ask the author" %}</div>
+ <div class="user-stats-table">
+ {% for question in questions.object_list %}
+ {% if question.favourite_count %}
+ {% if question.favorited_myself %}
+ <div class="favorites-count">
+ <img title="{% trans "this question was selected as favorite" %} {{question.favourite_count}} {% trans "number of times" %}" src="/content/images/vote-favorite-on.png">
+ <div><b>{{question.favourite_count|intcomma}}</b></div>
+ </div>
+ {% else %}
+ <div class="favorites-count-off">
+ <img title="{% trans "this question was selected as favorite" %} {{question.favourite_count}} {% trans "number of times" %}" src="/content/images/vote-favorite-off.png">
+ <div><b>{{question.favourite_count|intcomma}}</b></div>
+ </div>
+ {% endif %}
+ {% else %}
+ <div class="favorites-empty"> </div>
+ {% endif %}
+ <div id="question-summary-{{question.id}}" class="question-summary narrow">
+ <a style="text-decoration: none;" href="/questions/{{question.id}}/{{question.get_question_title}}">
+ <div class="stats">
+ <div class="votes">
+ <div class="vote-count-post">{{question.score|intcomma}}</div>
+ {% trans "votes" %}
+
+ </div>
+ <div title="{% if question.answer_accepted %}{% trans "the answer has been accepted to be correct" %}{% endif %}" class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
+ <div class="answer-count-post">{{question.answer_count|intcomma}}</div>
+ {% trans "answer" %}
+
+ </div>
+ <div class="views">
+ <div class="views-count-post">{{question.view_count|cnprog_intword|safe}}</div>
+ {% trans "views" %}
+ </div>
+ </div>
+ </a>
+ <div class="bookQuestionItem">
+ <h3>
+ <a title="{{question.summary}}" href="/questions/{{question.id}}/{{question.title}}">{{question.title}}</a>
+ </h3>
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% "see questions tagged with" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ <div class="started">
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ </div>
+ </div>
+ </div>
+ <br clear="both"/>
+ {% endfor %}
+ </div>
+ </div>
+{% endblock %}
+{% block tail %}
+ <div class="pager">
+ {% cnprog_paginator context %}
+
+ </div>
+ <div class="bookFeed">
+ <div id="feeds">
+ <a href="/feeds/rss" title="{% trans "subscribe to book RSS feed" %}">{% trans "subscribe to the questions feed" %}</a>
+ </div>
+ </div>
+
+{% endblock %}
+<!-- end template book.html -->
diff --git a/templates/close.html b/templates/close.html
new file mode 100644
index 00000000..32df3e82
--- /dev/null
+++ b/templates/close.html
@@ -0,0 +1,36 @@
+<!-- template close.html -->
+{% extends "base_content.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+{% block title %}{% spaceless %}{% trans "Close question" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $('#btBack').bind('click', function(){ history.back(); });
+ });
+ </script>
+{% endblock %}
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Close question" %}
+</div>
+<div id="main-body" style="width:100%;margin-bottom:10px">
+ <p>{% trans "Close the question" %}: <a href="{{ question.get_absolute_url }}">
+ <strong>{{ question.get_question_title }}</strong></a>
+ </p>
+
+ <form id="fmclose" action="{% url close question.id %}" method="post" >
+ <p>
+ <strong>{% trans "Reasons" %}:</strong> {{ form.reason }}
+ </p>
+ <div id="" style="padding-top:20px">
+ <input type="submit" value="{% trans "OK to close" %}" class="submit" />
+ <input id="btBack" type="button" class="submit" value="{% trans "Cancel" %}" />
+
+ </div>
+
+ </form>
+</div>
+{% endblock %}
+<!-- end template close.html -->
diff --git a/templates/content/images/box-arrow.gif b/templates/content/images/box-arrow.gif
new file mode 100644
index 00000000..89dcf5b3
--- /dev/null
+++ b/templates/content/images/box-arrow.gif
Binary files differ
diff --git a/templates/content/images/bullet_green.gif b/templates/content/images/bullet_green.gif
new file mode 100644
index 00000000..fa530910
--- /dev/null
+++ b/templates/content/images/bullet_green.gif
Binary files differ
diff --git a/templates/content/images/cc-88x31.png b/templates/content/images/cc-88x31.png
new file mode 100644
index 00000000..0f2a0f10
--- /dev/null
+++ b/templates/content/images/cc-88x31.png
Binary files differ
diff --git a/templates/content/images/cc-wiki.png b/templates/content/images/cc-wiki.png
new file mode 100644
index 00000000..3e680538
--- /dev/null
+++ b/templates/content/images/cc-wiki.png
Binary files differ
diff --git a/templates/content/images/close-small-hover.png b/templates/content/images/close-small-hover.png
new file mode 100644
index 00000000..7899aec7
--- /dev/null
+++ b/templates/content/images/close-small-hover.png
Binary files differ
diff --git a/templates/content/images/close-small.png b/templates/content/images/close-small.png
new file mode 100644
index 00000000..5a99d31f
--- /dev/null
+++ b/templates/content/images/close-small.png
Binary files differ
diff --git a/templates/content/images/cnprog_logo_200_56.gif b/templates/content/images/cnprog_logo_200_56.gif
new file mode 100644
index 00000000..ab690de2
--- /dev/null
+++ b/templates/content/images/cnprog_logo_200_56.gif
Binary files differ
diff --git a/templates/content/images/dash.gif b/templates/content/images/dash.gif
new file mode 100644
index 00000000..d1ddc507
--- /dev/null
+++ b/templates/content/images/dash.gif
Binary files differ
diff --git a/templates/content/images/djangomade124x25_grey.gif b/templates/content/images/djangomade124x25_grey.gif
new file mode 100644
index 00000000..d34bb311
--- /dev/null
+++ b/templates/content/images/djangomade124x25_grey.gif
Binary files differ
diff --git a/templates/content/images/dot-g.gif b/templates/content/images/dot-g.gif
new file mode 100644
index 00000000..5d6bb28e
--- /dev/null
+++ b/templates/content/images/dot-g.gif
Binary files differ
diff --git a/templates/content/images/dot-list.gif b/templates/content/images/dot-list.gif
new file mode 100644
index 00000000..f6a6b865
--- /dev/null
+++ b/templates/content/images/dot-list.gif
Binary files differ
diff --git a/templates/content/images/edit.png b/templates/content/images/edit.png
new file mode 100644
index 00000000..dcb09be0
--- /dev/null
+++ b/templates/content/images/edit.png
Binary files differ
diff --git a/templates/content/images/expander-arrow-hide.gif b/templates/content/images/expander-arrow-hide.gif
new file mode 100644
index 00000000..feb6a618
--- /dev/null
+++ b/templates/content/images/expander-arrow-hide.gif
Binary files differ
diff --git a/templates/content/images/expander-arrow-show.gif b/templates/content/images/expander-arrow-show.gif
new file mode 100644
index 00000000..6825c56e
--- /dev/null
+++ b/templates/content/images/expander-arrow-show.gif
Binary files differ
diff --git a/templates/content/images/favicon.gif b/templates/content/images/favicon.gif
new file mode 100644
index 00000000..910c2666
--- /dev/null
+++ b/templates/content/images/favicon.gif
Binary files differ
diff --git a/templates/content/images/feed-icon-small.png b/templates/content/images/feed-icon-small.png
new file mode 100644
index 00000000..b3c949d2
--- /dev/null
+++ b/templates/content/images/feed-icon-small.png
Binary files differ
diff --git a/templates/content/images/grippie.png b/templates/content/images/grippie.png
new file mode 100644
index 00000000..6524d416
--- /dev/null
+++ b/templates/content/images/grippie.png
Binary files differ
diff --git a/templates/content/images/indicator.gif b/templates/content/images/indicator.gif
new file mode 100644
index 00000000..1c72ebb5
--- /dev/null
+++ b/templates/content/images/indicator.gif
Binary files differ
diff --git a/templates/content/images/logo.png b/templates/content/images/logo.png
new file mode 100644
index 00000000..640eb1da
--- /dev/null
+++ b/templates/content/images/logo.png
Binary files differ
diff --git a/templates/content/images/logo1.png b/templates/content/images/logo1.png
new file mode 100644
index 00000000..d79a6271
--- /dev/null
+++ b/templates/content/images/logo1.png
Binary files differ
diff --git a/templates/content/images/logo2.png b/templates/content/images/logo2.png
new file mode 100644
index 00000000..bd3cccd9
--- /dev/null
+++ b/templates/content/images/logo2.png
Binary files differ
diff --git a/templates/content/images/medala.gif b/templates/content/images/medala.gif
new file mode 100644
index 00000000..93dd1a39
--- /dev/null
+++ b/templates/content/images/medala.gif
Binary files differ
diff --git a/templates/content/images/medala_on.gif b/templates/content/images/medala_on.gif
new file mode 100644
index 00000000..a18f9e85
--- /dev/null
+++ b/templates/content/images/medala_on.gif
Binary files differ
diff --git a/templates/content/images/new.gif b/templates/content/images/new.gif
new file mode 100644
index 00000000..8a220b53
--- /dev/null
+++ b/templates/content/images/new.gif
Binary files differ
diff --git a/templates/content/images/nophoto.png b/templates/content/images/nophoto.png
new file mode 100644
index 00000000..2daf0ffd
--- /dev/null
+++ b/templates/content/images/nophoto.png
Binary files differ
diff --git a/templates/content/images/openid.gif b/templates/content/images/openid.gif
new file mode 100644
index 00000000..8540e12b
--- /dev/null
+++ b/templates/content/images/openid.gif
Binary files differ
diff --git a/templates/content/images/openid/aol.gif b/templates/content/images/openid/aol.gif
new file mode 100644
index 00000000..decc4f12
--- /dev/null
+++ b/templates/content/images/openid/aol.gif
Binary files differ
diff --git a/templates/content/images/openid/blogger.ico b/templates/content/images/openid/blogger.ico
new file mode 100644
index 00000000..1b9730b0
--- /dev/null
+++ b/templates/content/images/openid/blogger.ico
Binary files differ
diff --git a/templates/content/images/openid/claimid.ico b/templates/content/images/openid/claimid.ico
new file mode 100644
index 00000000..2b80f491
--- /dev/null
+++ b/templates/content/images/openid/claimid.ico
Binary files differ
diff --git a/templates/content/images/openid/facebook.gif b/templates/content/images/openid/facebook.gif
new file mode 100644
index 00000000..b997b358
--- /dev/null
+++ b/templates/content/images/openid/facebook.gif
Binary files differ
diff --git a/templates/content/images/openid/flickr.ico b/templates/content/images/openid/flickr.ico
new file mode 100644
index 00000000..11f6e07f
--- /dev/null
+++ b/templates/content/images/openid/flickr.ico
Binary files differ
diff --git a/templates/content/images/openid/google.gif b/templates/content/images/openid/google.gif
new file mode 100644
index 00000000..1b6cd07b
--- /dev/null
+++ b/templates/content/images/openid/google.gif
Binary files differ
diff --git a/templates/content/images/openid/livejournal.ico b/templates/content/images/openid/livejournal.ico
new file mode 100644
index 00000000..f3d21ec5
--- /dev/null
+++ b/templates/content/images/openid/livejournal.ico
Binary files differ
diff --git a/templates/content/images/openid/myopenid.ico b/templates/content/images/openid/myopenid.ico
new file mode 100644
index 00000000..ceb06e6a
--- /dev/null
+++ b/templates/content/images/openid/myopenid.ico
Binary files differ
diff --git a/templates/content/images/openid/openid-inputicon.gif b/templates/content/images/openid/openid-inputicon.gif
new file mode 100644
index 00000000..cde836c8
--- /dev/null
+++ b/templates/content/images/openid/openid-inputicon.gif
Binary files differ
diff --git a/templates/content/images/openid/openid.gif b/templates/content/images/openid/openid.gif
new file mode 100644
index 00000000..c718b0e6
--- /dev/null
+++ b/templates/content/images/openid/openid.gif
Binary files differ
diff --git a/templates/content/images/openid/technorati.ico b/templates/content/images/openid/technorati.ico
new file mode 100644
index 00000000..fa1083c1
--- /dev/null
+++ b/templates/content/images/openid/technorati.ico
Binary files differ
diff --git a/templates/content/images/openid/verisign.ico b/templates/content/images/openid/verisign.ico
new file mode 100644
index 00000000..3953af93
--- /dev/null
+++ b/templates/content/images/openid/verisign.ico
Binary files differ
diff --git a/templates/content/images/openid/vidoop.ico b/templates/content/images/openid/vidoop.ico
new file mode 100644
index 00000000..bbd9a0d5
--- /dev/null
+++ b/templates/content/images/openid/vidoop.ico
Binary files differ
diff --git a/templates/content/images/openid/wordpress.ico b/templates/content/images/openid/wordpress.ico
new file mode 100644
index 00000000..31b7d2c2
--- /dev/null
+++ b/templates/content/images/openid/wordpress.ico
Binary files differ
diff --git a/templates/content/images/openid/yahoo.gif b/templates/content/images/openid/yahoo.gif
new file mode 100644
index 00000000..42adbfa5
--- /dev/null
+++ b/templates/content/images/openid/yahoo.gif
Binary files differ
diff --git a/templates/content/images/quest-bg.gif b/templates/content/images/quest-bg.gif
new file mode 100644
index 00000000..b7540238
--- /dev/null
+++ b/templates/content/images/quest-bg.gif
Binary files differ
diff --git a/templates/content/images/vote-accepted-on.png b/templates/content/images/vote-accepted-on.png
new file mode 100644
index 00000000..2026f3bc
--- /dev/null
+++ b/templates/content/images/vote-accepted-on.png
Binary files differ
diff --git a/templates/content/images/vote-accepted.png b/templates/content/images/vote-accepted.png
new file mode 100644
index 00000000..ecd18551
--- /dev/null
+++ b/templates/content/images/vote-accepted.png
Binary files differ
diff --git a/templates/content/images/vote-arrow-down-on.png b/templates/content/images/vote-arrow-down-on.png
new file mode 100644
index 00000000..048dbb44
--- /dev/null
+++ b/templates/content/images/vote-arrow-down-on.png
Binary files differ
diff --git a/templates/content/images/vote-arrow-down.png b/templates/content/images/vote-arrow-down.png
new file mode 100644
index 00000000..e4fdec0a
--- /dev/null
+++ b/templates/content/images/vote-arrow-down.png
Binary files differ
diff --git a/templates/content/images/vote-arrow-up-on.png b/templates/content/images/vote-arrow-up-on.png
new file mode 100644
index 00000000..56ad0c25
--- /dev/null
+++ b/templates/content/images/vote-arrow-up-on.png
Binary files differ
diff --git a/templates/content/images/vote-arrow-up.png b/templates/content/images/vote-arrow-up.png
new file mode 100644
index 00000000..6e9a51c7
--- /dev/null
+++ b/templates/content/images/vote-arrow-up.png
Binary files differ
diff --git a/templates/content/images/vote-favorite-off.png b/templates/content/images/vote-favorite-off.png
new file mode 100644
index 00000000..c1bef074
--- /dev/null
+++ b/templates/content/images/vote-favorite-off.png
Binary files differ
diff --git a/templates/content/images/vote-favorite-on.png b/templates/content/images/vote-favorite-on.png
new file mode 100644
index 00000000..1f9c14ab
--- /dev/null
+++ b/templates/content/images/vote-favorite-on.png
Binary files differ
diff --git a/templates/content/js/com.cnprog.editor.js b/templates/content/js/com.cnprog.editor.js
new file mode 100644
index 00000000..289d9866
--- /dev/null
+++ b/templates/content/js/com.cnprog.editor.js
@@ -0,0 +1,68 @@
+/*
+ jQuery TextAreaResizer plugin
+ Created on 17th January 2008 by Ryan O'Dell
+ Version 1.0.4
+*/(function($){var textarea,staticOffset;var iLastMousePos=0;var iMin=32;var grip;$.fn.TextAreaResizer=function(){return this.each(function(){textarea=$(this).addClass('processed'),staticOffset=null;$(this).wrap('<div class="resizable-textarea"><span></span></div>').parent().append($('<div class="grippie"></div>').bind("mousedown",{el:this},startDrag));var grippie=$('div.grippie',$(this).parent())[0];grippie.style.marginRight=(grippie.offsetWidth-$(this)[0].offsetWidth)+'px'})};function startDrag(e){textarea=$(e.data.el);textarea.blur();iLastMousePos=mousePosition(e).y;staticOffset=textarea.height()-iLastMousePos;textarea.css('opacity',0.25);$(document).mousemove(performDrag).mouseup(endDrag);return false}function performDrag(e){var iThisMousePos=mousePosition(e).y;var iMousePos=staticOffset+iThisMousePos;if(iLastMousePos>=(iThisMousePos)){iMousePos-=5}iLastMousePos=iThisMousePos;iMousePos=Math.max(iMin,iMousePos);textarea.height(iMousePos+'px');if(iMousePos<iMin){endDrag(e)}return false}function endDrag(e){$(document).unbind('mousemove',performDrag).unbind('mouseup',endDrag);textarea.css('opacity',1);textarea.focus();textarea=null;staticOffset=null;iLastMousePos=0}function mousePosition(e){return{x:e.clientX+document.documentElement.scrollLeft,y:e.clientY+document.documentElement.scrollTop}}})(jQuery);
+/*
+ * Autocomplete - jQuery plugin 1.0.2
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i<data.length;i++){if(data[i].result.toLowerCase()==q.toLowerCase()){result=data[i];break;}}}if(typeof fn=="function")fn(result);else $input.trigger("result",result&&[result.data,result.value]);}$.each(trimWords($input.val()),function(i,value){request(value,findValueCallback,findValueCallback);});}).bind("flushCache",function(){cache.flush();}).bind("setOptions",function(){$.extend(options,arguments[1]);if("data"in arguments[1])cache.populate();}).bind("unautocomplete",function(){select.unbind();$input.unbind();$(input.form).unbind(".autocomplete");});function selectCurrent(){var selected=select.selected();if(!selected)return false;var v=selected.result;previousValue=v;if(options.multiple){var words=trimWords($input.val());if(words.length>1){v=words.slice(0,words.length-1).join(options.multipleSeparator)+options.multipleSeparator+v;}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&&currentValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value){return[""];}var words=value.split(options.multipleSeparator);var result=[];$.each(words,function(i,value){if($.trim(value))result[i]=$.trim(value);});return result;}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$.Autocompleter.Selection(input,previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else
+$input.val("");}});}if(wasVisible)$.Autocompleter.Selection(input,input.value.length,input.value.length);};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else
+if(data[q]){return data[q];}else
+if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.Autocompleter.Selection=function(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}else if(field.setSelectionRange){field.setSelectionRange(start,end);}else{if(field.selectionStart){field.selectionStart=start;field.selectionEnd=end;}}field.focus();};})(jQuery);
+/*
+ * TypeWatch 2.0 - Original by Denny Ferrassoli / Refactored by Charles Christolini
+ * Copyright(c) 2007 Denny Ferrassoli - DennyDotNet.com
+ * Coprright(c) 2008 Charles Christolini - BinaryPie.com
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+*/(function(jQuery){jQuery.fn.typeWatch=function(o){var options=jQuery.extend({wait:750,callback:function(){},highlight:true,captureLength:2},o);function checkElement(timer,override){var elTxt=jQuery(timer.el).val();if((elTxt.length>options.captureLength&&elTxt.toUpperCase()!=timer.text)||(override&&elTxt.length>options.captureLength)){timer.text=elTxt.toUpperCase();timer.cb(elTxt)}};function watchElement(elem){if(elem.type.toUpperCase()=="TEXT"||elem.nodeName.toUpperCase()=="TEXTAREA"){var timer={timer:null,text:jQuery(elem).val().toUpperCase(),cb:options.callback,el:elem,wait:options.wait};if(options.highlight){jQuery(elem).focus(function(){this.select()})}var startWatch=function(evt){var timerWait=timer.wait;var overrideBool=false;if(evt.keyCode==13&&this.type.toUpperCase()=="TEXT"){timerWait=1;overrideBool=true}var timerCallbackFx=function(){checkElement(timer,overrideBool)};clearTimeout(timer.timer);timer.timer=setTimeout(timerCallbackFx,timerWait)};jQuery(elem).keydown(startWatch)}};return this.each(function(index){watchElement(this)})}})(jQuery);
+/*
+Ajax upload
+*/jQuery.extend({createUploadIframe:function(d,b){var a="jUploadFrame"+d;if(window.ActiveXObject){var c=document.createElement('<iframe id="'+a+'" name="'+a+'" />');if(typeof b=="boolean"){c.src="javascript:false"}else{if(typeof b=="string"){c.src=b}}}else{var c=document.createElement("iframe");c.id=a;c.name=a}c.style.position="absolute";c.style.top="-1000px";c.style.left="-1000px";document.body.appendChild(c);return c},createUploadForm:function(g,b){var e="jUploadForm"+g;var a="jUploadFile"+g;var d=$('<form action="" method="POST" name="'+e+'" id="'+e+'" enctype="multipart/form-data"></form>');var c=$("#"+b);var f=$(c).clone();$(c).attr("id",a);$(c).before(f);$(c).appendTo(d);$(d).css("position","absolute");$(d).css("top","-1200px");$(d).css("left","-1200px");$(d).appendTo("body");return d},ajaxFileUpload:function(k){k=jQuery.extend({},jQuery.ajaxSettings,k);var a=new Date().getTime();var b=jQuery.createUploadForm(a,k.fileElementId);var i=jQuery.createUploadIframe(a,k.secureuri);var h="jUploadFrame"+a;var j="jUploadForm"+a;if(k.global&&!jQuery.active++){jQuery.event.trigger("ajaxStart")}var c=false;var f={};if(k.global){jQuery.event.trigger("ajaxSend",[f,k])}var d=function(l){var p=document.getElementById(h);try{if(p.contentWindow){f.responseText=p.contentWindow.document.body?p.contentWindow.document.body.innerText:null;f.responseXML=p.contentWindow.document.XMLDocument?p.contentWindow.document.XMLDocument:p.contentWindow.document}else{if(p.contentDocument){f.responseText=p.contentDocument.document.body?p.contentDocument.document.body.textContent||document.body.innerText:null;f.responseXML=p.contentDocument.document.XMLDocument?p.contentDocument.document.XMLDocument:p.contentDocument.document}}}catch(o){jQuery.handleError(k,f,null,o)}if(f||l=="timeout"){c=true;var m;try{m=l!="timeout"?"success":"error";if(m!="error"){var n=jQuery.uploadHttpData(f,k.dataType);if(k.success){k.success(n,m)}if(k.global){jQuery.event.trigger("ajaxSuccess",[f,k])}}else{jQuery.handleError(k,f,m)}}catch(o){m="error";jQuery.handleError(k,f,m,o)}if(k.global){jQuery.event.trigger("ajaxComplete",[f,k])}if(k.global&&!--jQuery.active){jQuery.event.trigger("ajaxStop")}if(k.complete){k.complete(f,m)}jQuery(p).unbind();setTimeout(function(){try{$(p).remove();$(b).remove()}catch(q){jQuery.handleError(k,f,null,q)}},100);f=null}};if(k.timeout>0){setTimeout(function(){if(!c){d("timeout")}},k.timeout)}try{var b=$("#"+j);$(b).attr("action",k.url);$(b).attr("method","POST");$(b).attr("target",h);if(b.encoding){b.encoding="multipart/form-data"}else{b.enctype="multipart/form-data"}$(b).submit()}catch(g){jQuery.handleError(k,f,null,g)}if(window.attachEvent){document.getElementById(h).attachEvent("onload",d)}else{document.getElementById(h).addEventListener("load",d,false)}return{abort:function(){}}},uploadHttpData:function(r,type){var data=!type;data=type=="xml"||data?r.responseXML:r.responseText;if(type=="script"){jQuery.globalEval(data)}if(type=="json"){eval("data = "+data)}if(type=="html"){jQuery("<div>").html(data).evalScripts()}return data}});
+/*Upload call*/
+function ajaxFileUpload(imageUrl)
+{
+ $("#loading").ajaxStart(function(){
+ $(this).show();
+ }).ajaxComplete(function(){
+ $(this).hide();
+ });
+
+ $("#upload").ajaxStart(function(){
+ $(this).hide();
+ }).ajaxComplete(function(){
+ $(this).show();
+ });
+
+ $.ajaxFileUpload
+ (
+ {
+ url:'/upload/',
+ secureuri:false,
+ fileElementId:'file-upload',
+ dataType: 'xml',
+ success: function (data, status)
+ {
+ var fileURL = $(data).find('file_url').text();
+ var error = $(data).find('error').text();
+ if(error != ''){
+ alert(error);
+ }else{
+ imageUrl.attr('value', fileURL);
+ }
+
+ },
+ error: function (data, status, e)
+ {
+ alert(e);
+ }
+ }
+ )
+
+ return false;
+} \ No newline at end of file
diff --git a/templates/content/js/com.cnprog.i18n.js b/templates/content/js/com.cnprog.i18n.js
new file mode 100644
index 00000000..2d8c90a6
--- /dev/null
+++ b/templates/content/js/com.cnprog.i18n.js
@@ -0,0 +1,90 @@
+// Evgeny Fadeev evgeny.fadeev@gmail.com localized for english and chinese
+// i18nLang variable must be set in html header to extract correct language
+var i18nZh = {
+ 'insufficient privilege':'用户权限不在操作范围',
+ 'cannot pick own answer as best':'不能设置自己的回答为最佳答案',
+ 'anonymous users cannot select favorite questions':'匿名用户不能收藏问题,请先',
+ 'please login':'注册或者登录',
+ 'anonymous users cannot vote':'匿名用户不能投票',
+ '>15 points requried to upvote':'需要+15积分才能投支持票。',
+ '>100 points required to downvote':'需要+100积分才能投反对票。',
+ 'please see': '查看',
+ 'cannot vote for own posts':'不能给自己的帖子投票',
+ 'daily vote cap exhausted':'对不起,您已用完今日所有的投票。',
+ 'cannot revoke old vote':'这个投票已经过时,不能撤销。',
+ 'please confirm offensive':"确定要归类该帖为广告、人身攻击、恶意言论吗?",
+ 'anonymous users cannot flag offensive posts':'匿名用户不能操作,请先',
+ 'cannot flag message as offensive twice':'不能重复操作。',
+ 'flag offensive cap exhausted':'对不起,您已用完今日所有的5次‘水帖’操作。',
+ 'need >15 points to report spam':"需要+15积分才能归类‘垃圾帖’。",
+ 'confirm delete':"确定要删除/撤销删除该帖吗?",
+ 'anonymous users cannot delete/undelete':"匿名用户不能删除或撤销删除帖子",
+ 'post recovered':"操作成功!该帖子已被恢复。",
+ 'post deleted':"操作成功!该帖子已删除。",
+ 'add comment':'添加评论',
+ 'community reputation points':'社区积分',
+ 'to comment, need':'评论需要',
+ 'delete this comment':'删除此评论',
+ 'hide comments':"隐藏评论",
+ 'add a comment':"添加评论",
+ 'comments':"评论",
+ 'confirm delete comment':"真要删除此评论吗?",
+ 'characters':'字符',
+ 'can write':'还可写',
+ 'click to close':'点击消息框关闭',
+ 'loading...':'读取中...',
+ 'tags cannot be empty':'标签不能为空。',
+ 'tablimits info':"最多5个标签,每个标签长度小于20个字符。",
+ 'content cannot be empty':'内容不能为空。',
+ 'content minchars': '请输入至少 {0} 字符。',
+ 'please enter title':'请输入标题。',
+ 'title minchars':"请输入至少 {0} 字符。",
+ 'delete':'删除',
+ 'undelete': '取消',
+ 'bold':'粗体',
+ 'italic':'斜体',
+ 'link':'超链接',
+ 'quote':'引用',
+ 'preformatted text':'代码',
+ 'image':'图片',
+ 'numbered list':'数字编号列表',
+ 'bulleted list':'项目符号列表',
+ 'heading':'标题',
+ 'horizontal bar':'水平线',
+ 'undo':'撤销',
+ 'redo':'重做',
+ 'enter image url':'<b>输入图片地址</b></p><p>示例:<br />http://www.example.com/image.jpg \"我的截图\"',
+ 'enter url':'<b>输入Web地址</b></p><p>示例:<br />http://www.cnprog.com/ \"我的网站\"</p>"',
+ 'upload image':'或者上传本地图片:',
+};
+
+var i18nEn = {
+ 'to comment, need': 'to comment, need reputation ',
+ 'please see':'please see ',
+ 'community reputation points':' ',
+ 'upload image':'Upload image:',
+ 'enter image url':'enter URL of the image, e.g. http://www.example.com/image.jpg \"image title\"',
+ 'enter url':'enter Web address, e.g. http://www.example.com \"page title\"',
+ 'daily vote cap exhausted':'sorry, you\'ve used up todays vote cap',
+ 'cannot pick own answer as best':'cannot accept own answer',
+ 'cannot revoke old vote':'older votes cannot be revoked',
+ 'please confirm offensive':'are you sure this post is offensive, contains spam, advertising, malicious remarks, etc.?',
+ 'flag offensive cap exhausted':'sorry, you\'ve used up todays cap of flagging offensive messages',
+ 'confirm delete':'are you sure you want to delete this?',
+ 'anonymous users cannot delete/undelete':'anonymous users cannot delete or undelete posts',
+ 'post recovered':'your post is now restored!',
+ 'post deleted':'your post has been deleted',
+ 'confirm delete comment':'do you really want to delete this comment?',
+ 'can write':'have ',
+ 'tablimits info':'up to 5 tags, no more than 20 characters each',
+ 'content minchars': 'please enter more than {0} characters',
+ 'title minchars':"please enter at least {0} characters",
+ 'characters':'characters left',
+};
+
+var i18n = {
+ 'en':i18nEn,
+ 'zh':i18nZh
+};
+
+var i18n_dict = i18n[i18nLang];
diff --git a/templates/content/js/com.cnprog.post.js b/templates/content/js/com.cnprog.post.js
new file mode 100644
index 00000000..bd4b00fd
--- /dev/null
+++ b/templates/content/js/com.cnprog.post.js
@@ -0,0 +1,616 @@
+/*
+Scripts for cnprog.com
+Project Name: Lanai
+All Rights Resevred 2008. CNPROG.COM
+*/
+var lanai =
+{
+ /**
+ * Finds any <pre><code></code></pre> tags which aren't registered for
+ * pretty printing, adds the appropriate class name and invokes prettify.
+ */
+ highlightSyntax: function(){
+ var styled = false;
+ $("pre code").parent().each(function(){
+ if (!$(this).hasClass('prettyprint')){
+ $(this).addClass('prettyprint');
+ styled = true;
+ }
+ });
+
+ if (styled){
+ prettyPrint();
+ }
+ }
+};
+
+var Vote = function(){
+ // All actions are related to a question
+ var questionId;
+ // The object we operate on actually. It can be a question or an answer.
+ var postId;
+ var questionAuthorId;
+ var currentUserId;
+ var answerContainerIdPrefix = 'answer-container-';
+ var voteContainerId = 'vote-buttons';
+ var imgIdPrefixAccept = 'answer-img-accept-';
+ var imgClassPrefixFavorite = 'question-img-favorite';
+ var imgIdPrefixQuestionVoteup = 'question-img-upvote-';
+ var imgIdPrefixQuestionVotedown = 'question-img-downvote-';
+ var imgIdPrefixAnswerVoteup = 'answer-img-upvote-';
+ var imgIdPrefixAnswerVotedown = 'answer-img-downvote-';
+ var divIdFavorite = 'favorite-number';
+ var commentLinkIdPrefix = 'comment-';
+ var voteNumberClass = "vote-number";
+ var offensiveIdPrefixQuestionFlag = 'question-offensive-flag-';
+ var offensiveIdPrefixAnswerFlag = 'answer-offensive-flag-';
+ var offensiveClassFlag = 'offensive-flag';
+ var questionControlsId = 'question-controls';
+ var removeQuestionLinkIdPrefix = 'question-delete-link-';
+ var removeAnswerLinkIdPrefix = 'answer-delete-link-';
+
+ var acceptAnonymousMessage = $.i18n._('insufficient privilege');
+ var acceptOwnAnswerMessage = $.i18n._('cannot pick own answer as best');
+ var favoriteAnonymousMessage = $.i18n._('anonymous user cannot select favorite questions')
+ + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
+ + $.i18n._('please login') + "</a>";
+ var voteAnonymousMessage = $.i18n._('anonymous users cannot vote')
+ + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
+ + $.i18n._('please login') + "</a>";
+ var upVoteRequiredScoreMessage = $.i18n._('>15 points requried to upvote')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var downVoteRequiredScoreMessage = $.i18n._('>100 points requried to downvote')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var voteOwnDeniedMessage = $.i18n._('cannot vote for own posts');
+ var voteRequiredMoreVotes = $.i18n._('daily vote cap exhausted')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var voteDenyCancelMessage = $.i18n._('cannot revoke old vote')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var offensiveConfirmation = $.i18n._('please confirm offensive');
+ var offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts')
+ + "<a href='/account/signin/?next=/questions/{{QuestionID}}'>"
+ + $.i18n._('please login') + "</a>";
+ var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam')
+ + $.i18n._('please see') + "<a href='/faq'>faq</a>";
+ var removeConfirmation = $.i18n._('confirm delete');
+ var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete');
+ var recoveredMessage = $.i18n._('post recovered');
+ var deletedMessage = $.i18n._('post deleted');
+
+ var VoteType = {
+ acceptAnswer : 0,
+ questionUpVote : 1,
+ questionDownVote : 2,
+ favorite : 4,
+ answerUpVote: 5,
+ answerDownVote:6,
+ offensiveQuestion : 7,
+ offensiveAnswer:8,
+ removeQuestion: 9,
+ removeAnswer:10
+ };
+
+ var getFavoriteButton = function(){
+ var favoriteButton = 'div.'+ voteContainerId +' img[class='+ imgClassPrefixFavorite +']';
+ return $(favoriteButton);
+ };
+ var getFavoriteNumber = function(){
+ var favoriteNumber = '#'+ divIdFavorite ;
+ return $(favoriteNumber);
+ };
+ var getQuestionVoteUpButton = function(){
+ var questionVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVoteup +']';
+ return $(questionVoteUpButton);
+ };
+ var getQuestionVoteDownButton = function(){
+ var questionVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixQuestionVotedown +']';
+ return $(questionVoteDownButton);
+ };
+ var getAnswerVoteUpButtons = function(){
+ var answerVoteUpButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVoteup +']';
+ return $(answerVoteUpButton);
+ };
+ var getAnswerVoteDownButtons = function(){
+ var answerVoteDownButton = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAnswerVotedown +']';
+ return $(answerVoteDownButton);
+ };
+ var getAnswerVoteUpButton = function(id){
+ var answerVoteUpButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVoteup + id + ']';
+ return $(answerVoteUpButton);
+ };
+ var getAnswerVoteDownButton = function(id){
+ var answerVoteDownButton = 'div.'+ voteContainerId +' img[id='+ imgIdPrefixAnswerVotedown + id + ']';
+ return $(answerVoteDownButton);
+ };
+
+ var getOffensiveQuestionFlag = function(){
+ var offensiveQuestionFlag = 'table[id=question-table] span[class='+ offensiveClassFlag +']';
+ return $(offensiveQuestionFlag);
+ };
+
+ var getOffensiveAnswerFlags = function(){
+ var offensiveQuestionFlag = 'div.answer span[class='+ offensiveClassFlag +']';
+ return $(offensiveQuestionFlag);
+ };
+
+ var getremoveQuestionLink = function(){
+ var removeQuestionLink = 'div#question-controls a[id^='+ removeQuestionLinkIdPrefix +']';
+ return $(removeQuestionLink);
+ };
+
+ var getremoveAnswersLinks = function(){
+ var removeAnswerLinks = 'div.answer-controls a[id^='+ removeAnswerLinkIdPrefix +']';
+ return $(removeAnswerLinks);
+ };
+
+ var setVoteImage = function(voteType, undo, object){
+ var flag = undo ? "" : "-on";
+ var arrow = (voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote) ? "up" : "down";
+ object.attr("src", "/content/images/vote-arrow-"+ arrow + flag +".png");
+
+ // if undo voting, then undo the pair of arrows.
+ if(undo){
+ if(voteType == VoteType.questionUpVote || voteType == VoteType.questionDownVote){
+ $(getQuestionVoteUpButton()).attr("src", "/content/images/vote-arrow-up.png");
+ $(getQuestionVoteDownButton()).attr("src", "/content/images/vote-arrow-down.png");
+ }
+ else{
+ $(getAnswerVoteUpButton(postId)).attr("src", "/content/images/vote-arrow-up.png");
+ $(getAnswerVoteDownButton(postId)).attr("src", "/content/images/vote-arrow-down.png");
+ }
+ }
+ };
+
+ var setVoteNumber = function(object, number){
+ var voteNumber = object.parent('div.'+ voteContainerId).find('div.'+ voteNumberClass);
+ $(voteNumber).text(number);
+ };
+
+ var bindEvents = function(){
+ // accept answers
+ if(questionAuthorId == currentUserId){
+ var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
+ $(acceptedButtons).unbind('click').click(function(event){
+ Vote.accept($(event.target))
+ });
+ }
+ // set favorite question
+ var favoriteButton = getFavoriteButton();
+ favoriteButton.unbind('click').click(function(event){
+ Vote.favorite($(event.target))
+ });
+
+ // question vote up
+ var questionVoteUpButton = getQuestionVoteUpButton();
+ questionVoteUpButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.questionUpVote)
+ });
+
+ var questionVoteDownButton = getQuestionVoteDownButton();
+ questionVoteDownButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.questionDownVote)
+ });
+
+ var answerVoteUpButton = getAnswerVoteUpButtons();
+ answerVoteUpButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.answerUpVote)
+ });
+
+ var answerVoteDownButton = getAnswerVoteDownButtons();
+ answerVoteDownButton.unbind('click').click(function(event){
+ Vote.vote($(event.target), VoteType.answerDownVote)
+ });
+
+ getOffensiveQuestionFlag().unbind('click').click(function(event){
+ Vote.offensive(this, VoteType.offensiveQuestion)
+ });
+
+ getOffensiveAnswerFlags().unbind('click').click(function(event){
+ Vote.offensive(this, VoteType.offensiveAnswer)
+ });
+
+ getremoveQuestionLink().unbind('click').click(function(event){
+ Vote.remove(this, VoteType.removeQuestion);
+ });
+
+ getremoveAnswersLinks().unbind('click').click(function(event){
+ Vote.remove(this, VoteType.removeAnswer)
+ });
+ };
+
+ var submit = function(object, voteType, callback) {
+ $.ajax({
+ type: "POST",
+ cache: false,
+ dataType: "json",
+ url: "/questions/" + questionId + "/vote/",
+ data: { "type": voteType, "postId": postId },
+ error: handleFail,
+ success: function(data){callback(object, voteType, data)}});
+ };
+
+ var handleFail = function(xhr, msg){
+ alert("Callback invoke error: " + msg)
+ };
+
+ // callback function for Accept Answer action
+ var callback_accept = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, acceptAnonymousMessage);
+ }
+ else if(data.allowed == "-1"){
+ showMessage(object, acceptOwnAnswerMessage);
+ }
+ else if(data.status == "1"){
+ object.attr("src", "/content/images/vote-accepted.png");
+ $("#"+answerContainerIdPrefix+postId).removeClass("accepted-answer");
+ $("#"+commentLinkIdPrefix+postId).removeClass("comment-link-accepted");
+ }
+ else if(data.success == "1"){
+ var acceptedButtons = 'div.'+ voteContainerId +' img[id^='+ imgIdPrefixAccept +']';
+ $(acceptedButtons).attr("src", "/content/images/vote-accepted.png");
+ var answers = ("div[id^="+answerContainerIdPrefix +"]");
+ $(answers).removeClass("accepted-answer");
+ var commentLinks = ("div[id^="+answerContainerIdPrefix +"] div[id^="+ commentLinkIdPrefix +"]");
+ $(commentLinks).removeClass("comment-link-accepted");
+
+ object.attr("src", "/content/images/vote-accepted-on.png");
+ $("#"+answerContainerIdPrefix+postId).addClass("accepted-answer");
+ $("#"+commentLinkIdPrefix+postId).addClass("comment-link-accepted");
+ }
+ else{
+ showMessage(object, data.message);
+ }
+ };
+
+ var callback_favorite = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if(data.status == "1"){
+ object.attr("src", "/content/images/vote-favorite-off.png");
+ var fav = getFavoriteNumber();
+ fav.removeClass("my-favorite-number");
+ if(data.count == 0)
+ data.count = '';
+ fav.text(data.count);
+ }
+ else if(data.success == "1"){
+ object.attr("src", "/content/images/vote-favorite-on.png");
+ var fav = getFavoriteNumber();
+ fav.text(data.count);
+ fav.addClass("my-favorite-number");
+ }
+ else{
+ showMessage(object, data.message);
+ }
+ };
+
+ var callback_vote = function(object, voteType, data){
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if(data.allowed == "-3"){
+ showMessage(object, voteRequiredMoreVotes);
+ }
+ else if(data.allowed == "-2"){
+ if(voteType == VoteType.questionUpVote || voteType == VoteType.answerUpVote){
+ showMessage(object, upVoteRequiredScoreMessage);
+ }
+ else if(voteType == VoteType.questionDownVote || voteType == VoteType.answerDownVote){
+ showMessage(object, downVoteRequiredScoreMessage);
+ }
+ }
+ else if(data.allowed == "-1"){
+ showMessage(object, voteOwnDeniedMessage);
+ }
+ else if(data.status == "2"){
+ showMessage(object, voteDenyCancelMessage);
+ }
+ else if(data.status == "1"){
+ setVoteImage(voteType, true, object);
+ setVoteNumber(object, data.count);
+ }
+ else if(data.success == "1"){
+ setVoteImage(voteType, false, object);
+ setVoteNumber(object, data.count);
+ if(data.message.length > 0)
+ showMessage(object, data.message);
+ }
+ };
+
+ var callback_offensive = function(object, voteType, data){
+ object = $(object);
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if(data.allowed == "-3"){
+ showMessage(object, offensiveNoFlagsLeftMessage);
+ }
+ else if(data.allowed == "-2"){
+ showMessage(object, offensiveNoPermissionMessage);
+ }
+ else if(data.status == "1"){
+ showMessage(object, offensiveTwiceMessage);
+ }
+ else if(data.success == "1"){
+ $(object).children('span[class=darkred]').text("("+ data.count +")");
+ }
+ };
+
+ var callback_remove = function(object, voteType, data){
+ alert(data.status);
+ if(data.allowed == "0" && data.success == "0"){
+ showMessage(object, removeAnonymousMessage.replace("{{QuestionID}}", questionId));
+ }
+ else if (data.success == "1"){
+ if (removeActionType == 'delete'){
+ postNode.addClass('deleted');
+ postRemoveLink.innerHTML = $.i18n._('undelete');
+ showMessage(object, deletedMessage);
+ }
+ else if (removeActionType == 'undelete') {
+ postNode.removeClass('deleted');
+ postRemoveLink.innerHTML = $.i18n._('delete');
+ showMessage(object, recoveredMessage);
+ }
+ }
+ };
+
+ return {
+ init : function(qId, questionAuthor, userId){
+ questionId = qId;
+ questionAuthorId = questionAuthor;
+ currentUserId = userId;
+ bindEvents();
+ },
+
+ // Accept answer public function
+ accept: function(object){
+ postId = object.attr("id").substring(imgIdPrefixAccept.length);
+ submit(object, VoteType.acceptAnswer, callback_accept);
+ },
+
+ favorite: function(object){
+ if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ submit(object, VoteType.favorite, callback_favorite);
+ },
+
+ vote: function(object, voteType){
+ if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage(object, voteAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ if(voteType == VoteType.answerUpVote){
+ postId = object.attr("id").substring(imgIdPrefixAnswerVoteup.length);
+ }
+ else if(voteType == VoteType.answerDownVote){
+ postId = object.attr("id").substring(imgIdPrefixAnswerVotedown.length);
+ }
+
+ submit(object, voteType, callback_vote);
+ },
+
+ offensive: function(object, voteType){
+ if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ if(confirm(offensiveConfirmation)){
+ postId = object.id.substr(object.id.lastIndexOf('-') + 1);
+ submit(object, voteType, callback_offensive);
+ }
+ },
+
+ remove: function(object, voteType){
+ if(!currentUserId || currentUserId.toUpperCase() == "NONE"){
+ showMessage($(object), removeAnonymousMessage.replace("{{QuestionID}}", questionId));
+ return false;
+ }
+ if(confirm(removeConfirmation)){
+ bits = object.id.split('-');
+ postId = bits.pop();/* this seems to be used within submit! */
+ postType = bits.shift();
+
+ if (postType == 'answer'){
+ postNode = $('#answer-container-' + postId);
+ postRemoveLink = object;
+ if (postNode.hasClass('deleted')){
+ removeActionType = 'undelete';
+ }
+ else {
+ removeActionType = 'delete';
+ }
+ }
+ submit($(object), voteType, callback_remove);
+
+
+ }
+ }
+ }
+} ();
+
+
+// site comments
+function createComments(type) {
+ var objectType = type;
+ var jDivInit = function(id) {
+ return $("#comments-" + objectType + '-' + id);
+ };
+
+ var appendLoaderImg = function(id) {
+ appendLoader("#comments-" + objectType + '-' + id + " div.comments");
+ };
+
+ var canPostComments = function(id, jDiv) {
+ var jHidden = jDiv.siblings("#can-post-comments-" + objectType + '-' + id);
+ return jHidden.val().toLowerCase() == "true";
+ };
+
+ var renderForm = function(id, jDiv) {
+ var formId = "form-comments-" + objectType + "-" + id;
+ if (canPostComments(id, jDiv)) {
+ if (jDiv.find("#" + formId).length == 0) {
+ var form = '<form id="' + formId + '" class="post-comments"><div>';
+ form += '<textarea name="comment" cols="60" rows="5" maxlength="300" onblur="'+ objectType +'Comments.updateTextCounter(this)" ';
+ form += 'onfocus="' + objectType + 'Comments.updateTextCounter(this)" onkeyup="'+ objectType +'Comments.updateTextCounter(this)"></textarea>';
+ form += '<input type="submit" value="'
+ + $.i18n._('add comment') + '" /><br><span class="text-counter"></span>';
+ form += '<span class="form-error"></span></div></form>';
+
+ jDiv.append(form);
+
+ setupFormValidation("#" + formId,
+ { comment: { required: true, minlength: 10} }, '',
+ function() { postComment(id, formId); });
+ }
+ }
+ else {
+ var divId = "comments-rep-needed-" + objectType + '-' + id;
+ if (jDiv.find("#" + divId).length == 0) {
+ jDiv.append('<div id="' + divId + '" style="color:red">'
+ + $.i18n._('to comment, need') + ' ' +
+ + repNeededForComments + ' ' + $.i18n._('community reputation points')
+ + '<a href="/faq" class="comment-user">' + $.i18n._('please see') + 'faq</a></span>');
+ }
+ }
+ };
+
+ var getComments = function(id, jDiv) {
+ appendLoaderImg(id);
+ $.getJSON("/" + objectType + "s/" + id + "/comments/", function(json) { showComments(id, json); });
+ };
+
+ var showComments = function(id, json) {
+ var jDiv = jDivInit(id);
+
+ jDiv = jDiv.find("div.comments"); // this div should contain any fetched comments..
+ jDiv.find("div[id^='comment-" + objectType + "-'" + "]").remove(); // clean previous calls..
+
+ removeLoader();
+
+ if (json && json.length > 0) {
+ for (var i = 0; i < json.length; i++)
+ renderComment(jDiv, json[i]);
+
+ jDiv.children().show();
+ }
+ };
+
+ // {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null}
+ var renderComment = function(jDiv, json) {
+ var html = '<div id="comment-' + objectType + "-" + json.id + '" style="display:none">' + json.text;
+ html += json.user_url ? '&nbsp;&ndash;&nbsp;<a href="' + json.user_url + '"' : '<span';
+ html += ' class="comment-user">' + json.user_display_name + (json.user_url ? '</a>' : '</span>');
+ html += ' <span class="comment-date">(' + json.add_date + ')</span>';
+
+ if (json.delete_url) {
+ var img = "/content/images/close-small.png";
+ var imgHover = "/content/images/close-small-hover.png";
+ html += '<img onclick="' + objectType + 'Comments.deleteComment($(this), ' + json.object_id + ', \'' + json.delete_url + '\')" src="' + img;
+ html += '" onmouseover="$(this).attr(\'src\', \'' + imgHover + '\')" onmouseout="$(this).attr(\'src\', \'' + img
+ html += '\')" title="' + $.i18n._('delete this comment') + '" />';
+ }
+
+ html += '</div>';
+
+ jDiv.append(html);
+ };
+
+ var postComment = function(id, formId) {
+ appendLoaderImg(id);
+
+ var formSelector = "#" + formId;
+ var textarea = $(formSelector + " textarea");
+
+ $.ajax({
+ type: "POST",
+ url: "/" + objectType + "s/" + id + "/comments/",
+ dataType: "json",
+ data: { comment: textarea.val() },
+ success: function(json) {
+ showComments(id, json);
+ textarea.val("");
+ commentsFactory[objectType].updateTextCounter(textarea);
+ enableSubmitButton(formSelector);
+ },
+ error: function(res, textStatus, errorThrown) {
+ removeLoader();
+ showMessage(formSelector, res.responseText);
+ enableSubmitButton(formSelector);
+ }
+ });
+ };
+
+ // public methods..
+ return {
+
+ init: function() {
+ // Setup "show comments" clicks..
+ $("a[id^='comments-link-" + objectType + "-" + "']").unbind("click").click(function() { commentsFactory[objectType].show($(this).attr("id").substr(("comments-link-" + objectType + "-").length)); });
+ },
+
+ show: function(id) {
+ var jDiv = jDivInit(id);
+ getComments(id, jDiv);
+ renderForm(id, jDiv);
+ jDiv.show();
+ if (canPostComments(id, jDiv)) jDiv.find("textarea").get(0).focus();
+ jDiv.siblings("a").unbind("click").click(function(){
+ commentsFactory[objectType].hide(id);
+ }).text($.i18n._('hide comments'));
+ },
+
+ hide: function(id) {
+ var jDiv = jDivInit(id);
+ var len = jDiv.children("div.comments").children().length;
+ var anchorText = len == 0 ? $.i18n._('add a comment') : $.i18n._('comments') + ' (<b>' + len + "</b>)";
+
+ jDiv.hide();
+ jDiv.siblings("a").unbind("click").click(function() { commentsFactory[objectType].show(id); }).html(anchorText);
+ jDiv.children("div.comments").children().hide();
+ },
+
+ deleteComment: function(jImg, id, deleteUrl) {
+ if (confirm($.i18n._('confirm delete comment'))) {
+ jImg.hide();
+ appendLoaderImg(id);
+ $.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) {
+ showComments(id, json);
+ }, "json");
+ }
+ },
+
+ updateTextCounter: function(textarea) {
+ var length = textarea.value ? textarea.value.length : 0;
+ var color = length > 270 ? "#f00" : length > 200 ? "#f60" : "#999";
+ var jSpan = $(textarea).siblings("span.text-counter");
+ jSpan.html($.i18n._('can write')
+ + (300 - length) + ' '
+ + $.i18n._('characters')).css("color", color);
+ }
+ };
+}
+
+var questionComments = createComments('question');
+var answerComments = createComments('answer');
+
+$().ready(function() {
+ questionComments.init();
+ answerComments.init();
+});
+
+var commentsFactory = {'question' : questionComments, 'answer' : answerComments};
+
+/*
+Prettify
+http://www.apache.org/licenses/LICENSE-2.0
+*/
+var PR_SHOULD_USE_CONTINUATION = true; var PR_TAB_WIDTH = 8; var PR_normalizedHtml; var PR; var prettyPrintOne; var prettyPrint; function _pr_isIE6() { var isIE6 = navigator && navigator.userAgent && /\bMSIE 6\./.test(navigator.userAgent); _pr_isIE6 = function() { return isIE6; }; return isIE6; } (function() { function wordSet(words) { words = words.split(/ /g); var set = {}; for (var i = words.length; --i >= 0; ) { var w = words[i]; if (w) { set[w] = null; } } return set; } var FLOW_CONTROL_KEYWORDS = "break continue do else for if return while "; var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " + "double enum extern float goto int long register short signed sizeof " + "static struct switch typedef union unsigned void volatile "; var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " + "new operator private protected public this throw true try "; var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " + "concept concept_map const_cast constexpr decltype " + "dynamic_cast explicit export friend inline late_check " + "mutable namespace nullptr reinterpret_cast static_assert static_cast " + "template typeid typename typeof using virtual wchar_t where "; var JAVA_KEYWORDS = COMMON_KEYWORDS + "boolean byte extends final finally implements import instanceof null " + "native package strictfp super synchronized throws transient "; var CSHARP_KEYWORDS = JAVA_KEYWORDS + "as base by checked decimal delegate descending event " + "fixed foreach from group implicit in interface internal into is lock " + "object out override orderby params readonly ref sbyte sealed " + "stackalloc string select uint ulong unchecked unsafe ushort var "; var JSCRIPT_KEYWORDS = COMMON_KEYWORDS + "debugger eval export function get null set undefined var with " + "Infinity NaN "; var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " + "goto if import last local my next no our print package redo require " + "sub undef unless until use wantarray while BEGIN END "; var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " + "elif except exec finally from global import in is lambda " + "nonlocal not or pass print raise try with yield " + "False True None "; var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" + " defined elsif end ensure false in module next nil not or redo rescue " + "retry self super then true undef unless until when yield BEGIN END "; var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " + "function in local set then until "; var ALL_KEYWORDS = (CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS + PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS); var PR_STRING = 'str'; var PR_KEYWORD = 'kwd'; var PR_COMMENT = 'com'; var PR_TYPE = 'typ'; var PR_LITERAL = 'lit'; var PR_PUNCTUATION = 'pun'; var PR_PLAIN = 'pln'; var PR_TAG = 'tag'; var PR_DECLARATION = 'dec'; var PR_SOURCE = 'src'; var PR_ATTRIB_NAME = 'atn'; var PR_ATTRIB_VALUE = 'atv'; var PR_NOCODE = 'nocode'; function isWordChar(ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); } function spliceArrayInto(inserted, container, containerPosition, countReplaced) { inserted.unshift(containerPosition, countReplaced || 0); try { container.splice.apply(container, inserted); } finally { inserted.splice(0, 2); } } var REGEXP_PRECEDER_PATTERN = function() { var preceders = ["!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=", "&=", "(", "*", "*=", "+=", ",", "-=", "->", "/", "/=", ":", "::", ";", "<", "<<", "<<=", "<=", "=", "==", "===", ">", ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[", "^", "^=", "^^", "^^=", "{", "|", "|=", "||", "||=", "~", "break", "case", "continue", "delete", "do", "else", "finally", "instanceof", "return", "throw", "try", "typeof"]; var pattern = '(?:' + '(?:(?:^|[^0-9.])\\.{1,3})|' + '(?:(?:^|[^\\+])\\+)|' + '(?:(?:^|[^\\-])-)'; for (var i = 0; i < preceders.length; ++i) { var preceder = preceders[i]; if (isWordChar(preceder.charAt(0))) { pattern += '|\\b' + preceder; } else { pattern += '|' + preceder.replace(/([^=<>:&])/g, '\\$1'); } } pattern += '|^)\\s*$'; return new RegExp(pattern); } (); var pr_amp = /&/g; var pr_lt = /</g; var pr_gt = />/g; var pr_quot = /\"/g; function attribToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;').replace(pr_quot, '&quot;'); } function textToHtml(str) { return str.replace(pr_amp, '&amp;').replace(pr_lt, '&lt;').replace(pr_gt, '&gt;'); } var pr_ltEnt = /&lt;/g; var pr_gtEnt = /&gt;/g; var pr_aposEnt = /&apos;/g; var pr_quotEnt = /&quot;/g; var pr_ampEnt = /&amp;/g; var pr_nbspEnt = /&nbsp;/g; function htmlToText(html) { var pos = html.indexOf('&'); if (pos < 0) { return html; } for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0; ) { var end = html.indexOf(';', pos); if (end >= 0) { var num = html.substring(pos + 3, end); var radix = 10; if (num && num.charAt(0) === 'x') { num = num.substring(1); radix = 16; } var codePoint = parseInt(num, radix); if (!isNaN(codePoint)) { html = (html.substring(0, pos) + String.fromCharCode(codePoint) + html.substring(end + 1)); } } } return html.replace(pr_ltEnt, '<').replace(pr_gtEnt, '>').replace(pr_aposEnt, "'").replace(pr_quotEnt, '"').replace(pr_ampEnt, '&').replace(pr_nbspEnt, ' '); } function isRawContent(node) { return 'XMP' === node.tagName; } function normalizedHtml(node, out) { switch (node.nodeType) { case 1: var name = node.tagName.toLowerCase(); out.push('<', name); for (var i = 0; i < node.attributes.length; ++i) { var attr = node.attributes[i]; if (!attr.specified) { continue; } out.push(' '); normalizedHtml(attr, out); } out.push('>'); for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } if (node.firstChild || !/^(?:br|link|img)$/.test(name)) { out.push('<\/', name, '>'); } break; case 2: out.push(node.name.toLowerCase(), '="', attribToHtml(node.value), '"'); break; case 3: case 4: out.push(textToHtml(node.nodeValue)); break; } } var PR_innerHtmlWorks = null; function getInnerHtml(node) { if (null === PR_innerHtmlWorks) { var testNode = document.createElement('PRE'); testNode.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />')); PR_innerHtmlWorks = !/</.test(testNode.innerHTML); } if (PR_innerHtmlWorks) { var content = node.innerHTML; if (isRawContent(node)) { content = textToHtml(content); } return content; } var out = []; for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } return out.join(''); } function makeTabExpander(tabWidth) { var SPACES = ' '; var charInLine = 0; return function(plainText) { var out = null; var pos = 0; for (var i = 0, n = plainText.length; i < n; ++i) { var ch = plainText.charAt(i); switch (ch) { case '\t': if (!out) { out = []; } out.push(plainText.substring(pos, i)); var nSpaces = tabWidth - (charInLine % tabWidth); charInLine += nSpaces; for (; nSpaces >= 0; nSpaces -= SPACES.length) { out.push(SPACES.substring(0, nSpaces)); } pos = i + 1; break; case '\n': charInLine = 0; break; default: ++charInLine; } } if (!out) { return plainText; } out.push(plainText.substring(pos)); return out.join(''); }; } var pr_chunkPattern = /(?:[^<]+|<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\/?[a-zA-Z][^>]*>|<)/g; var pr_commentPrefix = /^<!--/; var pr_cdataPrefix = /^<\[CDATA\[/; var pr_brPrefix = /^<br\b/i; var pr_tagNameRe = /^<(\/?)([a-zA-Z]+)/; function extractTags(s) { var matches = s.match(pr_chunkPattern); var sourceBuf = []; var sourceBufLen = 0; var extractedTags = []; if (matches) { for (var i = 0, n = matches.length; i < n; ++i) { var match = matches[i]; if (match.length > 1 && match.charAt(0) === '<') { if (pr_commentPrefix.test(match)) { continue; } if (pr_cdataPrefix.test(match)) { sourceBuf.push(match.substring(9, match.length - 3)); sourceBufLen += match.length - 12; } else if (pr_brPrefix.test(match)) { sourceBuf.push('\n'); ++sourceBufLen; } else { if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) { var name = match.match(pr_tagNameRe)[2]; var depth = 1; end_tag_loop: for (var j = i + 1; j < n; ++j) { var name2 = matches[j].match(pr_tagNameRe); if (name2 && name2[2] === name) { if (name2[1] === '/') { if (--depth === 0) { break end_tag_loop; } } else { ++depth; } } } if (j < n) { extractedTags.push(sourceBufLen, matches.slice(i, j + 1).join('')); i = j; } else { extractedTags.push(sourceBufLen, match); } } else { extractedTags.push(sourceBufLen, match); } } } else { var literalText = htmlToText(match); sourceBuf.push(literalText); sourceBufLen += literalText.length; } } } return { source: sourceBuf.join(''), tags: extractedTags }; } function isNoCodeTag(tag) { return !!tag.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g, ' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/); } function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) { var shortcuts = {}; (function() { var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns); for (var i = allPatterns.length; --i >= 0; ) { var patternParts = allPatterns[i]; var shortcutChars = patternParts[3]; if (shortcutChars) { for (var c = shortcutChars.length; --c >= 0; ) { shortcuts[shortcutChars.charAt(c)] = patternParts; } } } })(); var nPatterns = fallthroughStylePatterns.length; var notWs = /\S/; return function(sourceCode, opt_basePos) { opt_basePos = opt_basePos || 0; var decorations = [opt_basePos, PR_PLAIN]; var lastToken = ''; var pos = 0; var tail = sourceCode; while (tail.length) { var style; var token = null; var match; var patternParts = shortcuts[tail.charAt(0)]; if (patternParts) { match = tail.match(patternParts[1]); token = match[0]; style = patternParts[0]; } else { for (var i = 0; i < nPatterns; ++i) { patternParts = fallthroughStylePatterns[i]; var contextPattern = patternParts[2]; if (contextPattern && !contextPattern.test(lastToken)) { continue; } match = tail.match(patternParts[1]); if (match) { token = match[0]; style = patternParts[0]; break; } } if (!token) { style = PR_PLAIN; token = tail.substring(0, 1); } } decorations.push(opt_basePos + pos, style); pos += token.length; tail = tail.substring(token.length); if (style !== PR_COMMENT && notWs.test(token)) { lastToken = token; } } return decorations; }; } var PR_MARKUP_LEXER = createSimpleLexer([], [[PR_PLAIN, /^[^<]+/, null], [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/, null], [PR_COMMENT, /^<!--[\s\S]*?(?:-->|$)/, null], [PR_SOURCE, /^<\?[\s\S]*?(?:\?>|$)/, null], [PR_SOURCE, /^<%[\s\S]*?(?:%>|$)/, null], [PR_SOURCE, /^<(script|style|xmp)\b[^>]*>[\s\S]*?<\/\1\b[^>]*>/i, null], [PR_TAG, /^<\/?\w[^<>]*>/, null]]); var PR_SOURCE_CHUNK_PARTS = /^(<[^>]*>)([\s\S]*)(<\/[^>]*>)$/; function tokenizeMarkup(source) { var decorations = PR_MARKUP_LEXER(source); for (var i = 0; i < decorations.length; i += 2) { if (decorations[i + 1] === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var sourceChunk = source.substring(start, end); var match = sourceChunk.match(PR_SOURCE_CHUNK_PARTS); if (match) { decorations.splice(i, 2, start, PR_TAG, start + match[1].length, PR_SOURCE, start + match[1].length + (match[2] || '').length, PR_TAG); } } } return decorations; } var PR_TAG_LEXER = createSimpleLexer([[PR_ATTRIB_VALUE, /^\'[^\']*(?:\'|$)/, null, "'"], [PR_ATTRIB_VALUE, /^\"[^\"]*(?:\"|$)/, null, '"'], [PR_PUNCTUATION, /^[<>\/=]+/, null, '<>/=']], [[PR_TAG, /^[\w:\-]+/, /^</], [PR_ATTRIB_VALUE, /^[\w\-]+/, /^=/], [PR_ATTRIB_NAME, /^[\w:\-]+/, null], [PR_PLAIN, /^\s+/, null, ' \t\r\n']]); function splitTagAttributes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_TAG) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var chunk = source.substring(start, end); var subDecorations = PR_TAG_LEXER(chunk, start); spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function sourceDecorator(options) { var shortcutStylePatterns = [], fallthroughStylePatterns = []; if (options.tripleQuotedStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/, null, '\'"']); } else if (options.multiLineStrings) { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/, null, '\'"`']); } else { shortcutStylePatterns.push([PR_STRING, /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/, null, '"\'']); } fallthroughStylePatterns.push([PR_PLAIN, /^(?:[^\'\"\`\/\#]+)/, null, ' \r\n']); if (options.hashComments) { shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); } if (options.cStyleComments) { fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); fallthroughStylePatterns.push([PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); } if (options.regexLiterals) { var REGEX_LITERAL = ('^/(?=[^/*])' + '(?:[^/\\x5B\\x5C]' + '|\\x5C[\\s\\S]' + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' + '(?:/|$)'); fallthroughStylePatterns.push([PR_STRING, new RegExp(REGEX_LITERAL), REGEXP_PRECEDER_PATTERN]); } var keywords = wordSet(options.keywords); options = null; var splitStringAndCommentTokens = createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); var styleLiteralIdentifierPuncRecognizer = createSimpleLexer([], [[PR_PLAIN, /^\s+/, null, ' \r\n'], [PR_PLAIN, /^[a-z_$@][a-z_$@0-9]*/i, null], [PR_LITERAL, /^0x[a-f0-9]+[a-z]/i, null], [PR_LITERAL, /^(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d+)(?:e[+\-]?\d+)?[a-z]*/i, null, '123456789'], [PR_PUNCTUATION, /^[^\s\w\.$@]+/, null]]); function splitNonStringNonCommentTokens(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_PLAIN) { var start, end, chunk, subDecs; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; chunk = source.substring(start, end); subDecs = styleLiteralIdentifierPuncRecognizer(chunk, start); for (var j = 0, m = subDecs.length; j < m; j += 2) { var subStyle = subDecs[j + 1]; if (subStyle === PR_PLAIN) { var subStart = subDecs[j]; var subEnd = j + 2 < m ? subDecs[j + 2] : chunk.length; var token = source.substring(subStart, subEnd); if (token === '.') { subDecs[j + 1] = PR_PUNCTUATION; } else if (token in keywords) { subDecs[j + 1] = PR_KEYWORD; } else if (/^@?[A-Z][A-Z$]*[a-z][A-Za-z$]*$/.test(token)) { subDecs[j + 1] = token.charAt(0) === '@' ? PR_LITERAL : PR_TYPE; } } } spliceArrayInto(subDecs, decorations, i, 2); i += subDecs.length - 2; } } return decorations; } return function(sourceCode) { var decorations = splitStringAndCommentTokens(sourceCode); decorations = splitNonStringNonCommentTokens(sourceCode, decorations); return decorations; }; } var decorateSource = sourceDecorator({ keywords: ALL_KEYWORDS, hashComments: true, cStyleComments: true, multiLineStrings: true, regexLiterals: true }); function splitSourceNodes(source, decorations) { for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; if (style === PR_SOURCE) { var start, end; start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var subDecorations = decorateSource(source.substring(start, end)); for (var j = 0, m = subDecorations.length; j < m; j += 2) { subDecorations[j] += start; } spliceArrayInto(subDecorations, decorations, i, 2); i += subDecorations.length - 2; } } return decorations; } function splitSourceAttributes(source, decorations) { var nextValueIsSource = false; for (var i = 0; i < decorations.length; i += 2) { var style = decorations[i + 1]; var start, end; if (style === PR_ATTRIB_NAME) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; nextValueIsSource = /^on|^style$/i.test(source.substring(start, end)); } else if (style === PR_ATTRIB_VALUE) { if (nextValueIsSource) { start = decorations[i]; end = i + 2 < decorations.length ? decorations[i + 2] : source.length; var attribValue = source.substring(start, end); var attribLen = attribValue.length; var quoted = (attribLen >= 2 && /^[\"\']/.test(attribValue) && attribValue.charAt(0) === attribValue.charAt(attribLen - 1)); var attribSource; var attribSourceStart; var attribSourceEnd; if (quoted) { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue; } else { attribSourceStart = start + 1; attribSourceEnd = end - 1; attribSource = attribValue.substring(1, attribValue.length - 1); } var attribSourceDecorations = decorateSource(attribSource); for (var j = 0, m = attribSourceDecorations.length; j < m; j += 2) { attribSourceDecorations[j] += attribSourceStart; } if (quoted) { attribSourceDecorations.push(attribSourceEnd, PR_ATTRIB_VALUE); spliceArrayInto(attribSourceDecorations, decorations, i + 2, 0); } else { spliceArrayInto(attribSourceDecorations, decorations, i, 2); } } nextValueIsSource = false; } } return decorations; } function decorateMarkup(sourceCode) { var decorations = tokenizeMarkup(sourceCode); decorations = splitTagAttributes(sourceCode, decorations); decorations = splitSourceNodes(sourceCode, decorations); decorations = splitSourceAttributes(sourceCode, decorations); return decorations; } function recombineTagsAndDecorations(sourceText, extractedTags, decorations) { var html = []; var outputIdx = 0; var openDecoration = null; var currentDecoration = null; var tagPos = 0; var decPos = 0; var tabExpander = makeTabExpander(PR_TAB_WIDTH); var adjacentSpaceRe = /([\r\n ]) /g; var startOrSpaceRe = /(^| ) /gm; var newlineRe = /\r\n?|\n/g; var trailingSpaceRe = /[ \r\n]$/; var lastWasSpace = true; function emitTextUpTo(sourceIdx) { if (sourceIdx > outputIdx) { if (openDecoration && openDecoration !== currentDecoration) { html.push('</span>'); openDecoration = null; } if (!openDecoration && currentDecoration) { openDecoration = currentDecoration; html.push('<span class="', openDecoration, '">'); } var htmlChunk = textToHtml(tabExpander(sourceText.substring(outputIdx, sourceIdx))).replace(lastWasSpace ? startOrSpaceRe : adjacentSpaceRe, '$1&nbsp;'); lastWasSpace = trailingSpaceRe.test(htmlChunk); html.push(htmlChunk.replace(newlineRe, '<br />')); outputIdx = sourceIdx; } } while (true) { var outputTag; if (tagPos < extractedTags.length) { if (decPos < decorations.length) { outputTag = extractedTags[tagPos] <= decorations[decPos]; } else { outputTag = true; } } else { outputTag = false; } if (outputTag) { emitTextUpTo(extractedTags[tagPos]); if (openDecoration) { html.push('</span>'); openDecoration = null; } html.push(extractedTags[tagPos + 1]); tagPos += 2; } else if (decPos < decorations.length) { emitTextUpTo(decorations[decPos]); currentDecoration = decorations[decPos + 1]; decPos += 2; } else { break; } } emitTextUpTo(sourceText.length); if (openDecoration) { html.push('</span>'); } return html.join(''); } var langHandlerRegistry = {}; function registerLangHandler(handler, fileExtensions) { for (var i = fileExtensions.length; --i >= 0; ) { var ext = fileExtensions[i]; if (!langHandlerRegistry.hasOwnProperty(ext)) { langHandlerRegistry[ext] = handler; } else if ('console' in window) { console.log('cannot override language handler %s', ext); } } } registerLangHandler(decorateSource, ['default-code']); registerLangHandler(decorateMarkup, ['default-markup', 'html', 'htm', 'xhtml', 'xml', 'xsl']); registerLangHandler(sourceDecorator({ keywords: CPP_KEYWORDS, hashComments: true, cStyleComments: true }), ['c', 'cc', 'cpp', 'cs', 'cxx', 'cyc']); registerLangHandler(sourceDecorator({ keywords: JAVA_KEYWORDS, cStyleComments: true }), ['java']); registerLangHandler(sourceDecorator({ keywords: SH_KEYWORDS, hashComments: true, multiLineStrings: true }), ['bsh', 'csh', 'sh']); registerLangHandler(sourceDecorator({ keywords: PYTHON_KEYWORDS, hashComments: true, multiLineStrings: true, tripleQuotedStrings: true }), ['cv', 'py']); registerLangHandler(sourceDecorator({ keywords: PERL_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['perl', 'pl', 'pm']); registerLangHandler(sourceDecorator({ keywords: RUBY_KEYWORDS, hashComments: true, multiLineStrings: true, regexLiterals: true }), ['rb']); registerLangHandler(sourceDecorator({ keywords: JSCRIPT_KEYWORDS, cStyleComments: true, regexLiterals: true }), ['js']); function prettyPrintOne(sourceCodeHtml, opt_langExtension) { try { var sourceAndExtractedTags = extractTags(sourceCodeHtml); var source = sourceAndExtractedTags.source; var extractedTags = sourceAndExtractedTags.tags; if (!langHandlerRegistry.hasOwnProperty(opt_langExtension)) { opt_langExtension = /^\s*</.test(source) ? 'default-markup' : 'default-code'; } var decorations = langHandlerRegistry[opt_langExtension].call({}, source); return recombineTagsAndDecorations(source, extractedTags, decorations); } catch (e) { if ('console' in window) { console.log(e); console.trace(); } return sourceCodeHtml; } } function prettyPrint(opt_whenDone) { var isIE6 = _pr_isIE6(); var codeSegments = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')]; var elements = []; for (var i = 0; i < codeSegments.length; ++i) { for (var j = 0; j < codeSegments[i].length; ++j) { elements.push(codeSegments[i][j]); } } codeSegments = null; var k = 0; function doWork() { var endTime = (PR_SHOULD_USE_CONTINUATION ? new Date().getTime() + 250 : Infinity); for (; k < elements.length && new Date().getTime() < endTime; k++) { var cs = elements[k]; if (cs.className && cs.className.indexOf('prettyprint') >= 0) { var langExtension = cs.className.match(/\blang-(\w+)\b/); if (langExtension) { langExtension = langExtension[1]; } var nested = false; for (var p = cs.parentNode; p; p = p.parentNode) { if ((p.tagName === 'pre' || p.tagName === 'code' || p.tagName === 'xmp') && p.className && p.className.indexOf('prettyprint') >= 0) { nested = true; break; } } if (!nested) { var content = getInnerHtml(cs); content = content.replace(/(?:\r\n?|\n)$/, ''); var newContent = prettyPrintOne(content, langExtension); if (!isRawContent(cs)) { cs.innerHTML = newContent; } else { var pre = document.createElement('PRE'); for (var i = 0; i < cs.attributes.length; ++i) { var a = cs.attributes[i]; if (a.specified) { var aname = a.name.toLowerCase(); if (aname === 'class') { pre.className = a.value; } else { pre.setAttribute(a.name, a.value); } } } pre.innerHTML = newContent; cs.parentNode.replaceChild(pre, cs); cs = pre; } if (isIE6 && cs.tagName === 'PRE') { var lineBreaks = cs.getElementsByTagName('br'); for (var j = lineBreaks.length; --j >= 0; ) { var lineBreak = lineBreaks[j]; lineBreak.parentNode.replaceChild(document.createTextNode('\r\n'), lineBreak); } } } } } if (k < elements.length) { setTimeout(doWork, 250); } else if (opt_whenDone) { opt_whenDone(); } } doWork(); } window['PR_normalizedHtml'] = normalizedHtml; window['prettyPrintOne'] = prettyPrintOne; window['prettyPrint'] = prettyPrint; window['PR'] = { 'createSimpleLexer': createSimpleLexer, 'registerLangHandler': registerLangHandler, 'sourceDecorator': sourceDecorator, 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 'PR_COMMENT': PR_COMMENT, 'PR_DECLARATION': PR_DECLARATION, 'PR_KEYWORD': PR_KEYWORD, 'PR_LITERAL': PR_LITERAL, 'PR_NOCODE': PR_NOCODE, 'PR_PLAIN': PR_PLAIN, 'PR_PUNCTUATION': PR_PUNCTUATION, 'PR_SOURCE': PR_SOURCE, 'PR_STRING': PR_STRING, 'PR_TAG': PR_TAG, 'PR_TYPE': PR_TYPE }; })();
diff --git a/templates/content/js/com.cnprog.utils.js b/templates/content/js/com.cnprog.utils.js
new file mode 100644
index 00000000..e271ed78
--- /dev/null
+++ b/templates/content/js/com.cnprog.utils.js
@@ -0,0 +1,122 @@
+var showMessage = function(object, msg) {
+ var div = $('<div class="vote-notification"><h3>' + msg + '</h3>('
+ + $.i18n._('click to close') + ')</div>');
+
+ div.click(function(event) {
+ $(".vote-notification").fadeOut("fast", function() { $(this).remove(); });
+ });
+
+ object.parent().append(div);
+ div.fadeIn("fast");
+};
+
+var notify = function() {
+ var visible = false;
+ return {
+ show: function(html) {
+ if (html) {
+ $("body").css("margin-top", "2.2em");
+ $(".notify span").html(html);
+ }
+ $(".notify").fadeIn("slow");
+ visible = true;
+ },
+ close: function(doPostback) {
+ if (doPostback) {
+ $.post("/messages/markread/", { formdata: "required" });
+ }
+ $(".notify").fadeOut("fast");
+ $("body").css("margin-top", "0");
+ visible = false;
+ },
+ isVisible: function() { return visible; }
+ };
+} ();
+
+function appendLoader(containerSelector) {
+ $(containerSelector).append('<img class="ajax-loader" '
+ +'src="/content/images/indicator.gif" title="'
+ +$.i18n._('loading...')
+ +'" alt="'
+ +$.i18n._('loading...')
+ +'" />');
+}
+
+function removeLoader() {
+ $("img.ajax-loader").remove();
+}
+
+function setupFormValidation(formSelector, validationRules, validationMessages, onSubmitCallback) {
+ enableSubmitButton(formSelector);
+ $(formSelector).validate({
+ rules: (validationRules ? validationRules : {}),
+ messages: (validationMessages ? validationMessages : {}),
+ errorElement: "span",
+ errorClass: "form-error",
+ errorPlacement: function(error, element) {
+ var span = element.next().find("span.form-error");
+ if (span.length == 0) {
+ span = element.parent().find("span.form-error");
+ }
+ span.replaceWith(error);
+ },
+ submitHandler: function(form) {
+ disableSubmitButton(formSelector);
+
+ if (onSubmitCallback)
+ onSubmitCallback();
+ else
+ form.submit();
+ }
+ });
+}
+
+function enableSubmitButton(formSelector) {
+ setSubmitButtonDisabled(formSelector, false);
+}
+function disableSubmitButton(formSelector) {
+ setSubmitButtonDisabled(formSelector, true);
+}
+function setSubmitButtonDisabled(formSelector, isDisabled) {
+ $(formSelector).find("input[type='submit']").attr("disabled", isDisabled ? "true" : "");
+}
+
+var CPValidator = function(){
+ return {
+ getQuestionFormRules : function(){
+ return {
+ tags: {
+ required: true,
+ maxlength: 105
+ },
+ text: {
+ required: true,
+ minlength: 10
+ },
+ title: {
+ required: true,
+ minlength: 10
+ }
+ };
+ },
+ getQuestionFormMessages: function(){
+ return {
+ tags: {
+ required: " " + $.i18n._('tags cannot be empty'),
+ maxlength: " " + $.i18n._('tablimits info'),
+ },
+ text: {
+ required: " " + $.i18n._('content cannot be empty'),
+ minlength: jQuery.format(' ' + $.i18n._('content minchars'))
+ },
+ title: {
+ required: " " + $.i18n._('please enter title'),
+ minlength: jQuery.format(' ' + $.i18n._('title minchars'))
+ }
+ };
+ }
+ };
+}();
+//Search Engine Keyword Highlight with Javascript
+//http://scott.yang.id.au/code/se-hilite/
+Hilite={elementid:"content",exact:true,max_nodes:1000,onload:true,style_name:"hilite",style_name_suffix:true,debug_referrer:""};Hilite.search_engines=[["local","q"],["cnprog\\.","q"],["google\\.","q"],["search\\.yahoo\\.","p"],["search\\.msn\\.","q"],["search\\.live\\.","query"],["search\\.aol\\.","userQuery"],["ask\\.com","q"],["altavista\\.","q"],["feedster\\.","q"],["search\\.lycos\\.","q"],["alltheweb\\.","q"],["technorati\\.com/search/([^\\?/]+)",1],["dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)",1,true]];Hilite.decodeReferrer=function(d){var g=null;var e=new RegExp("");for(var c=0;c<Hilite.search_engines.length;c++){var f=Hilite.search_engines[c];e.compile("^http://(www\\.)?"+f[0],"i");var b=d.match(e);if(b){var a;if(isNaN(f[1])){a=Hilite.decodeReferrerQS(d,f[1])}else{a=b[f[1]+1]}if(a){a=decodeURIComponent(a);if(f.length>2&&f[2]){a=decodeURIComponent(a)}a=a.replace(/\'|"/g,"");a=a.split(/[\s,\+\.]+/);return a}break}}return null};Hilite.decodeReferrerQS=function(f,d){var b=f.indexOf("?");var c;if(b>=0){var a=new String(f.substring(b+1));b=0;c=0;while((b>=0)&&((c=a.indexOf("=",b))>=0)){var e,g;e=a.substring(b,c);b=a.indexOf("&",c)+1;if(e==d){if(b<=0){return a.substring(c+1)}else{return a.substring(c+1,b-1)}}else{if(b<=0){return null}}}}return null};Hilite.hiliteElement=function(f,e){if(!e||f.childNodes.length==0){return}var c=new Array();for(var b=0;b<e.length;b++){e[b]=e[b].toLowerCase();if(Hilite.exact){c.push("\\b"+e[b]+"\\b")}else{c.push(e[b])}}c=new RegExp(c.join("|"),"i");var a={};for(var b=0;b<e.length;b++){if(Hilite.style_name_suffix){a[e[b]]=Hilite.style_name+(b+1)}else{a[e[b]]=Hilite.style_name}}var d=function(m){var j=c.exec(m.data);if(j){var n=j[0];var i="";var h=m.splitText(j.index);var g=h.splitText(n.length);var l=m.ownerDocument.createElement("SPAN");m.parentNode.replaceChild(l,h);l.className=a[n.toLowerCase()];l.appendChild(h);return l}else{return m}};Hilite.walkElements(f.childNodes[0],1,d)};Hilite.hilite=function(){var a=Hilite.debug_referrer?Hilite.debug_referrer:document.referrer;var b=null;a=Hilite.decodeReferrer(a);if(a&&((Hilite.elementid&&(b=document.getElementById(Hilite.elementid)))||(b=document.body))){Hilite.hiliteElement(b,a)}};Hilite.walkElements=function(d,f,e){var a=/^(script|style|textarea)/i;var c=0;while(d&&f>0){c++;if(c>=Hilite.max_nodes){var b=function(){Hilite.walkElements(d,f,e)};setTimeout(b,50);return}if(d.nodeType==1){if(!a.test(d.tagName)&&d.childNodes.length>0){d=d.childNodes[0];f++;continue}}else{if(d.nodeType==3){d=e(d)}}if(d.nextSibling){d=d.nextSibling}else{while(f>0){d=d.parentNode;f--;if(d.nextSibling){d=d.nextSibling;break}}}}};if(Hilite.onload){if(window.attachEvent){window.attachEvent("onload",Hilite.hilite)}else{if(window.addEventListener){window.addEventListener("load",Hilite.hilite,false)}else{var __onload=window.onload;window.onload=function(){Hilite.hilite();__onload()}}}};
diff --git a/templates/content/js/compress.bat b/templates/content/js/compress.bat
new file mode 100644
index 00000000..aa31271c
--- /dev/null
+++ b/templates/content/js/compress.bat
@@ -0,0 +1,6 @@
+#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 wmd\wmd.js -o wmd\wmd-min.js
+#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 wmd\showdown.js -o wmd\showdown-min.js
+#java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 com.cnprog.post.js -o com.cnprog.post.pack.js
+java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 se_hilite_src.js -o se_hilite.js
+
+pause \ No newline at end of file
diff --git a/templates/content/js/excanvas.pack.js b/templates/content/js/excanvas.pack.js
new file mode 100644
index 00000000..71d6fbd9
--- /dev/null
+++ b/templates/content/js/excanvas.pack.js
@@ -0,0 +1 @@
+if(!window.CanvasRenderingContext2D){(function(){var m=Math;var mr=m.round;var ms=m.sin;var mc=m.cos;var Z=10;var Z2=Z/2;var G_vmlCanvasManager_={init:function(opt_doc){var doc=opt_doc||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var self=this;doc.attachEvent("onreadystatechange",function(){self.init_(doc)})}},init_:function(doc){if(doc.readyState=="complete"){if(!doc.namespaces["g_vml_"]){doc.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var ss=doc.createStyleSheet();ss.cssText="canvas{display:inline-block;overflow:hidden;"+"text-align:left;width:300px;height:150px}"+"g_vml_\\:*{behavior:url(#default#VML)}";var els=doc.getElementsByTagName("canvas");for(var i=0;i<els.length;i++){if(!els[i].getContext){this.initElement(els[i])}}}},fixElement_:function(el){var outerHTML=el.outerHTML;var newEl=el.ownerDocument.createElement(outerHTML);if(outerHTML.slice(-2)!="/>"){var tagName="/"+el.tagName;var ns;while((ns=el.nextSibling)&&ns.tagName!=tagName){ns.removeNode()}if(ns){ns.removeNode()}}el.parentNode.replaceChild(newEl,el);return newEl},initElement:function(el){el=this.fixElement_(el);el.getContext=function(){if(this.context_){return this.context_}return this.context_=new CanvasRenderingContext2D_(this)};el.attachEvent('onpropertychange',onPropertyChange);el.attachEvent('onresize',onResize);var attrs=el.attributes;if(attrs.width&&attrs.width.specified){el.style.width=attrs.width.nodeValue+"px"}else{el.width=el.clientWidth}if(attrs.height&&attrs.height.specified){el.style.height=attrs.height.nodeValue+"px"}else{el.height=el.clientHeight}return el}};function onPropertyChange(e){var el=e.srcElement;switch(e.propertyName){case'width':el.style.width=el.attributes.width.nodeValue+"px";el.getContext().clearRect();break;case'height':el.style.height=el.attributes.height.nodeValue+"px";el.getContext().clearRect();break}}function onResize(e){var el=e.srcElement;if(el.firstChild){el.firstChild.style.width=el.clientWidth+'px';el.firstChild.style.height=el.clientHeight+'px'}}G_vmlCanvasManager_.init();var dec2hex=[];for(var i=0;i<16;i++){for(var j=0;j<16;j++){dec2hex[i*16+j]=i.toString(16)+j.toString(16)}}function createMatrixIdentity(){return[[1,0,0],[0,1,0],[0,0,1]]}function matrixMultiply(m1,m2){var result=createMatrixIdentity();for(var x=0;x<3;x++){for(var y=0;y<3;y++){var sum=0;for(var z=0;z<3;z++){sum+=m1[x][z]*m2[z][y]}result[x][y]=sum}}return result}function copyState(o1,o2){o2.fillStyle=o1.fillStyle;o2.lineCap=o1.lineCap;o2.lineJoin=o1.lineJoin;o2.lineWidth=o1.lineWidth;o2.miterLimit=o1.miterLimit;o2.shadowBlur=o1.shadowBlur;o2.shadowColor=o1.shadowColor;o2.shadowOffsetX=o1.shadowOffsetX;o2.shadowOffsetY=o1.shadowOffsetY;o2.strokeStyle=o1.strokeStyle;o2.arcScaleX_=o1.arcScaleX_;o2.arcScaleY_=o1.arcScaleY_}function processStyle(styleString){var str,alpha=1;styleString=String(styleString);if(styleString.substring(0,3)=="rgb"){var start=styleString.indexOf("(",3);var end=styleString.indexOf(")",start+1);var guts=styleString.substring(start+1,end).split(",");str="#";for(var i=0;i<3;i++){str+=dec2hex[Number(guts[i])]}if((guts.length==4)&&(styleString.substr(3,1)=="a")){alpha=guts[3]}}else{str=styleString}return[str,alpha]}function processLineCap(lineCap){switch(lineCap){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function CanvasRenderingContext2D_(surfaceElement){this.m_=createMatrixIdentity();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=Z*1;this.globalAlpha=1;this.canvas=surfaceElement;var el=surfaceElement.ownerDocument.createElement('div');el.style.width=surfaceElement.clientWidth+'px';el.style.height=surfaceElement.clientHeight+'px';el.style.overflow='hidden';el.style.position='absolute';surfaceElement.appendChild(el);this.element_=el;this.arcScaleX_=1;this.arcScaleY_=1}var contextPrototype=CanvasRenderingContext2D_.prototype;contextPrototype.clearRect=function(){this.element_.innerHTML="";this.currentPath_=[]};contextPrototype.beginPath=function(){this.currentPath_=[]};contextPrototype.moveTo=function(aX,aY){this.currentPath_.push({type:"moveTo",x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.lineTo=function(aX,aY){this.currentPath_.push({type:"lineTo",x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.bezierCurveTo=function(aCP1x,aCP1y,aCP2x,aCP2y,aX,aY){this.currentPath_.push({type:"bezierCurveTo",cp1x:aCP1x,cp1y:aCP1y,cp2x:aCP2x,cp2y:aCP2y,x:aX,y:aY});this.currentX_=aX;this.currentY_=aY};contextPrototype.quadraticCurveTo=function(aCPx,aCPy,aX,aY){var cp1x=this.currentX_+2.0/3.0*(aCPx-this.currentX_);var cp1y=this.currentY_+2.0/3.0*(aCPy-this.currentY_);var cp2x=cp1x+(aX-this.currentX_)/3.0;var cp2y=cp1y+(aY-this.currentY_)/3.0;this.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,aX,aY)};contextPrototype.arc=function(aX,aY,aRadius,aStartAngle,aEndAngle,aClockwise){aRadius*=Z;var arcType=aClockwise?"at":"wa";var xStart=aX+(mc(aStartAngle)*aRadius)-Z2;var yStart=aY+(ms(aStartAngle)*aRadius)-Z2;var xEnd=aX+(mc(aEndAngle)*aRadius)-Z2;var yEnd=aY+(ms(aEndAngle)*aRadius)-Z2;if(xStart==xEnd&&!aClockwise){xStart+=0.125}this.currentPath_.push({type:arcType,x:aX,y:aY,radius:aRadius,xStart:xStart,yStart:yStart,xEnd:xEnd,yEnd:yEnd})};contextPrototype.rect=function(aX,aY,aWidth,aHeight){this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath()};contextPrototype.strokeRect=function(aX,aY,aWidth,aHeight){this.beginPath();this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();this.stroke()};contextPrototype.fillRect=function(aX,aY,aWidth,aHeight){this.beginPath();this.moveTo(aX,aY);this.lineTo(aX+aWidth,aY);this.lineTo(aX+aWidth,aY+aHeight);this.lineTo(aX,aY+aHeight);this.closePath();this.fill()};contextPrototype.createLinearGradient=function(aX0,aY0,aX1,aY1){var gradient=new CanvasGradient_("gradient");return gradient};contextPrototype.createRadialGradient=function(aX0,aY0,aR0,aX1,aY1,aR1){var gradient=new CanvasGradient_("gradientradial");gradient.radius1_=aR0;gradient.radius2_=aR1;gradient.focus_.x=aX0;gradient.focus_.y=aY0;return gradient};contextPrototype.drawImage=function(image,var_args){var dx,dy,dw,dh,sx,sy,sw,sh;var oldRuntimeWidth=image.runtimeStyle.width;var oldRuntimeHeight=image.runtimeStyle.height;image.runtimeStyle.width='auto';image.runtimeStyle.height='auto';var w=image.width;var h=image.height;image.runtimeStyle.width=oldRuntimeWidth;image.runtimeStyle.height=oldRuntimeHeight;if(arguments.length==3){dx=arguments[1];dy=arguments[2];sx=sy=0;sw=dw=w;sh=dh=h}else if(arguments.length==5){dx=arguments[1];dy=arguments[2];dw=arguments[3];dh=arguments[4];sx=sy=0;sw=w;sh=h}else if(arguments.length==9){sx=arguments[1];sy=arguments[2];sw=arguments[3];sh=arguments[4];dx=arguments[5];dy=arguments[6];dw=arguments[7];dh=arguments[8]}else{throw"Invalid number of arguments";}var d=this.getCoords_(dx,dy);var w2=sw/2;var h2=sh/2;var vmlStr=[];var W=10;var H=10;vmlStr.push(' <g_vml_:group',' coordsize="',Z*W,',',Z*H,'"',' coordorigin="0,0"',' style="width:',W,';height:',H,';position:absolute;');if(this.m_[0][0]!=1||this.m_[0][1]){var filter=[];filter.push("M11='",this.m_[0][0],"',","M12='",this.m_[1][0],"',","M21='",this.m_[0][1],"',","M22='",this.m_[1][1],"',","Dx='",mr(d.x/Z),"',","Dy='",mr(d.y/Z),"'");var max=d;var c2=this.getCoords_(dx+dw,dy);var c3=this.getCoords_(dx,dy+dh);var c4=this.getCoords_(dx+dw,dy+dh);max.x=Math.max(max.x,c2.x,c3.x,c4.x);max.y=Math.max(max.y,c2.y,c3.y,c4.y);vmlStr.push("padding:0 ",mr(max.x/Z),"px ",mr(max.y/Z),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",filter.join(""),", sizingmethod='clip');")}else{vmlStr.push("top:",mr(d.y/Z),"px;left:",mr(d.x/Z),"px;")}vmlStr.push(' ">','<g_vml_:image src="',image.src,'"',' style="width:',Z*dw,';',' height:',Z*dh,';"',' cropleft="',sx/w,'"',' croptop="',sy/h,'"',' cropright="',(w-sx-sw)/w,'"',' cropbottom="',(h-sy-sh)/h,'"',' />','</g_vml_:group>');this.element_.insertAdjacentHTML("BeforeEnd",vmlStr.join(""))};contextPrototype.stroke=function(aFill){var lineStr=[];var lineOpen=false;var a=processStyle(aFill?this.fillStyle:this.strokeStyle);var color=a[0];var opacity=a[1]*this.globalAlpha;var W=10;var H=10;lineStr.push('<g_vml_:shape',' fillcolor="',color,'"',' filled="',Boolean(aFill),'"',' style="position:absolute;width:',W,';height:',H,';"',' coordorigin="0 0" coordsize="',Z*W,' ',Z*H,'"',' stroked="',!aFill,'"',' strokeweight="',this.lineWidth,'"',' strokecolor="',color,'"',' path="');var newSeq=false;var min={x:null,y:null};var max={x:null,y:null};for(var i=0;i<this.currentPath_.length;i++){var p=this.currentPath_[i];if(p.type=="moveTo"){lineStr.push(" m ");var c=this.getCoords_(p.x,p.y);lineStr.push(mr(c.x),",",mr(c.y))}else if(p.type=="lineTo"){lineStr.push(" l ");var c=this.getCoords_(p.x,p.y);lineStr.push(mr(c.x),",",mr(c.y))}else if(p.type=="close"){lineStr.push(" x ")}else if(p.type=="bezierCurveTo"){lineStr.push(" c ");var c=this.getCoords_(p.x,p.y);var c1=this.getCoords_(p.cp1x,p.cp1y);var c2=this.getCoords_(p.cp2x,p.cp2y);lineStr.push(mr(c1.x),",",mr(c1.y),",",mr(c2.x),",",mr(c2.y),",",mr(c.x),",",mr(c.y))}else if(p.type=="at"||p.type=="wa"){lineStr.push(" ",p.type," ");var c=this.getCoords_(p.x,p.y);var cStart=this.getCoords_(p.xStart,p.yStart);var cEnd=this.getCoords_(p.xEnd,p.yEnd);lineStr.push(mr(c.x-this.arcScaleX_*p.radius),",",mr(c.y-this.arcScaleY_*p.radius)," ",mr(c.x+this.arcScaleX_*p.radius),",",mr(c.y+this.arcScaleY_*p.radius)," ",mr(cStart.x),",",mr(cStart.y)," ",mr(cEnd.x),",",mr(cEnd.y))}if(c){if(min.x==null||c.x<min.x){min.x=c.x}if(max.x==null||c.x>max.x){max.x=c.x}if(min.y==null||c.y<min.y){min.y=c.y}if(max.y==null||c.y>max.y){max.y=c.y}}}lineStr.push(' ">');if(typeof this.fillStyle=="object"){var focus={x:"50%",y:"50%"};var width=(max.x-min.x);var height=(max.y-min.y);var dimension=(width>height)?width:height;focus.x=mr((this.fillStyle.focus_.x/width)*100+50)+"%";focus.y=mr((this.fillStyle.focus_.y/height)*100+50)+"%";var colors=[];if(this.fillStyle.type_=="gradientradial"){var inside=(this.fillStyle.radius1_/dimension*100);var expansion=(this.fillStyle.radius2_/dimension*100)-inside}else{var inside=0;var expansion=100}var insidecolor={offset:null,color:null};var outsidecolor={offset:null,color:null};this.fillStyle.colors_.sort(function(cs1,cs2){return cs1.offset-cs2.offset});for(var i=0;i<this.fillStyle.colors_.length;i++){var fs=this.fillStyle.colors_[i];colors.push((fs.offset*expansion)+inside,"% ",fs.color,",");if(fs.offset>insidecolor.offset||insidecolor.offset==null){insidecolor.offset=fs.offset;insidecolor.color=fs.color}if(fs.offset<outsidecolor.offset||outsidecolor.offset==null){outsidecolor.offset=fs.offset;outsidecolor.color=fs.color}}colors.pop();lineStr.push('<g_vml_:fill',' color="',outsidecolor.color,'"',' color2="',insidecolor.color,'"',' type="',this.fillStyle.type_,'"',' focusposition="',focus.x,', ',focus.y,'"',' colors="',colors.join(""),'"',' opacity="',opacity,'" />')}else if(aFill){lineStr.push('<g_vml_:fill color="',color,'" opacity="',opacity,'" />')}else{lineStr.push('<g_vml_:stroke',' opacity="',opacity,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',processLineCap(this.lineCap),'"',' weight="',this.lineWidth,'px"',' color="',color,'" />')}lineStr.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",lineStr.join(""))};contextPrototype.fill=function(){this.stroke(true)};contextPrototype.closePath=function(){this.currentPath_.push({type:"close"})};contextPrototype.getCoords_=function(aX,aY){return{x:Z*(aX*this.m_[0][0]+aY*this.m_[1][0]+this.m_[2][0])-Z2,y:Z*(aX*this.m_[0][1]+aY*this.m_[1][1]+this.m_[2][1])-Z2}};contextPrototype.save=function(){var o={};copyState(this,o);this.aStack_.push(o);this.mStack_.push(this.m_);this.m_=matrixMultiply(createMatrixIdentity(),this.m_)};contextPrototype.restore=function(){copyState(this.aStack_.pop(),this);this.m_=this.mStack_.pop()};contextPrototype.translate=function(aX,aY){var m1=[[1,0,0],[0,1,0],[aX,aY,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.rotate=function(aRot){var c=mc(aRot);var s=ms(aRot);var m1=[[c,s,0],[-s,c,0],[0,0,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.scale=function(aX,aY){this.arcScaleX_*=aX;this.arcScaleY_*=aY;var m1=[[aX,0,0],[0,aY,0],[0,0,1]];this.m_=matrixMultiply(m1,this.m_)};contextPrototype.clip=function(){};contextPrototype.arcTo=function(){};contextPrototype.createPattern=function(){return new CanvasPattern_};function CanvasGradient_(aType){this.type_=aType;this.radius1_=0;this.radius2_=0;this.colors_=[];this.focus_={x:0,y:0}}CanvasGradient_.prototype.addColorStop=function(aOffset,aColor){aColor=processStyle(aColor);this.colors_.push({offset:1-aOffset,color:aColor})};function CanvasPattern_(){}G_vmlCanvasManager=G_vmlCanvasManager_;CanvasRenderingContext2D=CanvasRenderingContext2D_;CanvasGradient=CanvasGradient_;CanvasPattern=CanvasPattern_})()}
diff --git a/templates/content/js/flot-build.bat b/templates/content/js/flot-build.bat
new file mode 100644
index 00000000..28304966
--- /dev/null
+++ b/templates/content/js/flot-build.bat
@@ -0,0 +1,3 @@
+java -jar yuicompressor-2.4.2.jar --type js --charset utf-8 jquery.flot.js -o jquery.flot.pack.js
+
+pause \ No newline at end of file
diff --git a/templates/content/js/jquery-1.2.6.js b/templates/content/js/jquery-1.2.6.js
new file mode 100644
index 00000000..88e661ee
--- /dev/null
+++ b/templates/content/js/jquery-1.2.6.js
@@ -0,0 +1,3549 @@
+(function(){
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+
+// Map over jQuery in case of overwrite
+var _jQuery = window.jQuery,
+// Map over the $ in case of overwrite
+ _$ = window.$;
+
+var jQuery = window.jQuery = window.$ = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context );
+};
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,
+
+// Is it a simple selector
+ isSimple = /^.[^:#\[\.]*$/,
+
+// Will speed up references to undefined, and allows munging its name.
+ undefined;
+
+jQuery.fn = jQuery.prototype = {
+ init: function( selector, context ) {
+ // Make sure that a selection was provided
+ selector = selector || document;
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+ // Handle HTML strings
+ if ( typeof selector == "string" ) {
+ // Are we dealing with HTML string or an ID?
+ var match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] )
+ selector = jQuery.clean( [ match[1] ], context );
+
+ // HANDLE: $("#id")
+ else {
+ var elem = document.getElementById( match[3] );
+
+ // Make sure an element was located
+ if ( elem ){
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id != match[3] )
+ return jQuery().find( selector );
+
+ // Otherwise, we inject the element directly into the jQuery object
+ return jQuery( elem );
+ }
+ selector = [];
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return jQuery( context ).find( selector );
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) )
+ return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+ return this.setArray(jQuery.makeArray(selector));
+ },
+
+ // The current version of jQuery being used
+ jquery: "1.2.6",
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ // The number of elements contained in the matched element set
+ length: 0,
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == undefined ?
+
+ // Return a 'clean' array
+ jQuery.makeArray( this ) :
+
+ // Return just the object
+ this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery( elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Force the current matched set of elements to become
+ // the specified array of elements (destroying the stack in the process)
+ // You should use pushStack() in order to do this, but maintain the stack
+ setArray: function( elems ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ this.length = 0;
+ Array.prototype.push.apply( this, elems );
+
+ return this;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ var ret = -1;
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem && elem.jquery ? elem[0] : elem
+ , this );
+ },
+
+ attr: function( name, value, type ) {
+ var options = name;
+
+ // Look for the case where we're accessing a style value
+ if ( name.constructor == String )
+ if ( value === undefined )
+ return this[0] && jQuery[ type || "attr" ]( this[0], name );
+
+ else {
+ options = {};
+ options[ name ] = value;
+ }
+
+ // Check to see if we're setting style values
+ return this.each(function(i){
+ // Set all the styles
+ for ( name in options )
+ jQuery.attr(
+ type ?
+ this.style :
+ this,
+ name, jQuery.prop( this, options[ name ], type, i, name )
+ );
+ });
+ },
+
+ css: function( key, value ) {
+ // ignore negative width and height values
+ if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+ value = undefined;
+ return this.attr( key, value, "curCSS" );
+ },
+
+ text: function( text ) {
+ if ( typeof text != "object" && text != null )
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+ var ret = "";
+
+ jQuery.each( text || this, function(){
+ jQuery.each( this.childNodes, function(){
+ if ( this.nodeType != 8 )
+ ret += this.nodeType != 1 ?
+ this.nodeValue :
+ jQuery.fn.text( [ this ] );
+ });
+ });
+
+ return ret;
+ },
+
+ wrapAll: function( html ) {
+ if ( this[0] )
+ // The elements to wrap the target around
+ jQuery( html, this[0].ownerDocument )
+ .clone()
+ .insertBefore( this[0] )
+ .map(function(){
+ var elem = this;
+
+ while ( elem.firstChild )
+ elem = elem.firstChild;
+
+ return elem;
+ })
+ .append(this);
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ return this.each(function(){
+ jQuery( this ).contents().wrapAll( html );
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function(){
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, false, function(elem){
+ if (this.nodeType == 1)
+ this.appendChild( elem );
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, true, function(elem){
+ if (this.nodeType == 1)
+ this.insertBefore( elem, this.firstChild );
+ });
+ },
+
+ before: function() {
+ return this.domManip(arguments, false, false, function(elem){
+ this.parentNode.insertBefore( elem, this );
+ });
+ },
+
+ after: function() {
+ return this.domManip(arguments, false, true, function(elem){
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ },
+
+ end: function() {
+ return this.prevObject || jQuery( [] );
+ },
+
+ find: function( selector ) {
+ var elems = jQuery.map(this, function(elem){
+ return jQuery.find( selector, elem );
+ });
+
+ return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+ jQuery.unique( elems ) :
+ elems );
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function(){
+ if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var clone = this.cloneNode(true),
+ container = document.createElement("div");
+ container.appendChild(clone);
+ return jQuery.clean([container.innerHTML])[0];
+ } else
+ return this.cloneNode(true);
+ });
+
+ // Need to set the expando to null on the cloned set if it exists
+ // removeData doesn't work here, IE removes it from the original as well
+ // this is primarily for IE but the data expando shouldn't be copied over in any browser
+ var clone = ret.find("*").andSelf().each(function(){
+ if ( this[ expando ] != undefined )
+ this[ expando ] = null;
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true )
+ this.find("*").andSelf().each(function(i){
+ if (this.nodeType == 3)
+ return;
+ var events = jQuery.data( this, "events" );
+
+ for ( var type in events )
+ for ( var handler in events[ type ] )
+ jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+ });
+
+ // Return the cloned set
+ return ret;
+ },
+
+ filter: function( selector ) {
+ return this.pushStack(
+ jQuery.isFunction( selector ) &&
+ jQuery.grep(this, function(elem, i){
+ return selector.call( elem, i );
+ }) ||
+
+ jQuery.multiFilter( selector, this ) );
+ },
+
+ not: function( selector ) {
+ if ( selector.constructor == String )
+ // test special case where just one selector is passed in
+ if ( isSimple.test( selector ) )
+ return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+ else
+ selector = jQuery.multiFilter( selector, this );
+
+ var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+ return this.filter(function() {
+ return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+ });
+ },
+
+ add: function( selector ) {
+ return this.pushStack( jQuery.unique( jQuery.merge(
+ this.get(),
+ typeof selector == 'string' ?
+ jQuery( selector ) :
+ jQuery.makeArray( selector )
+ )));
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+ },
+
+ hasClass: function( selector ) {
+ return this.is( "." + selector );
+ },
+
+ val: function( value ) {
+ if ( value == undefined ) {
+
+ if ( this.length ) {
+ var elem = this[0];
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type == "select-one";
+
+ // Nothing was selected
+ if ( index < 0 )
+ return null;
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ if ( option.selected ) {
+ // Get the specifc value for the option
+ value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+
+ // We don't need an array for one selects
+ if ( one )
+ return value;
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+
+ // Everything else, we just grab the value
+ } else
+ return (this[0].value || "").replace(/\r/g, "");
+
+ }
+
+ return undefined;
+ }
+
+ if( value.constructor == Number )
+ value += '';
+
+ return this.each(function(){
+ if ( this.nodeType != 1 )
+ return;
+
+ if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+ this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+ jQuery.inArray(this.name, value) >= 0);
+
+ else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = jQuery.makeArray(value);
+
+ jQuery( "option", this ).each(function(){
+ this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+ jQuery.inArray( this.text, values ) >= 0);
+ });
+
+ if ( !values.length )
+ this.selectedIndex = -1;
+
+ } else
+ this.value = value;
+ });
+ },
+
+ html: function( value ) {
+ return value == undefined ?
+ (this[0] ?
+ this[0].innerHTML :
+ null) :
+ this.empty().append( value );
+ },
+
+ replaceWith: function( value ) {
+ return this.after( value ).remove();
+ },
+
+ eq: function( i ) {
+ return this.slice( i, i + 1 );
+ },
+
+ slice: function() {
+ return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function(elem, i){
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ },
+
+ data: function( key, value ){
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length )
+ data = jQuery.data( this[0], key );
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ } else
+ return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+ jQuery.data( this, key, value );
+ });
+ },
+
+ removeData: function( key ){
+ return this.each(function(){
+ jQuery.removeData( this, key );
+ });
+ },
+
+ domManip: function( args, table, reverse, callback ) {
+ var clone = this.length > 1, elems;
+
+ return this.each(function(){
+ if ( !elems ) {
+ elems = jQuery.clean( args, this.ownerDocument );
+
+ if ( reverse )
+ elems.reverse();
+ }
+
+ var obj = this;
+
+ if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+ obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+ var scripts = jQuery( [] );
+
+ jQuery.each(elems, function(){
+ var elem = clone ?
+ jQuery( this ).clone( true )[0] :
+ this;
+
+ // execute all scripts after the elements have been injected
+ if ( jQuery.nodeName( elem, "script" ) )
+ scripts = scripts.add( elem );
+ else {
+ // Remove any inner scripts for later evaluation
+ if ( elem.nodeType == 1 )
+ scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+ // Inject the elements into the document
+ callback.call( obj, elem );
+ }
+ });
+
+ scripts.each( evalScript );
+ });
+ }
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+function evalScript( i, elem ) {
+ if ( elem.src )
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+
+ else
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+ if ( elem.parentNode )
+ elem.parentNode.removeChild( elem );
+}
+
+function now(){
+ return +new Date;
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+ // Handle a deep copy situation
+ if ( target.constructor == Boolean ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target != "object" && typeof target != "function" )
+ target = {};
+
+ // extend jQuery itself if only one argument is passed
+ if ( length == i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ )
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null )
+ // Extend the base object
+ for ( var name in options ) {
+ var src = target[ name ], copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy )
+ continue;
+
+ // Recurse if we're merging object values
+ if ( deep && copy && typeof copy == "object" && !copy.nodeType )
+ target[ name ] = jQuery.extend( deep,
+ // Never move original objects, clone them
+ src || ( copy.length != null ? [ ] : { } )
+ , copy );
+
+ // Don't bring in undefined values
+ else if ( copy !== undefined )
+ target[ name ] = copy;
+
+ }
+
+ // Return the modified object
+ return target;
+};
+
+var expando = "jQuery" + now(), uuid = 0, windowData = {},
+ // exclude the following css properties to add px
+ exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ // cache defaultView
+ defaultView = document.defaultView || {};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep )
+ window.jQuery = _jQuery;
+
+ return jQuery;
+ },
+
+ // See test/unit/core.js for details concerning this function.
+ isFunction: function( fn ) {
+ return !!fn && typeof fn != "string" && !fn.nodeName &&
+ fn.constructor != Array && /^[\s[]?function/.test( fn + "" );
+ },
+
+ // check if an element is in a (or is an) XML document
+ isXMLDoc: function( elem ) {
+ return elem.documentElement && !elem.body ||
+ elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+ },
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ data = jQuery.trim( data );
+
+ if ( data ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+ if ( jQuery.browser.msie )
+ script.text = data;
+ else
+ script.appendChild( document.createTextNode( data ) );
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+ },
+
+ cache: {},
+
+ data: function( elem, name, data ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // Compute a unique ID for the element
+ if ( !id )
+ id = elem[ expando ] = ++uuid;
+
+ // Only generate the data cache if we're
+ // trying to access or manipulate it
+ if ( name && !jQuery.cache[ id ] )
+ jQuery.cache[ id ] = {};
+
+ // Prevent overriding the named cache with undefined values
+ if ( data !== undefined )
+ jQuery.cache[ id ][ name ] = data;
+
+ // Return the named cache data, or the ID for the element
+ return name ?
+ jQuery.cache[ id ][ name ] :
+ id;
+ },
+
+ removeData: function( elem, name ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( jQuery.cache[ id ] ) {
+ // Remove the section of cache data
+ delete jQuery.cache[ id ][ name ];
+
+ // If we've removed all the data, remove the element's cache
+ name = "";
+
+ for ( name in jQuery.cache[ id ] )
+ break;
+
+ if ( !name )
+ jQuery.removeData( elem );
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ // Clean up the element expando
+ try {
+ delete elem[ expando ];
+ } catch(e){
+ // IE has trouble directly removing the expando
+ // but it's ok with using removeAttribute
+ if ( elem.removeAttribute )
+ elem.removeAttribute( expando );
+ }
+
+ // Completely remove the data cache
+ delete jQuery.cache[ id ];
+ }
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0, length = object.length;
+
+ if ( args ) {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.apply( object[ name ], args ) === false )
+ break;
+ } else
+ for ( ; i < length; )
+ if ( callback.apply( object[ i++ ], args ) === false )
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.call( object[ name ], name, object[ name ] ) === false )
+ break;
+ } else
+ for ( var value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+ }
+
+ return object;
+ },
+
+ prop: function( elem, value, type, i, name ) {
+ // Handle executable functions
+ if ( jQuery.isFunction( value ) )
+ value = value.call( elem, i );
+
+ // Handle passing in a number to a CSS property
+ return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+ value + "px" :
+ value;
+ },
+
+ className: {
+ // internal only, use addClass("class")
+ add: function( elem, classNames ) {
+ jQuery.each((classNames || "").split(/\s+/), function(i, className){
+ if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+ elem.className += (elem.className ? " " : "") + className;
+ });
+ },
+
+ // internal only, use removeClass("class")
+ remove: function( elem, classNames ) {
+ if (elem.nodeType == 1)
+ elem.className = classNames != undefined ?
+ jQuery.grep(elem.className.split(/\s+/), function(className){
+ return !jQuery.className.has( classNames, className );
+ }).join(" ") :
+ "";
+ },
+
+ // internal only, use hasClass("class")
+ has: function( elem, className ) {
+ return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( var name in options )
+ elem.style[ name ] = old[ name ];
+ },
+
+ css: function( elem, name, force ) {
+ if ( name == "width" || name == "height" ) {
+ var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+ function getWH() {
+ val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+ var padding = 0, border = 0;
+ jQuery.each( which, function() {
+ padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ });
+ val -= Math.round(padding + border);
+ }
+
+ if ( jQuery(elem).is(":visible") )
+ getWH();
+ else
+ jQuery.swap( elem, props, getWH );
+
+ return Math.max(0, val);
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret, style = elem.style;
+
+ // A helper method for determining if an element's values are broken
+ function color( elem ) {
+ if ( !jQuery.browser.safari )
+ return false;
+
+ // defaultView is cached
+ var ret = defaultView.getComputedStyle( elem, null );
+ return !ret || ret.getPropertyValue("color") == "";
+ }
+
+ // We need to handle opacity special in IE
+ if ( name == "opacity" && jQuery.browser.msie ) {
+ ret = jQuery.attr( style, "opacity" );
+
+ return ret == "" ?
+ "1" :
+ ret;
+ }
+ // Opera sometimes will give the wrong display answer, this fixes it, see #2037
+ if ( jQuery.browser.opera && name == "display" ) {
+ var save = style.outline;
+ style.outline = "0 solid black";
+ style.outline = save;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( name.match( /float/i ) )
+ name = styleFloat;
+
+ if ( !force && style && style[ name ] )
+ ret = style[ name ];
+
+ else if ( defaultView.getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( name.match( /float/i ) )
+ name = "float";
+
+ name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+ var computedStyle = defaultView.getComputedStyle( elem, null );
+
+ if ( computedStyle && !color( elem ) )
+ ret = computedStyle.getPropertyValue( name );
+
+ // If the element isn't reporting its values properly in Safari
+ // then some display: none elements are involved
+ else {
+ var swap = [], stack = [], a = elem, i = 0;
+
+ // Locate all of the parent display: none elements
+ for ( ; a && color(a); a = a.parentNode )
+ stack.unshift(a);
+
+ // Go through and make them visible, but in reverse
+ // (It would be better if we knew the exact display type that they had)
+ for ( ; i < stack.length; i++ )
+ if ( color( stack[ i ] ) ) {
+ swap[ i ] = stack[ i ].style.display;
+ stack[ i ].style.display = "block";
+ }
+
+ // Since we flip the display style, we have to handle that
+ // one special, otherwise get the value
+ ret = name == "display" && swap[ stack.length - 1 ] != null ?
+ "none" :
+ ( computedStyle && computedStyle.getPropertyValue( name ) ) || "";
+
+ // Finally, revert the display styles back
+ for ( i = 0; i < swap.length; i++ )
+ if ( swap[ i ] != null )
+ stack[ i ].style.display = swap[ i ];
+ }
+
+ // We should always get a number back from opacity
+ if ( name == "opacity" && ret == "" )
+ ret = "1";
+
+ } else if ( elem.currentStyle ) {
+ var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+ // Remember the original values
+ var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = ret || 0;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret;
+ },
+
+ clean: function( elems, context ) {
+ var ret = [];
+ context = context || document;
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if (typeof context.createElement == 'undefined')
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+ jQuery.each(elems, function(i, elem){
+ if ( !elem )
+ return;
+
+ if ( elem.constructor == Number )
+ elem += '';
+
+ // Convert html string into DOM nodes
+ if ( typeof elem == "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+ all :
+ front + "></" + tag + ">";
+ });
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+ var wrap =
+ // option or optgroup
+ !tags.indexOf("<opt") &&
+ [ 1, "<select multiple='multiple'>", "</select>" ] ||
+
+ !tags.indexOf("<leg") &&
+ [ 1, "<fieldset>", "</fieldset>" ] ||
+
+ tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+ [ 1, "<table>", "</table>" ] ||
+
+ !tags.indexOf("<tr") &&
+ [ 2, "<table><tbody>", "</tbody></table>" ] ||
+
+ // <thead> matched above
+ (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+ [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+
+ !tags.indexOf("<col") &&
+ [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+ // IE can't serialize <link> and <script> tags normally
+ jQuery.browser.msie &&
+ [ 1, "div<div>", "</div>" ] ||
+
+ [ 0, "", "" ];
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( wrap[0]-- )
+ div = div.lastChild;
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( jQuery.browser.msie ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+ div.childNodes :
+ [];
+
+ for ( var j = tbody.length - 1; j >= 0 ; --j )
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( /^\s/.test( elem ) )
+ div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+
+ }
+
+ elem = jQuery.makeArray( div.childNodes );
+ }
+
+ if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) )
+ return;
+
+ if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options )
+ ret.push( elem );
+
+ else
+ ret = jQuery.merge( ret, elem );
+
+ });
+
+ return ret;
+ },
+
+ attr: function( elem, name, value ) {
+ // don't set attributes on text and comment nodes
+ if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+ return undefined;
+
+ var notxml = !jQuery.isXMLDoc( elem ),
+ // Whether we are setting (or getting)
+ set = value !== undefined,
+ msie = jQuery.browser.msie;
+
+ // Try to normalize/fix the name
+ name = notxml && jQuery.props[ name ] || name;
+
+ // Only do all the following if this is a node (faster for style)
+ // IE elem.getAttribute passes even for style
+ if ( elem.tagName ) {
+
+ // These attributes require special treatment
+ var special = /href|src|style/.test( name );
+
+ // Safari mis-reports the default selected property of a hidden option
+ // Accessing the parent's selectedIndex property fixes it
+ if ( name == "selected" && jQuery.browser.safari )
+ elem.parentNode.selectedIndex;
+
+ // If applicable, access the attribute via the DOM 0 way
+ if ( name in elem && notxml && !special ) {
+ if ( set ){
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+ throw "type property can't be changed";
+
+ elem[ name ] = value;
+ }
+
+ // browsers index elements by id/name on forms, give priority to attributes.
+ if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
+ return elem.getAttributeNode( name ).nodeValue;
+
+ return elem[ name ];
+ }
+
+ if ( msie && notxml && name == "style" )
+ return jQuery.attr( elem.style, "cssText", value );
+
+ if ( set )
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+
+ var attr = msie && notxml && special
+ // Some attributes require a special call on IE
+ ? elem.getAttribute( name, 2 )
+ : elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return attr === null ? undefined : attr;
+ }
+
+ // elem is actually elem.style ... set the style
+
+ // IE uses filters for opacity
+ if ( msie && name == "opacity" ) {
+ if ( set ) {
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ elem.zoom = 1;
+
+ // Set the alpha filter to set the opacity
+ elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+ (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+ }
+
+ return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+ (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
+ "";
+ }
+
+ name = name.replace(/-([a-z])/ig, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ if ( set )
+ elem[ name ] = value;
+
+ return elem[ name ];
+ },
+
+ trim: function( text ) {
+ return (text || "").replace( /^\s+|\s+$/g, "" );
+ },
+
+ makeArray: function( array ) {
+ var ret = [];
+
+ if( array != null ){
+ var i = array.length;
+ //the window, strings and functions also have 'length'
+ if( i == null || array.split || array.setInterval || array.call )
+ ret[0] = array;
+ else
+ while( i )
+ ret[--i] = array[i];
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ for ( var i = 0, length = array.length; i < length; i++ )
+ // Use === because on IE, window == document
+ if ( array[ i ] === elem )
+ return i;
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ // We have to loop this way because IE & Opera overwrite the length
+ // expando of getElementsByTagName
+ var i = 0, elem, pos = first.length;
+ // Also, we need to make sure that the correct elements are being returned
+ // (IE returns comment nodes in a '*' query)
+ if ( jQuery.browser.msie ) {
+ while ( elem = second[ i++ ] )
+ if ( elem.nodeType != 8 )
+ first[ pos++ ] = elem;
+
+ } else
+ while ( elem = second[ i++ ] )
+ first[ pos++ ] = elem;
+
+ return first;
+ },
+
+ unique: function( array ) {
+ var ret = [], done = {};
+
+ try {
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ var id = jQuery.data( array[ i ] );
+
+ if ( !done[ id ] ) {
+ done[ id ] = true;
+ ret.push( array[ i ] );
+ }
+ }
+
+ } catch( e ) {
+ ret = array;
+ }
+
+ return ret;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [];
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ )
+ if ( !inv != !callback( elems[ i ], i ) )
+ ret.push( elems[ i ] );
+
+ return ret;
+ },
+
+ map: function( elems, callback ) {
+ var ret = [];
+
+ // Go through the array, translating each of the items to their
+ // new value (or values).
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ var value = callback( elems[ i ], i );
+
+ if ( value != null )
+ ret[ ret.length ] = value;
+ }
+
+ return ret.concat.apply( [], ret );
+ }
+});
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+ version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
+ safari: /webkit/.test( userAgent ),
+ opera: /opera/.test( userAgent ),
+ msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+ mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+var styleFloat = jQuery.browser.msie ?
+ "styleFloat" :
+ "cssFloat";
+
+jQuery.extend({
+ // Check to see if the W3C box model is being used
+ boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
+
+ props: {
+ "for": "htmlFor",
+ "class": "className",
+ "float": styleFloat,
+ cssFloat: styleFloat,
+ styleFloat: styleFloat,
+ readonly: "readOnly",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing"
+ }
+});
+
+jQuery.each({
+ parent: function(elem){return elem.parentNode;},
+ parents: function(elem){return jQuery.dir(elem,"parentNode");},
+ next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
+ prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
+ nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
+ prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
+ siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
+ children: function(elem){return jQuery.sibling(elem.firstChild);},
+ contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
+}, function(name, fn){
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = jQuery.map( this, fn );
+
+ if ( selector && typeof selector == "string" )
+ ret = jQuery.multiFilter( selector, ret );
+
+ return this.pushStack( jQuery.unique( ret ) );
+ };
+});
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function(name, original){
+ jQuery.fn[ name ] = function() {
+ var args = arguments;
+
+ return this.each(function(){
+ for ( var i = 0, length = args.length; i < length; i++ )
+ jQuery( args[ i ] )[ original ]( this );
+ });
+ };
+});
+
+jQuery.each({
+ removeAttr: function( name ) {
+ jQuery.attr( this, name, "" );
+ if (this.nodeType == 1)
+ this.removeAttribute( name );
+ },
+
+ addClass: function( classNames ) {
+ jQuery.className.add( this, classNames );
+ },
+
+ removeClass: function( classNames ) {
+ jQuery.className.remove( this, classNames );
+ },
+
+ toggleClass: function( classNames ) {
+ jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
+ },
+
+ remove: function( selector ) {
+ if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
+ // Prevent memory leaks
+ jQuery( "*", this ).add(this).each(function(){
+ jQuery.event.remove(this);
+ jQuery.removeData(this);
+ });
+ if (this.parentNode)
+ this.parentNode.removeChild( this );
+ }
+ },
+
+ empty: function() {
+ // Remove element nodes and prevent memory leaks
+ jQuery( ">*", this ).remove();
+
+ // Remove any remaining nodes
+ while ( this.firstChild )
+ this.removeChild( this.firstChild );
+ }
+}, function(name, fn){
+ jQuery.fn[ name ] = function(){
+ return this.each( fn, arguments );
+ };
+});
+
+jQuery.each([ "Height", "Width" ], function(i, name){
+ var type = name.toLowerCase();
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ return this[0] == window ?
+ // Opera reports document.body.client[Width/Height] properly in both quirks and standards
+ jQuery.browser.opera && document.body[ "client" + name ] ||
+
+ // Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
+ jQuery.browser.safari && window[ "inner" + name ] ||
+
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
+
+ // Get document width or height
+ this[0] == document ?
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ Math.max(
+ Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]),
+ Math.max(document.body["offset" + name], document.documentElement["offset" + name])
+ ) :
+
+ // Get or set width or height on the element
+ size == undefined ?
+ // Get width or height on the element
+ (this.length ? jQuery.css( this[0], type ) : null) :
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ this.css( type, size.constructor == String ? size : size + "px" );
+ };
+});
+
+// Helper function used by the dimensions and offset modules
+function num(elem, prop) {
+ return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
+}var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ?
+ "(?:[\\w*_-]|\\\\.)" :
+ "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
+ quickChild = new RegExp("^>\\s*(" + chars + "+)"),
+ quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
+ quickClass = new RegExp("^([#.]?)(" + chars + "*)");
+
+jQuery.extend({
+ expr: {
+ "": function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},
+ "#": function(a,i,m){return a.getAttribute("id")==m[2];},
+ ":": {
+ // Position Checks
+ lt: function(a,i,m){return i<m[3]-0;},
+ gt: function(a,i,m){return i>m[3]-0;},
+ nth: function(a,i,m){return m[3]-0==i;},
+ eq: function(a,i,m){return m[3]-0==i;},
+ first: function(a,i){return i==0;},
+ last: function(a,i,m,r){return i==r.length-1;},
+ even: function(a,i){return i%2==0;},
+ odd: function(a,i){return i%2;},
+
+ // Child Checks
+ "first-child": function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},
+ "last-child": function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},
+ "only-child": function(a){return !jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},
+
+ // Parent Checks
+ parent: function(a){return a.firstChild;},
+ empty: function(a){return !a.firstChild;},
+
+ // Text Check
+ contains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},
+
+ // Visibility
+ visible: function(a){return "hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},
+ hidden: function(a){return "hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},
+
+ // Form attributes
+ enabled: function(a){return !a.disabled;},
+ disabled: function(a){return a.disabled;},
+ checked: function(a){return a.checked;},
+ selected: function(a){return a.selected||jQuery.attr(a,"selected");},
+
+ // Form elements
+ text: function(a){return "text"==a.type;},
+ radio: function(a){return "radio"==a.type;},
+ checkbox: function(a){return "checkbox"==a.type;},
+ file: function(a){return "file"==a.type;},
+ password: function(a){return "password"==a.type;},
+ submit: function(a){return "submit"==a.type;},
+ image: function(a){return "image"==a.type;},
+ reset: function(a){return "reset"==a.type;},
+ button: function(a){return "button"==a.type||jQuery.nodeName(a,"button");},
+ input: function(a){return /input|select|textarea|button/i.test(a.nodeName);},
+
+ // :has()
+ has: function(a,i,m){return jQuery.find(m[3],a).length;},
+
+ // :header
+ header: function(a){return /h\d/i.test(a.nodeName);},
+
+ // :animated
+ animated: function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}
+ }
+ },
+
+ // The regular expressions that power the parsing engine
+ parse: [
+ // Match: [@value='test'], [@foo]
+ /^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+ // Match: :contains('foo')
+ /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+ // Match: :even, :last-child, #id, .class
+ new RegExp("^([:.#]*)(" + chars + "+)")
+ ],
+
+ multiFilter: function( expr, elems, not ) {
+ var old, cur = [];
+
+ while ( expr && expr != old ) {
+ old = expr;
+ var f = jQuery.filter( expr, elems, not );
+ expr = f.t.replace(/^\s*,\s*/, "" );
+ cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+ }
+
+ return cur;
+ },
+
+ find: function( t, context ) {
+ // Quickly handle non-string expressions
+ if ( typeof t != "string" )
+ return [ t ];
+
+ // check to make sure context is a DOM element or a document
+ if ( context && context.nodeType != 1 && context.nodeType != 9)
+ return [ ];
+
+ // Set the correct context (if none is provided)
+ context = context || document;
+
+ // Initialize the search
+ var ret = [context], done = [], last, nodeName;
+
+ // Continue while a selector expression exists, and while
+ // we're no longer looping upon ourselves
+ while ( t && last != t ) {
+ var r = [];
+ last = t;
+
+ t = jQuery.trim(t);
+
+ var foundToken = false,
+
+ // An attempt at speeding up child selectors that
+ // point to a specific element tag
+ re = quickChild,
+
+ m = re.exec(t);
+
+ if ( m ) {
+ nodeName = m[1].toUpperCase();
+
+ // Perform our own iteration and filter
+ for ( var i = 0; ret[i]; i++ )
+ for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+ if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
+ r.push( c );
+
+ ret = r;
+ t = t.replace( re, "" );
+ if ( t.indexOf(" ") == 0 ) continue;
+ foundToken = true;
+ } else {
+ re = /^([>+~])\s*(\w*)/i;
+
+ if ( (m = re.exec(t)) != null ) {
+ r = [];
+
+ var merge = {};
+ nodeName = m[2].toUpperCase();
+ m = m[1];
+
+ for ( var j = 0, rl = ret.length; j < rl; j++ ) {
+ var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+ for ( ; n; n = n.nextSibling )
+ if ( n.nodeType == 1 ) {
+ var id = jQuery.data(n);
+
+ if ( m == "~" && merge[id] ) break;
+
+ if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
+ if ( m == "~" ) merge[id] = true;
+ r.push( n );
+ }
+
+ if ( m == "+" ) break;
+ }
+ }
+
+ ret = r;
+
+ // And remove the token
+ t = jQuery.trim( t.replace( re, "" ) );
+ foundToken = true;
+ }
+ }
+
+ // See if there's still an expression, and that we haven't already
+ // matched a token
+ if ( t && !foundToken ) {
+ // Handle multiple expressions
+ if ( !t.indexOf(",") ) {
+ // Clean the result set
+ if ( context == ret[0] ) ret.shift();
+
+ // Merge the result sets
+ done = jQuery.merge( done, ret );
+
+ // Reset the context
+ r = ret = [context];
+
+ // Touch up the selector string
+ t = " " + t.substr(1,t.length);
+
+ } else {
+ // Optimize for the case nodeName#idName
+ var re2 = quickID;
+ var m = re2.exec(t);
+
+ // Re-organize the results, so that they're consistent
+ if ( m ) {
+ m = [ 0, m[2], m[3], m[1] ];
+
+ } else {
+ // Otherwise, do a traditional filter check for
+ // ID, class, and element selectors
+ re2 = quickClass;
+ m = re2.exec(t);
+ }
+
+ m[2] = m[2].replace(/\\/g, "");
+
+ var elem = ret[ret.length-1];
+
+ // Try to do a global search by ID, where we can
+ if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
+ // Optimization for HTML document case
+ var oid = elem.getElementById(m[2]);
+
+ // Do a quick check for the existence of the actual ID attribute
+ // to avoid selecting by the name attribute in IE
+ // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+ if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+ oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+ // Do a quick check for node name (where applicable) so
+ // that div#foo searches will be really fast
+ ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+ } else {
+ // We need to find all descendant elements
+ for ( var i = 0; ret[i]; i++ ) {
+ // Grab the tag name being searched for
+ var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
+
+ // Handle IE7 being really dumb about <object>s
+ if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+ tag = "param";
+
+ r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+ }
+
+ // It's faster to filter by class and be done with it
+ if ( m[1] == "." )
+ r = jQuery.classFilter( r, m[2] );
+
+ // Same with ID filtering
+ if ( m[1] == "#" ) {
+ var tmp = [];
+
+ // Try to find the element with the ID
+ for ( var i = 0; r[i]; i++ )
+ if ( r[i].getAttribute("id") == m[2] ) {
+ tmp = [ r[i] ];
+ break;
+ }
+
+ r = tmp;
+ }
+
+ ret = r;
+ }
+
+ t = t.replace( re2, "" );
+ }
+
+ }
+
+ // If a selector string still exists
+ if ( t ) {
+ // Attempt to filter it
+ var val = jQuery.filter(t,r);
+ ret = r = val.r;
+ t = jQuery.trim(val.t);
+ }
+ }
+
+ // An error occurred with the selector;
+ // just return an empty set instead
+ if ( t )
+ ret = [];
+
+ // Remove the root context
+ if ( ret && context == ret[0] )
+ ret.shift();
+
+ // And combine the results
+ done = jQuery.merge( done, ret );
+
+ return done;
+ },
+
+ classFilter: function(r,m,not){
+ m = " " + m + " ";
+ var tmp = [];
+ for ( var i = 0; r[i]; i++ ) {
+ var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+ if ( !not && pass || not && !pass )
+ tmp.push( r[i] );
+ }
+ return tmp;
+ },
+
+ filter: function(t,r,not) {
+ var last;
+
+ // Look for common filter expressions
+ while ( t && t != last ) {
+ last = t;
+
+ var p = jQuery.parse, m;
+
+ for ( var i = 0; p[i]; i++ ) {
+ m = p[i].exec( t );
+
+ if ( m ) {
+ // Remove what we just matched
+ t = t.substring( m[0].length );
+
+ m[2] = m[2].replace(/\\/g, "");
+ break;
+ }
+ }
+
+ if ( !m )
+ break;
+
+ // :not() is a special case that can be optimized by
+ // keeping it out of the expression list
+ if ( m[1] == ":" && m[2] == "not" )
+ // optimize if only one selector found (most common case)
+ r = isSimple.test( m[3] ) ?
+ jQuery.filter(m[3], r, true).r :
+ jQuery( r ).not( m[3] );
+
+ // We can get a big speed boost by filtering by class here
+ else if ( m[1] == "." )
+ r = jQuery.classFilter(r, m[2], not);
+
+ else if ( m[1] == "[" ) {
+ var tmp = [], type = m[3];
+
+ for ( var i = 0, rl = r.length; i < rl; i++ ) {
+ var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+
+ if ( z == null || /href|src|selected/.test(m[2]) )
+ z = jQuery.attr(a,m[2]) || '';
+
+ if ( (type == "" && !!z ||
+ type == "=" && z == m[5] ||
+ type == "!=" && z != m[5] ||
+ type == "^=" && z && !z.indexOf(m[5]) ||
+ type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+ (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+ tmp.push( a );
+ }
+
+ r = tmp;
+
+ // We can get a speed boost by handling nth-child here
+ } else if ( m[1] == ":" && m[2] == "nth-child" ) {
+ var merge = {}, tmp = [],
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+ !/\D/.test(m[3]) && "0n+" + m[3] || m[3]),
+ // calculate the numbers (first)n+(last) including if they are negative
+ first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
+
+ // loop through all the elements left in the jQuery object
+ for ( var i = 0, rl = r.length; i < rl; i++ ) {
+ var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode);
+
+ if ( !merge[id] ) {
+ var c = 1;
+
+ for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+ if ( n.nodeType == 1 )
+ n.nodeIndex = c++;
+
+ merge[id] = true;
+ }
+
+ var add = false;
+
+ if ( first == 0 ) {
+ if ( node.nodeIndex == last )
+ add = true;
+ } else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 )
+ add = true;
+
+ if ( add ^ not )
+ tmp.push( node );
+ }
+
+ r = tmp;
+
+ // Otherwise, find the expression to execute
+ } else {
+ var fn = jQuery.expr[ m[1] ];
+ if ( typeof fn == "object" )
+ fn = fn[ m[2] ];
+
+ if ( typeof fn == "string" )
+ fn = eval("false||function(a,i){return " + fn + ";}");
+
+ // Execute it against the current filter
+ r = jQuery.grep( r, function(elem, i){
+ return fn(elem, i, m, r);
+ }, not );
+ }
+ }
+
+ // Return an array of filtered elements (r)
+ // and the modified expression string (t)
+ return { r: r, t: t };
+ },
+
+ dir: function( elem, dir ){
+ var matched = [],
+ cur = elem[dir];
+ while ( cur && cur != document ) {
+ if ( cur.nodeType == 1 )
+ matched.push( cur );
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function(cur,result,dir,elem){
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] )
+ if ( cur.nodeType == 1 && ++num == result )
+ break;
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType == 1 && n != elem )
+ r.push( n );
+ }
+
+ return r;
+ }
+});
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code orignated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+ // Bind an event to an element
+ // Original by Dean Edwards
+ add: function(elem, types, handler, data) {
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return;
+
+ // For whatever reason, IE has trouble passing the window object
+ // around, causing it to be cloned in the process
+ if ( jQuery.browser.msie && elem.setInterval )
+ elem = window;
+
+ // Make sure that the function being executed has a unique ID
+ if ( !handler.guid )
+ handler.guid = this.guid++;
+
+ // if data is passed, bind to handler
+ if( data != undefined ) {
+ // Create temporary function pointer to original handler
+ var fn = handler;
+
+ // Create unique handler function, wrapped around original handler
+ handler = this.proxy( fn, function() {
+ // Pass arguments and context to original handler
+ return fn.apply(this, arguments);
+ });
+
+ // Store data in unique handler
+ handler.data = data;
+ }
+
+ // Init the element's event structure
+ var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+ handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+ // Handle the second event of a trigger and when
+ // an event is called after a page has unloaded
+ if ( typeof jQuery != "undefined" && !jQuery.event.triggered )
+ return jQuery.event.handle.apply(arguments.callee.elem, arguments);
+ });
+ // Add elem as a property of the handle function
+ // This is to prevent a memory leak with non-native
+ // event in IE.
+ handle.elem = elem;
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type) {
+ // Namespaced event handlers
+ var parts = type.split(".");
+ type = parts[0];
+ handler.type = parts[1];
+
+ // Get the current list of functions bound to this event
+ var handlers = events[type];
+
+ // Init the event handler queue
+ if (!handlers) {
+ handlers = events[type] = {};
+
+ // Check for a special event handler
+ // Only use addEventListener/attachEvent if the special
+ // events handler returns false
+ if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
+ // Bind the global event handler to the element
+ if (elem.addEventListener)
+ elem.addEventListener(type, handle, false);
+ else if (elem.attachEvent)
+ elem.attachEvent("on" + type, handle);
+ }
+ }
+
+ // Add the function to the element's handler list
+ handlers[handler.guid] = handler;
+
+ // Keep track of which events have been used, for global triggering
+ jQuery.event.global[type] = true;
+ });
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ guid: 1,
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function(elem, types, handler) {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return;
+
+ var events = jQuery.data(elem, "events"), ret, index;
+
+ if ( events ) {
+ // Unbind all events for the element
+ if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") )
+ for ( var type in events )
+ this.remove( elem, type + (types || "") );
+ else {
+ // types is actually an event object here
+ if ( types.type ) {
+ handler = types.handler;
+ types = types.type;
+ }
+
+ // Handle multiple events seperated by a space
+ // jQuery(...).unbind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type){
+ // Namespaced event handlers
+ var parts = type.split(".");
+ type = parts[0];
+
+ if ( events[type] ) {
+ // remove the given handler for the given type
+ if ( handler )
+ delete events[type][handler.guid];
+
+ // remove all handlers for the given type
+ else
+ for ( handler in events[type] )
+ // Handle the removal of namespaced events
+ if ( !parts[1] || events[type][handler].type == parts[1] )
+ delete events[type][handler];
+
+ // remove generic event handler if no more handlers exist
+ for ( ret in events[type] ) break;
+ if ( !ret ) {
+ if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
+ if (elem.removeEventListener)
+ elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+ else if (elem.detachEvent)
+ elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+ }
+ ret = null;
+ delete events[type];
+ }
+ }
+ });
+ }
+
+ // Remove the expando if it's no longer used
+ for ( ret in events ) break;
+ if ( !ret ) {
+ var handle = jQuery.data( elem, "handle" );
+ if ( handle ) handle.elem = null;
+ jQuery.removeData( elem, "events" );
+ jQuery.removeData( elem, "handle" );
+ }
+ }
+ },
+
+ trigger: function(type, data, elem, donative, extra) {
+ // Clone the incoming data, if any
+ data = jQuery.makeArray(data);
+
+ if ( type.indexOf("!") >= 0 ) {
+ type = type.slice(0, -1);
+ var exclusive = true;
+ }
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // Only trigger if we've ever bound an event for it
+ if ( this.global[type] )
+ jQuery("*").add([window, document]).trigger(type, data);
+
+ // Handle triggering a single element
+ } else {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return undefined;
+
+ var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
+ // Check to see if we need to provide a fake event, or not
+ event = !data[0] || !data[0].preventDefault;
+
+ // Pass along a fake event
+ if ( event ) {
+ data.unshift({
+ type: type,
+ target: elem,
+ preventDefault: function(){},
+ stopPropagation: function(){},
+ timeStamp: now()
+ });
+ data[0][expando] = true; // no need to fix fake event
+ }
+
+ // Enforce the right trigger type
+ data[0].type = type;
+ if ( exclusive )
+ data[0].exclusive = true;
+
+ // Trigger the event, it is assumed that "handle" is a function
+ var handle = jQuery.data(elem, "handle");
+ if ( handle )
+ val = handle.apply( elem, data );
+
+ // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
+ if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+ val = false;
+
+ // Extra functions don't get the custom event object
+ if ( event )
+ data.shift();
+
+ // Handle triggering of extra function
+ if ( extra && jQuery.isFunction( extra ) ) {
+ // call the extra function and tack the current return value on the end for possible inspection
+ ret = extra.apply( elem, val == null ? data : data.concat( val ) );
+ // if anything is returned, give it precedence and have it overwrite the previous value
+ if (ret !== undefined)
+ val = ret;
+ }
+
+ // Trigger the native events (except for clicks on links)
+ if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+ this.triggered = true;
+ try {
+ elem[ type ]();
+ // prevent IE from throwing an error for some hidden elements
+ } catch (e) {}
+ }
+
+ this.triggered = false;
+ }
+
+ return val;
+ },
+
+ handle: function(event) {
+ // returned undefined or false
+ var val, ret, namespace, all, handlers;
+
+ event = arguments[0] = jQuery.event.fix( event || window.event );
+
+ // Namespaced event handlers
+ namespace = event.type.split(".");
+ event.type = namespace[0];
+ namespace = namespace[1];
+ // Cache this now, all = true means, any handler
+ all = !namespace && !event.exclusive;
+
+ handlers = ( jQuery.data(this, "events") || {} )[event.type];
+
+ for ( var j in handlers ) {
+ var handler = handlers[j];
+
+ // Filter the functions by class
+ if ( all || handler.type == namespace ) {
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ event.handler = handler;
+ event.data = handler.data;
+
+ ret = handler.apply( this, arguments );
+
+ if ( val !== false )
+ val = ret;
+
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+
+ return val;
+ },
+
+ fix: function(event) {
+ if ( event[expando] == true )
+ return event;
+
+ // store a copy of the original event object
+ // and "clone" to set read-only properties
+ var originalEvent = event;
+ event = { originalEvent: originalEvent };
+ var props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");
+ for ( var i=props.length; i; i-- )
+ event[ props[i] ] = originalEvent[ props[i] ];
+
+ // Mark it as fixed
+ event[expando] = true;
+
+ // add preventDefault and stopPropagation since
+ // they will not work on the clone
+ event.preventDefault = function() {
+ // if preventDefault exists run it on the original event
+ if (originalEvent.preventDefault)
+ originalEvent.preventDefault();
+ // otherwise set the returnValue property of the original event to false (IE)
+ originalEvent.returnValue = false;
+ };
+ event.stopPropagation = function() {
+ // if stopPropagation exists run it on the original event
+ if (originalEvent.stopPropagation)
+ originalEvent.stopPropagation();
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ originalEvent.cancelBubble = true;
+ };
+
+ // Fix timeStamp
+ event.timeStamp = event.timeStamp || now();
+
+ // Fix target property, if necessary
+ if ( !event.target )
+ event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+
+ // check if target is a textnode (safari)
+ if ( event.target.nodeType == 3 )
+ event.target = event.target.parentNode;
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && event.fromElement )
+ event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && event.clientX != null ) {
+ var doc = document.documentElement, body = document.body;
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+ }
+
+ // Add which for key events
+ if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+ event.which = event.charCode || event.keyCode;
+
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+ if ( !event.metaKey && event.ctrlKey )
+ event.metaKey = event.ctrlKey;
+
+ // Add which for click: 1 == left; 2 == middle; 3 == right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && event.button )
+ event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+
+ return event;
+ },
+
+ proxy: function( fn, proxy ){
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
+ // So proxy can be declared as an argument
+ return proxy;
+ },
+
+ special: {
+ ready: {
+ setup: function() {
+ // Make sure the ready event is setup
+ bindReady();
+ return;
+ },
+
+ teardown: function() { return; }
+ },
+
+ mouseenter: {
+ setup: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
+ return true;
+ },
+
+ teardown: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
+ return true;
+ },
+
+ handler: function(event) {
+ // If we actually just moused on to a sub-element, ignore it
+ if ( withinElement(event, this) ) return true;
+ // Execute the right handlers by setting the event type to mouseenter
+ event.type = "mouseenter";
+ return jQuery.event.handle.apply(this, arguments);
+ }
+ },
+
+ mouseleave: {
+ setup: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
+ return true;
+ },
+
+ teardown: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
+ return true;
+ },
+
+ handler: function(event) {
+ // If we actually just moused on to a sub-element, ignore it
+ if ( withinElement(event, this) ) return true;
+ // Execute the right handlers by setting the event type to mouseleave
+ event.type = "mouseleave";
+ return jQuery.event.handle.apply(this, arguments);
+ }
+ }
+ }
+};
+
+jQuery.fn.extend({
+ bind: function( type, data, fn ) {
+ return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+ jQuery.event.add( this, type, fn || data, fn && data );
+ });
+ },
+
+ one: function( type, data, fn ) {
+ var one = jQuery.event.proxy( fn || data, function(event) {
+ jQuery(this).unbind(event, one);
+ return (fn || data).apply( this, arguments );
+ });
+ return this.each(function(){
+ jQuery.event.add( this, type, one, fn && data);
+ });
+ },
+
+ unbind: function( type, fn ) {
+ return this.each(function(){
+ jQuery.event.remove( this, type, fn );
+ });
+ },
+
+ trigger: function( type, data, fn ) {
+ return this.each(function(){
+ jQuery.event.trigger( type, data, this, true, fn );
+ });
+ },
+
+ triggerHandler: function( type, data, fn ) {
+ return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments, i = 1;
+
+ // link all the functions, so any of them can unbind this click handler
+ while( i < args.length )
+ jQuery.event.proxy( fn, args[i++] );
+
+ return this.click( jQuery.event.proxy( fn, function(event) {
+ // Figure out which function to execute
+ this.lastToggle = ( this.lastToggle || 0 ) % i;
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ this.lastToggle++ ].apply( this, arguments ) || false;
+ }));
+ },
+
+ hover: function(fnOver, fnOut) {
+ return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
+ },
+
+ ready: function(fn) {
+ // Attach the listeners
+ bindReady();
+
+ // If the DOM is already ready
+ if ( jQuery.isReady )
+ // Execute the function immediately
+ fn.call( document, jQuery );
+
+ // Otherwise, remember the function for later
+ else
+ // Add the function to the wait list
+ jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+
+ return this;
+ }
+});
+
+jQuery.extend({
+ isReady: false,
+ readyList: [],
+ // Handle when the DOM is ready
+ ready: function() {
+ // Make sure that the DOM is not already loaded
+ if ( !jQuery.isReady ) {
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If there are functions bound, to execute
+ if ( jQuery.readyList ) {
+ // Execute all of them
+ jQuery.each( jQuery.readyList, function(){
+ this.call( document );
+ });
+
+ // Reset the list of functions
+ jQuery.readyList = null;
+ }
+
+ // Trigger any bound ready events
+ jQuery(document).triggerHandler("ready");
+ }
+ }
+});
+
+var readyBound = false;
+
+function bindReady(){
+ if ( readyBound ) return;
+ readyBound = true;
+
+ // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
+ if ( document.addEventListener && !jQuery.browser.opera)
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+
+ // If IE is used and is not in a frame
+ // Continually check to see if the document is ready
+ if ( jQuery.browser.msie && window == top ) (function(){
+ if (jQuery.isReady) return;
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch( error ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ })();
+
+ if ( jQuery.browser.opera )
+ document.addEventListener( "DOMContentLoaded", function () {
+ if (jQuery.isReady) return;
+ for (var i = 0; i < document.styleSheets.length; i++)
+ if (document.styleSheets[i].disabled) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ }, false);
+
+ if ( jQuery.browser.safari ) {
+ var numStyles;
+ (function(){
+ if (jQuery.isReady) return;
+ if ( document.readyState != "loaded" && document.readyState != "complete" ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ if ( numStyles === undefined )
+ numStyles = jQuery("style, link[rel=stylesheet]").length;
+ if ( document.styleSheets.length != numStyles ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ })();
+ }
+
+ // A fallback to window.onload, that will always work
+ jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+ "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
+ "submit,keydown,keypress,keyup,error").split(","), function(i, name){
+
+ // Handle event binding
+ jQuery.fn[name] = function(fn){
+ return fn ? this.bind(name, fn) : this.trigger(name);
+ };
+});
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event, elem) {
+ // Check if mouse(over|out) are still within the same parent element
+ var parent = event.relatedTarget;
+ // Traverse up the tree
+ while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
+ // Return true if we actually just moused on to a sub-element
+ return parent == elem;
+};
+
+// Prevent memory leaks in IE
+// And prevent errors on refresh with events like mouseover in other browsers
+// Window isn't included so as not to unbind existing unload events
+jQuery(window).bind("unload", function() {
+ jQuery("*").add(document).unbind();
+});
+jQuery.fn.extend({
+ // Keep a copy of the old load
+ _load: jQuery.fn.load,
+
+ load: function( url, params, callback ) {
+ if ( typeof url != 'string' )
+ return this._load( url );
+
+ var off = url.indexOf(" ");
+ if ( off >= 0 ) {
+ var selector = url.slice(off, url.length);
+ url = url.slice(0, off);
+ }
+
+ callback = callback || function(){};
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params )
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = null;
+
+ // Otherwise, build a param string
+ } else {
+ params = jQuery.param( params );
+ type = "POST";
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ complete: function(res, status){
+ // If successful, inject the HTML into all the matched elements
+ if ( status == "success" || status == "notmodified" )
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div/>")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ res.responseText );
+
+ self.each( callback, [res.responseText, status, res] );
+ }
+ });
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param(this.serializeArray());
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return jQuery.nodeName(this, "form") ?
+ jQuery.makeArray(this.elements) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ (this.checked || /select|textarea/i.test(this.nodeName) ||
+ /text|hidden|password/i.test(this.type));
+ })
+ .map(function(i, elem){
+ var val = jQuery(this).val();
+ return val == null ? null :
+ val.constructor == Array ?
+ jQuery.map( val, function(val, i){
+ return {name: elem.name, value: val};
+ }) :
+ {name: elem.name, value: val};
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+ jQuery.fn[o] = function(f){
+ return this.bind(o, f);
+ };
+});
+
+var jsc = now();
+
+jQuery.extend({
+ get: function( url, data, callback, type ) {
+ // shift arguments if data argument was ommited
+ if ( jQuery.isFunction( data ) ) {
+ callback = data;
+ data = null;
+ }
+
+ return jQuery.ajax({
+ type: "GET",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get(url, null, callback, "script");
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get(url, data, callback, "json");
+ },
+
+ post: function( url, data, callback, type ) {
+ if ( jQuery.isFunction( data ) ) {
+ callback = data;
+ data = {};
+ }
+
+ return jQuery.ajax({
+ type: "POST",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ ajaxSetup: function( settings ) {
+ jQuery.extend( jQuery.ajaxSettings, settings );
+ },
+
+ ajaxSettings: {
+ url: location.href,
+ global: true,
+ type: "GET",
+ timeout: 0,
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ data: null,
+ username: null,
+ password: null,
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ script: "text/javascript, application/javascript",
+ json: "application/json, text/javascript",
+ text: "text/plain",
+ _default: "*/*"
+ }
+ },
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+
+ ajax: function( s ) {
+ // Extend the settings, but re-extend 's' so that it can be
+ // checked again later (in the test suite, specifically)
+ s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+ var jsonp, jsre = /=\?(&|$)/g, status, data,
+ type = s.type.toUpperCase();
+
+ // convert data if not already a string
+ if ( s.data && s.processData && typeof s.data != "string" )
+ s.data = jQuery.param(s.data);
+
+ // Handle JSONP Parameter Callbacks
+ if ( s.dataType == "jsonp" ) {
+ if ( type == "GET" ) {
+ if ( !s.url.match(jsre) )
+ s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+ } else if ( !s.data || !s.data.match(jsre) )
+ s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+ s.dataType = "json";
+ }
+
+ // Build temporary JSONP function
+ if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+ jsonp = "jsonp" + jsc++;
+
+ // Replace the =? sequence both in the query string and the data
+ if ( s.data )
+ s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+ s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+ // We need to make sure
+ // that a JSONP style response is executed properly
+ s.dataType = "script";
+
+ // Handle JSONP-style loading
+ window[ jsonp ] = function(tmp){
+ data = tmp;
+ success();
+ complete();
+ // Garbage collect
+ window[ jsonp ] = undefined;
+ try{ delete window[ jsonp ]; } catch(e){}
+ if ( head )
+ head.removeChild( script );
+ };
+ }
+
+ if ( s.dataType == "script" && s.cache == null )
+ s.cache = false;
+
+ if ( s.cache === false && type == "GET" ) {
+ var ts = now();
+ // try replacing _= if it is there
+ var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+ }
+
+ // If data is available, append data to url for get requests
+ if ( s.data && type == "GET" ) {
+ s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+ // IE likes to send both get and post data, prevent this
+ s.data = null;
+ }
+
+ // Watch for a new set of requests
+ if ( s.global && ! jQuery.active++ )
+ jQuery.event.trigger( "ajaxStart" );
+
+ // Matches an absolute URL, and saves the domain
+ var remote = /^(?:\w+:)?\/\/([^\/?#]+)/;
+
+ // If we're requesting a remote document
+ // and trying to load JSON or Script with a GET
+ if ( s.dataType == "script" && type == "GET"
+ && remote.test(s.url) && remote.exec(s.url)[1] != location.host ){
+ var head = document.getElementsByTagName("head")[0];
+ var script = document.createElement("script");
+ script.src = s.url;
+ if (s.scriptCharset)
+ script.charset = s.scriptCharset;
+
+ // Handle Script loading
+ if ( !jsonp ) {
+ var done = false;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function(){
+ if ( !done && (!this.readyState ||
+ this.readyState == "loaded" || this.readyState == "complete") ) {
+ done = true;
+ success();
+ complete();
+ head.removeChild( script );
+ }
+ };
+ }
+
+ head.appendChild(script);
+
+ // We handle everything using the script element injection
+ return undefined;
+ }
+
+ var requestDone = false;
+
+ // Create the request object; Microsoft failed to properly
+ // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+ var xhr = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if( s.username )
+ xhr.open(type, s.url, s.async, s.username, s.password);
+ else
+ xhr.open(type, s.url, s.async);
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ // Set the correct header, if data is being sent
+ if ( s.data )
+ xhr.setRequestHeader("Content-Type", s.contentType);
+
+ // Set the If-Modified-Since header, if ifModified mode.
+ if ( s.ifModified )
+ xhr.setRequestHeader("If-Modified-Since",
+ jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+ // Set header so the called script knows that it's an XMLHttpRequest
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+ // Set the Accepts header for the server, depending on the dataType
+ xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+ s.accepts[ s.dataType ] + ", */*" :
+ s.accepts._default );
+ } catch(e){}
+
+ // Allow custom headers/mimetypes
+ if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
+ // cleanup active request counter
+ s.global && jQuery.active--;
+ // close opended socket
+ xhr.abort();
+ return false;
+ }
+
+ if ( s.global )
+ jQuery.event.trigger("ajaxSend", [xhr, s]);
+
+ // Wait for a response to come back
+ var onreadystatechange = function(isTimeout){
+ // The transfer is complete and the data is available, or the request timed out
+ if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
+ requestDone = true;
+
+ // clear poll interval
+ if (ival) {
+ clearInterval(ival);
+ ival = null;
+ }
+
+ status = isTimeout == "timeout" && "timeout" ||
+ !jQuery.httpSuccess( xhr ) && "error" ||
+ s.ifModified && jQuery.httpNotModified( xhr, s.url ) && "notmodified" ||
+ "success";
+
+ if ( status == "success" ) {
+ // Watch for, and catch, XML document parse errors
+ try {
+ // process the data (runs the xml through httpData regardless of callback)
+ data = jQuery.httpData( xhr, s.dataType, s.dataFilter );
+ } catch(e) {
+ status = "parsererror";
+ }
+ }
+
+ // Make sure that the request was successful or notmodified
+ if ( status == "success" ) {
+ // Cache Last-Modified header, if ifModified mode.
+ var modRes;
+ try {
+ modRes = xhr.getResponseHeader("Last-Modified");
+ } catch(e) {} // swallow exception thrown by FF if header is not available
+
+ if ( s.ifModified && modRes )
+ jQuery.lastModified[s.url] = modRes;
+
+ // JSONP handles its own success callback
+ if ( !jsonp )
+ success();
+ } else
+ jQuery.handleError(s, xhr, status);
+
+ // Fire the complete handlers
+ complete();
+
+ // Stop memory leaks
+ if ( s.async )
+ xhr = null;
+ }
+ };
+
+ if ( s.async ) {
+ // don't attach the handler to the request, just poll it instead
+ var ival = setInterval(onreadystatechange, 13);
+
+ // Timeout checker
+ if ( s.timeout > 0 )
+ setTimeout(function(){
+ // Check to see if the request is still happening
+ if ( xhr ) {
+ // Cancel the request
+ xhr.abort();
+
+ if( !requestDone )
+ onreadystatechange( "timeout" );
+ }
+ }, s.timeout);
+ }
+
+ // Send the data
+ try {
+ xhr.send(s.data);
+ } catch(e) {
+ jQuery.handleError(s, xhr, null, e);
+ }
+
+ // firefox 1.5 doesn't fire statechange for sync requests
+ if ( !s.async )
+ onreadystatechange();
+
+ function success(){
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success )
+ s.success( data, status );
+
+ // Fire the global callback
+ if ( s.global )
+ jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
+ }
+
+ function complete(){
+ // Process result
+ if ( s.complete )
+ s.complete(xhr, status);
+
+ // The request was completed
+ if ( s.global )
+ jQuery.event.trigger( "ajaxComplete", [xhr, s] );
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+ }
+
+ // return XMLHttpRequest to allow aborting the request etc.
+ return xhr;
+ },
+
+ handleError: function( s, xhr, status, e ) {
+ // If a local callback was specified, fire it
+ if ( s.error ) s.error( xhr, status, e );
+
+ // Fire the global callback
+ if ( s.global )
+ jQuery.event.trigger( "ajaxError", [xhr, s, e] );
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Determines if an XMLHttpRequest was successful or not
+ httpSuccess: function( xhr ) {
+ try {
+ // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+ return !xhr.status && location.protocol == "file:" ||
+ ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223 ||
+ jQuery.browser.safari && xhr.status == undefined;
+ } catch(e){}
+ return false;
+ },
+
+ // Determines if an XMLHttpRequest returns NotModified
+ httpNotModified: function( xhr, url ) {
+ try {
+ var xhrRes = xhr.getResponseHeader("Last-Modified");
+
+ // Firefox always returns 200. check Last-Modified date
+ return xhr.status == 304 || xhrRes == jQuery.lastModified[url] ||
+ jQuery.browser.safari && xhr.status == undefined;
+ } catch(e){}
+ return false;
+ },
+
+ httpData: function( xhr, type, filter ) {
+ var ct = xhr.getResponseHeader("content-type"),
+ xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if ( xml && data.documentElement.tagName == "parsererror" )
+ throw "parsererror";
+
+ // Allow a pre-filtering function to sanitize the response
+ if( filter )
+ data = filter( data, type );
+
+ // If the type is "script", eval it in global context
+ if ( type == "script" )
+ jQuery.globalEval( data );
+
+ // Get the JavaScript object, if JSON is used.
+ if ( type == "json" )
+ data = eval("(" + data + ")");
+
+ return data;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a ) {
+ var s = [];
+
+ // If an array was passed in, assume that it is an array
+ // of form elements
+ if ( a.constructor == Array || a.jquery )
+ // Serialize the form elements
+ jQuery.each( a, function(){
+ s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );
+ });
+
+ // Otherwise, assume that it's an object of key/value pairs
+ else
+ // Serialize the key/values
+ for ( var j in a )
+ // If the value is an array then the key names need to be repeated
+ if ( a[j] && a[j].constructor == Array )
+ jQuery.each( a[j], function(){
+ s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) );
+ });
+ else
+ s.push( encodeURIComponent(j) + "=" + encodeURIComponent( jQuery.isFunction(a[j]) ? a[j]() : a[j] ) );
+
+ // Return the resulting serialization
+ return s.join("&").replace(/%20/g, "+");
+ }
+
+});
+jQuery.fn.extend({
+ show: function(speed,callback){
+ return speed ?
+ this.animate({
+ height: "show", width: "show", opacity: "show"
+ }, speed, callback) :
+
+ this.filter(":hidden").each(function(){
+ this.style.display = this.oldblock || "";
+ if ( jQuery.css(this,"display") == "none" ) {
+ var elem = jQuery("<" + this.tagName + " />").appendTo("body");
+ this.style.display = elem.css("display");
+ // handle an edge condition where css is - div { display:none; } or similar
+ if (this.style.display == "none")
+ this.style.display = "block";
+ elem.remove();
+ }
+ }).end();
+ },
+
+ hide: function(speed,callback){
+ return speed ?
+ this.animate({
+ height: "hide", width: "hide", opacity: "hide"
+ }, speed, callback) :
+
+ this.filter(":visible").each(function(){
+ this.oldblock = this.oldblock || jQuery.css(this,"display");
+ this.style.display = "none";
+ }).end();
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2 ){
+ return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+ this._toggle.apply( this, arguments ) :
+ fn ?
+ this.animate({
+ height: "toggle", width: "toggle", opacity: "toggle"
+ }, fn, fn2) :
+ this.each(function(){
+ jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+ });
+ },
+
+ slideDown: function(speed,callback){
+ return this.animate({height: "show"}, speed, callback);
+ },
+
+ slideUp: function(speed,callback){
+ return this.animate({height: "hide"}, speed, callback);
+ },
+
+ slideToggle: function(speed, callback){
+ return this.animate({height: "toggle"}, speed, callback);
+ },
+
+ fadeIn: function(speed, callback){
+ return this.animate({opacity: "show"}, speed, callback);
+ },
+
+ fadeOut: function(speed, callback){
+ return this.animate({opacity: "hide"}, speed, callback);
+ },
+
+ fadeTo: function(speed,to,callback){
+ return this.animate({opacity: to}, speed, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed(speed, easing, callback);
+
+ return this[ optall.queue === false ? "each" : "queue" ](function(){
+ if ( this.nodeType != 1)
+ return false;
+
+ var opt = jQuery.extend({}, optall), p,
+ hidden = jQuery(this).is(":hidden"), self = this;
+
+ for ( p in prop ) {
+ if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+ return opt.complete.call(this);
+
+ if ( p == "height" || p == "width" ) {
+ // Store display property
+ opt.display = jQuery.css(this, "display");
+
+ // Make sure that nothing sneaks out
+ opt.overflow = this.style.overflow;
+ }
+ }
+
+ if ( opt.overflow != null )
+ this.style.overflow = "hidden";
+
+ opt.curAnim = jQuery.extend({}, prop);
+
+ jQuery.each( prop, function(name, val){
+ var e = new jQuery.fx( self, opt, name );
+
+ if ( /toggle|show|hide/.test(val) )
+ e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+ else {
+ var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
+ start = e.cur(true) || 0;
+
+ if ( parts ) {
+ var end = parseFloat(parts[2]),
+ unit = parts[3] || "px";
+
+ // We need to compute starting value
+ if ( unit != "px" ) {
+ self.style[ name ] = (end || 1) + unit;
+ start = ((end || 1) / e.cur(true)) * start;
+ self.style[ name ] = start + unit;
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] )
+ end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+ e.custom( start, end, unit );
+ } else
+ e.custom( start, val, "" );
+ }
+ });
+
+ // For JS strict compliance
+ return true;
+ });
+ },
+
+ queue: function(type, fn){
+ if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) {
+ fn = type;
+ type = "fx";
+ }
+
+ if ( !type || (typeof type == "string" && !fn) )
+ return queue( this[0], type );
+
+ return this.each(function(){
+ if ( fn.constructor == Array )
+ queue(this, type, fn);
+ else {
+ queue(this, type).push( fn );
+
+ if ( queue(this, type).length == 1 )
+ fn.call(this);
+ }
+ });
+ },
+
+ stop: function(clearQueue, gotoEnd){
+ var timers = jQuery.timers;
+
+ if (clearQueue)
+ this.queue([]);
+
+ this.each(function(){
+ // go in reverse order so anything added to the queue during the loop is ignored
+ for ( var i = timers.length - 1; i >= 0; i-- )
+ if ( timers[i].elem == this ) {
+ if (gotoEnd)
+ // force the next step to be the last
+ timers[i](true);
+ timers.splice(i, 1);
+ }
+ });
+
+ // start the next in the queue if the last step wasn't forced
+ if (!gotoEnd)
+ this.dequeue();
+
+ return this;
+ }
+
+});
+
+var queue = function( elem, type, array ) {
+ if ( elem ){
+
+ type = type || "fx";
+
+ var q = jQuery.data( elem, type + "queue" );
+
+ if ( !q || array )
+ q = jQuery.data( elem, type + "queue", jQuery.makeArray(array) );
+
+ }
+ return q;
+};
+
+jQuery.fn.dequeue = function(type){
+ type = type || "fx";
+
+ return this.each(function(){
+ var q = queue(this, type);
+
+ q.shift();
+
+ if ( q.length )
+ q[0].call( this );
+ });
+};
+
+jQuery.extend({
+
+ speed: function(speed, easing, fn) {
+ var opt = speed && speed.constructor == Object ? speed : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && easing.constructor != Function && easing
+ };
+
+ opt.duration = (opt.duration && opt.duration.constructor == Number ?
+ opt.duration :
+ jQuery.fx.speeds[opt.duration]) || jQuery.fx.speeds.def;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function(){
+ if ( opt.queue !== false )
+ jQuery(this).dequeue();
+ if ( jQuery.isFunction( opt.old ) )
+ opt.old.call( this );
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p, n, firstNum, diff ) {
+ return firstNum + diff * p;
+ },
+ swing: function( p, n, firstNum, diff ) {
+ return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+ }
+ },
+
+ timers: [],
+ timerId: null,
+
+ fx: function( elem, options, prop ){
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ if ( !options.orig )
+ options.orig = {};
+ }
+
+});
+
+jQuery.fx.prototype = {
+
+ // Simple function for setting a style value
+ update: function(){
+ if ( this.options.step )
+ this.options.step.call( this.elem, this.now, this );
+
+ (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+ // Set display property to block for height/width animations
+ if ( this.prop == "height" || this.prop == "width" )
+ this.elem.style.display = "block";
+ },
+
+ // Get the current size
+ cur: function(force){
+ if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null )
+ return this.elem[ this.prop ];
+
+ var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+ return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+ },
+
+ // Start an animation from one number to another
+ custom: function(from, to, unit){
+ this.startTime = now();
+ this.start = from;
+ this.end = to;
+ this.unit = unit || this.unit || "px";
+ this.now = this.start;
+ this.pos = this.state = 0;
+ this.update();
+
+ var self = this;
+ function t(gotoEnd){
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ jQuery.timers.push(t);
+
+ if ( jQuery.timerId == null ) {
+ jQuery.timerId = setInterval(function(){
+ var timers = jQuery.timers;
+
+ for ( var i = 0; i < timers.length; i++ )
+ if ( !timers[i]() )
+ timers.splice(i--, 1);
+
+ if ( !timers.length ) {
+ clearInterval( jQuery.timerId );
+ jQuery.timerId = null;
+ }
+ }, 13);
+ }
+ },
+
+ // Simple 'show' function
+ show: function(){
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ this.custom(0, this.cur());
+
+ // Make sure that we start at a small width/height to avoid any
+ // flash of content
+ if ( this.prop == "width" || this.prop == "height" )
+ this.elem.style[this.prop] = "1px";
+
+ // Start by showing the element
+ jQuery(this.elem).show();
+ },
+
+ // Simple 'hide' function
+ hide: function(){
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom(this.cur(), 0);
+ },
+
+ // Each step of an animation
+ step: function(gotoEnd){
+ var t = now();
+
+ if ( gotoEnd || t > this.options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ this.options.curAnim[ this.prop ] = true;
+
+ var done = true;
+ for ( var i in this.options.curAnim )
+ if ( this.options.curAnim[i] !== true )
+ done = false;
+
+ if ( done ) {
+ if ( this.options.display != null ) {
+ // Reset the overflow
+ this.elem.style.overflow = this.options.overflow;
+
+ // Reset the display
+ this.elem.style.display = this.options.display;
+ if ( jQuery.css(this.elem, "display") == "none" )
+ this.elem.style.display = "block";
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( this.options.hide )
+ this.elem.style.display = "none";
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( this.options.hide || this.options.show )
+ for ( var p in this.options.curAnim )
+ jQuery.attr(this.elem.style, p, this.options.orig[p]);
+ }
+
+ if ( done )
+ // Execute the complete function
+ this.options.complete.call( this.elem );
+
+ return false;
+ } else {
+ var n = t - this.startTime;
+ this.state = n / this.options.duration;
+
+ // Perform the easing function, defaults to swing
+ this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
+ this.now = this.start + ((this.end - this.start) * this.pos);
+
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+
+};
+
+jQuery.extend( jQuery.fx, {
+ speeds:{
+ slow: 600,
+ fast: 200,
+ // Default speed
+ def: 400
+ },
+ step: {
+ scrollLeft: function(fx){
+ fx.elem.scrollLeft = fx.now;
+ },
+
+ scrollTop: function(fx){
+ fx.elem.scrollTop = fx.now;
+ },
+
+ opacity: function(fx){
+ jQuery.attr(fx.elem.style, "opacity", fx.now);
+ },
+
+ _default: function(fx){
+ fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+ }
+ }
+});
+// The Offset Method
+// Originally By Brandon Aaron, part of the Dimension Plugin
+// http://jquery.com/plugins/project/dimensions
+jQuery.fn.offset = function() {
+ var left = 0, top = 0, elem = this[0], results;
+
+ if ( elem ) with ( jQuery.browser ) {
+ var parent = elem.parentNode,
+ offsetChild = elem,
+ offsetParent = elem.offsetParent,
+ doc = elem.ownerDocument,
+ safari2 = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent),
+ css = jQuery.curCSS,
+ fixed = css(elem, "position") == "fixed";
+
+ // Use getBoundingClientRect if available
+ if ( elem.getBoundingClientRect ) {
+ var box = elem.getBoundingClientRect();
+
+ // Add the document scroll offsets
+ add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+ box.top + Math.max(doc.documentElement.scrollTop, doc.body.scrollTop));
+
+ // IE adds the HTML element's border, by default it is medium which is 2px
+ // IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
+ // IE 7 standards mode, the border is always 2px
+ // This border/offset is typically represented by the clientLeft and clientTop properties
+ // However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
+ // Therefore this method will be off by 2px in IE while in quirksmode
+ add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );
+
+ // Otherwise loop through the offsetParents and parentNodes
+ } else {
+
+ // Initial element offsets
+ add( elem.offsetLeft, elem.offsetTop );
+
+ // Get parent offsets
+ while ( offsetParent ) {
+ // Add offsetParent offsets
+ add( offsetParent.offsetLeft, offsetParent.offsetTop );
+
+ // Mozilla and Safari > 2 does not include the border on offset parents
+ // However Mozilla adds the border for table or table cells
+ if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
+ border( offsetParent );
+
+ // Add the document scroll offsets if position is fixed on any offsetParent
+ if ( !fixed && css(offsetParent, "position") == "fixed" )
+ fixed = true;
+
+ // Set offsetChild to previous offsetParent unless it is the body element
+ offsetChild = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
+ // Get next offsetParent
+ offsetParent = offsetParent.offsetParent;
+ }
+
+ // Get parent scroll offsets
+ while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
+ // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
+ if ( !/^inline|table.*$/i.test(css(parent, "display")) )
+ // Subtract parent scroll offsets
+ add( -parent.scrollLeft, -parent.scrollTop );
+
+ // Mozilla does not add the border for a parent that has overflow != visible
+ if ( mozilla && css(parent, "overflow") != "visible" )
+ border( parent );
+
+ // Get next parent
+ parent = parent.parentNode;
+ }
+
+ // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
+ // Mozilla doubles body offsets with a non-absolutely positioned offsetChild
+ if ( (safari2 && (fixed || css(offsetChild, "position") == "absolute")) ||
+ (mozilla && css(offsetChild, "position") != "absolute") )
+ add( -doc.body.offsetLeft, -doc.body.offsetTop );
+
+ // Add the document scroll offsets if position is fixed
+ if ( fixed )
+ add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+ Math.max(doc.documentElement.scrollTop, doc.body.scrollTop));
+ }
+
+ // Return an object with top and left properties
+ results = { top: top, left: left };
+ }
+
+ function border(elem) {
+ add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) );
+ }
+
+ function add(l, t) {
+ left += parseInt(l, 10) || 0;
+ top += parseInt(t, 10) || 0;
+ }
+
+ return results;
+};
+
+
+jQuery.fn.extend({
+ position: function() {
+ var left = 0, top = 0, results;
+
+ if ( this[0] ) {
+ // Get *real* offsetParent
+ var offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= num( this, 'marginTop' );
+ offset.left -= num( this, 'marginLeft' );
+
+ // Add offsetParent borders
+ parentOffset.top += num( offsetParent, 'borderTopWidth' );
+ parentOffset.left += num( offsetParent, 'borderLeftWidth' );
+
+ // Subtract the two offsets
+ results = {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ }
+
+ return results;
+ },
+
+ offsetParent: function() {
+ var offsetParent = this[0].offsetParent;
+ while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
+ offsetParent = offsetParent.offsetParent;
+ return jQuery(offsetParent);
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ['Left', 'Top'], function(i, name) {
+ var method = 'scroll' + name;
+
+ jQuery.fn[ method ] = function(val) {
+ if (!this[0]) return;
+
+ return val != undefined ?
+
+ // Set the scroll offset
+ this.each(function() {
+ this == window || this == document ?
+ window.scrollTo(
+ !i ? val : jQuery(window).scrollLeft(),
+ i ? val : jQuery(window).scrollTop()
+ ) :
+ this[ method ] = val;
+ }) :
+
+ // Return the scroll offset
+ this[0] == window || this[0] == document ?
+ self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
+ jQuery.boxModel && document.documentElement[ method ] ||
+ document.body[ method ] :
+ this[0][ method ];
+ };
+});
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function(i, name){
+
+ var tl = i ? "Left" : "Top", // top or left
+ br = i ? "Right" : "Bottom"; // bottom or right
+
+ // innerHeight and innerWidth
+ jQuery.fn["inner" + name] = function(){
+ return this[ name.toLowerCase() ]() +
+ num(this, "padding" + tl) +
+ num(this, "padding" + br);
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn["outer" + name] = function(margin) {
+ return this["inner" + name]() +
+ num(this, "border" + tl + "Width") +
+ num(this, "border" + br + "Width") +
+ (margin ?
+ num(this, "margin" + tl) + num(this, "margin" + br) : 0);
+ };
+
+});})();
diff --git a/templates/content/js/jquery-1.2.6.min.js b/templates/content/js/jquery-1.2.6.min.js
new file mode 100644
index 00000000..82b98e1d
--- /dev/null
+++ b/templates/content/js/jquery-1.2.6.min.js
@@ -0,0 +1,32 @@
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else
+return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else
+return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else
+selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++){var option=options[i];if(option.selected){value=jQuery.browser.msie&&!option.attributes.value.specified?option.text:option.value;if(one)return value;values.push(value);}}return values;}else
+return(this[0].value||"").replace(/\r/g,"");}return undefined;}if(value.constructor==Number)value+='';return this.each(function(){if(this.nodeType!=1)return;if(value.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,value)>=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else
+this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else
+return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else
+jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments[i])!=null)for(var name in options){var src=target[name],copy=options[name];if(target===copy)continue;if(deep&&copy&&typeof copy=="object"&&!copy.nodeType)target[name]=jQuery.extend(deep,src||(copy.length!=null?[]:{}),copy);else if(copy!==undefined)target[name]=copy;}return target;};var expando="jQuery"+now(),uuid=0,windowData={},exclude=/z-?index|font-?weight|opacity|zoom|line-?height/i,defaultView=document.defaultView||{};jQuery.extend({noConflict:function(deep){window.$=_$;if(deep)window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/^[\s[]?function/.test(fn+"");},isXMLDoc:function(elem){return elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim(data);if(data){var head=document.getElementsByTagName("head")[0]||document.documentElement,script=document.createElement("script");script.type="text/javascript";if(jQuery.browser.msie)script.text=data;else
+script.appendChild(document.createTextNode(data));head.insertBefore(script,head.firstChild);head.removeChild(script);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData:function(elem,name){elem=elem==window?windowData:elem;var id=elem[expando];if(name){if(jQuery.cache[id]){delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])break;if(!name)jQuery.removeData(elem);}}else{try{delete elem[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute(expando);}delete jQuery.cache[id];}},each:function(object,callback,args){var name,i=0,length=object.length;if(args){if(length==undefined){for(name in object)if(callback.apply(object[name],args)===false)break;}else
+for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else{if(length==undefined){for(name in object)if(callback.call(object[name],name,object[name])===false)break;}else
+for(var value=object[0];i<length&&callback.call(value,i,value)!==false;value=object[++i]){}}return object;},prop:function(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(name)?value+"px":value;},className:{add:function(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function(i,className){if(elem.nodeType==1&&!jQuery.className.has(elem.className,className))elem.className+=(elem.className?" ":"")+className;});},remove:function(elem,classNames){if(elem.nodeType==1)elem.className=classNames!=undefined?jQuery.grep(elem.className.split(/\s+/),function(className){return!jQuery.className.has(classNames,className);}).join(" "):"";},has:function(elem,className){return jQuery.inArray(className,(elem.className||elem).toString().split(/\s+/))>-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else
+jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i<stack.length;i++)if(color(stack[i])){swap[i]=stack[i].style.display;stack[i].style.display="block";}ret=name=="display"&&swap[stack.length-1]!=null?"none":(computedStyle&&computedStyle.getPropertyValue(name))||"";for(i=0;i<swap.length;i++)if(swap[i]!=null)stack[i].style.display=swap[i];}if(name=="opacity"&&ret=="")ret="1";}else if(elem.currentStyle){var camelCase=name.replace(/\-(\w)/g,function(all,letter){return letter.toUpperCase();});ret=elem.currentStyle[name]||elem.currentStyle[camelCase];if(!/^\d+(px)?$/i.test(ret)&&/^\d/.test(ret)){var left=style.left,rsLeft=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;style.left=ret||0;ret=style.pixelLeft+"px";style.left=left;elem.runtimeStyle.left=rsLeft;}}return ret;},clean:function(elems,context){var ret=[];context=context||document;if(typeof context.createElement=='undefined')context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;jQuery.each(elems,function(i,elem){if(!elem)return;if(elem.constructor==Number)elem+='';if(typeof elem=="string"){elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+"></"+tag+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!tags.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!tags.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!tags.indexOf("<td")||!tags.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!tags.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?div.firstChild&&div.firstChild.childNodes:wrap[1]=="<table>"&&tags.indexOf("<tbody")<0?div.childNodes:[];for(var j=tbody.length-1;j>=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else
+ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&&notxml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&&notxml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&&notxml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else
+while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)return i;return-1;},merge:function(first,second){var i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i++])if(elem.nodeType!=8)first[pos++]=elem;}else
+while(elem=second[i++])first[pos++]=elem;return first;},unique:function(array){var ret=[],done={};try{for(var i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}return ret;},grep:function(elems,callback,inv){var ret=[];for(var i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=[];for(var i=0,length=elems.length;i<length;i++){var value=callback(elems[i],i);if(value!=null)ret[ret.length]=value;}return ret.concat.apply([],ret);}});var userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)};var styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend({boxModel:!jQuery.browser.msie||document.compatMode=="CSS1Compat",props:{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing"}});jQuery.each({parent:function(elem){return elem.parentNode;},parents:function(elem){return jQuery.dir(elem,"parentNode");},next:function(elem){return jQuery.nth(elem,2,"nextSibling");},prev:function(elem){return jQuery.nth(elem,2,"previousSibling");},nextAll:function(elem){return jQuery.dir(elem,"nextSibling");},prevAll:function(elem){return jQuery.dir(elem,"previousSibling");},siblings:function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},children:function(elem){return jQuery.sibling(elem.firstChild);},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}},function(name,fn){jQuery.fn[name]=function(selector){var ret=jQuery.map(this,fn);if(selector&&typeof selector=="string")ret=jQuery.multiFilter(selector,ret);return this.pushStack(jQuery.unique(ret));};});jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(name,original){jQuery.fn[name]=function(){var args=arguments;return this.each(function(){for(var i=0,length=args.length;i<length;i++)jQuery(args[i])[original](this);});};});jQuery.each({removeAttr:function(name){jQuery.attr(this,name,"");if(this.nodeType==1)this.removeAttribute(name);},addClass:function(classNames){jQuery.className.add(this,classNames);},removeClass:function(classNames){jQuery.className.remove(this,classNames);},toggleClass:function(classNames){jQuery.className[jQuery.className.has(this,classNames)?"remove":"add"](this,classNames);},remove:function(selector){if(!selector||jQuery.filter(selector,[this]).r.length){jQuery("*",this).add(this).each(function(){jQuery.event.remove(this);jQuery.removeData(this);});if(this.parentNode)this.parentNode.removeChild(this);}},empty:function(){jQuery(">*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return i<m[3]-0;},gt:function(a,i,m){return i>m[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j<rl;j++){var n=m=="~"||m=="+"?ret[j].nextSibling:ret[j].firstChild;for(;n;n=n.nextSibling)if(n.nodeType==1){var id=jQuery.data(n);if(m=="~"&&merge[id])break;if(!nodeName||n.nodeName.toUpperCase()==nodeName){if(m=="~")merge[id]=true;r.push(n);}if(m=="+")break;}}ret=r;t=jQuery.trim(t.replace(re,""));foundToken=true;}}if(t&&!foundToken){if(!t.indexOf(",")){if(context==ret[0])ret.shift();done=jQuery.merge(done,ret);r=ret=[context];t=" "+t.substr(1,t.length);}else{var re2=quickID;var m=re2.exec(t);if(m){m=[0,m[2],m[3],m[1]];}else{re2=quickClass;m=re2.exec(t);}m[2]=m[2].replace(/\\/g,"");var elem=ret[ret.length-1];if(m[1]=="#"&&elem&&elem.getElementById&&!jQuery.isXMLDoc(elem)){var oid=elem.getElementById(m[2]);if((jQuery.browser.msie||jQuery.browser.opera)&&oid&&typeof oid.id=="string"&&oid.id!=m[2])oid=jQuery('[@id="'+m[2]+'"]',elem)[0];ret=r=oid&&(!m[3]||jQuery.nodeName(oid,m[3]))?[oid]:[];}else{for(var i=0;ret[i];i++){var tag=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];if(tag=="*"&&ret[i].nodeName.toLowerCase()=="object")tag="param";r=jQuery.merge(r,ret[i].getElementsByTagName(tag));}if(m[1]==".")r=jQuery.classFilter(r,m[2]);if(m[1]=="#"){var tmp=[];for(var i=0;r[i];i++)if(r[i].getAttribute("id")==m[2]){tmp=[r[i]];break;}r=tmp;}ret=r;}t=t.replace(re2,"");}}if(t){var val=jQuery.filter(t,r);ret=r=val.r;t=jQuery.trim(val.t);}}if(t)ret=[];if(ret&&context==ret[0])ret.shift();done=jQuery.merge(done,ret);return done;},classFilter:function(r,m,not){m=" "+m+" ";var tmp=[];for(var i=0;r[i];i++){var pass=(" "+r[i].className+" ").indexOf(m)>=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i<rl;i++){var a=r[i],z=a[jQuery.props[m[2]]||m[2]];if(z==null||/href|src|selected/.test(m[2]))z=jQuery.attr(a,m[2])||'';if((type==""&&!!z||type=="="&&z==m[5]||type=="!="&&z!=m[5]||type=="^="&&z&&!z.indexOf(m[5])||type=="$="&&z.substr(z.length-m[5].length)==m[5]||(type=="*="||type=="~=")&&z.indexOf(m[5])>=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i<rl;i++){var node=r[i],parentNode=node.parentNode,id=jQuery.data(parentNode);if(!merge[id]){var c=1;for(var n=parentNode.firstChild;n;n=n.nextSibling)if(n.nodeType==1)n.nodeIndex=c++;merge[id]=true;}var add=false;if(first==0){if(node.nodeIndex==last)add=true;}else if((node.nodeIndex-last)%first==0&&(node.nodeIndex-last)/first>=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else
+for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i<args.length)jQuery.event.proxy(fn,args[i++]);return this.click(jQuery.event.proxy(fn,function(event){this.lastToggle=(this.lastToggle||0)%i;event.preventDefault();return args[this.lastToggle++].apply(this,arguments)||false;}));},hover:function(fnOver,fnOut){return this.bind('mouseenter',fnOver).bind('mouseleave',fnOut);},ready:function(fn){bindReady();if(jQuery.isReady)fn.call(document,jQuery);else
+jQuery.readyList.push(function(){return fn.call(this,jQuery);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.call(document);});jQuery.readyList=null;}jQuery(document).triggerHandler("ready");}}});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(document.addEventListener&&!jQuery.browser.opera)document.addEventListener("DOMContentLoaded",jQuery.ready,false);if(jQuery.browser.msie&&window==top)(function(){if(jQuery.isReady)return;try{document.documentElement.doScroll("left");}catch(error){setTimeout(arguments.callee,0);return;}jQuery.ready();})();if(jQuery.browser.opera)document.addEventListener("DOMContentLoaded",function(){if(jQuery.isReady)return;for(var i=0;i<document.styleSheets.length;i++)if(document.styleSheets[i].disabled){setTimeout(arguments.callee,0);return;}jQuery.ready();},false);if(jQuery.browser.safari){var numStyles;(function(){if(jQuery.isReady)return;if(document.readyState!="loaded"&&document.readyState!="complete"){setTimeout(arguments.callee,0);return;}if(numStyles===undefined)numStyles=jQuery("style, link[rel=stylesheet]").length;if(document.styleSheets.length!=numStyles){setTimeout(arguments.callee,0);return;}jQuery.ready();})();}jQuery.event.add(window,"load",jQuery.ready);}jQuery.each(("blur,focus,load,resize,scroll,unload,click,dblclick,"+"mousedown,mouseup,mousemove,mouseover,mouseout,change,select,"+"submit,keydown,keypress,keyup,error").split(","),function(i,name){jQuery.fn[name]=function(fn){return fn?this.bind(name,fn):this.trigger(name);};});var withinElement=function(event,elem){var parent=event.relatedTarget;while(parent&&parent!=elem)try{parent=parent.parentNode;}catch(error){parent=elem;}return parent==elem;};jQuery(window).bind("unload",function(){jQuery("*").add(document).unbind();});jQuery.fn.extend({_load:jQuery.fn.load,load:function(url,params,callback){if(typeof url!='string')return this._load(url);var off=url.indexOf(" ");if(off>=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("<div/>").append(res.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else
+xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else
+jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else
+for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else
+s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else
+e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;i<timers.length;i++)if(!timers[i]())timers.splice(i--,1);if(!timers.length){clearInterval(jQuery.timerId);jQuery.timerId=null;}},13);}},show:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.show=true;this.custom(0,this.cur());if(this.prop=="width"||this.prop=="height")this.elem.style[this.prop]="1px";jQuery(this.elem).show();},hide:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0);},step:function(gotoEnd){var t=now();if(gotoEnd||t>this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file
diff --git a/templates/content/js/jquery.ajaxfileupload.js b/templates/content/js/jquery.ajaxfileupload.js
new file mode 100644
index 00000000..106de906
--- /dev/null
+++ b/templates/content/js/jquery.ajaxfileupload.js
@@ -0,0 +1,195 @@
+jQuery.extend({
+ createUploadIframe: function(id, uri){
+ //create frame
+ var frameId = 'jUploadFrame' + id;
+ if(window.ActiveXObject) {
+ var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
+ if(typeof uri== 'boolean'){
+ io.src = 'javascript:false';
+ }
+ else if(typeof uri== 'string'){
+ io.src = uri;
+ }
+ }
+ else {
+ var io = document.createElement('iframe');
+ io.id = frameId;
+ io.name = frameId;
+ }
+ io.style.position = 'absolute';
+ io.style.top = '-1000px';
+ io.style.left = '-1000px';
+
+ document.body.appendChild(io);
+ return io;
+ },
+ createUploadForm: function(id, fileElementId)
+ {
+ //create form
+ var formId = 'jUploadForm' + id;
+ var fileId = 'jUploadFile' + id;
+ var form = $('<form action="" method="POST" name="' + formId + '" id="' + formId
+ + '" enctype="multipart/form-data"></form>');
+ var oldElement = $('#' + fileElementId);
+ var newElement = $(oldElement).clone();
+ $(oldElement).attr('id', fileId);
+ $(oldElement).before(newElement);
+ $(oldElement).appendTo(form);
+ //set attributes
+ $(form).css('position', 'absolute');
+ $(form).css('top', '-1200px');
+ $(form).css('left', '-1200px');
+ $(form).appendTo('body');
+ return form;
+ },
+
+ ajaxFileUpload: function(s) {
+ // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
+ s = jQuery.extend({}, jQuery.ajaxSettings, s);
+ var id = new Date().getTime()
+ var form = jQuery.createUploadForm(id, s.fileElementId);
+ var io = jQuery.createUploadIframe(id, s.secureuri);
+ var frameId = 'jUploadFrame' + id;
+ var formId = 'jUploadForm' + id;
+ // Watch for a new set of requests
+ if ( s.global && ! jQuery.active++ )
+ {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+ var requestDone = false;
+ // Create the request object
+ var xml = {}
+ if ( s.global )
+ jQuery.event.trigger("ajaxSend", [xml, s]);
+ // Wait for a response to come back
+ var uploadCallback = function(isTimeout)
+ {
+ var io = document.getElementById(frameId);
+ try {
+ if(io.contentWindow){
+ xml.responseText = io.contentWindow.document.body ?
+ io.contentWindow.document.body.innerText : null;
+ xml.responseXML = io.contentWindow.document.XMLDocument ?
+ io.contentWindow.document.XMLDocument : io.contentWindow.document;
+
+ }
+ else if(io.contentDocument)
+ {
+ xml.responseText = io.contentDocument.document.body ?
+ io.contentDocument.document.body.textContent || document.body.innerText : null;
+ xml.responseXML = io.contentDocument.document.XMLDocument ?
+ io.contentDocument.document.XMLDocument : io.contentDocument.document;
+ }
+ }
+ catch(e)
+ {
+ jQuery.handleError(s, xml, null, e);
+ }
+ if ( xml || isTimeout == "timeout")
+ {
+ requestDone = true;
+ var status;
+ try {
+ status = isTimeout != "timeout" ? "success" : "error";
+ // Make sure that the request was successful or notmodified
+ if ( status != "error" )
+ {
+ // process the data (runs the xml through httpData regardless of callback)
+ var data = jQuery.uploadHttpData( xml, s.dataType );
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success )
+ s.success( data, status );
+
+ // Fire the global callback
+ if( s.global )
+ jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+ } else
+ jQuery.handleError(s, xml, status);
+ } catch(e)
+ {
+ status = "error";
+ jQuery.handleError(s, xml, status, e);
+ }
+
+ // The request was completed
+ if( s.global )
+ jQuery.event.trigger( "ajaxComplete", [xml, s] );
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+
+ // Process result
+ if ( s.complete )
+ s.complete(xml, status);
+
+ jQuery(io).unbind();
+
+ setTimeout(function()
+ { try
+ {
+ $(io).remove();
+ $(form).remove();
+
+ } catch(e) {
+ jQuery.handleError(s, xml, null, e);
+ }
+ }, 100)
+ xml = null;
+ }
+ }
+ // Timeout checker
+ if ( s.timeout > 0 ) {
+ setTimeout(function(){
+ // Check to see if the request is still happening
+ if( !requestDone ) uploadCallback( "timeout" );
+ }, s.timeout);
+ }
+ try
+ {
+ // var io = $('#' + frameId);
+ var form = $('#' + formId);
+ $(form).attr('action', s.url);
+ $(form).attr('method', 'POST');
+ $(form).attr('target', frameId);
+ if(form.encoding)
+ {
+ form.encoding = 'multipart/form-data';
+ }
+ else
+ {
+ form.enctype = 'multipart/form-data';
+ }
+ $(form).submit();
+
+ } catch(e)
+ {
+ jQuery.handleError(s, xml, null, e);
+ }
+ if(window.attachEvent){
+ document.getElementById(frameId).attachEvent('onload', uploadCallback);
+ }
+ else{
+ document.getElementById(frameId).addEventListener('load', uploadCallback, false);
+ }
+ return {abort: function () {}};
+
+ },
+
+ uploadHttpData: function( r, type ) {
+ var data = !type;
+ data = type == "xml" || data ? r.responseXML : r.responseText;
+ // If the type is "script", eval it in global context
+ if ( type == "script" )
+ jQuery.globalEval( data );
+ // Get the JavaScript object, if JSON is used.
+ if ( type == "json" )
+ eval( "data = " + data );
+ // evaluate scripts within html
+ if ( type == "html" )
+ jQuery("<div>").html(data).evalScripts();
+ //alert($('param', data).each(function(){alert($(this).attr('value'));}));
+ return data;
+ }
+})
+
diff --git a/templates/content/js/jquery.flot.js b/templates/content/js/jquery.flot.js
new file mode 100644
index 00000000..83b61929
--- /dev/null
+++ b/templates/content/js/jquery.flot.js
@@ -0,0 +1,2421 @@
+/* Javascript plotting library for jQuery, v. 0.5.
+ *
+ * Released under the MIT license by IOLA, December 2007.
+ *
+ */
+
+(function($) {
+ function Plot(target_, data_, options_) {
+ // data is on the form:
+ // [ series1, series2 ... ]
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label" }
+
+ var series = [],
+ options = {
+ // the color theme used for graphs
+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
+ legend: {
+ show: true,
+ noColumns: 1, // number of colums in legend table
+ labelFormatter: null, // fn: string -> string
+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
+ position: "ne", // position of default legend container within plot
+ margin: 5, // distance from grid edge to default legend container within plot
+ backgroundColor: null, // null means auto-detect
+ backgroundOpacity: 0.85 // set to 0 to avoid background
+ },
+ xaxis: {
+ mode: null, // null or "time"
+ min: null, // min. value to show, null means set automatically
+ max: null, // max. value to show, null means set automatically
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
+ tickFormatter: null, // fn: number -> string
+ labelWidth: null, // size of tick labels in pixels
+ labelHeight: null,
+
+ // mode specific options
+ tickDecimals: null, // no. of decimals, null means auto
+ tickSize: null, // number or [number, "unit"]
+ minTickSize: null, // number or [number, "unit"]
+ monthNames: null, // list of names of months
+ timeformat: null // format string to use
+ },
+ yaxis: {
+ autoscaleMargin: 0.02
+ },
+ x2axis: {
+ autoscaleMargin: null
+ },
+ y2axis: {
+ autoscaleMargin: 0.02
+ },
+ points: {
+ show: false,
+ radius: 3,
+ lineWidth: 2, // in pixels
+ fill: true,
+ fillColor: "#ffffff"
+ },
+ lines: {
+ // we don't put in show: false so we can see
+ // whether lines were actively disabled
+ lineWidth: 2, // in pixels
+ fill: false,
+ fillColor: null,
+ steps: false
+ },
+ bars: {
+ show: false,
+ lineWidth: 2, // in pixels
+ barWidth: 1, // in units of the x axis
+ fill: true,
+ fillColor: null,
+ align: "left", // or "center"
+ horizontal: false // when horizontal, left is now top
+ },
+ threshold: null, // or { below: number, color: color spec}
+ grid: {
+ color: "#545454", // primary color used for outline and labels
+ backgroundColor: null, // null for transparent, else color
+ tickColor: "#dddddd", // color used for the ticks
+ labelMargin: 5, // in pixels
+ borderWidth: 2, // in pixels
+ borderColor: null, // set if different from the grid color
+ markings: null, // array of ranges or fn: axes -> array of ranges
+ markingsColor: "#f4f4f4",
+ markingsLineWidth: 2,
+ // interactive stuff
+ clickable: false,
+ hoverable: false,
+ autoHighlight: true, // highlight in case mouse is near
+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
+ },
+ selection: {
+ mode: null, // one of null, "x", "y" or "xy"
+ color: "#e8cfac"
+ },
+ crosshair: {
+ mode: null, // one of null, "x", "y" or "xy",
+ color: "#aa0000"
+ },
+ shadowSize: 3
+ },
+ canvas = null, // the canvas for the plot itself
+ overlay = null, // canvas for interactive stuff on top of plot
+ eventHolder = null, // jQuery object that events should be bound to
+ ctx = null, octx = null,
+ target = $(target_),
+ axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} },
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
+ canvasWidth = 0, canvasHeight = 0,
+ plotWidth = 0, plotHeight = 0,
+ // dedicated to storing data for buggy standard compliance cases
+ workarounds = {};
+
+ this.setData = setData;
+ this.setupGrid = setupGrid;
+ this.draw = draw;
+ this.clearSelection = clearSelection;
+ this.setSelection = setSelection;
+ this.getCanvas = function() { return canvas; };
+ this.getPlotOffset = function() { return plotOffset; };
+ this.getData = function() { return series; };
+ this.getAxes = function() { return axes; };
+ this.setCrosshair = setCrosshair;
+ this.clearCrosshair = function () { setCrosshair(null); };
+ this.highlight = highlight;
+ this.unhighlight = unhighlight;
+
+ // initialize
+ parseOptions(options_);
+ setData(data_);
+ constructCanvas();
+ setupGrid();
+ draw();
+
+
+ function setData(d) {
+ series = parseData(d);
+
+ fillInSeriesOptions();
+ processData();
+ }
+
+ function parseData(d) {
+ var res = [];
+ for (var i = 0; i < d.length; ++i) {
+ var s;
+ if (d[i].data) {
+ s = {};
+ for (var v in d[i])
+ s[v] = d[i][v];
+ }
+ else {
+ s = { data: d[i] };
+ }
+ res.push(s);
+ }
+
+ return res;
+ }
+
+ function parseOptions(o) {
+ $.extend(true, options, o);
+ if (options.grid.borderColor == null)
+ options.grid.borderColor = options.grid.color
+ // backwards compatibility, to be removed in future
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
+ options.xaxis.ticks = options.xaxis.noTicks;
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
+ options.yaxis.ticks = options.yaxis.noTicks;
+ if (options.grid.coloredAreas)
+ options.grid.markings = options.grid.coloredAreas;
+ if (options.grid.coloredAreasColor)
+ options.grid.markingsColor = options.grid.coloredAreasColor;
+ }
+
+ function fillInSeriesOptions() {
+ var i;
+
+ // collect what we already got of colors
+ var neededColors = series.length,
+ usedColors = [],
+ assignedColors = [];
+ for (i = 0; i < series.length; ++i) {
+ var sc = series[i].color;
+ if (sc != null) {
+ --neededColors;
+ if (typeof sc == "number")
+ assignedColors.push(sc);
+ else
+ usedColors.push(parseColor(series[i].color));
+ }
+ }
+
+ // we might need to generate more colors if higher indices
+ // are assigned
+ for (i = 0; i < assignedColors.length; ++i) {
+ neededColors = Math.max(neededColors, assignedColors[i] + 1);
+ }
+
+ // produce colors as needed
+ var colors = [], variation = 0;
+ i = 0;
+ while (colors.length < neededColors) {
+ var c;
+ if (options.colors.length == i) // check degenerate case
+ c = new Color(100, 100, 100);
+ else
+ c = parseColor(options.colors[i]);
+
+ // vary color if needed
+ var sign = variation % 2 == 1 ? -1 : 1;
+ var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
+ c.scale(factor, factor, factor);
+
+ // FIXME: if we're getting to close to something else,
+ // we should probably skip this one
+ colors.push(c);
+
+ ++i;
+ if (i >= options.colors.length) {
+ i = 0;
+ ++variation;
+ }
+ }
+
+ // fill in the options
+ var colori = 0, s;
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ // assign colors
+ if (s.color == null) {
+ s.color = colors[colori].toString();
+ ++colori;
+ }
+ else if (typeof s.color == "number")
+ s.color = colors[s.color].toString();
+
+ // copy the rest
+ s.lines = $.extend(true, {}, options.lines, s.lines);
+ s.points = $.extend(true, {}, options.points, s.points);
+ s.bars = $.extend(true, {}, options.bars, s.bars);
+
+ // turn on lines automatically in case nothing is set
+ if (s.lines.show == null && !s.bars.show && !s.points.show)
+ s.lines.show = true;
+ if (s.shadowSize == null)
+ s.shadowSize = options.shadowSize;
+
+ if (!s.xaxis)
+ s.xaxis = axes.xaxis;
+
+ if (s.xaxis == 1)
+ s.xaxis = axes.xaxis;
+ else if (s.xaxis == 2)
+ s.xaxis = axes.x2axis;
+
+ if (!s.yaxis)
+ s.yaxis = axes.yaxis;
+
+ if (s.yaxis == 1)
+ s.yaxis = axes.yaxis;
+ else if (s.yaxis == 2)
+ s.yaxis = axes.y2axis;
+
+ if (!s.threshold)
+ s.threshold = options.threshold;
+ s.subseries = null;
+ }
+ }
+
+ function processData() {
+ var topSentry = Number.POSITIVE_INFINITY,
+ bottomSentry = Number.NEGATIVE_INFINITY,
+ axis, i, j, k, m, s;
+
+ for (axis in axes) {
+ axes[axis].datamin = topSentry;
+ axes[axis].datamax = bottomSentry;
+ axes[axis].min = options[axis].min;
+ axes[axis].max = options[axis].max;
+ axes[axis].used = false;
+ }
+
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ s.datapoints = { points: [], incr: 2 };
+
+ var data = s.data,
+ points = s.datapoints.points,
+ axisx = s.xaxis, axisy = s.yaxis,
+ xmin = topSentry, xmax = bottomSentry,
+ ymin = topSentry, ymax = bottomSentry,
+ x, y, p, incr, format = [];
+
+ // determine the increment
+ if (s.bars.show) {
+ s.datapoints.incr = 3;
+ format.push({ d: 0 });
+ }
+
+ /*
+ // examine data to find out how to copy
+ for (j = 0; j < data.length; ++j) {
+ }*/
+
+
+ axisx.used = axisy.used = true;
+ incr = s.datapoints.incr;
+
+ for (j = k = 0; j < data.length; ++j, k += incr) {
+ p = data[j];
+ x = null;
+ y = null;
+
+ if (data[j] != null) {
+ x = p[0];
+ y = p[1];
+ }
+
+ // convert to number
+ if (x != null && !isNaN(x = +x)) {
+ if (x < xmin)
+ xmin = x;
+ if (x > xmax)
+ xmax = x
+ }
+ else
+ x = null;
+
+ if (y != null && !isNaN(y = +y)) {
+ if (y < ymin)
+ ymin = y;
+ if (y > ymax)
+ ymax = y;
+ }
+ else
+ y = null;
+
+ if (x == null || y == null)
+ x = y = null; // make sure everything is cleared
+
+ for (m = 2; m < incr; ++m)
+ points[k + m] = p[m] == null ? format[m-2].d : p[m];
+
+ points[k + 1] = y;
+ points[k] = x;
+ }
+
+ if (s.bars.show) {
+ // make sure we got room for the bar on the dancing floor
+ var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
+ if(s.bars.horizontal) {
+ ymin += delta;
+ ymax += delta + s.bars.barWidth;
+ }
+ else {
+ xmin += delta;
+ xmax += delta + s.bars.barWidth;
+ }
+ }
+
+ axisx.datamin = Math.min(axisx.datamin, xmin);
+ axisx.datamax = Math.max(axisx.datamax, xmax);
+ axisy.datamin = Math.min(axisy.datamin, ymin);
+ axisy.datamax = Math.max(axisy.datamax, ymax);
+
+
+ // step charts
+ if (s.lines.show && s.lines.steps) {
+ p = [];
+ // copy, inserting extra points to make steps
+ for (j = k = 0; j < points.length; j += incr, k += incr) {
+ x = points[j];
+ y = points[j + 1];
+ if (j > 0
+ && points[j - incr] != null
+ && x != null
+ && points[j - incr + 1] != y) {
+ p[k] = x;
+ p[k + 1] = points[j - incr + 1];
+ k += incr;
+ }
+
+ p[k] = x;
+ p[k + 1] = y;
+ }
+ s.datapoints.linespoints = p;
+ }
+
+ // possibly split data points because of threshold
+ if (s.threshold) {
+ var orig = $.extend({}, s), thresholded = $.extend({}, s);
+ orig.datapoints = { points: [], incr: incr };
+ thresholded.datapoints = { points: [], incr: incr };
+
+ thresholded.color = s.threshold.color;
+
+ var below = s.threshold.below,
+ origpoints = orig.datapoints.points,
+ threspoints = thresholded.datapoints.points;
+
+ // ordinary points
+ for (j = 0; j < points.length; j += incr) {
+ x = points[j];
+ y = points[j + 1];
+
+ if (y < below)
+ p = threspoints;
+ else
+ p = origpoints;
+
+ p.push(x);
+ p.push(y);
+ }
+
+ // possibly split lines
+ if (s.lines.show) {
+ var lp = s.datapoints.linespoints || points;
+
+ origpoints = [];
+ threspoints = [];
+ p = origpoints;
+
+ for (j = 0; j < lp.length; j += incr) {
+ x = lp[j];
+ y = lp[j + 1];
+
+ var prevp = p;
+ if (y != null) {
+ if (y < below)
+ p = threspoints;
+ else
+ p = origpoints;
+ }
+
+ if (p != prevp && x != null && j > 0 && lp[j - incr] != null) {
+ // find intersection and add it to both
+ k = (x - lp[j - incr]) / (y - lp[j - incr + 1]) * (below - y) + x;
+ prevp.push(k);
+ prevp.push(below);
+ p.push(null); // start new segment
+ p.push(null);
+ p.push(k);
+ p.push(below);
+ }
+
+ p.push(x);
+ p.push(y);
+ }
+
+ orig.datapoints.linespoints = origpoints
+ thresholded.datapoints.linespoints = threspoints;
+ }
+
+ s.subseries = [orig, thresholded];
+ }
+ }
+ }
+
+ function constructCanvas() {
+ function makeCanvas(width, height) {
+ var c = document.createElement('canvas');
+ c.width = width;
+ c.height = height;
+ if ($.browser.msie) // excanvas hack
+ c = window.G_vmlCanvasManager.initElement(c);
+ return c;
+ }
+
+ canvasWidth = target.width();
+ canvasHeight = target.height();
+ target.html(""); // clear target
+ if (target.css("position") == 'static')
+ target.css("position", "relative"); // for positioning labels and overlay
+
+ if (canvasWidth <= 0 || canvasHeight <= 0)
+ throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
+
+ // the canvas
+ canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(target).get(0);
+ ctx = canvas.getContext("2d");
+
+ // overlay canvas for interactive features
+ overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(target).get(0);
+ octx = overlay.getContext("2d");
+
+ // we include the canvas in the event holder too, because IE 7
+ // sometimes has trouble with the stacking order
+ eventHolder = $([overlay, canvas]);
+
+ // bind events
+ if (options.selection.mode != null || options.crosshair.mode != null
+ || options.grid.hoverable) {
+ // FIXME: temp. work-around until jQuery bug 4398 is fixed
+ eventHolder.each(function () {
+ this.onmousemove = onMouseMove;
+ });
+
+ if (options.selection.mode != null)
+ eventHolder.mousedown(onMouseDown);
+ }
+
+ if (options.crosshair.mode != null)
+ eventHolder.mouseout(onMouseOut);
+
+ if (options.grid.clickable)
+ eventHolder.click(onClick);
+ }
+
+ function setupGrid() {
+ function setupAxis(axis, options) {
+ setRange(axis, options);
+ prepareTickGeneration(axis, options);
+ setTicks(axis, options);
+ // add transformation helpers
+ if (axis == axes.xaxis || axis == axes.x2axis) {
+ // data point to canvas coordinate
+ axis.p2c = function (p) { return (p - axis.min) * axis.scale; };
+ // canvas coordinate to data point
+ axis.c2p = function (c) { return axis.min + c / axis.scale; };
+ }
+ else {
+ axis.p2c = function (p) { return (axis.max - p) * axis.scale; };
+ axis.c2p = function (p) { return axis.max - p / axis.scale; };
+ }
+ }
+
+ for (var axis in axes)
+ setupAxis(axes[axis], options[axis]);
+
+ setSpacing();
+ insertLabels();
+ insertLegend();
+ }
+
+ function setRange(axis, axisOptions) {
+ var min = axisOptions.min != null ? +axisOptions.min : axis.datamin,
+ max = axisOptions.max != null ? +axisOptions.max : axis.datamax;
+
+ // degenerate case
+ if (min == Number.POSITIVE_INFINITY)
+ min = 0;
+ if (max == Number.NEGATIVE_INFINITY)
+ max = 1;
+
+ if (max - min == 0.0) {
+ // degenerate case
+ var widen = max == 0 ? 1 : 0.01;
+
+ if (axisOptions.min == null)
+ min -= widen;
+ // alway widen max if we couldn't widen min to ensure we
+ // don't fall into min == max which doesn't work
+ if (axisOptions.max == null || axisOptions.min != null)
+ max += widen;
+ }
+ else {
+ // consider autoscaling
+ var margin = axisOptions.autoscaleMargin;
+ if (margin != null) {
+ if (axisOptions.min == null) {
+ min -= (max - min) * margin;
+ // make sure we don't go below zero if all values
+ // are positive
+ if (min < 0 && axis.datamin >= 0)
+ min = 0;
+ }
+ if (axisOptions.max == null) {
+ max += (max - min) * margin;
+ if (max > 0 && axis.datamax <= 0)
+ max = 0;
+ }
+ }
+ }
+ axis.min = min;
+ axis.max = max;
+ }
+
+ function prepareTickGeneration(axis, axisOptions) {
+ // estimate number of ticks
+ var noTicks;
+ if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0)
+ noTicks = axisOptions.ticks;
+ else if (axis == axes.xaxis || axis == axes.x2axis)
+ noTicks = canvasWidth / 100;
+ else
+ noTicks = canvasHeight / 60;
+
+ var delta = (axis.max - axis.min) / noTicks;
+ var size, generator, unit, formatter, i, magn, norm;
+
+ if (axisOptions.mode == "time") {
+ // pretty handling of time
+
+ // map of app. size of time units in milliseconds
+ var timeUnitSize = {
+ "second": 1000,
+ "minute": 60 * 1000,
+ "hour": 60 * 60 * 1000,
+ "day": 24 * 60 * 60 * 1000,
+ "month": 30 * 24 * 60 * 60 * 1000,
+ "year": 365.2425 * 24 * 60 * 60 * 1000
+ };
+
+
+ // the allowed tick sizes, after 1 year we use
+ // an integer algorithm
+ var spec = [
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
+ [30, "second"],
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
+ [30, "minute"],
+ [1, "hour"], [2, "hour"], [4, "hour"],
+ [8, "hour"], [12, "hour"],
+ [1, "day"], [2, "day"], [3, "day"],
+ [0.25, "month"], [0.5, "month"], [1, "month"],
+ [2, "month"], [3, "month"], [6, "month"],
+ [1, "year"]
+ ];
+
+ var minSize = 0;
+ if (axisOptions.minTickSize != null) {
+ if (typeof axisOptions.tickSize == "number")
+ minSize = axisOptions.tickSize;
+ else
+ minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]];
+ }
+
+ for (i = 0; i < spec.length - 1; ++i)
+ if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
+ break;
+ size = spec[i][0];
+ unit = spec[i][1];
+
+ // special-case the possibility of several years
+ if (unit == "year") {
+ magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
+ norm = (delta / timeUnitSize.year) / magn;
+ if (norm < 1.5)
+ size = 1;
+ else if (norm < 3)
+ size = 2;
+ else if (norm < 7.5)
+ size = 5;
+ else
+ size = 10;
+
+ size *= magn;
+ }
+
+ if (axisOptions.tickSize) {
+ size = axisOptions.tickSize[0];
+ unit = axisOptions.tickSize[1];
+ }
+
+ generator = function(axis) {
+ var ticks = [],
+ tickSize = axis.tickSize[0], unit = axis.tickSize[1],
+ d = new Date(axis.min);
+
+ var step = tickSize * timeUnitSize[unit];
+
+ if (unit == "second")
+ d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
+ if (unit == "minute")
+ d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
+ if (unit == "hour")
+ d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
+ if (unit == "month")
+ d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
+ if (unit == "year")
+ d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
+
+ // reset smaller components
+ d.setUTCMilliseconds(0);
+ if (step >= timeUnitSize.minute)
+ d.setUTCSeconds(0);
+ if (step >= timeUnitSize.hour)
+ d.setUTCMinutes(0);
+ if (step >= timeUnitSize.day)
+ d.setUTCHours(0);
+ if (step >= timeUnitSize.day * 4)
+ d.setUTCDate(1);
+ if (step >= timeUnitSize.year)
+ d.setUTCMonth(0);
+
+
+ var carry = 0, v = Number.NaN, prev;
+ do {
+ prev = v;
+ v = d.getTime();
+ ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
+ if (unit == "month") {
+ if (tickSize < 1) {
+ // a bit complicated - we'll divide the month
+ // up but we need to take care of fractions
+ // so we don't end up in the middle of a day
+ d.setUTCDate(1);
+ var start = d.getTime();
+ d.setUTCMonth(d.getUTCMonth() + 1);
+ var end = d.getTime();
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
+ carry = d.getUTCHours();
+ d.setUTCHours(0);
+ }
+ else
+ d.setUTCMonth(d.getUTCMonth() + tickSize);
+ }
+ else if (unit == "year") {
+ d.setUTCFullYear(d.getUTCFullYear() + tickSize);
+ }
+ else
+ d.setTime(v + step);
+ } while (v < axis.max && v != prev);
+
+ return ticks;
+ };
+
+ formatter = function (v, axis) {
+ var d = new Date(v);
+
+ // first check global format
+ if (axisOptions.timeformat != null)
+ return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames);
+
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
+ var span = axis.max - axis.min;
+
+ if (t < timeUnitSize.minute)
+ fmt = "%h:%M:%S";
+ else if (t < timeUnitSize.day) {
+ if (span < 2 * timeUnitSize.day)
+ fmt = "%h:%M";
+ else
+ fmt = "%b %d %h:%M";
+ }
+ else if (t < timeUnitSize.month)
+ fmt = "%b %d";
+ else if (t < timeUnitSize.year) {
+ if (span < timeUnitSize.year)
+ fmt = "%b";
+ else
+ fmt = "%b %y";
+ }
+ else
+ fmt = "%y";
+
+ return $.plot.formatDate(d, fmt, axisOptions.monthNames);
+ };
+ }
+ else {
+ // pretty rounding of base-10 numbers
+ var maxDec = axisOptions.tickDecimals;
+ var dec = -Math.floor(Math.log(delta) / Math.LN10);
+ if (maxDec != null && dec > maxDec)
+ dec = maxDec;
+
+ magn = Math.pow(10, -dec);
+ norm = delta / magn; // norm is between 1.0 and 10.0
+
+ if (norm < 1.5)
+ size = 1;
+ else if (norm < 3) {
+ size = 2;
+ // special case for 2.5, requires an extra decimal
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
+ size = 2.5;
+ ++dec;
+ }
+ }
+ else if (norm < 7.5)
+ size = 5;
+ else
+ size = 10;
+
+ size *= magn;
+
+ if (axisOptions.minTickSize != null && size < axisOptions.minTickSize)
+ size = axisOptions.minTickSize;
+
+ if (axisOptions.tickSize != null)
+ size = axisOptions.tickSize;
+
+ axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec);
+
+ generator = function (axis) {
+ var ticks = [];
+
+ // spew out all possible ticks
+ var start = floorInBase(axis.min, axis.tickSize),
+ i = 0, v = Number.NaN, prev;
+ do {
+ prev = v;
+ v = start + i * axis.tickSize;
+ ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
+ ++i;
+ } while (v < axis.max && v != prev);
+ return ticks;
+ };
+
+ formatter = function (v, axis) {
+ return v.toFixed(axis.tickDecimals);
+ };
+ }
+
+ axis.tickSize = unit ? [size, unit] : size;
+ axis.tickGenerator = generator;
+ if ($.isFunction(axisOptions.tickFormatter))
+ axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); };
+ else
+ axis.tickFormatter = formatter;
+ if (axisOptions.labelWidth != null)
+ axis.labelWidth = axisOptions.labelWidth;
+ if (axisOptions.labelHeight != null)
+ axis.labelHeight = axisOptions.labelHeight;
+ }
+
+ function setTicks(axis, axisOptions) {
+ axis.ticks = [];
+
+ if (!axis.used)
+ return;
+
+ if (axisOptions.ticks == null)
+ axis.ticks = axis.tickGenerator(axis);
+ else if (typeof axisOptions.ticks == "number") {
+ if (axisOptions.ticks > 0)
+ axis.ticks = axis.tickGenerator(axis);
+ }
+ else if (axisOptions.ticks) {
+ var ticks = axisOptions.ticks;
+
+ if ($.isFunction(ticks))
+ // generate the ticks
+ ticks = ticks({ min: axis.min, max: axis.max });
+
+ // clean up the user-supplied ticks, copy them over
+ var i, v;
+ for (i = 0; i < ticks.length; ++i) {
+ var label = null;
+ var t = ticks[i];
+ if (typeof t == "object") {
+ v = t[0];
+ if (t.length > 1)
+ label = t[1];
+ }
+ else
+ v = t;
+ if (label == null)
+ label = axis.tickFormatter(v, axis);
+ axis.ticks[i] = { v: v, label: label };
+ }
+ }
+
+ if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) {
+ // snap to ticks
+ if (axisOptions.min == null)
+ axis.min = Math.min(axis.min, axis.ticks[0].v);
+ if (axisOptions.max == null && axis.ticks.length > 1)
+ axis.max = Math.min(axis.max, axis.ticks[axis.ticks.length - 1].v);
+ }
+ }
+
+ function setSpacing() {
+ function measureXLabels(axis) {
+ // to avoid measuring the widths of the labels, we
+ // construct fixed-size boxes and put the labels inside
+ // them, we don't need the exact figures and the
+ // fixed-size box content is easy to center
+ if (axis.labelWidth == null)
+ axis.labelWidth = canvasWidth / 6;
+
+ // measure x label heights
+ if (axis.labelHeight == null) {
+ labels = [];
+ for (i = 0; i < axis.ticks.length; ++i) {
+ l = axis.ticks[i].label;
+ if (l)
+ labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>');
+ }
+
+ axis.labelHeight = 0;
+ if (labels.length > 0) {
+ var dummyDiv = $('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'
+ + labels.join("") + '<div style="clear:left"></div></div>').appendTo(target);
+ axis.labelHeight = dummyDiv.height();
+ dummyDiv.remove();
+ }
+ }
+ }
+
+ function measureYLabels(axis) {
+ if (axis.labelWidth == null || axis.labelHeight == null) {
+ var i, labels = [], l;
+ // calculate y label dimensions
+ for (i = 0; i < axis.ticks.length; ++i) {
+ l = axis.ticks[i].label;
+ if (l)
+ labels.push('<div class="tickLabel">' + l + '</div>');
+ }
+
+ if (labels.length > 0) {
+ var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">'
+ + labels.join("") + '</div>').appendTo(target);
+ if (axis.labelWidth == null)
+ axis.labelWidth = dummyDiv.width();
+ if (axis.labelHeight == null)
+ axis.labelHeight = dummyDiv.find("div").height();
+ dummyDiv.remove();
+ }
+
+ if (axis.labelWidth == null)
+ axis.labelWidth = 0;
+ if (axis.labelHeight == null)
+ axis.labelHeight = 0;
+ }
+ }
+
+ measureXLabels(axes.xaxis);
+ measureYLabels(axes.yaxis);
+ measureXLabels(axes.x2axis);
+ measureYLabels(axes.y2axis);
+
+ // get the most space needed around the grid for things
+ // that may stick out
+ var maxOutset = options.grid.borderWidth;
+ for (i = 0; i < series.length; ++i)
+ maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
+
+ plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset;
+
+ var margin = options.grid.labelMargin + options.grid.borderWidth;
+
+ if (axes.xaxis.labelHeight > 0)
+ plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin);
+ if (axes.yaxis.labelWidth > 0)
+ plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin);
+
+ if (axes.x2axis.labelHeight > 0)
+ plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin);
+
+ if (axes.y2axis.labelWidth > 0)
+ plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin);
+
+ plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
+ plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
+
+ // precompute how much the axis is scaling a point in canvas space
+ axes.xaxis.scale = plotWidth / (axes.xaxis.max - axes.xaxis.min);
+ axes.yaxis.scale = plotHeight / (axes.yaxis.max - axes.yaxis.min);
+ axes.x2axis.scale = plotWidth / (axes.x2axis.max - axes.x2axis.min);
+ axes.y2axis.scale = plotHeight / (axes.y2axis.max - axes.y2axis.min);
+ }
+
+ function draw() {
+ drawGrid();
+ for (var i = 0; i < series.length; ++i) {
+ var s = series[i];
+ if (s.subseries)
+ for (var j = 0; j < s.subseries.length; ++j)
+ drawSeries(s.subseries[j]);
+ else
+ drawSeries(s);
+ }
+ }
+
+ function extractRange(ranges, coord) {
+ var firstAxis = coord + "axis",
+ secondaryAxis = coord + "2axis",
+ axis, from, to, reverse;
+
+ if (ranges[firstAxis]) {
+ axis = axes[firstAxis];
+ from = ranges[firstAxis].from;
+ to = ranges[firstAxis].to;
+ }
+ else if (ranges[secondaryAxis]) {
+ axis = axes[secondaryAxis];
+ from = ranges[secondaryAxis].from;
+ to = ranges[secondaryAxis].to;
+ }
+ else {
+ // backwards-compat stuff - to be removed in future
+ axis = axes[firstAxis];
+ from = ranges[coord + "1"];
+ to = ranges[coord + "2"];
+ }
+
+ // auto-reverse as an added bonus
+ if (from != null && to != null && from > to)
+ return { from: to, to: from, axis: axis };
+
+ return { from: from, to: to, axis: axis };
+ }
+
+ function drawGrid() {
+ var i;
+
+ ctx.save();
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // draw background, if any
+ if (options.grid.backgroundColor) {
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
+ }
+
+ // draw markings
+ var markings = options.grid.markings;
+ if (markings) {
+ if ($.isFunction(markings))
+ // xmin etc. are backwards-compatible, to be removed in future
+ markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis });
+
+ for (i = 0; i < markings.length; ++i) {
+ var m = markings[i],
+ xrange = extractRange(m, "x"),
+ yrange = extractRange(m, "y");
+
+ // fill in missing
+ if (xrange.from == null)
+ xrange.from = xrange.axis.min;
+ if (xrange.to == null)
+ xrange.to = xrange.axis.max;
+ if (yrange.from == null)
+ yrange.from = yrange.axis.min;
+ if (yrange.to == null)
+ yrange.to = yrange.axis.max;
+
+ // clip
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
+ continue;
+
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
+
+ if (xrange.from == xrange.to && yrange.from == yrange.to)
+ continue;
+
+ // then draw
+ xrange.from = xrange.axis.p2c(xrange.from);
+ xrange.to = xrange.axis.p2c(xrange.to);
+ yrange.from = yrange.axis.p2c(yrange.from);
+ yrange.to = yrange.axis.p2c(yrange.to);
+
+ if (xrange.from == xrange.to || yrange.from == yrange.to) {
+ // draw line
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
+ ctx.beginPath();
+ ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
+ //ctx.moveTo(Math.floor(xrange.from), yrange.from);
+ //ctx.lineTo(Math.floor(xrange.to), yrange.to);
+ ctx.moveTo(xrange.from, yrange.from);
+ ctx.lineTo(xrange.to, yrange.to);
+ ctx.stroke();
+ }
+ else {
+ // fill area
+ ctx.fillStyle = m.color || options.grid.markingsColor;
+ ctx.fillRect(xrange.from, yrange.to,
+ xrange.to - xrange.from,
+ yrange.from - yrange.to);
+ }
+ }
+ }
+
+ // draw the inner grid
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = options.grid.tickColor;
+ ctx.beginPath();
+ var v, axis = axes.xaxis;
+ for (i = 0; i < axis.ticks.length; ++i) {
+ v = axis.ticks[i].v;
+ if (v <= axis.min || v >= axes.xaxis.max)
+ continue; // skip those lying on the axes
+
+ ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0);
+ ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight);
+ }
+
+ axis = axes.yaxis;
+ for (i = 0; i < axis.ticks.length; ++i) {
+ v = axis.ticks[i].v;
+ if (v <= axis.min || v >= axis.max)
+ continue;
+
+ ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
+ ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
+ }
+
+ axis = axes.x2axis;
+ for (i = 0; i < axis.ticks.length; ++i) {
+ v = axis.ticks[i].v;
+ if (v <= axis.min || v >= axis.max)
+ continue;
+
+ ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5);
+ ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5);
+ }
+
+ axis = axes.y2axis;
+ for (i = 0; i < axis.ticks.length; ++i) {
+ v = axis.ticks[i].v;
+ if (v <= axis.min || v >= axis.max)
+ continue;
+
+ ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
+ ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
+ }
+
+ ctx.stroke();
+
+ if (options.grid.borderWidth) {
+ // draw border
+ var bw = options.grid.borderWidth;
+ ctx.lineWidth = bw;
+ ctx.strokeStyle = options.grid.borderColor;
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
+ }
+
+ ctx.restore();
+ }
+
+ function insertLabels() {
+ target.find(".tickLabels").remove();
+
+ var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'];
+
+ function addLabels(axis, labelGenerator) {
+ for (var i = 0; i < axis.ticks.length; ++i) {
+ var tick = axis.ticks[i];
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
+ continue;
+ html.push(labelGenerator(tick, axis));
+ }
+ }
+
+ var margin = options.grid.labelMargin + options.grid.borderWidth;
+
+ addLabels(axes.xaxis, function (tick, axis) {
+ return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
+ });
+
+
+ addLabels(axes.yaxis, function (tick, axis) {
+ return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + margin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>";
+ });
+
+ addLabels(axes.x2axis, function (tick, axis) {
+ return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
+ });
+
+ addLabels(axes.y2axis, function (tick, axis) {
+ return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>";
+ });
+
+ html.push('</div>');
+
+ target.append(html.join(""));
+ }
+
+ function drawSeries(series) {
+ if (series.lines.show)
+ drawSeriesLines(series);
+ if (series.bars.show)
+ drawSeriesBars(series);
+ if (series.points.show)
+ drawSeriesPoints(series);
+ }
+
+ function drawSeriesLines(series) {
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
+ var points = datapoints.linespoints || datapoints.points,
+ incr = datapoints.incr,
+ prevx = null, prevy = null;
+
+ ctx.beginPath();
+ for (var i = incr; i < points.length; i += incr) {
+ var x1 = points[i - incr], y1 = points[i - incr + 1],
+ x2 = points[i], y2 = points[i + 1];
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min) {
+ if (y2 < axisy.min)
+ continue; // line segment is outside
+ // compute new intersection point
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min) {
+ if (y1 < axisy.min)
+ continue;
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max) {
+ if (y2 > axisy.max)
+ continue;
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max) {
+ if (y1 > axisy.max)
+ continue;
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (x1 != prevx || y1 != prevy)
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
+
+ prevx = x2;
+ prevy = y2;
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
+ }
+ ctx.stroke();
+ }
+
+ function plotLineArea(datapoints, axisx, axisy) {
+ var points = datapoints.linespoints || datapoints.points,
+ incr = datapoints.incr,
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
+ top, lastX = 0, areaOpen = false;
+
+ for (var i = incr; i < points.length; i += incr) {
+ var x1 = points[i - incr], y1 = points[i - incr + 1],
+ x2 = points[i], y2 = points[i + 1];
+
+ if (areaOpen && x1 != null && x2 == null) {
+ // close area
+ ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
+ ctx.fill();
+ areaOpen = false;
+ continue;
+ }
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip x values
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (!areaOpen) {
+ // open area
+ ctx.beginPath();
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
+ areaOpen = true;
+ }
+
+ // now first check the case where both is outside
+ if (y1 >= axisy.max && y2 >= axisy.max) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
+ lastX = x2;
+ continue;
+ }
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
+ lastX = x2;
+ continue;
+ }
+
+ // else it's a bit more complicated, there might
+ // be two rectangles and two triangles we need to fill
+ // in; to find these keep track of the current x values
+ var x1old = x1, x2old = x2;
+
+ // and clip the y values, without shortcutting
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+
+ // if the x value was changed we got a rectangle
+ // to fill
+ if (x1 != x1old) {
+ if (y1 <= axisy.min)
+ top = axisy.min;
+ else
+ top = axisy.max;
+
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top));
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(top));
+ }
+
+ // fill the triangles
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+
+ // fill the other rectangle if it's there
+ if (x2 != x2old) {
+ if (y2 <= axisy.min)
+ top = axisy.min;
+ else
+ top = axisy.max;
+
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(top));
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top));
+ }
+
+ lastX = Math.max(x2, x2old);
+ }
+
+ if (areaOpen) {
+ ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
+ ctx.fill();
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+ ctx.lineJoin = "round";
+
+ var lw = series.lines.lineWidth,
+ sw = series.shadowSize;
+ // FIXME: consider another form of shadow when filling is turned on
+ if (lw > 0 && sw > 0) {
+ // draw shadow as a thick and thin line with transparency
+ ctx.lineWidth = sw;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ var xoffset = 1;
+ plotLine(series.datapoints, xoffset, Math.sqrt((lw/2 + sw/2)*(lw/2 + sw/2) - xoffset*xoffset), series.xaxis, series.yaxis);
+ ctx.lineWidth = sw/2;
+ plotLine(series.datapoints, xoffset, Math.sqrt((lw/2 + sw/4)*(lw/2 + sw/4) - xoffset*xoffset), series.xaxis, series.yaxis);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
+ }
+
+ if (lw > 0)
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function drawSeriesPoints(series) {
+ function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
+ var points = datapoints.points, incr = datapoints.incr;
+
+ for (var i = 0; i < points.length; i += incr) {
+ var x = points[i], y = points[i + 1];
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ continue;
+
+ ctx.beginPath();
+ ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, true);
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ ctx.fill();
+ }
+ ctx.stroke();
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ var lw = series.lines.lineWidth,
+ sw = series.shadowSize,
+ radius = series.points.radius;
+ if (lw > 0 && sw > 0) {
+ // draw shadow in two steps
+ var w = sw / 2;
+ ctx.lineWidth = w;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ plotPoints(series.datapoints, radius, null, w + w/2, 2 * Math.PI,
+ series.xaxis, series.yaxis);
+
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
+ plotPoints(series.datapoints, radius, null, w/2, 2 * Math.PI,
+ series.xaxis, series.yaxis);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ plotPoints(series.datapoints, radius,
+ getFillStyle(series.points, series.color), 0, 2 * Math.PI,
+ series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) {
+ var left, right, bottom, top,
+ drawLeft, drawRight, drawTop, drawBottom,
+ tmp;
+
+ if (horizontal) {
+ drawBottom = drawRight = drawTop = true;
+ drawLeft = false;
+ left = b;
+ right = x;
+ top = y + barLeft;
+ bottom = y + barRight;
+
+ // account for negative bars
+ if (right < left) {
+ tmp = right;
+ right = left;
+ left = tmp;
+ drawLeft = true;
+ drawRight = false;
+ }
+ }
+ else {
+ drawLeft = drawRight = drawTop = true;
+ drawBottom = false;
+ left = x + barLeft;
+ right = x + barRight;
+ bottom = b;
+ top = y;
+
+ // account for negative bars
+ if (top < bottom) {
+ tmp = top;
+ top = bottom;
+ bottom = tmp;
+ drawBottom = true;
+ drawTop = false;
+ }
+ }
+
+ // clip
+ if (right < axisx.min || left > axisx.max ||
+ top < axisy.min || bottom > axisy.max)
+ return;
+
+ if (left < axisx.min) {
+ left = axisx.min;
+ drawLeft = false;
+ }
+
+ if (right > axisx.max) {
+ right = axisx.max;
+ drawRight = false;
+ }
+
+ if (bottom < axisy.min) {
+ bottom = axisy.min;
+ drawBottom = false;
+ }
+
+ if (top > axisy.max) {
+ top = axisy.max;
+ drawTop = false;
+ }
+
+ left = axisx.p2c(left);
+ bottom = axisy.p2c(bottom);
+ right = axisx.p2c(right);
+ top = axisy.p2c(top);
+
+ // fill the bar
+ if (fillStyleCallback) {
+ c.beginPath();
+ c.moveTo(left, bottom);
+ c.lineTo(left, top);
+ c.lineTo(right, top);
+ c.lineTo(right, bottom);
+ c.fillStyle = fillStyleCallback(bottom, top);
+ c.fill();
+ }
+
+ // draw outline
+ if (drawLeft || drawRight || drawTop || drawBottom) {
+ c.beginPath();
+
+ // FIXME: inline moveTo is buggy with excanvas
+ c.moveTo(left, bottom + offset);
+ if (drawLeft)
+ c.lineTo(left, top + offset);
+ else
+ c.moveTo(left, top + offset);
+ if (drawTop)
+ c.lineTo(right, top + offset);
+ else
+ c.moveTo(right, top + offset);
+ if (drawRight)
+ c.lineTo(right, bottom + offset);
+ else
+ c.moveTo(right, bottom + offset);
+ if (drawBottom)
+ c.lineTo(left, bottom + offset);
+ else
+ c.moveTo(left, bottom + offset);
+ c.stroke();
+ }
+ }
+
+ function drawSeriesBars(series) {
+ function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
+ var points = datapoints.points, incr = datapoints.incr;
+
+ for (var i = 0; i < points.length; i += incr) {
+ if (points[i] == null)
+ continue;
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal);
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
+ ctx.lineWidth = series.bars.lineWidth;
+ ctx.strokeStyle = series.color;
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
+ var fill = filloptions.fill;
+ if (!fill)
+ return null;
+
+ if (filloptions.fillColor)
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
+
+ var c = parseColor(seriesColor);
+ c.a = typeof fill == "number" ? fill : 0.4;
+ c.normalize();
+ return c.toString();
+ }
+
+ function insertLegend() {
+ target.find(".legend").remove();
+
+ if (!options.legend.show)
+ return;
+
+ var fragments = [], rowStarted = false,
+ lf = options.legend.labelFormatter, s, label;
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ label = s.label;
+ if (!label)
+ continue;
+
+ if (i % options.legend.noColumns == 0) {
+ if (rowStarted)
+ fragments.push('</tr>');
+ fragments.push('<tr>');
+ rowStarted = true;
+ }
+
+ if (lf)
+ label = lf(label, s);
+
+ fragments.push(
+ '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' +
+ '<td class="legendLabel">' + label + '</td>');
+ }
+ if (rowStarted)
+ fragments.push('</tr>');
+
+ if (fragments.length == 0)
+ return;
+
+ var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
+ if (options.legend.container != null)
+ $(options.legend.container).html(table);
+ else {
+ var pos = "",
+ p = options.legend.position,
+ m = options.legend.margin;
+ if (m[0] == null)
+ m = [m, m];
+ if (p.charAt(0) == "n")
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
+ else if (p.charAt(0) == "s")
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
+ if (p.charAt(1) == "e")
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
+ else if (p.charAt(1) == "w")
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(target);
+ if (options.legend.backgroundOpacity != 0.0) {
+ // put in the transparent background
+ // separately to avoid blended labels and
+ // label boxes
+ var c = options.legend.backgroundColor;
+ if (c == null) {
+ var tmp;
+ if (options.grid.backgroundColor && typeof options.grid.backgroundColor == "string")
+ tmp = options.grid.backgroundColor;
+ else
+ tmp = extractColor(legend);
+ c = parseColor(tmp).adjust(null, null, null, 1).toString();
+ }
+ var div = legend.children();
+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
+ }
+ }
+ }
+
+
+ // interactive features
+
+ var lastMousePos = { pageX: null, pageY: null },
+ selection = {
+ first: { x: -1, y: -1}, second: { x: -1, y: -1},
+ show: false, active: false },
+ crosshair = { pos: { x: -1, y: -1 } },
+ highlights = [],
+ clickIsMouseUp = false,
+ redrawTimeout = null,
+ hoverTimeout = null;
+
+ // Returns the data item the mouse is over, or null if none is found
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
+ var maxDistance = options.grid.mouseActiveRadius,
+ lowestDistance = maxDistance * maxDistance + 1,
+ item = null, foundPoint = false, i, j;
+
+ for (var i = 0; i < series.length; ++i) {
+ if (!seriesFilter(series[i]))
+ continue;
+
+ var s = series[i],
+ axisx = s.xaxis,
+ axisy = s.yaxis,
+ points = s.datapoints.points,
+ incr = s.datapoints.incr,
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
+ my = axisy.c2p(mouseY),
+ maxx = maxDistance / axisx.scale,
+ maxy = maxDistance / axisy.scale;
+
+ if (s.lines.show || s.points.show) {
+ for (j = 0; j < points.length; j += incr) {
+ var x = points[j], y = points[j + 1];
+ if (x == null)
+ continue;
+
+ // For points and lines, the cursor must be within a
+ // certain distance to the data point
+ if (x - mx > maxx || x - mx < -maxx ||
+ y - my > maxy || y - my < -maxy)
+ continue;
+
+ // We have to calculate distances in pixels, not in
+ // data units, because the scales of the axes may be different
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
+ dy = Math.abs(axisy.p2c(y) - mouseY),
+ dist = dx * dx + dy * dy; // no idea in taking sqrt
+ if (dist < lowestDistance) {
+ lowestDistance = dist;
+ item = [i, j / incr];
+ }
+ }
+ }
+
+ if (s.bars.show && !item) { // no other point can be nearby
+ var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
+ barRight = barLeft + s.bars.barWidth;
+
+ for (j = 0; j < points.length; j += incr) {
+ var x = points[j], y = points[j + 1], b = points[j + 2];
+ if (x == null)
+ continue;
+
+ // for a bar graph, the cursor must be inside the bar
+ if (series[i].bars.horizontal ?
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
+ my >= y + barLeft && my <= y + barRight) :
+ (mx >= x + barLeft && mx <= x + barRight &&
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
+ item = [i, j / incr];
+ }
+ }
+ }
+
+ if (item) {
+ i = item[0];
+ j = item[1];
+
+ return { datapoint: series[i].data[j],
+ dataIndex: j,
+ series: series[i],
+ seriesIndex: i }
+ }
+
+ return null;
+ }
+
+ function onMouseMove(ev) {
+ // FIXME: temp. work-around until jQuery bug 4398 is fixed
+ var e = ev || window.event;
+ if (e.pageX == null && e.clientX != null) {
+ var de = document.documentElement, b = document.body;
+ lastMousePos.pageX = e.clientX + (de && de.scrollLeft || b.scrollLeft || 0) - (de.clientLeft || 0);
+ lastMousePos.pageY = e.clientY + (de && de.scrollTop || b.scrollTop || 0) - (de.clientTop || 0);
+ }
+ else {
+ lastMousePos.pageX = e.pageX;
+ lastMousePos.pageY = e.pageY;
+ }
+
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", lastMousePos,
+ function (s) { return s["hoverable"] != false; });
+
+ if (options.crosshair.mode != null) {
+ if (!selection.active) {
+ setPositionFromEvent(crosshair.pos, lastMousePos);
+ triggerRedrawOverlay();
+ }
+ else
+ crosshair.pos.x = -1; // hide the crosshair while selecting
+ }
+
+ if (selection.active) {
+ target.trigger("plotselecting", [ selectionIsSane() ? getSelectionForEvent() : null ]);
+
+ updateSelection(lastMousePos);
+ }
+ }
+
+ function onMouseDown(e) {
+ if (e.which != 1) // only accept left-click
+ return;
+
+ // cancel out any text selections
+ document.body.focus();
+
+ // prevent text selection and drag in old-school browsers
+ if (document.onselectstart !== undefined && workarounds.onselectstart == null) {
+ workarounds.onselectstart = document.onselectstart;
+ document.onselectstart = function () { return false; };
+ }
+ if (document.ondrag !== undefined && workarounds.ondrag == null) {
+ workarounds.ondrag = document.ondrag;
+ document.ondrag = function () { return false; };
+ }
+
+ setSelectionPos(selection.first, e);
+
+ lastMousePos.pageX = null;
+ selection.active = true;
+ $(document).one("mouseup", onSelectionMouseUp);
+ }
+
+ function onMouseOut(ev) {
+ if (options.crosshair.mode != null && crosshair.pos.x != -1) {
+ crosshair.pos.x = -1;
+ triggerRedrawOverlay();
+ }
+ }
+
+ function onClick(e) {
+ if (clickIsMouseUp) {
+ clickIsMouseUp = false;
+ return;
+ }
+
+ triggerClickHoverEvent("plotclick", e,
+ function (s) { return s["clickable"] != false; });
+ }
+
+ /*
+ function userPositionInCanvasSpace(pos) {
+ return { x: parseInt(pos.x != null ? axes.xaxis.p2c(pos.x) : axes.x2axis.p2c(pos.x2)),
+ y: parseInt(pos.y != null ? axes.yaxis.p2c(pos.y) : axes.y2axis.p2c(pos.y2)) };
+ }
+
+ function positionInDivSpace(pos) {
+ var cpos = userPositionInCanvasSpace(pos);
+ return { x: cpos.x + plotOffset.left,
+ y: cpos.y + plotOffset.top };
+ }*/
+
+ // trigger click or hover event (they send the same parameters
+ // so we share their code)
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
+ var offset = eventHolder.offset(),
+ pos = { pageX: event.pageX, pageY: event.pageY },
+ canvasX = event.pageX - offset.left - plotOffset.left,
+ canvasY = event.pageY - offset.top - plotOffset.top;
+
+ if (axes.xaxis.used)
+ pos.x = axes.xaxis.c2p(canvasX);
+ if (axes.yaxis.used)
+ pos.y = axes.yaxis.c2p(canvasY);
+ if (axes.x2axis.used)
+ pos.x2 = axes.x2axis.c2p(canvasX);
+ if (axes.y2axis.used)
+ pos.y2 = axes.y2axis.c2p(canvasY);
+
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
+
+ if (item) {
+ // fill in mouse pos for any listeners out there
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
+ }
+
+ if (options.grid.autoHighlight) {
+ // clear auto-highlights
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.auto == eventname &&
+ !(item && h.series == item.series && h.point == item.datapoint))
+ unhighlight(h.series, h.point);
+ }
+
+ if (item)
+ highlight(item.series, item.datapoint, eventname);
+ }
+
+ target.trigger(eventname, [ pos, item ]);
+ }
+
+ function triggerRedrawOverlay() {
+ if (!redrawTimeout)
+ redrawTimeout = setTimeout(redrawOverlay, 30);
+ }
+
+ function redrawOverlay() {
+ redrawTimeout = null;
+
+ // redraw highlights
+ octx.save();
+ octx.clearRect(0, 0, canvasWidth, canvasHeight);
+ octx.translate(plotOffset.left, plotOffset.top);
+
+ var i, hi;
+ for (i = 0; i < highlights.length; ++i) {
+ hi = highlights[i];
+
+ if (hi.series.bars.show)
+ drawBarHighlight(hi.series, hi.point);
+ else
+ drawPointHighlight(hi.series, hi.point);
+ }
+
+ // redraw selection
+ if (selection.show && selectionIsSane()) {
+ octx.strokeStyle = parseColor(options.selection.color).scale(null, null, null, 0.8).toString();
+ octx.lineWidth = 1;
+ ctx.lineJoin = "round";
+ octx.fillStyle = parseColor(options.selection.color).scale(null, null, null, 0.4).toString();
+
+ var x = Math.min(selection.first.x, selection.second.x),
+ y = Math.min(selection.first.y, selection.second.y),
+ w = Math.abs(selection.second.x - selection.first.x),
+ h = Math.abs(selection.second.y - selection.first.y);
+
+ octx.fillRect(x, y, w, h);
+ octx.strokeRect(x, y, w, h);
+ }
+
+ // redraw crosshair
+ var pos = crosshair.pos, mode = options.crosshair.mode;
+ if (mode != null && pos.x != -1) {
+ octx.strokeStyle = parseColor(options.crosshair.color).scale(null, null, null, 0.8).toString();
+ octx.lineWidth = 1;
+ ctx.lineJoin = "round";
+
+ octx.beginPath();
+ if (mode.indexOf("x") != -1) {
+ octx.moveTo(pos.x, 0);
+ octx.lineTo(pos.x, plotHeight);
+ }
+ if (mode.indexOf("y") != -1) {
+ octx.moveTo(0, pos.y);
+ octx.lineTo(plotWidth, pos.y);
+ }
+ octx.stroke();
+
+ }
+ octx.restore();
+ }
+
+ function highlight(s, point, auto) {
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number")
+ point = s.data[point];
+
+ var i = indexOfHighlight(s, point);
+ if (i == -1) {
+ highlights.push({ series: s, point: point, auto: auto });
+
+ triggerRedrawOverlay();
+ }
+ else if (!auto)
+ highlights[i].auto = false;
+ }
+
+ function unhighlight(s, point) {
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number")
+ point = s.data[point];
+
+ var i = indexOfHighlight(s, point);
+ if (i != -1) {
+ highlights.splice(i, 1);
+
+ triggerRedrawOverlay();
+ }
+ }
+
+ function indexOfHighlight(s, p) {
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.series == s && h.point[0] == p[0]
+ && h.point[1] == p[1])
+ return i;
+ }
+ return -1;
+ }
+
+ function drawPointHighlight(series, point) {
+ var x = point[0], y = point[1],
+ axisx = series.xaxis, axisy = series.yaxis;
+
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ return;
+
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
+ octx.lineWidth = pointRadius;
+ octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
+ var radius = 1.5 * pointRadius;
+ octx.beginPath();
+ octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, true);
+ octx.stroke();
+ }
+
+ function drawBarHighlight(series, point) {
+ octx.lineWidth = series.bars.lineWidth;
+ octx.strokeStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
+ var fillStyle = parseColor(series.color).scale(1, 1, 1, 0.5).toString();
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
+ 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal);
+ }
+
+ function setPositionFromEvent(pos, e) {
+ var offset = eventHolder.offset();
+ pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plotWidth);
+ pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plotHeight);
+ }
+
+ function setCrosshair(pos) {
+ if (pos == null)
+ crosshair.pos.x = -1;
+ else {
+ crosshair.pos.x = clamp(0, pos.x != null ? axes.xaxis.p2c(pos.x) : axes.x2axis.p2c(pos.x2), plotWidth);
+ crosshair.pos.y = clamp(0, pos.y != null ? axes.yaxis.p2c(pos.y) : axes.y2axis.p2c(pos.y2), plotHeight);
+ }
+ triggerRedrawOverlay();
+ }
+
+ function getSelectionForEvent() {
+ var x1 = Math.min(selection.first.x, selection.second.x),
+ x2 = Math.max(selection.first.x, selection.second.x),
+ y1 = Math.max(selection.first.y, selection.second.y),
+ y2 = Math.min(selection.first.y, selection.second.y);
+
+ var r = {};
+ if (axes.xaxis.used)
+ r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) };
+ if (axes.x2axis.used)
+ r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) };
+ if (axes.yaxis.used)
+ r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) };
+ if (axes.y2axis.used)
+ r.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) };
+ return r;
+ }
+
+ function triggerSelectedEvent() {
+ var r = getSelectionForEvent();
+
+ target.trigger("plotselected", [ r ]);
+
+ // backwards-compat stuff, to be removed in future
+ if (axes.xaxis.used && axes.yaxis.used)
+ target.trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
+ }
+
+ function onSelectionMouseUp(e) {
+ // revert drag stuff for old-school browsers
+ if (document.onselectstart !== undefined)
+ document.onselectstart = workarounds.onselectstart;
+ if (document.ondrag !== undefined)
+ document.ondrag = workarounds.ondrag;
+
+ // no more draggy-dee-drag
+ selection.active = false;
+ updateSelection(e);
+
+ if (selectionIsSane()) {
+ triggerSelectedEvent();
+ clickIsMouseUp = true;
+ }
+ else {
+ // this counts as a clear
+ target.trigger("plotunselected", [ ]);
+ target.trigger("plotselecting", [ null ]);
+ }
+
+ return false;
+ }
+
+ function setSelectionPos(pos, e) {
+ setPositionFromEvent(pos, e);
+
+ if (options.selection.mode == "y") {
+ if (pos == selection.first)
+ pos.x = 0;
+ else
+ pos.x = plotWidth;
+ }
+
+ if (options.selection.mode == "x") {
+ if (pos == selection.first)
+ pos.y = 0;
+ else
+ pos.y = plotHeight;
+ }
+ }
+
+ function updateSelection(pos) {
+ if (pos.pageX == null)
+ return;
+
+ setSelectionPos(selection.second, pos);
+ if (selectionIsSane()) {
+ selection.show = true;
+ triggerRedrawOverlay();
+ }
+ else
+ clearSelection(true);
+ }
+
+ function clearSelection(preventEvent) {
+ if (selection.show) {
+ selection.show = false;
+ triggerRedrawOverlay();
+ if (!preventEvent)
+ target.trigger("plotunselected", [ ]);
+ }
+ }
+
+ function setSelection(ranges, preventEvent) {
+ var range;
+
+ if (options.selection.mode == "y") {
+ selection.first.x = 0;
+ selection.second.x = plotWidth;
+ }
+ else {
+ range = extractRange(ranges, "x");
+
+ selection.first.x = range.axis.p2c(range.from);
+ selection.second.x = range.axis.p2c(range.to);
+ }
+
+ if (options.selection.mode == "x") {
+ selection.first.y = 0;
+ selection.second.y = plotHeight;
+ }
+ else {
+ range = extractRange(ranges, "y");
+
+ selection.first.y = range.axis.p2c(range.from);
+ selection.second.y = range.axis.p2c(range.to);
+ }
+
+ selection.show = true;
+ triggerRedrawOverlay();
+ if (!preventEvent)
+ triggerSelectedEvent();
+ }
+
+ function selectionIsSane() {
+ var minSize = 5;
+ return Math.abs(selection.second.x - selection.first.x) >= minSize &&
+ Math.abs(selection.second.y - selection.first.y) >= minSize;
+ }
+
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
+ if (typeof spec == "string")
+ return spec;
+ else {
+ // assume this is a gradient spec; IE currently only
+ // supports a simple vertical gradient properly, so that's
+ // what we support too
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
+
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
+ var c = spec.colors[i];
+ gradient.addColorStop(i / (l - 1), typeof c == "string" ? c : parseColor(defaultColor).scale(c.brightness, c.brightness, c.brightness, c.opacity));
+ }
+
+ return gradient;
+ }
+ }
+ }
+
+ $.plot = function(target, data, options) {
+ var plot = new Plot(target, data, options);
+ /*var t0 = new Date();
+ var t1 = new Date();
+ var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
+ if (window.console)
+ console.log(tstr);
+ else
+ alert(tstr);*/
+ return plot;
+ };
+
+ // returns a string with the date d formatted according to fmt
+ $.plot.formatDate = function(d, fmt, monthNames) {
+ var leftPad = function(n) {
+ n = "" + n;
+ return n.length == 1 ? "0" + n : n;
+ };
+
+ var r = [];
+ var escape = false;
+ if (monthNames == null)
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+ for (var i = 0; i < fmt.length; ++i) {
+ var c = fmt.charAt(i);
+
+ if (escape) {
+ switch (c) {
+ case 'h': c = "" + d.getUTCHours(); break;
+ case 'H': c = leftPad(d.getUTCHours()); break;
+ case 'M': c = leftPad(d.getUTCMinutes()); break;
+ case 'S': c = leftPad(d.getUTCSeconds()); break;
+ case 'd': c = "" + d.getUTCDate(); break;
+ case 'm': c = "" + (d.getUTCMonth() + 1); break;
+ case 'y': c = "" + d.getUTCFullYear(); break;
+ case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
+ }
+ r.push(c);
+ escape = false;
+ }
+ else {
+ if (c == "%")
+ escape = true;
+ else
+ r.push(c);
+ }
+ }
+ return r.join("");
+ };
+
+ // round to nearby lower multiple of base
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+ function clamp(min, value, max) {
+ if (value < min)
+ return min;
+ else if (value > max)
+ return max;
+ else
+ return value;
+ }
+
+ // color helpers, inspiration from the jquery color animation
+ // plugin by John Resig
+ function Color (r, g, b, a) {
+
+ var rgba = ['r','g','b','a'];
+ var x = 4; //rgba.length
+
+ while (-1<--x) {
+ this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
+ }
+
+ this.toString = function() {
+ if (this.a >= 1.0) {
+ return "rgb("+[this.r,this.g,this.b].join(",")+")";
+ } else {
+ return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")";
+ }
+ };
+
+ this.scale = function(rf, gf, bf, af) {
+ x = 4; //rgba.length
+ while (-1<--x) {
+ if (arguments[x] != null)
+ this[rgba[x]] *= arguments[x];
+ }
+ return this.normalize();
+ };
+
+ this.adjust = function(rd, gd, bd, ad) {
+ x = 4; //rgba.length
+ while (-1<--x) {
+ if (arguments[x] != null)
+ this[rgba[x]] += arguments[x];
+ }
+ return this.normalize();
+ };
+
+ this.clone = function() {
+ return new Color(this.r, this.b, this.g, this.a);
+ };
+
+ var limit = function(val,minVal,maxVal) {
+ return Math.max(Math.min(val, maxVal), minVal);
+ };
+
+ this.normalize = function() {
+ this.r = clamp(0, parseInt(this.r), 255);
+ this.g = clamp(0, parseInt(this.g), 255);
+ this.b = clamp(0, parseInt(this.b), 255);
+ this.a = clamp(0, this.a, 1);
+ return this;
+ };
+
+ this.normalize();
+ }
+
+ var lookupColors = {
+ aqua:[0,255,255],
+ azure:[240,255,255],
+ beige:[245,245,220],
+ black:[0,0,0],
+ blue:[0,0,255],
+ brown:[165,42,42],
+ cyan:[0,255,255],
+ darkblue:[0,0,139],
+ darkcyan:[0,139,139],
+ darkgrey:[169,169,169],
+ darkgreen:[0,100,0],
+ darkkhaki:[189,183,107],
+ darkmagenta:[139,0,139],
+ darkolivegreen:[85,107,47],
+ darkorange:[255,140,0],
+ darkorchid:[153,50,204],
+ darkred:[139,0,0],
+ darksalmon:[233,150,122],
+ darkviolet:[148,0,211],
+ fuchsia:[255,0,255],
+ gold:[255,215,0],
+ green:[0,128,0],
+ indigo:[75,0,130],
+ khaki:[240,230,140],
+ lightblue:[173,216,230],
+ lightcyan:[224,255,255],
+ lightgreen:[144,238,144],
+ lightgrey:[211,211,211],
+ lightpink:[255,182,193],
+ lightyellow:[255,255,224],
+ lime:[0,255,0],
+ magenta:[255,0,255],
+ maroon:[128,0,0],
+ navy:[0,0,128],
+ olive:[128,128,0],
+ orange:[255,165,0],
+ pink:[255,192,203],
+ purple:[128,0,128],
+ violet:[128,0,128],
+ red:[255,0,0],
+ silver:[192,192,192],
+ white:[255,255,255],
+ yellow:[255,255,0]
+ };
+
+ function extractColor(element) {
+ var color, elem = element;
+ do {
+ color = elem.css("background-color").toLowerCase();
+ // keep going until we find an element that has color, or
+ // we hit the body
+ if (color != '' && color != 'transparent')
+ break;
+ elem = elem.parent();
+ } while (!$.nodeName(elem.get(0), "body"));
+
+ // catch Safari's way of signalling transparent
+ if (color == "rgba(0, 0, 0, 0)")
+ return "transparent";
+
+ return color;
+ }
+
+ // parse string, returns Color
+ function parseColor(str) {
+ var result;
+
+ // Look for rgb(num,num,num)
+ if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
+ return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
+
+ // Look for rgba(num,num,num,num)
+ if (result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
+ return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
+
+ // Look for rgb(num%,num%,num%)
+ if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
+ return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
+
+ // Look for rgba(num%,num%,num%,num)
+ if (result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
+ return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
+
+ // Look for #a0b1c2
+ if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
+ return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
+
+ // Look for #fff
+ if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
+ return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16));
+
+ // Otherwise, we're most likely dealing with a named color
+ var name = $.trim(str).toLowerCase();
+ if (name == "transparent")
+ return new Color(255, 255, 255, 0);
+ else {
+ result = lookupColors[name];
+ return new Color(result[0], result[1], result[2]);
+ }
+ }
+
+})(jQuery);
diff --git a/templates/content/js/jquery.flot.pack.js b/templates/content/js/jquery.flot.pack.js
new file mode 100644
index 00000000..a5714f12
--- /dev/null
+++ b/templates/content/js/jquery.flot.pack.js
@@ -0,0 +1 @@
+(function(F){function D(AO,e,f){var W=[],o={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{mode:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null},yaxis:{autoscaleMargin:0.02},x2axis:{autoscaleMargin:null},y2axis:{autoscaleMargin:0.02},points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff"},lines:{show:false,lineWidth:2,fill:false,fillColor:null},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left"},grid:{color:"#545454",backgroundColor:null,tickColor:"#dddddd",labelMargin:5,borderWidth:2,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},selection:{mode:null,color:"#e8cfac"},shadowSize:4},X=null,AP=null,AQ=null,g=null,AX=null,K=AO,AA={xaxis:{},yaxis:{},x2axis:{},y2axis:{}},m={left:0,right:0,top:0,bottom:0},AI=0,Z=0,N=0,AB=0,S={};this.setData=n;this.setupGrid=s;this.draw=AU;this.clearSelection=I;this.setSelection=AC;this.getCanvas=function(){return X};this.getPlotOffset=function(){return m};this.getData=function(){return W};this.getAxes=function(){return AA};this.highlight=AS;this.unhighlight=AH;y(f);n(e);j();s();AU();function n(AY){W=U(AY);c();t()}function U(Ac){var Aa=[];for(var AZ=0;AZ<Ac.length;++AZ){var Ab;if(Ac[AZ].data){Ab={};for(var AY in Ac[AZ]){Ab[AY]=Ac[AZ][AY]}}else{Ab={data:Ac[AZ]}}Aa.push(Ab)}return Aa}function y(AY){F.extend(true,o,AY);if(o.xaxis.noTicks&&o.xaxis.ticks==null){o.xaxis.ticks=o.xaxis.noTicks}if(o.yaxis.noTicks&&o.yaxis.ticks==null){o.yaxis.ticks=o.yaxis.noTicks}if(o.grid.coloredAreas){o.grid.markings=o.grid.coloredAreas}if(o.grid.coloredAreasColor){o.grid.markingsColor=o.grid.coloredAreasColor}}function c(){var Ad;var Ai=W.length,AY=[],Ab=[];for(Ad=0;Ad<W.length;++Ad){var Ah=W[Ad].color;if(Ah!=null){--Ai;if(typeof Ah=="number"){Ab.push(Ah)}else{AY.push(E(W[Ad].color))}}}for(Ad=0;Ad<Ab.length;++Ad){Ai=Math.max(Ai,Ab[Ad]+1)}var AZ=[],Ac=0;Ad=0;while(AZ.length<Ai){var Ag;if(o.colors.length==Ad){Ag=new G(100,100,100)}else{Ag=E(o.colors[Ad])}var Aa=Ac%2==1?-1:1;var Af=1+Aa*Math.ceil(Ac/2)*0.2;Ag.scale(Af,Af,Af);AZ.push(Ag);++Ad;if(Ad>=o.colors.length){Ad=0;++Ac}}var Ae=0,Aj;for(Ad=0;Ad<W.length;++Ad){Aj=W[Ad];if(Aj.color==null){Aj.color=AZ[Ae].toString();++Ae}else{if(typeof Aj.color=="number"){Aj.color=AZ[Aj.color].toString()}}Aj.lines=F.extend(true,{},o.lines,Aj.lines);Aj.points=F.extend(true,{},o.points,Aj.points);Aj.bars=F.extend(true,{},o.bars,Aj.bars);if(Aj.shadowSize==null){Aj.shadowSize=o.shadowSize}if(Aj.xaxis&&Aj.xaxis==2){Aj.xaxis=AA.x2axis}else{Aj.xaxis=AA.xaxis}if(Aj.yaxis&&Aj.yaxis==2){Aj.yaxis=AA.y2axis}else{Aj.yaxis=AA.yaxis}}}function t(){var Aa=Number.POSITIVE_INFINITY,AZ=Number.NEGATIVE_INFINITY,Ab;for(Ab in AA){AA[Ab].datamin=Aa;AA[Ab].datamax=AZ;AA[Ab].used=false}for(var Ae=0;Ae<W.length;++Ae){var Ad=W[Ae].data,Aj=W[Ae].xaxis,Ai=W[Ae].yaxis,AY=0,Ah=0;if(W[Ae].bars.show){AY=W[Ae].bars.align=="left"?0:-W[Ae].bars.barWidth/2;Ah=AY+W[Ae].bars.barWidth}Aj.used=Ai.used=true;for(var Ac=0;Ac<Ad.length;++Ac){if(Ad[Ac]==null){continue}var Ag=Ad[Ac][0],Af=Ad[Ac][1];if(Ag!=null&&!isNaN(Ag=+Ag)){if(Ag+AY<Aj.datamin){Aj.datamin=Ag+AY}if(Ag+Ah>Aj.datamax){Aj.datamax=Ag+Ah}}if(Af!=null&&!isNaN(Af=+Af)){if(Af<Ai.datamin){Ai.datamin=Af}if(Af>Ai.datamax){Ai.datamax=Af}}if(Ag==null||Af==null||isNaN(Ag)||isNaN(Af)){Ad[Ac]=null}}}for(Ab in AA){if(AA[Ab].datamin==Aa){AA[Ab].datamin=0}if(AA[Ab].datamax==AZ){AA[Ab].datamax=1}}}function j(){AI=K.width();Z=K.height();K.html("");K.css("position","relative");if(AI<=0||Z<=0){throw"Invalid dimensions for plot, width = "+AI+", height = "+Z}X=F('<canvas width="'+AI+'" height="'+Z+'"></canvas>').appendTo(K).get(0);if(F.browser.msie){X=window.G_vmlCanvasManager.initElement(X)}g=X.getContext("2d");AP=F('<canvas style="position:absolute;left:0px;top:0px;" width="'+AI+'" height="'+Z+'"></canvas>').appendTo(K).get(0);if(F.browser.msie){AP=window.G_vmlCanvasManager.initElement(AP)}AX=AP.getContext("2d");AQ=F([AP,X]);if(o.selection.mode!=null||o.grid.hoverable){AQ.each(function(){this.onmousemove=J});if(o.selection.mode!=null){AQ.mousedown(AN)}}if(o.grid.clickable){AQ.click(k)}}function s(){function AY(Ab,Aa){Q(Ab,Aa);L(Ab,Aa);w(Ab,Aa);if(Ab==AA.xaxis||Ab==AA.x2axis){Ab.p2c=function(Ac){return(Ac-Ab.min)*Ab.scale};Ab.c2p=function(Ac){return Ab.min+Ac/Ab.scale}}else{Ab.p2c=function(Ac){return(Ab.max-Ac)*Ab.scale};Ab.c2p=function(Ac){return Ab.max-Ac/Ab.scale}}}for(var AZ in AA){AY(AA[AZ],o[AZ])}AW();p();AV()}function Q(Ab,Ad){var Aa=Ad.min!=null?Ad.min:Ab.datamin;var AY=Ad.max!=null?Ad.max:Ab.datamax;if(AY-Aa==0){var AZ;if(AY==0){AZ=1}else{AZ=0.01}Aa-=AZ;AY+=AZ}else{var Ac=Ad.autoscaleMargin;if(Ac!=null){if(Ad.min==null){Aa-=(AY-Aa)*Ac;if(Aa<0&&Ab.datamin>=0){Aa=0}}if(Ad.max==null){AY+=(AY-Aa)*Ac;if(AY>0&&Ab.datamax<=0){AY=0}}}}Ab.min=Aa;Ab.max=AY}function L(Ad,Ag){var Ac;if(typeof Ag.ticks=="number"&&Ag.ticks>0){Ac=Ag.ticks}else{if(Ad==AA.xaxis||Ad==AA.x2axis){Ac=AI/100}else{Ac=Z/60}}var Al=(Ad.max-Ad.min)/Ac;var Ao,Ah,Aj,Ak,Af,Aa,AZ;if(Ag.mode=="time"){function An(Av,Ap,Ar){var Aq=function(Ax){Ax=""+Ax;return Ax.length==1?"0"+Ax:Ax};var Au=[];var At=false;if(Ar==null){Ar=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}for(var As=0;As<Ap.length;++As){var Aw=Ap.charAt(As);if(At){switch(Aw){case"h":Aw=""+Av.getUTCHours();break;case"H":Aw=Aq(Av.getUTCHours());break;case"M":Aw=Aq(Av.getUTCMinutes());break;case"S":Aw=Aq(Av.getUTCSeconds());break;case"d":Aw=""+Av.getUTCDate();break;case"m":Aw=""+(Av.getUTCMonth()+1);break;case"y":Aw=""+Av.getUTCFullYear();break;case"b":Aw=""+Ar[Av.getUTCMonth()];break}Au.push(Aw);At=false}else{if(Aw=="%"){At=true}else{Au.push(Aw)}}}return Au.join("")}var Ai={second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000};var Am=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]];var Ab=0;if(Ag.minTickSize!=null){if(typeof Ag.tickSize=="number"){Ab=Ag.tickSize}else{Ab=Ag.minTickSize[0]*Ai[Ag.minTickSize[1]]}}for(Af=0;Af<Am.length-1;++Af){if(Al<(Am[Af][0]*Ai[Am[Af][1]]+Am[Af+1][0]*Ai[Am[Af+1][1]])/2&&Am[Af][0]*Ai[Am[Af][1]]>=Ab){break}}Ao=Am[Af][0];Aj=Am[Af][1];if(Aj=="year"){Aa=Math.pow(10,Math.floor(Math.log(Al/Ai.year)/Math.LN10));AZ=(Al/Ai.year)/Aa;if(AZ<1.5){Ao=1}else{if(AZ<3){Ao=2}else{if(AZ<7.5){Ao=5}else{Ao=10}}}Ao*=Aa}if(Ag.tickSize){Ao=Ag.tickSize[0];Aj=Ag.tickSize[1]}Ah=function(Ar){var Aw=[],Au=Ar.tickSize[0],Ax=Ar.tickSize[1],Av=new Date(Ar.min);var Aq=Au*Ai[Ax];if(Ax=="second"){Av.setUTCSeconds(C(Av.getUTCSeconds(),Au))}if(Ax=="minute"){Av.setUTCMinutes(C(Av.getUTCMinutes(),Au))}if(Ax=="hour"){Av.setUTCHours(C(Av.getUTCHours(),Au))}if(Ax=="month"){Av.setUTCMonth(C(Av.getUTCMonth(),Au))}if(Ax=="year"){Av.setUTCFullYear(C(Av.getUTCFullYear(),Au))}Av.setUTCMilliseconds(0);if(Aq>=Ai.minute){Av.setUTCSeconds(0)}if(Aq>=Ai.hour){Av.setUTCMinutes(0)}if(Aq>=Ai.day){Av.setUTCHours(0)}if(Aq>=Ai.day*4){Av.setUTCDate(1)}if(Aq>=Ai.year){Av.setUTCMonth(0)}var Az=0,Ay=Number.NaN,As;do{As=Ay;Ay=Av.getTime();Aw.push({v:Ay,label:Ar.tickFormatter(Ay,Ar)});if(Ax=="month"){if(Au<1){Av.setUTCDate(1);var Ap=Av.getTime();Av.setUTCMonth(Av.getUTCMonth()+1);var At=Av.getTime();Av.setTime(Ay+Az*Ai.hour+(At-Ap)*Au);Az=Av.getUTCHours();Av.setUTCHours(0)}else{Av.setUTCMonth(Av.getUTCMonth()+Au)}}else{if(Ax=="year"){Av.setUTCFullYear(Av.getUTCFullYear()+Au)}else{Av.setTime(Ay+Aq)}}}while(Ay<Ar.max&&Ay!=As);return Aw};Ak=function(Ap,As){var At=new Date(Ap);if(Ag.timeformat!=null){return An(At,Ag.timeformat,Ag.monthNames)}var Aq=As.tickSize[0]*Ai[As.tickSize[1]];var Ar=As.max-As.min;if(Aq<Ai.minute){fmt="%h:%M:%S"}else{if(Aq<Ai.day){if(Ar<2*Ai.day){fmt="%h:%M"}else{fmt="%b %d %h:%M"}}else{if(Aq<Ai.month){fmt="%b %d"}else{if(Aq<Ai.year){if(Ar<Ai.year){fmt="%b"}else{fmt="%b %y"}}else{fmt="%y"}}}}return An(At,fmt,Ag.monthNames)}}else{var AY=Ag.tickDecimals;var Ae=-Math.floor(Math.log(Al)/Math.LN10);if(AY!=null&&Ae>AY){Ae=AY}Aa=Math.pow(10,-Ae);AZ=Al/Aa;if(AZ<1.5){Ao=1}else{if(AZ<3){Ao=2;if(AZ>2.25&&(AY==null||Ae+1<=AY)){Ao=2.5;++Ae}}else{if(AZ<7.5){Ao=5}else{Ao=10}}}Ao*=Aa;if(Ag.minTickSize!=null&&Ao<Ag.minTickSize){Ao=Ag.minTickSize}if(Ag.tickSize!=null){Ao=Ag.tickSize}Ad.tickDecimals=Math.max(0,(AY!=null)?AY:Ae);Ah=function(Ar){var At=[];var Au=C(Ar.min,Ar.tickSize),Aq=0,Ap=Number.NaN,As;do{As=Ap;Ap=Au+Aq*Ar.tickSize;At.push({v:Ap,label:Ar.tickFormatter(Ap,Ar)});++Aq}while(Ap<Ar.max&&Ap!=As);return At};Ak=function(Ap,Aq){return Ap.toFixed(Aq.tickDecimals)}}Ad.tickSize=Aj?[Ao,Aj]:Ao;Ad.tickGenerator=Ah;if(F.isFunction(Ag.tickFormatter)){Ad.tickFormatter=function(Ap,Aq){return""+Ag.tickFormatter(Ap,Aq)}}else{Ad.tickFormatter=Ak}if(Ag.labelWidth!=null){Ad.labelWidth=Ag.labelWidth}if(Ag.labelHeight!=null){Ad.labelHeight=Ag.labelHeight}}function w(Ac,Ae){Ac.ticks=[];if(!Ac.used){return }if(Ae.ticks==null){Ac.ticks=Ac.tickGenerator(Ac)}else{if(typeof Ae.ticks=="number"){if(Ae.ticks>0){Ac.ticks=Ac.tickGenerator(Ac)}}else{if(Ae.ticks){var Ad=Ae.ticks;if(F.isFunction(Ad)){Ad=Ad({min:Ac.min,max:Ac.max})}var Ab,AY;for(Ab=0;Ab<Ad.length;++Ab){var AZ=null;var Aa=Ad[Ab];if(typeof Aa=="object"){AY=Aa[0];if(Aa.length>1){AZ=Aa[1]}}else{AY=Aa}if(AZ==null){AZ=Ac.tickFormatter(AY,Ac)}Ac.ticks[Ab]={v:AY,label:AZ}}}}}if(Ae.autoscaleMargin!=null&&Ac.ticks.length>0){if(Ae.min==null){Ac.min=Math.min(Ac.min,Ac.ticks[0].v)}if(Ae.max==null&&Ac.ticks.length>1){Ac.max=Math.min(Ac.max,Ac.ticks[Ac.ticks.length-1].v)}}}function AW(){function AZ(Ac){if(Ac.labelWidth==null){Ac.labelWidth=AI/6}if(Ac.labelHeight==null){labels=[];for(i=0;i<Ac.ticks.length;++i){l=Ac.ticks[i].label;if(l){labels.push('<div class="tickLabel" style="float:left;width:'+Ac.labelWidth+'px">'+l+"</div>")}}Ac.labelHeight=0;if(labels.length>0){var Ab=F('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'+labels.join("")+'<div style="clear:left"></div></div>').appendTo(K);Ac.labelHeight=Ab.height();Ab.remove()}}}function AY(Ae){if(Ae.labelWidth==null||Ae.labelHeight==null){var Ad,Af=[],Ac;for(Ad=0;Ad<Ae.ticks.length;++Ad){Ac=Ae.ticks[Ad].label;if(Ac){Af.push('<div class="tickLabel">'+Ac+"</div>")}}if(Af.length>0){var Ab=F('<div style="position:absolute;top:-10000px;font-size:smaller">'+Af.join("")+"</div>").appendTo(K);if(Ae.labelWidth==null){Ae.labelWidth=Ab.width()}if(Ae.labelHeight==null){Ae.labelHeight=Ab.find("div").height()}Ab.remove()}if(Ae.labelWidth==null){Ae.labelWidth=0}if(Ae.labelHeight==null){Ae.labelHeight=0}}}AZ(AA.xaxis);AY(AA.yaxis);AZ(AA.x2axis);AY(AA.y2axis);var Aa=o.grid.borderWidth/2;for(i=0;i<W.length;++i){Aa=Math.max(Aa,2*(W[i].points.radius+W[i].points.lineWidth/2))}m.left=m.right=m.top=m.bottom=Aa;if(AA.xaxis.labelHeight>0){m.bottom=Math.max(Aa,AA.xaxis.labelHeight+o.grid.labelMargin)}if(AA.yaxis.labelWidth>0){m.left=Math.max(Aa,AA.yaxis.labelWidth+o.grid.labelMargin)}if(AA.x2axis.labelHeight>0){m.top=Math.max(Aa,AA.x2axis.labelHeight+o.grid.labelMargin)}if(AA.y2axis.labelWidth>0){m.right=Math.max(Aa,AA.y2axis.labelWidth+o.grid.labelMargin)}N=AI-m.left-m.right;AB=Z-m.bottom-m.top;AA.xaxis.scale=N/(AA.xaxis.max-AA.xaxis.min);AA.yaxis.scale=AB/(AA.yaxis.max-AA.yaxis.min);AA.x2axis.scale=N/(AA.x2axis.max-AA.x2axis.min);AA.y2axis.scale=AB/(AA.y2axis.max-AA.y2axis.min)}function AU(){a();for(var AY=0;AY<W.length;AY++){AK(W[AY])}}function V(AZ,Af){var Ac=Af+"axis",AY=Af+"2axis",Ab,Ae,Ad,Aa;if(AZ[Ac]){Ab=AA[Ac];Ae=AZ[Ac].from;Ad=AZ[Ac].to}else{if(AZ[AY]){Ab=AA[AY];Ae=AZ[AY].from;Ad=AZ[AY].to}else{Ab=AA[Ac];Ae=AZ[Af+"1"];Ad=AZ[Af+"2"]}}if(Ae!=null&&Ad!=null&&Ae>Ad){return{from:Ad,to:Ae,axis:Ab}}return{from:Ae,to:Ad,axis:Ab}}function a(){var Ac;g.save();g.clearRect(0,0,AI,Z);g.translate(m.left,m.top);if(o.grid.backgroundColor){g.fillStyle=o.grid.backgroundColor;g.fillRect(0,0,N,AB)}if(o.grid.markings){var AZ=o.grid.markings;if(F.isFunction(AZ)){AZ=AZ({xmin:AA.xaxis.min,xmax:AA.xaxis.max,ymin:AA.yaxis.min,ymax:AA.yaxis.max,xaxis:AA.xaxis,yaxis:AA.yaxis,x2axis:AA.x2axis,y2axis:AA.y2axis})}for(Ac=0;Ac<AZ.length;++Ac){var AY=AZ[Ac],Ae=V(AY,"x"),Ab=V(AY,"y");if(Ae.from==null){Ae.from=Ae.axis.min}if(Ae.to==null){Ae.to=Ae.axis.max}if(Ab.from==null){Ab.from=Ab.axis.min}if(Ab.to==null){Ab.to=Ab.axis.max}if(Ae.to<Ae.axis.min||Ae.from>Ae.axis.max||Ab.to<Ab.axis.min||Ab.from>Ab.axis.max){continue}Ae.from=Math.max(Ae.from,Ae.axis.min);Ae.to=Math.min(Ae.to,Ae.axis.max);Ab.from=Math.max(Ab.from,Ab.axis.min);Ab.to=Math.min(Ab.to,Ab.axis.max);if(Ae.from==Ae.to&&Ab.from==Ab.to){continue}Ae.from=Ae.axis.p2c(Ae.from);Ae.to=Ae.axis.p2c(Ae.to);Ab.from=Ab.axis.p2c(Ab.from);Ab.to=Ab.axis.p2c(Ab.to);if(Ae.from==Ae.to||Ab.from==Ab.to){g.strokeStyle=AY.color||o.grid.markingsColor;g.lineWidth=AY.lineWidth||o.grid.markingsLineWidth;g.moveTo(Math.floor(Ae.from),Math.floor(Ab.from));g.lineTo(Math.floor(Ae.to),Math.floor(Ab.to));g.stroke()}else{g.fillStyle=AY.color||o.grid.markingsColor;g.fillRect(Math.floor(Ae.from),Math.floor(Ab.to),Math.floor(Ae.to-Ae.from),Math.floor(Ab.from-Ab.to))}}}g.lineWidth=1;g.strokeStyle=o.grid.tickColor;g.beginPath();var Aa,Ad=AA.xaxis;for(Ac=0;Ac<Ad.ticks.length;++Ac){Aa=Ad.ticks[Ac].v;if(Aa<=Ad.min||Aa>=AA.xaxis.max){continue}g.moveTo(Math.floor(Ad.p2c(Aa))+g.lineWidth/2,0);g.lineTo(Math.floor(Ad.p2c(Aa))+g.lineWidth/2,AB)}Ad=AA.yaxis;for(Ac=0;Ac<Ad.ticks.length;++Ac){Aa=Ad.ticks[Ac].v;if(Aa<=Ad.min||Aa>=Ad.max){continue}g.moveTo(0,Math.floor(Ad.p2c(Aa))+g.lineWidth/2);g.lineTo(N,Math.floor(Ad.p2c(Aa))+g.lineWidth/2)}Ad=AA.x2axis;for(Ac=0;Ac<Ad.ticks.length;++Ac){Aa=Ad.ticks[Ac].v;if(Aa<=Ad.min||Aa>=Ad.max){continue}g.moveTo(Math.floor(Ad.p2c(Aa))+g.lineWidth/2,-5);g.lineTo(Math.floor(Ad.p2c(Aa))+g.lineWidth/2,5)}Ad=AA.y2axis;for(Ac=0;Ac<Ad.ticks.length;++Ac){Aa=Ad.ticks[Ac].v;if(Aa<=Ad.min||Aa>=Ad.max){continue}g.moveTo(N-5,Math.floor(Ad.p2c(Aa))+g.lineWidth/2);g.lineTo(N+5,Math.floor(Ad.p2c(Aa))+g.lineWidth/2)}g.stroke();if(o.grid.borderWidth){g.lineWidth=o.grid.borderWidth;g.strokeStyle=o.grid.color;g.lineJoin="round";g.strokeRect(0,0,N,AB)}g.restore()}function p(){K.find(".tickLabels").remove();var AY='<div class="tickLabels" style="font-size:smaller;color:'+o.grid.color+'">';function AZ(Ac,Ad){for(var Ab=0;Ab<Ac.ticks.length;++Ab){var Aa=Ac.ticks[Ab];if(!Aa.label||Aa.v<Ac.min||Aa.v>Ac.max){continue}AY+=Ad(Aa,Ac)}}AZ(AA.xaxis,function(Aa,Ab){return'<div style="position:absolute;top:'+(m.top+AB+o.grid.labelMargin)+"px;left:"+(m.left+Ab.p2c(Aa.v)-Ab.labelWidth/2)+"px;width:"+Ab.labelWidth+'px;text-align:center" class="tickLabel">'+Aa.label+"</div>"});AZ(AA.yaxis,function(Aa,Ab){return'<div style="position:absolute;top:'+(m.top+Ab.p2c(Aa.v)-Ab.labelHeight/2)+"px;right:"+(m.right+N+o.grid.labelMargin)+"px;width:"+Ab.labelWidth+'px;text-align:right" class="tickLabel">'+Aa.label+"</div>"});AZ(AA.x2axis,function(Aa,Ab){return'<div style="position:absolute;bottom:'+(m.bottom+AB+o.grid.labelMargin)+"px;left:"+(m.left+Ab.p2c(Aa.v)-Ab.labelWidth/2)+"px;width:"+Ab.labelWidth+'px;text-align:center" class="tickLabel">'+Aa.label+"</div>"});AZ(AA.y2axis,function(Aa,Ab){return'<div style="position:absolute;top:'+(m.top+Ab.p2c(Aa.v)-Ab.labelHeight/2)+"px;left:"+(m.left+N+o.grid.labelMargin)+"px;width:"+Ab.labelWidth+'px;text-align:left" class="tickLabel">'+Aa.label+"</div>"});AY+="</div>";K.append(AY)}function AK(AY){if(AY.lines.show||(!AY.bars.show&&!AY.points.show)){h(AY)}if(AY.bars.show){u(AY)}if(AY.points.show){v(AY)}}function h(Aa){function AZ(Aj,Ah,An,Am){var Ag,Ao=null,Ad=null,Ap=null;g.beginPath();for(var Ai=0;Ai<Aj.length;++Ai){Ag=Ao;Ao=Aj[Ai];if(Ag==null||Ao==null){continue}var Af=Ag[0],Al=Ag[1],Ae=Ao[0],Ak=Ao[1];if(Al<=Ak&&Al<Am.min){if(Ak<Am.min){continue}Af=(Am.min-Al)/(Ak-Al)*(Ae-Af)+Af;Al=Am.min}else{if(Ak<=Al&&Ak<Am.min){if(Al<Am.min){continue}Ae=(Am.min-Al)/(Ak-Al)*(Ae-Af)+Af;Ak=Am.min}}if(Al>=Ak&&Al>Am.max){if(Ak>Am.max){continue}Af=(Am.max-Al)/(Ak-Al)*(Ae-Af)+Af;Al=Am.max}else{if(Ak>=Al&&Ak>Am.max){if(Al>Am.max){continue}Ae=(Am.max-Al)/(Ak-Al)*(Ae-Af)+Af;Ak=Am.max}}if(Af<=Ae&&Af<An.min){if(Ae<An.min){continue}Al=(An.min-Af)/(Ae-Af)*(Ak-Al)+Al;Af=An.min}else{if(Ae<=Af&&Ae<An.min){if(Af<An.min){continue}Ak=(An.min-Af)/(Ae-Af)*(Ak-Al)+Al;Ae=An.min}}if(Af>=Ae&&Af>An.max){if(Ae>An.max){continue}Al=(An.max-Af)/(Ae-Af)*(Ak-Al)+Al;Af=An.max}else{if(Ae>=Af&&Ae>An.max){if(Af>An.max){continue}Ak=(An.max-Af)/(Ae-Af)*(Ak-Al)+Al;Ae=An.max}}if(Ad!=An.p2c(Af)||Ap!=Am.p2c(Al)+Ah){g.moveTo(An.p2c(Af),Am.p2c(Al)+Ah)}Ad=An.p2c(Ae);Ap=Am.p2c(Ak)+Ah;g.lineTo(Ad,Ap)}g.stroke()}function Ab(Aj,Aq,Ao){var Ah,Ar=null;var Ad=Math.min(Math.max(0,Ao.min),Ao.max);var Am,Ag=0;var Ap=false;for(var Ai=0;Ai<Aj.length;++Ai){Ah=Ar;Ar=Aj[Ai];if(Ap&&Ah!=null&&Ar==null){g.lineTo(Aq.p2c(Ag),Ao.p2c(Ad));g.fill();Ap=false;continue}if(Ah==null||Ar==null){continue}var Af=Ah[0],An=Ah[1],Ae=Ar[0],Al=Ar[1];if(Af<=Ae&&Af<Aq.min){if(Ae<Aq.min){continue}An=(Aq.min-Af)/(Ae-Af)*(Al-An)+An;Af=Aq.min}else{if(Ae<=Af&&Ae<Aq.min){if(Af<Aq.min){continue}Al=(Aq.min-Af)/(Ae-Af)*(Al-An)+An;Ae=Aq.min}}if(Af>=Ae&&Af>Aq.max){if(Ae>Aq.max){continue}An=(Aq.max-Af)/(Ae-Af)*(Al-An)+An;Af=Aq.max}else{if(Ae>=Af&&Ae>Aq.max){if(Af>Aq.max){continue}Al=(Aq.max-Af)/(Ae-Af)*(Al-An)+An;Ae=Aq.max}}if(!Ap){g.beginPath();g.moveTo(Aq.p2c(Af),Ao.p2c(Ad));Ap=true}if(An>=Ao.max&&Al>=Ao.max){g.lineTo(Aq.p2c(Af),Ao.p2c(Ao.max));g.lineTo(Aq.p2c(Ae),Ao.p2c(Ao.max));continue}else{if(An<=Ao.min&&Al<=Ao.min){g.lineTo(Aq.p2c(Af),Ao.p2c(Ao.min));g.lineTo(Aq.p2c(Ae),Ao.p2c(Ao.min));continue}}var As=Af,Ak=Ae;if(An<=Al&&An<Ao.min&&Al>=Ao.min){Af=(Ao.min-An)/(Al-An)*(Ae-Af)+Af;An=Ao.min}else{if(Al<=An&&Al<Ao.min&&An>=Ao.min){Ae=(Ao.min-An)/(Al-An)*(Ae-Af)+Af;Al=Ao.min}}if(An>=Al&&An>Ao.max&&Al<=Ao.max){Af=(Ao.max-An)/(Al-An)*(Ae-Af)+Af;An=Ao.max}else{if(Al>=An&&Al>Ao.max&&An<=Ao.max){Ae=(Ao.max-An)/(Al-An)*(Ae-Af)+Af;Al=Ao.max}}if(Af!=As){if(An<=Ao.min){Am=Ao.min}else{Am=Ao.max}g.lineTo(Aq.p2c(As),Ao.p2c(Am));g.lineTo(Aq.p2c(Af),Ao.p2c(Am))}g.lineTo(Aq.p2c(Af),Ao.p2c(An));g.lineTo(Aq.p2c(Ae),Ao.p2c(Al));if(Ae!=Ak){if(Al<=Ao.min){Am=Ao.min}else{Am=Ao.max}g.lineTo(Aq.p2c(Ak),Ao.p2c(Am));g.lineTo(Aq.p2c(Ae),Ao.p2c(Am))}Ag=Math.max(Ae,Ak)}if(Ap){g.lineTo(Aq.p2c(Ag),Ao.p2c(Ad));g.fill()}}g.save();g.translate(m.left,m.top);g.lineJoin="round";var Ac=Aa.lines.lineWidth;var AY=Aa.shadowSize;if(AY>0){g.lineWidth=AY/2;g.strokeStyle="rgba(0,0,0,0.1)";AZ(Aa.data,Ac/2+AY/2+g.lineWidth/2,Aa.xaxis,Aa.yaxis);g.lineWidth=AY/2;g.strokeStyle="rgba(0,0,0,0.2)";AZ(Aa.data,Ac/2+g.lineWidth/2,Aa.xaxis,Aa.yaxis)}g.lineWidth=Ac;g.strokeStyle=Aa.color;AD(Aa.lines,Aa.color);if(Aa.lines.fill){Ab(Aa.data,Aa.xaxis,Aa.yaxis)}AZ(Aa.data,0,Aa.xaxis,Aa.yaxis);g.restore()}function v(AZ){function Ac(Ag,Ae,Ah,Ak,Ai){for(var Af=0;Af<Ag.length;++Af){if(Ag[Af]==null){continue}var Ad=Ag[Af][0],Aj=Ag[Af][1];if(Ad<Ak.min||Ad>Ak.max||Aj<Ai.min||Aj>Ai.max){continue}g.beginPath();g.arc(Ak.p2c(Ad),Ai.p2c(Aj),Ae,0,2*Math.PI,true);if(Ah){g.fill()}g.stroke()}}function Ab(Ag,Ai,Ae,Ak,Ah){for(var Af=0;Af<Ag.length;++Af){if(Ag[Af]==null){continue}var Ad=Ag[Af][0],Aj=Ag[Af][1];if(Ad<Ak.min||Ad>Ak.max||Aj<Ah.min||Aj>Ah.max){continue}g.beginPath();g.arc(Ak.p2c(Ad),Ah.p2c(Aj)+Ai,Ae,0,Math.PI,false);g.stroke()}}g.save();g.translate(m.left,m.top);var Aa=AZ.lines.lineWidth;var AY=AZ.shadowSize;if(AY>0){g.lineWidth=AY/2;g.strokeStyle="rgba(0,0,0,0.1)";Ab(AZ.data,AY/2+g.lineWidth/2,AZ.points.radius,AZ.xaxis,AZ.yaxis);g.lineWidth=AY/2;g.strokeStyle="rgba(0,0,0,0.2)";Ab(AZ.data,g.lineWidth/2,AZ.points.radius,AZ.xaxis,AZ.yaxis)}g.lineWidth=AZ.points.lineWidth;g.strokeStyle=AZ.color;AD(AZ.points,AZ.color);Ac(AZ.data,AZ.points.radius,AZ.points.fill,AZ.xaxis,AZ.yaxis);g.restore()}function AM(Aj,Ah,Ac,Ai,Aa,Ao,An,Ak,Af){var Am=true,Ae=true,Ab=true,Ad=false,AZ=Aj+Ac,Al=Aj+Ai,AY=0,Ag=Ah;if(Ag<AY){Ag=0;AY=Ah;Ad=true;Ab=false}if(Al<An.min||AZ>An.max||Ag<Ak.min||AY>Ak.max){return }if(AZ<An.min){AZ=An.min;Am=false}if(Al>An.max){Al=An.max;Ae=false}if(AY<Ak.min){AY=Ak.min;Ad=false}if(Ag>Ak.max){Ag=Ak.max;Ab=false}if(Ao){Af.beginPath();Af.moveTo(An.p2c(AZ),Ak.p2c(AY)+Aa);Af.lineTo(An.p2c(AZ),Ak.p2c(Ag)+Aa);Af.lineTo(An.p2c(Al),Ak.p2c(Ag)+Aa);Af.lineTo(An.p2c(Al),Ak.p2c(AY)+Aa);Af.fill()}if(Am||Ae||Ab||Ad){Af.beginPath();AZ=An.p2c(AZ);AY=Ak.p2c(AY);Al=An.p2c(Al);Ag=Ak.p2c(Ag);Af.moveTo(AZ,AY+Aa);if(Am){Af.lineTo(AZ,Ag+Aa)}else{Af.moveTo(AZ,Ag+Aa)}if(Ab){Af.lineTo(Al,Ag+Aa)}else{Af.moveTo(Al,Ag+Aa)}if(Ae){Af.lineTo(Al,AY+Aa)}else{Af.moveTo(Al,AY+Aa)}if(Ad){Af.lineTo(AZ,AY+Aa)}else{Af.moveTo(AZ,AY+Aa)}Af.stroke()}}function u(Aa){function AZ(Ae,Ab,Ad,Ah,Af,Ai,Ag){for(var Ac=0;Ac<Ae.length;Ac++){if(Ae[Ac]==null){continue}AM(Ae[Ac][0],Ae[Ac][1],Ab,Ad,Ah,Af,Ai,Ag,g)}}g.save();g.translate(m.left,m.top);g.lineJoin="round";g.lineWidth=Aa.bars.lineWidth;g.strokeStyle=Aa.color;AD(Aa.bars,Aa.color);var AY=Aa.bars.align=="left"?0:-Aa.bars.barWidth/2;AZ(Aa.data,AY,AY+Aa.bars.barWidth,0,Aa.bars.fill,Aa.xaxis,Aa.yaxis);g.restore()}function AD(Aa,AY){var AZ=Aa.fill;if(!AZ){return }if(Aa.fillColor){g.fillStyle=Aa.fillColor}else{var Ab=E(AY);Ab.a=typeof AZ=="number"?AZ:0.4;Ab.normalize();g.fillStyle=Ab.toString()}}function AV(){K.find(".legend").remove();if(!o.legend.show){return }var Ae=[];var Ac=false;for(i=0;i<W.length;++i){if(!W[i].label){continue}if(i%o.legend.noColumns==0){if(Ac){Ae.push("</tr>")}Ae.push("<tr>");Ac=true}var Ag=W[i].label;if(o.legend.labelFormatter!=null){Ag=o.legend.labelFormatter(Ag)}Ae.push('<td class="legendColorBox"><div style="border:1px solid '+o.legend.labelBoxBorderColor+';padding:1px"><div style="width:14px;height:10px;background-color:'+W[i].color+';overflow:hidden"></div></div></td><td class="legendLabel">'+Ag+"</td>")}if(Ac){Ae.push("</tr>")}if(Ae.length==0){return }var Ai='<table style="font-size:smaller;color:'+o.grid.color+'">'+Ae.join("")+"</table>";if(o.legend.container!=null){o.legend.container.html(Ai)}else{var Af="";var AZ=o.legend.position,Aa=o.legend.margin;if(AZ.charAt(0)=="n"){Af+="top:"+(Aa+m.top)+"px;"}else{if(AZ.charAt(0)=="s"){Af+="bottom:"+(Aa+m.bottom)+"px;"}}if(AZ.charAt(1)=="e"){Af+="right:"+(Aa+m.right)+"px;"}else{if(AZ.charAt(1)=="w"){Af+="left:"+(Aa+m.left)+"px;"}}var Ah=F('<div class="legend">'+Ai.replace('style="','style="position:absolute;'+Af+";")+"</div>").appendTo(K);if(o.legend.backgroundOpacity!=0){var Ad=o.legend.backgroundColor;if(Ad==null){var Ab;if(o.grid.backgroundColor){Ab=o.grid.backgroundColor}else{Ab=A(Ah)}Ad=E(Ab).adjust(null,null,null,1).toString()}var AY=Ah.children();F('<div style="position:absolute;width:'+AY.width()+"px;height:"+AY.height()+"px;"+Af+"background-color:"+Ad+';"> </div>').prependTo(Ah).css("opacity",o.legend.backgroundOpacity)}}}var AG={pageX:null,pageY:null},d={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false},AF=[],P=false,O=null,z=null;function AT(Ae,Ac){var Al=o.grid.mouseActiveRadius,Ar=Al*Al+1,At=null,An=false;function Ai(Ay,Ax){return{datapoint:W[Ay].data[Ax],dataIndex:Ax,series:W[Ay],seriesIndex:Ay}}for(var Aq=0;Aq<W.length;++Aq){var Aw=W[Aq].data,Ad=W[Aq].xaxis,Ab=W[Aq].yaxis,Am=Ad.c2p(Ae),Ak=Ab.c2p(Ac),AZ=Al/Ad.scale,AY=Al/Ab.scale,Av=W[Aq].bars.show,Au=!(W[Aq].bars.show&&!(W[Aq].lines.show||W[Aq].points.show)),Aa=W[Aq].bars.align=="left"?0:-W[Aq].bars.barWidth/2,As=Aa+W[Aq].bars.barWidth;for(var Ap=0;Ap<Aw.length;++Ap){if(Aw[Ap]==null){continue}var Ag=Aw[Ap][0],Af=Aw[Ap][1];if(Av){if(!An&&Am>=Ag+Aa&&Am<=Ag+As&&Ak>=Math.min(0,Af)&&Ak<=Math.max(0,Af)){At=Ai(Aq,Ap)}}if(Au){if((Ag-Am>AZ||Ag-Am<-AZ)||(Af-Ak>AY||Af-Ak<-AY)){continue}var Aj=Math.abs(Ad.p2c(Ag)-Ae),Ah=Math.abs(Ab.p2c(Af)-Ac),Ao=Aj*Aj+Ah*Ah;if(Ao<Ar){Ar=Ao;An=true;At=Ai(Aq,Ap)}}}}return At}function J(AZ){var Aa=AZ||window.event;if(Aa.pageX==null&&Aa.clientX!=null){var Ab=document.documentElement,AY=document.body;AG.pageX=Aa.clientX+(Ab&&Ab.scrollLeft||AY.scrollLeft||0);AG.pageY=Aa.clientY+(Ab&&Ab.scrollTop||AY.scrollTop||0)}else{AG.pageX=Aa.pageX;AG.pageY=Aa.pageY}if(o.grid.hoverable&&!z){z=setTimeout(R,100)}if(d.active){AL(AG)}}function AN(AY){if(AY.which!=1){return }document.body.focus();if(document.onselectstart!==undefined&&S.onselectstart==null){S.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&S.ondrag==null){S.ondrag=document.ondrag;document.ondrag=function(){return false}}AR(d.first,AY);AG.pageX=null;d.active=true;F(document).one("mouseup",Y)}function k(AY){if(P){P=false;return }M("plotclick",AY)}function R(){M("plothover",AG);z=null}function M(AZ,AY){var Aa=AQ.offset(),Af={pageX:AY.pageX,pageY:AY.pageY},Ad=AY.pageX-Aa.left-m.left,Ab=AY.pageY-Aa.top-m.top;if(AA.xaxis.used){Af.x=AA.xaxis.c2p(Ad)}if(AA.yaxis.used){Af.y=AA.yaxis.c2p(Ab)}if(AA.x2axis.used){Af.x2=AA.x2axis.c2p(Ad)}if(AA.y2axis.used){Af.y2=AA.y2axis.c2p(Ab)}var Ag=AT(Ad,Ab);if(Ag){Ag.pageX=parseInt(Ag.series.xaxis.p2c(Ag.datapoint[0])+Aa.left+m.left);Ag.pageY=parseInt(Ag.series.yaxis.p2c(Ag.datapoint[1])+Aa.top+m.top)}if(o.grid.autoHighlight){for(var Ac=0;Ac<AF.length;++Ac){var Ae=AF[Ac];if(Ae.auto&&!(Ag&&Ae.series==Ag.series&&Ae.point==Ag.datapoint)){AH(Ae.series,Ae.point)}}if(Ag){AS(Ag.series,Ag.datapoint,true)}}K.trigger(AZ,[Af,Ag])}function x(){if(!O){O=setTimeout(T,50)}}function T(){O=null;AX.save();AX.clearRect(0,0,AI,Z);AX.translate(m.left,m.top);var Ab,Aa;for(Ab=0;Ab<AF.length;++Ab){Aa=AF[Ab];if(Aa.series.bars.show){AJ(Aa.series,Aa.point)}else{AE(Aa.series,Aa.point)}}AX.restore();if(d.show&&b()){AX.strokeStyle=E(o.selection.color).scale(null,null,null,0.8).toString();AX.lineWidth=1;g.lineJoin="round";AX.fillStyle=E(o.selection.color).scale(null,null,null,0.4).toString();var AY=Math.min(d.first.x,d.second.x),Ad=Math.min(d.first.y,d.second.y),AZ=Math.abs(d.second.x-d.first.x),Ac=Math.abs(d.second.y-d.first.y);AX.fillRect(AY+m.left,Ad+m.top,AZ,Ac);AX.strokeRect(AY+m.left,Ad+m.top,AZ,Ac)}}function AS(Aa,AY,Ab){if(typeof Aa=="number"){Aa=W[Aa]}if(typeof AY=="number"){AY=Aa.data[AY]}var AZ=q(Aa,AY);if(AZ==-1){AF.push({series:Aa,point:AY,auto:Ab});x()}else{if(!Ab){AF[AZ].auto=false}}}function AH(Aa,AY){if(typeof Aa=="number"){Aa=W[Aa]}if(typeof AY=="number"){AY=Aa.data[AY]}var AZ=q(Aa,AY);if(AZ!=-1){AF.splice(AZ,1);x()}}function q(Aa,Ab){for(var AY=0;AY<AF.length;++AY){var AZ=AF[AY];if(AZ.series==Aa&&AZ.point[0]==Ab[0]&&AZ.point[1]==Ab[1]){return AY}}return -1}function AE(Ab,Aa){var AZ=Aa[0],Af=Aa[1],Ae=Ab.xaxis,Ad=Ab.yaxis;if(AZ<Ae.min||AZ>Ae.max||Af<Ad.min||Af>Ad.max){return }var Ac=Ab.points.radius+Ab.points.lineWidth/2;AX.lineWidth=Ac;AX.strokeStyle=E(Ab.color).scale(1,1,1,0.5).toString();var AY=1.5*Ac;AX.beginPath();AX.arc(Ae.p2c(AZ),Ad.p2c(Af),AY,0,2*Math.PI,true);AX.stroke()}function AJ(Aa,AY){AX.lineJoin="round";AX.lineWidth=Aa.bars.lineWidth;AX.strokeStyle=E(Aa.color).scale(1,1,1,0.5).toString();AX.fillStyle=E(Aa.color).scale(1,1,1,0.5).toString();var AZ=Aa.bars.align=="left"?0:-Aa.bars.barWidth/2;AM(AY[0],AY[1],AZ,AZ+Aa.bars.barWidth,0,true,Aa.xaxis,Aa.yaxis,AX)}function r(){var AZ=Math.min(d.first.x,d.second.x),AY=Math.max(d.first.x,d.second.x),Ab=Math.max(d.first.y,d.second.y),Aa=Math.min(d.first.y,d.second.y);var Ac={};if(AA.xaxis.used){Ac.xaxis={from:AA.xaxis.c2p(AZ),to:AA.xaxis.c2p(AY)}}if(AA.x2axis.used){Ac.x2axis={from:AA.x2axis.c2p(AZ),to:AA.x2axis.c2p(AY)}}if(AA.yaxis.used){Ac.yaxis={from:AA.yaxis.c2p(Ab),to:AA.yaxis.c2p(Aa)}}if(AA.y2axis.used){Ac.yaxis={from:AA.y2axis.c2p(Ab),to:AA.y2axis.c2p(Aa)}}K.trigger("plotselected",[Ac]);if(AA.xaxis.used&&AA.yaxis.used){K.trigger("selected",[{x1:Ac.xaxis.from,y1:Ac.yaxis.from,x2:Ac.xaxis.to,y2:Ac.yaxis.to}])}}function Y(AY){if(document.onselectstart!==undefined){document.onselectstart=S.onselectstart}if(document.ondrag!==undefined){document.ondrag=S.ondrag}d.active=false;AL(AY);if(b()){r();P=true}return false}function AR(Aa,AY){var AZ=AQ.offset();if(o.selection.mode=="y"){if(Aa==d.first){Aa.x=0}else{Aa.x=N}}else{Aa.x=AY.pageX-AZ.left-m.left;Aa.x=Math.min(Math.max(0,Aa.x),N)}if(o.selection.mode=="x"){if(Aa==d.first){Aa.y=0}else{Aa.y=AB}}else{Aa.y=AY.pageY-AZ.top-m.top;Aa.y=Math.min(Math.max(0,Aa.y),AB)}}function AL(AY){if(AY.pageX==null){return }AR(d.second,AY);if(b()){d.show=true;x()}else{I()}}function I(){if(d.show){d.show=false;x()}}function AC(AZ,AY){var Aa;if(o.selection.mode=="y"){d.first.x=0;d.second.x=N}else{Aa=V(AZ,"x");d.first.x=Aa.axis.p2c(Aa.from);d.second.x=Aa.axis.p2c(Aa.to)}if(o.selection.mode=="x"){d.first.y=0;d.second.y=AB}else{Aa=V(AZ,"y");d.first.y=Aa.axis.p2c(Aa.from);d.second.y=Aa.axis.p2c(Aa.to)}d.show=true;x();if(!AY){r()}}function b(){var AY=5;return Math.abs(d.second.x-d.first.x)>=AY&&Math.abs(d.second.y-d.first.y)>=AY}}F.plot=function(L,J,I){var K=new D(L,J,I);return K};function C(J,I){return I*Math.floor(J/I)}function H(J,K,I){if(K<J){return K}else{if(K>I){return I}else{return K}}}function G(O,N,J,L){var M=["r","g","b","a"];var I=4;while(-1<--I){this[M[I]]=arguments[I]||((I==3)?1:0)}this.toString=function(){if(this.a>=1){return"rgb("+[this.r,this.g,this.b].join(",")+")"}else{return"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"}};this.scale=function(R,Q,S,P){I=4;while(-1<--I){if(arguments[I]!=null){this[M[I]]*=arguments[I]}}return this.normalize()};this.adjust=function(R,Q,S,P){I=4;while(-1<--I){if(arguments[I]!=null){this[M[I]]+=arguments[I]}}return this.normalize()};this.clone=function(){return new G(this.r,this.b,this.g,this.a)};var K=function(Q,P,R){return Math.max(Math.min(Q,R),P)};this.normalize=function(){this.r=K(parseInt(this.r),0,255);this.g=K(parseInt(this.g),0,255);this.b=K(parseInt(this.b),0,255);this.a=K(this.a,0,1);return this};this.normalize()}var B={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};function A(J){var I,K=J;do{I=K.css("background-color").toLowerCase();if(I!=""&&I!="transparent"){break}K=K.parent()}while(!F.nodeName(K.get(0),"body"));if(I=="rgba(0, 0, 0, 0)"){return"transparent"}return I}function E(K){var I;if(I=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(K)){return new G(parseInt(I[1],10),parseInt(I[2],10),parseInt(I[3],10))}if(I=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(K)){return new G(parseInt(I[1],10),parseInt(I[2],10),parseInt(I[3],10),parseFloat(I[4]))}if(I=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(K)){return new G(parseFloat(I[1])*2.55,parseFloat(I[2])*2.55,parseFloat(I[3])*2.55)}if(I=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(K)){return new G(parseFloat(I[1])*2.55,parseFloat(I[2])*2.55,parseFloat(I[3])*2.55,parseFloat(I[4]))}if(I=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(K)){return new G(parseInt(I[1],16),parseInt(I[2],16),parseInt(I[3],16))}if(I=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(K)){return new G(parseInt(I[1]+I[1],16),parseInt(I[2]+I[2],16),parseInt(I[3]+I[3],16))}var J=F.trim(K).toLowerCase();if(J=="transparent"){return new G(255,255,255,0)}else{I=B[J];return new G(I[0],I[1],I[2])}}})(jQuery); \ No newline at end of file
diff --git a/templates/content/js/jquery.i18n.js b/templates/content/js/jquery.i18n.js
new file mode 100644
index 00000000..0a155a31
--- /dev/null
+++ b/templates/content/js/jquery.i18n.js
@@ -0,0 +1,133 @@
+/*
+ * jQuery i18n plugin
+ * @requires jQuery v1.1 or later
+ *
+ * Examples at: http://recurser.com/articles/2008/02/21/jquery-i18n-translation-plugin/
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Based on 'javascript i18n that almost doesn't suck' by markos
+ * http://markos.gaivo.net/blog/?p=100
+ *
+ * Revision: $Id$
+ * Version: 1.0.0 Feb-10-2008
+ */
+ (function($) {
+/**
+ * i18n provides a mechanism for translating strings using a jscript dictionary.
+ *
+ */
+
+
+/*
+ * i18n property list
+ */
+$.i18n = {
+
+/**
+ * setDictionary()
+ * Initialise the dictionary and translate nodes
+ *
+ * @param property_list i18n_dict : The dictionary to use for translation
+ */
+ setDictionary: function(i18n_dict) {
+ i18n_dict = i18n_dict;
+ },
+
+/**
+ * _()
+ * The actual translation function. Looks the given string up in the
+ * dictionary and returns the translation if one exists. If a translation
+ * is not found, returns the original word
+ *
+ * @param string str : The string to translate
+ * @param property_list params : params for using printf() on the string
+ * @return string : Translated word
+ *
+ */
+ _: function (str, params) {
+ var transl = str;
+ if (i18n_dict&& i18n_dict[str]) {
+ transl = i18n_dict[str];
+ }
+ return this.printf(transl, params);
+ },
+
+/**
+ * toEntity()
+ * Change non-ASCII characters to entity representation
+ *
+ * @param string str : The string to transform
+ * @return string result : Original string with non-ASCII content converted to entities
+ *
+ */
+ toEntity: function (str) {
+ var result = '';
+ for (var i=0;i<str.length; i++) {
+ if (str.charCodeAt(i) > 128)
+ result += "&#"+str.charCodeAt(i)+";";
+ else
+ result += str.charAt(i);
+ }
+ return result;
+ },
+
+/**
+ * stripStr()
+ *
+ * @param string str : The string to strip
+ * @return string result : Stripped string
+ *
+ */
+ stripStr: function(str) {
+ return str.replace(/^\s*/, "").replace(/\s*$/, "");
+ },
+
+/**
+ * stripStrML()
+ *
+ * @param string str : The multi-line string to strip
+ * @return string result : Stripped string
+ *
+ */
+ stripStrML: function(str) {
+ // Split because m flag doesn't exist before JS1.5 and we need to
+ // strip newlines anyway
+ var parts = str.split('\n');
+ for (var i=0; i<parts.length; i++)
+ parts[i] = stripStr(parts[i]);
+
+ // Don't join with empty strings, because it "concats" words
+ // And strip again
+ return stripStr(parts.join(" "));
+ },
+
+/*
+ * printf()
+ * C-printf like function, which substitutes %s with parameters
+ * given in list. %%s is used to escape %s.
+ *
+ * Doesn't work in IE5.0 (splice)
+ *
+ * @param string S : string to perform printf on.
+ * @param string L : Array of arguments for printf()
+ */
+ printf: function(S, L) {
+ if (!L) return S;
+
+ var nS = "";
+ var tS = S.split("%s");
+
+ for(var i=0; i<L.length; i++) {
+ if (tS[i].lastIndexOf('%') == tS[i].length-1 && i != L.length-1)
+ tS[i] += "s"+tS.splice(i+1,1)[0];
+ nS += tS[i] + L[i];
+ }
+ return nS + tS[tS.length-1];
+ }
+
+};
+
+
+})(jQuery);
diff --git a/templates/content/js/jquery.openid.js b/templates/content/js/jquery.openid.js
new file mode 100644
index 00000000..17e58e04
--- /dev/null
+++ b/templates/content/js/jquery.openid.js
@@ -0,0 +1,176 @@
+/*
+openid login boxes
+*/
+var providers_large = {
+ google: {
+ name: 'Google',
+ url: 'https://www.google.com/accounts/o8/id'
+ },
+ yahoo: {
+ name: 'Yahoo',
+ url: 'http://yahoo.com/'
+ },
+ aol: {
+ name: 'AOL',
+ label: 'Enter your AOL screenname.',
+ url: 'http://openid.aol.com/{username}'
+ },
+ openid: {
+ name: 'OpenID',
+ label: 'Enter your OpenID.',
+ url: 'http://'
+ }
+};
+var providers_small = {
+ myopenid: {
+ name: 'MyOpenID',
+ label: 'Enter your MyOpenID username.',
+ url: 'http://{username}.myopenid.com/'
+ },
+ livejournal: {
+ name: 'LiveJournal',
+ label: 'Enter your Livejournal username.',
+ url: 'http://{username}.livejournal.com/'
+ },
+ flickr: {
+ name: 'Flickr',
+ label: 'Enter your Flickr username.',
+ url: 'http://flickr.com/{username}/'
+ },
+ technorati: {
+ name: 'Technorati',
+ label: 'Enter your Technorati username.',
+ url: 'http://technorati.com/people/technorati/{username}/'
+ },
+ wordpress: {
+ name: 'Wordpress',
+ label: 'Enter your Wordpress.com username.',
+ url: 'http://{username}.wordpress.com/'
+ },
+ blogger: {
+ name: 'Blogger',
+ label: 'Your Blogger account',
+ url: 'http://{username}.blogspot.com/'
+ },
+ verisign: {
+ name: 'Verisign',
+ label: 'Your Verisign username',
+ url: 'http://{username}.pip.verisignlabs.com/'
+ },
+ vidoop: {
+ name: 'Vidoop',
+ label: 'Your Vidoop username',
+ url: 'http://{username}.myvidoop.com/'
+ },
+ verisign: {
+ name: 'Verisign',
+ label: 'Your Verisign username',
+ url: 'http://{username}.pip.verisignlabs.com/'
+ },
+ claimid: {
+ name: 'ClaimID',
+ label: 'Your ClaimID username',
+ url: 'http://claimid.com/{username}'
+ }
+};
+var providers = $.extend({}, providers_large, providers_small);
+
+var openid = {
+
+ cookie_expires: 6*30, // 6 months.
+ cookie_name: 'openid_provider',
+ cookie_path: '/',
+
+ img_path: '/content/images/openid/',
+
+ input_id: null,
+ provider_url: null,
+
+ init: function(input_id) {
+
+ var openid_btns = $('#openid_btns');
+ this.input_id = input_id;
+
+ $('#openid_choice').show();
+ //$('#openid_input_area').empty();
+
+ // add box for each provider
+ for (id in providers_large) {
+ openid_btns.append(this.getBoxHTML(providers_large[id], 'large', '.gif'));
+ }
+ if (providers_small) {
+ openid_btns.append('<br/>');
+ for (id in providers_small) {
+ openid_btns.append(this.getBoxHTML(providers_small[id], 'small', '.ico'));
+ }
+ }
+
+ var box_id = this.readCookie();
+ if (box_id) {
+ this.signin(box_id, true);
+ }
+ },
+ getBoxHTML: function(provider, box_size, image_ext) {
+
+ var box_id = provider["name"].toLowerCase();
+ return '<a title="'+provider["name"]+'" href="javascript: openid.signin(\''+ box_id +'\');"' +
+ ' style="background: #FFF url(' + this.img_path + box_id + image_ext+') no-repeat center center" ' + 'class="' + box_id + ' openid_' + box_size + '_btn"></a>';
+
+ },
+ /* Provider image click */
+ signin: function(box_id, onload) {
+ var provider = providers[box_id];
+ if (! provider) {
+ return;
+ }
+ this.highlight(box_id);
+ this.setCookie(box_id);
+
+ $('#'+this.input_id).val(provider['url']);
+ var input = $('#'+this.input_id);
+ if(document.selection){
+ var r = document.all.openid_url.createTextRange();
+ var res = r.findText("{username}");
+ if(res)
+ r.select();
+
+ }
+ else {
+ var text = input.val();
+ var searchText = "{username}";
+ var posStart = text.indexOf(searchText);
+ if(posStart > -1){
+ input.focus();
+ document.getElementById(this.input_id).setSelectionRange(posStart, posStart + searchText.length);
+ }
+ }
+ },
+
+ highlight: function (box_id) {
+ // remove previous highlight.
+ var highlight = $('#openid_highlight');
+ if (highlight) {
+ highlight.replaceWith($('#openid_highlight a')[0]);
+ }
+ // add new highlight.
+ $('.'+box_id).wrap('<div id="openid_highlight"></div>');
+ },
+
+ setCookie: function (value) {
+ var date = new Date();
+ date.setTime(date.getTime()+(this.cookie_expires*24*60*60*1000));
+ var expires = "; expires="+date.toGMTString();
+ document.cookie = this.cookie_name+"="+value+expires+"; path=" + this.cookie_path;
+ },
+
+ readCookie: function () {
+ var nameEQ = this.cookie_name + "=";
+ var ca = document.cookie.split(';');
+ for(var i=0;i < ca.length;i++) {
+ var c = ca[i];
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+ }
+ return null;
+ }
+};
diff --git a/templates/content/js/jquery.validate.pack.js b/templates/content/js/jquery.validate.pack.js
new file mode 100644
index 00000000..49134500
--- /dev/null
+++ b/templates/content/js/jquery.validate.pack.js
@@ -0,0 +1,15 @@
+/*
+ * jQuery validation plug-in 1.5
+ *
+ * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
+ * http://docs.jquery.com/Plugins/Validation
+ *
+ * Copyright (c) 2006 - 2008 Jörn Zaefferer
+ *
+ * $Id: jquery.validate.js 5952 2008-11-25 19:12:30Z joern.zaefferer $
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ */
+eval(function(p,a,c,k,e,r){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--)r[e(c)]=k[c]||e(c);k=[function(e){return r[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($){$.G($.2J,{1y:7(c){l(!6.E){c&&c.2g&&2T.1q&&1q.4Z("3r 2y, 4B\'t 1y, 6d 3r");8}p b=$.16(6[0],\'u\');l(b){8 b}b=1V $.u(c,6[0]);$.16(6[0],\'u\',b);l(b.q.3s){6.4I("1Y, 4E").1t(".4w").4p(7(){b.35=v});6.30(7(a){l(b.q.2g)a.5X();7 24(){l(b.q.3S){b.q.3S.12(b,b.V);8 H}8 v}l(b.35){b.35=H;8 24()}l(b.K()){l(b.1g){b.1v=v;8 H}8 24()}1c{b.2i();8 H}})}8 b},M:7(){l($(6[0]).2H(\'K\')){8 6.1y().K()}1c{p b=H;p a=$(6[0].K).1y();6.O(7(){b|=a.L(6)});8 b}},4L:7(a){p b={},$L=6;$.O(a.1H(/\\s/),7(){b[6]=$L.1G(6);$L.4G(6)});8 b},1b:7(h,k){p f=6[0];l(h){p i=$.16(f.K,\'u\').q;p d=i.1b;p c=$.u.2s(f);2q(h){1e"2o":$.G(c,$.u.1S(k));d[f.r]=c;l(k.J)i.J[f.r]=$.G(i.J[f.r],k.J);31;1e"63":l(!k){R d[f.r];8 c}p e={};$.O(k.1H(/\\s/),7(a,b){e[b]=c[b];R c[b]});8 e}}p g=$.u.3x($.G({},$.u.40(f),$.u.3W(f),$.u.3T(f),$.u.2s(f)),f);l(g.13){p j=g.13;R g.13;g=$.G({13:j},g)}8 g},Y:7(t){8 6.5A(6.2o(t).5w())}});$.G($.5t[":"],{5p:7(a){8!$.2b(a.T)},5m:7(a){8!!$.2b(a.T)},5i:7(a){8!a.3U}});$.1a=7(c,b){l(P.E==1)8 7(){p a=$.48(P);a.52(c);8 $.1a.1I(6,a)};l(P.E>2&&b.2m!=3A){b=$.48(P).4O(1)}l(b.2m!=3A){b=[b]}$.O(b,7(i,n){c=c.3u(1V 3t("\\\\{"+i+"\\\\}","g"),n)});8 c};$.u=7(b,a){6.q=$.G({},$.u.2D,b);6.V=a;6.3q()};$.G($.u,{2D:{J:{},21:{},1b:{},18:"3l",2B:"4H",2i:v,3k:$([]),2A:$([]),3s:v,3j:[],3h:H,4F:7(a){6.3g=a;l(6.q.4D&&!6.4C){6.q.1U&&6.q.1U.12(6,a,6.q.18);6.1E(a).2v()}},4z:7(a){l(!6.1u(a)&&(a.r Z 6.1j||!6.F(a))){6.L(a)}},4t:7(a){l(a.r Z 6.1j||a==6.4q){6.L(a)}},6n:7(a){l(a.r Z 6.1j)6.L(a)},37:7(a,b){$(a).2p(b)},1U:7(a,b){$(a).36(b)}},6g:7(a){$.G($.u.2D,a)},J:{13:"6e 4k 2H 13.",1R:"I 38 6 4k.",1F:"I N a M 1F 65.",1k:"I N a M 62.",1l:"I N a M 1l.",2a:"I N a M 1l (61).",20:"45 44 42 2E 5T5S 5R 2E.",1n:"I N a M 1n.",28:"45 44 42 5N 5M 2E.",1O:"I N 5G 1O",2d:"I N a M 5E 5C 1n.",3P:"I N 3O 5v T 5u.",3L:"I N a T 5q a M 5o.",15:$.1a("I N 3K 5n 2O {0} 2R."),1r:$.1a("I N 5k 5h {0} 2R."),2h:$.1a("I N a T 3E {0} 3R {1} 2R 5c."),27:$.1a("I N a T 3E {0} 3R {1}."),1m:$.1a("I N a T 5a 2O 47 43 3D {0}."),1w:$.1a("I N a T 51 2O 47 43 3D {0}.")},4f:H,4Y:{3q:7(){6.26=$(6.q.2A);6.3C=6.26.E&&6.26||$(6.V);6.2k=$(6.q.3k).2o(6.q.2A);6.1j={};6.4S={};6.1g=0;6.1d={};6.1f={};6.1J();p f=(6.21={});$.O(6.q.21,7(d,c){$.O(c.1H(/\\s/),7(a,b){f[b]=d})});p e=6.q.1b;$.O(e,7(b,a){e[b]=$.u.1S(a)});7 1p(a){p b=$.16(6[0].K,"u");b.q["3z"+a.1o]&&b.q["3z"+a.1o].12(b,6[0])}$(6.V).1p("3y 3w 4N",":2F, :4M, :4K, 23, 4J",1p).1p("4p",":3p, :3o",1p);l(6.q.3n)$(6.V).3m("1f-K.1y",6.q.3n)},K:7(){6.3v();$.G(6.1j,6.1z);6.1f=$.G({},6.1z);l(!6.M())$(6.V).2C("1f-K",[6]);6.1h();8 6.M()},3v:7(){6.2G();Q(p i=0,11=(6.1Z=6.11());11[i];i++){6.2n(11[i])}8 6.M()},L:7(a){a=6.2z(a);6.4q=a;6.2N(a);6.1Z=$(a);p b=6.2n(a);l(b){R 6.1f[a.r]}1c{6.1f[a.r]=v}l(!6.3i()){6.14.Y(6.2k)}6.1h();8 b},1h:7(b){l(b){$.G(6.1z,b);6.S=[];Q(p c Z b){6.S.Y({19:b[c],L:6.1X(c)[0]})}6.1i=$.3f(6.1i,7(a){8!(a.r Z b)})}6.q.1h?6.q.1h.12(6,6.1z,6.S):6.3e()},2x:7(){l($.2J.2x)$(6.V).2x();6.1j={};6.2G();6.2W();6.11().36(6.q.18)},3i:7(){8 6.2c(6.1f)},2c:7(a){p b=0;Q(p i Z a)b++;8 b},2W:7(){6.2w(6.14).2v()},M:7(){8 6.3d()==0},3d:7(){8 6.S.E},2i:7(){l(6.q.2i){3c{$(6.3b()||6.S.E&&6.S[0].L||[]).1t(":4A").3a()}39(e){}}},3b:7(){p a=6.3g;8 a&&$.3f(6.S,7(n){8 n.L.r==a.r}).E==1&&a},11:7(){p a=6,2u={};8 $([]).2o(6.V.11).1t(":1Y").1D(":30, :1J, :4y, [4x]").1D(6.q.3j).1t(7(){!6.r&&a.q.2g&&2T.1q&&1q.3l("%o 4v 3K r 4u",6);l(6.r Z 2u||!a.2c($(6).1b()))8 H;2u[6.r]=v;8 v})},2z:7(a){8 $(a)[0]},2t:7(){8 $(6.q.2B+"."+6.q.18,6.3C)},1J:7(){6.1i=[];6.S=[];6.1z={};6.1C=$([]);6.14=$([]);6.1v=H;6.1Z=$([])},2G:7(){6.1J();6.14=6.2t().Y(6.2k)},2N:7(a){6.1J();6.14=6.1E(a)},2n:7(d){d=6.2z(d);l(6.1u(d)){d=6.1X(d.r)[0]}p a=$(d).1b();p c=H;Q(W Z a){p b={W:W,2r:a[W]};3c{p f=$.u.1P[W].12(6,d.T,d,b.2r);l(f=="1T-1Q"){c=v;6m}c=H;l(f=="1d"){6.14=6.14.1D(6.1E(d));8}l(!f){6.4o(d,b);8 H}}39(e){6.q.2g&&2T.1q&&1q.6l("6k 6j 6i 6h L "+d.4n+", 2n 3O \'"+b.W+"\' W");6f e;}}l(c)8;l(6.2c(a))6.1i.Y(d);8 v},4l:7(a,b){l(!$.1x)8;p c=6.q.33?$(a).1x()[6.q.33]:$(a).1x();8 c&&c.J&&c.J[b]},4j:7(a,b){p m=6.q.J[a];8 m&&(m.2m==4i?m:m[b])},4h:7(){Q(p i=0;i<P.E;i++){l(P[i]!==2l)8 P[i]}8 2l},2j:7(a,b){8 6.4h(6.4j(a.r,b),6.4l(a,b),!6.q.3h&&a.6c||2l,$.u.J[b],"<4g>6b: 6a 19 68 Q "+a.r+"</4g>")},4o:7(b,a){p c=6.2j(b,a.W);l(17 c=="7")c=c.12(6,a.2r,b);6.S.Y({19:c,L:b});6.1z[b.r]=c;6.1j[b.r]=c},2w:7(a){l(6.q.1W)a.Y(a.64(6.q.1W));8 a},3e:7(){Q(p i=0;6.S[i];i++){p a=6.S[i];6.q.37&&6.q.37.12(6,a.L,6.q.18);6.2Z(a.L,a.19)}l(6.S.E){6.1C.Y(6.2k)}l(6.q.1s){Q(p i=0;6.1i[i];i++){6.2Z(6.1i[i])}}l(6.q.1U){Q(p i=0,11=6.4e();11[i];i++){6.q.1U.12(6,11[i],6.q.18)}}6.14=6.14.1D(6.1C);6.2W();6.2w(6.1C).4d()},4e:7(){8 6.1Z.1D(6.4c())},4c:7(){8 $(6.S).4b(7(){8 6.L})},2Z:7(a,c){p b=6.1E(a);l(b.E){b.36().2p(6.q.18);b.1G("4a")&&b.49(c)}1c{b=$("<"+6.q.2B+"/>").1G({"Q":6.2Y(a),4a:v}).2p(6.q.18).49(c||"");l(6.q.1W){b=b.2v().4d().60("<"+6.q.1W+">").5Z()}l(!6.26.5Y(b).E)6.q.46?6.q.46(b,$(a)):b.5W(a)}l(!c&&6.q.1s){b.2F("");17 6.q.1s=="1B"?b.2p(6.q.1s):6.q.1s(b)}6.1C.Y(b)},1E:7(a){8 6.2t().1t("[@Q=\'"+6.2Y(a)+"\']")},2Y:7(a){8 6.21[a.r]||(6.1u(a)?a.r:a.4n||a.r)},1u:7(a){8/3p|3o/i.U(a.1o)},1X:7(d){p c=6.V;8 $(5V.5U(d)).4b(7(a,b){8 b.K==c&&b.r==d&&b||41})},1K:7(a,b){2q(b.3Z.3Y()){1e\'23\':8 $("3X:2y",b).E;1e\'1Y\':l(6.1u(b))8 6.1X(b.r).1t(\':3U\').E}8 a.E},3B:7(b,a){8 6.2X[17 b]?6.2X[17 b](b,a):v},2X:{"5Q":7(b,a){8 b},"1B":7(b,a){8!!$(b,a.K).E},"7":7(b,a){8 b(a)}},F:7(a){8!$.u.1P.13.12(6,$.2b(a.T),a)&&"1T-1Q"},3V:7(a){l(!6.1d[a.r]){6.1g++;6.1d[a.r]=v}},4s:7(a,b){6.1g--;l(6.1g<0)6.1g=0;R 6.1d[a.r];l(b&&6.1g==0&&6.1v&&6.K()){$(6.V).30()}1c l(!b&&6.1g==0&&6.1v){$(6.V).2C("1f-K",[6])}},2f:7(a){8 $.16(a,"2f")||$.16(a,"2f",5O={2K:41,M:v,19:6.2j(a,"1R")})}},1M:{13:{13:v},1F:{1F:v},1k:{1k:v},1l:{1l:v},2a:{2a:v},20:{20:v},1n:{1n:v},28:{28:v},1O:{1O:v},2d:{2d:v}},3Q:7(a,b){a.2m==4i?6.1M[a]=b:$.G(6.1M,a)},3W:7(b){p a={};p c=$(b).1G(\'5K\');c&&$.O(c.1H(\' \'),7(){l(6 Z $.u.1M){$.G(a,$.u.1M[6])}});8 a},3T:7(c){p a={};p d=$(c);Q(W Z $.u.1P){p b=d.1G(W);l(b){a[W]=b}}l(a.15&&/-1|5J|5H/.U(a.15)){R a.15}8 a},40:7(a){l(!$.1x)8{};p b=$.16(a.K,\'u\').q.33;8 b?$(a).1x()[b]:$(a).1x()},2s:7(b){p a={};p c=$.16(b.K,\'u\');l(c.q.1b){a=$.u.1S(c.q.1b[b.r])||{}}8 a},3x:7(d,e){$.O(d,7(c,b){l(b===H){R d[c];8}l(b.2V||b.2e){p a=v;2q(17 b.2e){1e"1B":a=!!$(b.2e,e.K).E;31;1e"7":a=b.2e.12(e,e);31}l(a){d[c]=b.2V!==2l?b.2V:v}1c{R d[c]}}});$.O(d,7(a,b){d[a]=$.5F(b)?b(e):b});$.O([\'1r\',\'15\',\'1w\',\'1m\'],7(){l(d[6]){d[6]=2U(d[6])}});$.O([\'2h\',\'27\'],7(){l(d[6]){d[6]=[2U(d[6][0]),2U(d[6][1])]}});l($.u.4f){l(d.1w&&d.1m){d.27=[d.1w,d.1m];R d.1w;R d.1m}l(d.1r&&d.15){d.2h=[d.1r,d.15];R d.1r;R d.15}}l(d.J){R d.J}8 d},1S:7(a){l(17 a=="1B"){p b={};$.O(a.1H(/\\s/),7(){b[6]=v});a=b}8 a},5D:7(c,a,b){$.u.1P[c]=a;$.u.J[c]=b;l(a.E<3){$.u.3Q(c,$.u.1S(c))}},1P:{13:7(b,c,a){l(!6.3B(a,c))8"1T-1Q";2q(c.3Z.3Y()){1e\'23\':p d=$("3X:2y",c);8 d.E>0&&(c.1o=="23-5B"||($.2S.2Q&&!(d[0].5z[\'T\'].5y)?d[0].2F:d[0].T).E>0);1e\'1Y\':l(6.1u(c))8 6.1K(b,c)>0;5x:8 $.2b(b).E>0}},1R:7(e,h,d){l(6.F(h))8"1T-1Q";p g=6.2f(h);l(!6.q.J[h.r])6.q.J[h.r]={};6.q.J[h.r].1R=17 g.19=="7"?g.19(e):g.19;d=17 d=="1B"&&{1k:d}||d;l(g.2K!==e){g.2K=e;p i=6;6.3V(h);p f={};f[h.r]=e;$.2P($.G(v,{1k:d,3N:"2L",3M:"1y"+h.r,5s:"5r",16:f,1s:7(a){l(a){p b=i.1v;i.2N(h);i.1v=b;i.1i.Y(h);i.1h()}1c{p c={};c[h.r]=a||i.2j(h,"1R");i.1h(c)}g.M=a;i.4s(h,a)}},d));8"1d"}1c l(6.1d[h.r]){8"1d"}8 g.M},1r:7(b,c,a){8 6.F(c)||6.1K(b,c)>=a},15:7(b,c,a){8 6.F(c)||6.1K(b,c)<=a},2h:7(b,d,a){p c=6.1K(b,d);8 6.F(d)||(c>=a[0]&&c<=a[1])},1w:7(b,c,a){8 6.F(c)||b>=a},1m:7(b,c,a){8 6.F(c)||b<=a},27:7(b,c,a){8 6.F(c)||(b>=a[0]&&b<=a[1])},1F:7(a,b){8 6.F(b)||/^((([a-z]|\\d|[!#\\$%&\'\\*\\+\\-\\/=\\?\\^X`{\\|}~]|[\\A-\\C\\w-\\B\\x-\\y])+(\\.([a-z]|\\d|[!#\\$%&\'\\*\\+\\-\\/=\\?\\^X`{\\|}~]|[\\A-\\C\\w-\\B\\x-\\y])+)*)|((\\3J)((((\\29|\\1N)*(\\2M\\3I))?(\\29|\\1N)+)?(([\\3H-\\5l\\3G\\3F\\5j-\\5I\\4r]|\\5g|[\\5L-\\5f]|[\\5e-\\5d]|[\\A-\\C\\w-\\B\\x-\\y])|(\\\\([\\3H-\\1N\\3G\\3F\\2M-\\4r]|[\\A-\\C\\w-\\B\\x-\\y]))))*(((\\29|\\1N)*(\\2M\\3I))?(\\29|\\1N)+)?(\\3J)))@((([a-z]|\\d|[\\A-\\C\\w-\\B\\x-\\y])|(([a-z]|\\d|[\\A-\\C\\w-\\B\\x-\\y])([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])*([a-z]|\\d|[\\A-\\C\\w-\\B\\x-\\y])))\\.)+(([a-z]|[\\A-\\C\\w-\\B\\x-\\y])|(([a-z]|[\\A-\\C\\w-\\B\\x-\\y])([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])*([a-z]|[\\A-\\C\\w-\\B\\x-\\y])))\\.?$/i.U(a)},1k:7(a,b){8 6.F(b)||/^(5P?|5b):\\/\\/(((([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])|(%[\\1L-f]{2})|[!\\$&\'\\(\\)\\*\\+,;=]|:)*@)?(((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]))|((([a-z]|\\d|[\\A-\\C\\w-\\B\\x-\\y])|(([a-z]|\\d|[\\A-\\C\\w-\\B\\x-\\y])([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])*([a-z]|\\d|[\\A-\\C\\w-\\B\\x-\\y])))\\.)+(([a-z]|[\\A-\\C\\w-\\B\\x-\\y])|(([a-z]|[\\A-\\C\\w-\\B\\x-\\y])([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])*([a-z]|[\\A-\\C\\w-\\B\\x-\\y])))\\.?)(:\\d*)?)(\\/((([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])|(%[\\1L-f]{2})|[!\\$&\'\\(\\)\\*\\+,;=]|:|@)+(\\/(([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])|(%[\\1L-f]{2})|[!\\$&\'\\(\\)\\*\\+,;=]|:|@)*)*)?)?(\\?((([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])|(%[\\1L-f]{2})|[!\\$&\'\\(\\)\\*\\+,;=]|:|@)|[\\59-\\58]|\\/|\\?)*)?(\\#((([a-z]|\\d|-|\\.|X|~|[\\A-\\C\\w-\\B\\x-\\y])|(%[\\1L-f]{2})|[!\\$&\'\\(\\)\\*\\+,;=]|:|@)|\\/|\\?)*)?$/i.U(a)},1l:7(a,b){8 6.F(b)||!/57|56/.U(1V 55(a))},2a:7(a,b){8 6.F(b)||/^\\d{4}[\\/-]\\d{1,2}[\\/-]\\d{1,2}$/.U(a)},20:7(a,b){8 6.F(b)||/^\\d\\d?\\.\\d\\d?\\.\\d\\d\\d?\\d?$/.U(a)},1n:7(a,b){8 6.F(b)||/^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)(?:\\.\\d+)?$/.U(a)},28:7(a,b){8 6.F(b)||/^-?(?:\\d+|\\d{1,3}(?:\\.\\d{3})+)(?:,\\d+)?$/.U(a)},1O:7(a,b){8 6.F(b)||/^\\d+$/.U(a)},2d:7(b,e){l(6.F(e))8"1T-1Q";l(/[^0-9-]+/.U(b))8 H;p a=0,d=0,22=H;b=b.3u(/\\D/g,"");Q(n=b.E-1;n>=0;n--){p c=b.54(n);p d=53(c,10);l(22){l((d*=2)>9)d-=9}a+=d;22=!22}8(a%10)==0},3L:7(b,c,a){a=17 a=="1B"?a:"66|67?g|50";8 6.F(c)||b.69(1V 3t(".("+a+")$","i"))},3P:7(b,c,a){8 b==$(a).4X()}}})})(2I);(7($){p c=$.2P;p d={};$.2P=7(a){a=$.G(a,$.G({},$.4W,a));p b=a.3M;l(a.3N=="2L"){l(d[b]){d[b].2L()}8(d[b]=c.1I(6,P))}8 c.1I(6,P)}})(2I);(7($){$.O({3a:\'3y\',4V:\'3w\'},7(b,a){$.1A.32[a]={4U:7(){l($.2S.2Q)8 H;6.4T(b,$.1A.32[a].34,v)},4R:7(){l($.2S.2Q)8 H;6.4Q(b,$.1A.32[a].34,v)},34:7(e){P[0]=$.1A.38(e);P[0].1o=a;8 $.1A.24.1I(6,P)}}});$.G($.2J,{1p:7(d,e,c){8 6.3m(d,7(a){p b=$(a.4m);l(b.2H(e)){8 c.1I(b,P)}})},4P:7(a,b){8 6.2C(a,[$.1A.38({1o:a,4m:b})])}})})(2I);',62,396,'||||||this|function|return|||||||||||||if||||var|settings|name|||validator|true|uF900|uFDF0|uFFEF||u00A0|uFDCF|uD7FF||length|optional|extend|false|Please|messages|form|element|valid|enter|each|arguments|for|delete|errorList|value|test|currentForm|method|_|push|in||elements|call|required|toHide|maxlength|data|typeof|errorClass|message|format|rules|else|pending|case|invalid|pendingRequest|showErrors|successList|submitted|url|date|max|number|type|delegate|console|minlength|success|filter|checkable|formSubmitted|min|metadata|validate|errorMap|event|string|toShow|not|errorsFor|email|attr|split|apply|reset|getLength|da|classRuleSettings|x09|digits|methods|mismatch|remote|normalizeRule|dependency|unhighlight|new|wrapper|findByName|input|currentElements|dateDE|groups|bEven|select|handle||labelContainer|range|numberDE|x20|dateISO|trim|objectLength|creditcard|depends|previousValue|debug|rangelength|focusInvalid|defaultMessage|containers|undefined|constructor|check|add|addClass|switch|parameters|staticRules|errors|rulesCache|hide|addWrapper|resetForm|selected|clean|errorLabelContainer|errorElement|triggerHandler|defaults|ein|text|prepareForm|is|jQuery|fn|old|abort|x0d|prepareElement|than|ajax|msie|characters|browser|window|Number|param|hideErrors|dependTypes|idOrName|showLabel|submit|break|special|meta|handler|cancelSubmit|removeClass|highlight|fix|catch|focus|findLastActive|try|size|defaultShowErrors|grep|lastActive|ignoreTitle|numberOfInvalids|ignore|errorContainer|error|bind|invalidHandler|checkbox|radio|init|nothing|onsubmit|RegExp|replace|checkForm|focusout|normalizeRules|focusin|on|Array|depend|errorContext|to|between|x0c|x0b|x01|x0a|x22|no|accept|port|mode|the|equalTo|addClassRules|and|submitHandler|attributeRules|checked|startRequest|classRules|option|toLowerCase|nodeName|metadataRules|null|Sie|equal|geben|Bitte|errorPlacement|or|makeArray|html|generated|map|invalidElements|show|validElements|autoCreateRanges|strong|findDefined|String|customMessage|field|customMetaMessage|target|id|formatAndAdd|click|lastElement|x7f|stopRequest|onkeyup|assigned|has|cancel|disabled|image|onfocusout|visible|can|blockFocusCleanup|focusCleanup|button|onfocusin|removeAttr|label|find|textarea|file|removeAttrs|password|keyup|slice|triggerEvent|removeEventListener|teardown|valueCache|addEventListener|setup|blur|ajaxSettings|val|prototype|warn|gif|greater|unshift|parseInt|charAt|Date|NaN|Invalid|uF8FF|uE000|less|ftp|long|x7e|x5d|x5b|x21|least|unchecked|x0e|at|x08|filled|more|extension|blank|with|json|dataType|expr|again|same|get|default|specified|attributes|setArray|multiple|card|addMethod|credit|isFunction|only|524288|x1f|2147483647|class|x23|Nummer|eine|previous|https|boolean|Datum|ltiges|g|getElementsByName|document|insertAfter|preventDefault|append|parent|wrap|ISO|URL|remove|parents|address|png|jpe|defined|match|No|Warning|title|returning|This|throw|setDefaults|checking|when|occured|exception|log|continue|onclick'.split('|'),0,{})) \ No newline at end of file
diff --git a/templates/content/js/se_hilite.js b/templates/content/js/se_hilite.js
new file mode 100644
index 00000000..42e99c8e
--- /dev/null
+++ b/templates/content/js/se_hilite.js
@@ -0,0 +1 @@
+Hilite={elementid:"content",exact:true,max_nodes:1000,onload:true,style_name:"hilite",style_name_suffix:true,debug_referrer:""};Hilite.search_engines=[["google\\.","q"],["search\\.yahoo\\.","p"],["search\\.msn\\.","q"],["search\\.live\\.","query"],["search\\.aol\\.","userQuery"],["ask\\.com","q"],["altavista\\.","q"],["feedster\\.","q"],["search\\.lycos\\.","q"],["alltheweb\\.","q"],["technorati\\.com/search/([^\\?/]+)",1],["dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)",1,true]];Hilite.decodeReferrer=function(d){var g=null;var e=new RegExp("");for(var c=0;c<Hilite.search_engines.length;c++){var f=Hilite.search_engines[c];e.compile("^http://(www\\.)?"+f[0],"i");var b=d.match(e);if(b){var a;if(isNaN(f[1])){a=Hilite.decodeReferrerQS(d,f[1])}else{a=b[f[1]+1]}if(a){a=decodeURIComponent(a);if(f.length>2&&f[2]){a=decodeURIComponent(a)}a=a.replace(/\'|"/g,"");a=a.split(/[\s,\+\.]+/);return a}break}}return null};Hilite.decodeReferrerQS=function(f,d){var b=f.indexOf("?");var c;if(b>=0){var a=new String(f.substring(b+1));b=0;c=0;while((b>=0)&&((c=a.indexOf("=",b))>=0)){var e,g;e=a.substring(b,c);b=a.indexOf("&",c)+1;if(e==d){if(b<=0){return a.substring(c+1)}else{return a.substring(c+1,b-1)}}else{if(b<=0){return null}}}}return null};Hilite.hiliteElement=function(f,e){if(!e||f.childNodes.length==0){return}var c=new Array();for(var b=0;b<e.length;b++){e[b]=e[b].toLowerCase();if(Hilite.exact){c.push("\\b"+e[b]+"\\b")}else{c.push(e[b])}}c=new RegExp(c.join("|"),"i");var a={};for(var b=0;b<e.length;b++){if(Hilite.style_name_suffix){a[e[b]]=Hilite.style_name+(b+1)}else{a[e[b]]=Hilite.style_name}}var d=function(m){var j=c.exec(m.data);if(j){var n=j[0];var i="";var h=m.splitText(j.index);var g=h.splitText(n.length);var l=m.ownerDocument.createElement("SPAN");m.parentNode.replaceChild(l,h);l.className=a[n.toLowerCase()];l.appendChild(h);return l}else{return m}};Hilite.walkElements(f.childNodes[0],1,d)};Hilite.hilite=function(){var a=Hilite.debug_referrer?Hilite.debug_referrer:document.referrer;var b=null;a=Hilite.decodeReferrer(a);if(a&&((Hilite.elementid&&(b=document.getElementById(Hilite.elementid)))||(b=document.body))){Hilite.hiliteElement(b,a)}};Hilite.walkElements=function(d,f,e){var a=/^(script|style|textarea)/i;var c=0;while(d&&f>0){c++;if(c>=Hilite.max_nodes){var b=function(){Hilite.walkElements(d,f,e)};setTimeout(b,50);return}if(d.nodeType==1){if(!a.test(d.tagName)&&d.childNodes.length>0){d=d.childNodes[0];f++;continue}}else{if(d.nodeType==3){d=e(d)}}if(d.nextSibling){d=d.nextSibling}else{while(f>0){d=d.parentNode;f--;if(d.nextSibling){d=d.nextSibling;break}}}}};if(Hilite.onload){if(window.attachEvent){window.attachEvent("onload",Hilite.hilite)}else{if(window.addEventListener){window.addEventListener("load",Hilite.hilite,false)}else{var __onload=window.onload;window.onload=function(){Hilite.hilite();__onload()}}}}; \ No newline at end of file
diff --git a/templates/content/js/se_hilite_src.js b/templates/content/js/se_hilite_src.js
new file mode 100644
index 00000000..b604f156
--- /dev/null
+++ b/templates/content/js/se_hilite_src.js
@@ -0,0 +1,273 @@
+/**
+ * Search Engine Keyword Highlight (http://fucoder.com/code/se-hilite/)
+ *
+ * This module can be imported by any HTML page, and it would analyse the
+ * referrer for search engine keywords, and then highlight those keywords on
+ * the page, by wrapping them around <span class="hilite">...</span> tags.
+ * Document can then define styles else where to provide visual feedbacks.
+ *
+ * Usage:
+ *
+ * In HTML. Add the following line towards the end of the document.
+ *
+ * <script type="text/javascript" src="se_hilite.js"></script>
+ *
+ * In CSS, define the following style:
+ *
+ * .hilite { background-color: #ff0; }
+ *
+ * If Hilite.style_name_suffix is true, then define the follow styles:
+ *
+ * .hilite1 { background-color: #ff0; }
+ * .hilite2 { background-color: #f0f; }
+ * .hilite3 { background-color: #0ff; }
+ * .hilite4 ...
+ *
+ * @author Scott Yang <http://scott.yang.id.au/>
+ * @version 1.5
+ */
+
+// Configuration:
+Hilite = {
+ /**
+ * Element ID to be highlighted. If set, then only content inside this DOM
+ * element will be highlighted, otherwise everything inside document.body
+ * will be searched.
+ */
+ elementid: 'content',
+
+ /**
+ * Whether we are matching an exact word. For example, searching for
+ * "highlight" will only match "highlight" but not "highlighting" if exact
+ * is set to true.
+ */
+ exact: true,
+
+ /**
+ * Maximum number of DOM nodes to test, before handing the control back to
+ * the GUI thread. This prevents locking up the UI when parsing and
+ * replacing inside a large document.
+ */
+ max_nodes: 1000,
+
+ /**
+ * Whether to automatically hilite a section of the HTML document, by
+ * binding the "Hilite.hilite()" to window.onload() event. If this
+ * attribute is set to false, you can still manually trigger the hilite by
+ * calling Hilite.hilite() in Javascript after document has been fully
+ * loaded.
+ */
+ onload: true,
+
+ /**
+ * Name of the style to be used. Default to 'hilite'.
+ */
+ style_name: 'hilite',
+
+ /**
+ * Whether to use different style names for different search keywords by
+ * appending a number starting from 1, i.e. hilite1, hilite2, etc.
+ */
+ style_name_suffix: true,
+
+ /**
+ * Set it to override the document.referrer string. Used for debugging
+ * only.
+ */
+ debug_referrer: ''
+};
+
+Hilite.search_engines = [
+ ['google\\.', 'q'], // Google
+ ['search\\.yahoo\\.', 'p'], // Yahoo
+ ['search\\.msn\\.', 'q'], // MSN
+ ['search\\.live\\.', 'query'], // MSN Live
+ ['search\\.aol\\.', 'userQuery'], // AOL
+ ['ask\\.com', 'q'], // Ask.com
+ ['altavista\\.', 'q'], // AltaVista
+ ['feedster\\.', 'q'], // Feedster
+ ['search\\.lycos\\.', 'q'], // Lycos
+ ['alltheweb\\.', 'q'], // AllTheWeb
+ ['technorati\\.com/search/([^\\?/]+)', 1], // Technorati
+ ['dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)', 1, true] // DogPile
+];
+
+/**
+ * Decode the referrer string and return a list of search keywords.
+ */
+Hilite.decodeReferrer = function(referrer) {
+ var query = null;
+ var regex = new RegExp('');
+
+ for (var i = 0; i < Hilite.search_engines.length; i ++) {
+ var se = Hilite.search_engines[i];
+ regex.compile('^http://(www\\.)?' + se[0], 'i');
+ var match = referrer.match(regex);
+ if (match) {
+ var result;
+ if (isNaN(se[1])) {
+ result = Hilite.decodeReferrerQS(referrer, se[1]);
+ } else {
+ result = match[se[1] + 1];
+ }
+ if (result) {
+ result = decodeURIComponent(result);
+ // XXX: DogPile's URI requires decoding twice.
+ if (se.length > 2 && se[2])
+ result = decodeURIComponent(result);
+ result = result.replace(/\'|"/g, '');
+ result = result.split(/[\s,\+\.]+/);
+ return result;
+ }
+ break;
+ }
+ }
+ return null;
+};
+
+Hilite.decodeReferrerQS = function(referrer, match) {
+ var idx = referrer.indexOf('?');
+ var idx2;
+ if (idx >= 0) {
+ var qs = new String(referrer.substring(idx + 1));
+ idx = 0;
+ idx2 = 0;
+ while ((idx >= 0) && ((idx2 = qs.indexOf('=', idx)) >= 0)) {
+ var key, val;
+ key = qs.substring(idx, idx2);
+ idx = qs.indexOf('&', idx2) + 1;
+ if (key == match) {
+ if (idx <= 0) {
+ return qs.substring(idx2+1);
+ } else {
+ return qs.substring(idx2+1, idx - 1);
+ }
+ }
+ else if (idx <=0) {
+ return null;
+ }
+ }
+ }
+ return null;
+};
+
+/**
+ * Highlight a DOM element with a list of keywords.
+ */
+Hilite.hiliteElement = function(elm, query) {
+ if (!query || elm.childNodes.length == 0)
+ return;
+
+ var qre = new Array();
+ for (var i = 0; i < query.length; i ++) {
+ query[i] = query[i].toLowerCase();
+ if (Hilite.exact)
+ qre.push('\\b'+query[i]+'\\b');
+ else
+ qre.push(query[i]);
+ }
+
+ qre = new RegExp(qre.join("|"), "i");
+
+ var stylemapper = {};
+ for (var i = 0; i < query.length; i ++) {
+ if (Hilite.style_name_suffix)
+ stylemapper[query[i]] = Hilite.style_name+(i+1);
+ else
+ stylemapper[query[i]] = Hilite.style_name;
+ }
+
+ var textproc = function(node) {
+ var match = qre.exec(node.data);
+ if (match) {
+ var val = match[0];
+ var k = '';
+ var node2 = node.splitText(match.index);
+ var node3 = node2.splitText(val.length);
+ var span = node.ownerDocument.createElement('SPAN');
+ node.parentNode.replaceChild(span, node2);
+ span.className = stylemapper[val.toLowerCase()];
+ span.appendChild(node2);
+ return span;
+ } else {
+ return node;
+ }
+ };
+ Hilite.walkElements(elm.childNodes[0], 1, textproc);
+};
+
+/**
+ * Highlight a HTML document using keywords extracted from document.referrer.
+ * This is the main function to be called to perform search engine highlight
+ * on a document.
+ *
+ * Currently it would check for DOM element 'content', element 'container' and
+ * then document.body in that order, so it only highlights appropriate section
+ * on WordPress and Movable Type pages.
+ */
+Hilite.hilite = function() {
+ // If 'debug_referrer' then we will use that as our referrer string
+ // instead.
+ var q = Hilite.debug_referrer ? Hilite.debug_referrer : document.referrer;
+ var e = null;
+ q = Hilite.decodeReferrer(q);
+ if (q && ((Hilite.elementid &&
+ (e = document.getElementById(Hilite.elementid))) ||
+ (e = document.body)))
+ {
+ Hilite.hiliteElement(e, q);
+ }
+};
+
+Hilite.walkElements = function(node, depth, textproc) {
+ var skipre = /^(script|style|textarea)/i;
+ var count = 0;
+ while (node && depth > 0) {
+ count ++;
+ if (count >= Hilite.max_nodes) {
+ var handler = function() {
+ Hilite.walkElements(node, depth, textproc);
+ };
+ setTimeout(handler, 50);
+ return;
+ }
+
+ if (node.nodeType == 1) { // ELEMENT_NODE
+ if (!skipre.test(node.tagName) && node.childNodes.length > 0) {
+ node = node.childNodes[0];
+ depth ++;
+ continue;
+ }
+ } else if (node.nodeType == 3) { // TEXT_NODE
+ node = textproc(node);
+ }
+
+ if (node.nextSibling) {
+ node = node.nextSibling;
+ } else {
+ while (depth > 0) {
+ node = node.parentNode;
+ depth --;
+ if (node.nextSibling) {
+ node = node.nextSibling;
+ break;
+ }
+ }
+ }
+ }
+};
+
+// Trigger the highlight using the onload handler.
+if (Hilite.onload) {
+ if (window.attachEvent) {
+ window.attachEvent('onload', Hilite.hilite);
+ } else if (window.addEventListener) {
+ window.addEventListener('load', Hilite.hilite, false);
+ } else {
+ var __onload = window.onload;
+ window.onload = function() {
+ Hilite.hilite();
+ __onload();
+ };
+ }
+}
diff --git a/templates/content/js/wmd/images/wmd-buttons.png b/templates/content/js/wmd/images/wmd-buttons.png
new file mode 100644
index 00000000..50b37090
--- /dev/null
+++ b/templates/content/js/wmd/images/wmd-buttons.png
Binary files differ
diff --git a/templates/content/js/wmd/showdown-min.js b/templates/content/js/wmd/showdown-min.js
new file mode 100644
index 00000000..073613b1
--- /dev/null
+++ b/templates/content/js/wmd/showdown-min.js
@@ -0,0 +1 @@
+var Attacklab=Attacklab||{};Attacklab.showdown=Attacklab.showdown||{};Attacklab.showdown.converter=function(){var a;var j;var A;var i=0;this.makeHtml=function(H){a=new Array();j=new Array();A=new Array();H=H.replace(/~/g,"~T");H=H.replace(/\$/g,"~D");H=H.replace(/\r\n/g,"\n");H=H.replace(/\r/g,"\n");H="\n\n"+H+"\n\n";H=z(H);H=H.replace(/^[ \t]+$/mg,"");H=m(H);H=d(H);H=G(H);H=q(H);H=H.replace(/~D/g,"$$");H=H.replace(/~T/g,"~");return H};var d=function(H){var H=H.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,function(K,M,L,J,I){M=M.toLowerCase();a[M]=h(L);if(J){return J+I}else{if(I){j[M]=I.replace(/"/g,"&quot;")}}return""});return H};var m=function(J){J=J.replace(/\n/g,"\n\n");var I="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del";var H="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";J=J.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,x);J=J.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,x);J=J.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,x);J=J.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,x);J=J.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,x);J=J.replace(/\n\n/g,"\n");return J};var x=function(H,I){var J=I;J=J.replace(/\n\n/g,"\n");J=J.replace(/^\n/,"");J=J.replace(/\n+$/g,"");J="\n\n~K"+(A.push(J)-1)+"K\n\n";return J};var G=function(I){I=f(I);var H=o("<hr />");I=I.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,H);I=I.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,H);I=I.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,H);I=E(I);I=b(I);I=u(I);I=m(I);I=g(I);return I};var r=function(H){H=C(H);H=l(H);H=e(H);H=F(H);H=y(H);H=n(H);H=h(H);H=c(H);H=H.replace(/ +\n/g," <br />\n");return H};var l=function(I){var H=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;I=I.replace(H,function(K){var J=K.replace(/(.)<\/?code>(?=.)/g,"$1`");J=w(J,"\\`*_");return J});return I};var y=function(H){H=H.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,D);H=H.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,D);H=H.replace(/(\[([^\[\]]+)\])()()()()()/g,D);return H};var D=function(N,T,S,R,Q,P,M,L){if(L==undefined){L=""}var K=T;var I=S;var J=R.toLowerCase();var H=Q;var O=L;if(H==""){if(J==""){J=I.toLowerCase().replace(/ ?\n/g," ")}H="#"+J;if(a[J]!=undefined){H=a[J];if(j[J]!=undefined){O=j[J]}}else{if(K.search(/\(\s*\)$/m)>-1){H=""}else{return K}}}H=w(H,"*_");var U='<a href="'+H+'"';if(O!=""){O=O.replace(/"/g,"&quot;");O=w(O,"*_");U+=' title="'+O+'"'}U+=">"+I+"</a>";return U};var F=function(H){H=H.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,t);H=H.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,t);return H};var t=function(N,T,S,R,Q,P,M,L){var K=T;var J=S;var I=R.toLowerCase();var H=Q;var O=L;if(!O){O=""}if(H==""){if(I==""){I=J.toLowerCase().replace(/ ?\n/g," ")}H="#"+I;if(a[I]!=undefined){H=a[I];if(j[I]!=undefined){O=j[I]}}else{return K}}J=J.replace(/"/g,"&quot;");H=w(H,"*_");var U='<img src="'+H+'" alt="'+J+'"';O=O.replace(/"/g,"&quot;");O=w(O,"*_");U+=' title="'+O+'"';U+=" />";return U};var f=function(H){H=H.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,function(I,J){return o("<h1>"+r(J)+"</h1>")});H=H.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(J,I){return o("<h2>"+r(I)+"</h2>")});H=H.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(I,L,K){var J=L.length;return o("<h"+J+">"+r(K)+"</h"+J+">")});return H};var p;var E=function(I){I+="~0";var H=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;if(i){I=I.replace(H,function(K,N,M){var O=N;var L=(M.search(/[*+-]/g)>-1)?"ul":"ol";O=O.replace(/\n{2,}/g,"\n\n\n");var J=p(O);J=J.replace(/\s+$/,"");J="<"+L+">"+J+"</"+L+">\n";return J})}else{H=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;I=I.replace(H,function(L,P,N,K){var O=P;var Q=N;var M=(K.search(/[*+-]/g)>-1)?"ul":"ol";var Q=Q.replace(/\n{2,}/g,"\n\n\n");var J=p(Q);J=O+"<"+M+">\n"+J+"</"+M+">\n";return J})}I=I.replace(/~0/,"");return I};p=function(H){i++;H=H.replace(/\n{2,}$/,"\n");H+="~0";H=H.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,function(K,M,L,J,I){var O=I;var N=M;var P=L;if(N||(O.search(/\n{2,}/)>-1)){O=G(s(O))}else{O=E(s(O));O=O.replace(/\n$/,"");O=r(O)}return"<li>"+O+"</li>\n"});H=H.replace(/~0/g,"");i--;return H};var b=function(H){H+="~0";H=H.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(I,K,J){var L=K;var M=J;L=v(s(L));L=z(L);L=L.replace(/^\n+/g,"");L=L.replace(/\n+$/g,"");L="<pre><code>"+L+"\n</code></pre>";return o(L)+M});H=H.replace(/~0/,"");return H};var o=function(H){H=H.replace(/(^\n+|\n+$)/g,"");return"\n\n~K"+(A.push(H)-1)+"K\n\n"};var C=function(H){H=H.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(K,M,L,J,I){var N=J;N=N.replace(/^([ \t]*)/g,"");N=N.replace(/[ \t]*$/g,"");N=v(N);return M+"<code>"+N+"</code>"});return H};var v=function(H){H=H.replace(/&/g,"&amp;");H=H.replace(/</g,"&lt;");H=H.replace(/>/g,"&gt;");H=w(H,"*_{}[]\\",false);return H};var c=function(H){H=H.replace(/(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\1/g,"<strong>$2</strong>");H=H.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,"<em>$2</em>");return H};var u=function(H){H=H.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(I,J){var K=J;K=K.replace(/^[ \t]*>[ \t]?/gm,"~0");K=K.replace(/~0/g,"");K=K.replace(/^[ \t]+$/gm,"");K=G(K);K=K.replace(/(^|\n)/g,"$1 ");K=K.replace(/(\s*<pre>[^\r]+?<\/pre>)/gm,function(L,M){var N=M;N=N.replace(/^ /mg,"~0");N=N.replace(/~0/g,"");return N});return o("<blockquote>\n"+K+"\n</blockquote>")});return H};var g=function(N){N=N.replace(/^\n+/g,"");N=N.replace(/\n+$/g,"");var M=N.split(/\n{2,}/g);var J=new Array();var H=M.length;for(var I=0;I<H;I++){var L=M[I];if(L.search(/~K(\d+)K/g)>=0){J.push(L)}else{if(L.search(/\S/)>=0){L=r(L);L=L.replace(/^([ \t]*)/g,"<p>");L+="</p>";J.push(L)}}}H=J.length;for(var I=0;I<H;I++){while(J[I].search(/~K(\d+)K/)>=0){var K=A[RegExp.$1];K=K.replace(/\$/g,"$$$$");J[I]=J[I].replace(/~K\d+K/,K)}}return J.join("\n\n")};var h=function(H){H=H.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");H=H.replace(/<(?![a-z\/?\$!])/gi,"&lt;");return H};var e=function(H){H=H.replace(/\\(\\)/g,k);H=H.replace(/\\([`*_{}\[\]()>#+-.!])/g,k);return H};var n=function(H){H=H.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,'<a href="$1">$1</a>');H=H.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,function(I,J){return B(q(J))});return H};var B=function(J){function I(L){var K="0123456789ABCDEF";var M=L.charCodeAt(0);return(K.charAt(M>>4)+K.charAt(M&15))}var H=[function(K){return"&#"+K.charCodeAt(0)+";"},function(K){return"&#x"+I(K)+";"},function(K){return K}];J="mailto:"+J;J=J.replace(/./g,function(K){if(K=="@"){K=H[Math.floor(Math.random()*2)](K)}else{if(K!=":"){var L=Math.random();K=(L>0.9?H[2](K):L>0.45?H[1](K):H[0](K))}}return K});J='<a href="'+J+'">'+J+"</a>";J=J.replace(/">.+:/g,'">');return J};var q=function(H){H=H.replace(/~E(\d+)E/g,function(I,K){var J=parseInt(K);return String.fromCharCode(J)});return H};var s=function(H){H=H.replace(/^(\t|[ ]{1,4})/gm,"~0");H=H.replace(/~0/g,"");return H};var z=function(H){H=H.replace(/\t(?=\t)/g," ");H=H.replace(/\t/g,"~A~B");H=H.replace(/~B(.+?)~A/g,function(I,L,K){var N=L;var J=4-N.length%4;for(var M=0;M<J;M++){N+=" "}return N});H=H.replace(/~A/g," ");H=H.replace(/~B/g,"");return H};var w=function(L,I,J){var H="(["+I.replace(/([\[\]\\])/g,"\\$1")+"])";if(J){H="\\\\"+H}var K=new RegExp(H,"g");L=L.replace(K,k);return L};var k=function(H,J){var I=J.charCodeAt(0);return"~E"+I+"E"}};var Showdown=Attacklab.showdown;if(Attacklab.fileLoaded){Attacklab.fileLoaded("showdown.js")}; \ No newline at end of file
diff --git a/templates/content/js/wmd/showdown.js b/templates/content/js/wmd/showdown.js
new file mode 100644
index 00000000..3f4b9947
--- /dev/null
+++ b/templates/content/js/wmd/showdown.js
@@ -0,0 +1,1309 @@
+//
+// showdown.js -- A javascript port of Markdown.
+//
+// Copyright (c) 2007 John Fraser.
+//
+// Original Markdown Copyright (c) 2004-2005 John Gruber
+// <http://daringfireball.net/projects/markdown/>
+//
+// Redistributable under a BSD-style open source license.
+// See license.txt for more information.
+//
+// The full source distribution is at:
+//
+// A A L
+// T C A
+// T K B
+//
+// <http://www.attacklab.net/>
+//
+
+//
+// Wherever possible, Showdown is a straight, line-by-line port
+// of the Perl version of Markdown.
+//
+// This is not a normal parser design; it's basically just a
+// series of string substitutions. It's hard to read and
+// maintain this way, but keeping Showdown close to the original
+// design makes it easier to port new features.
+//
+// More importantly, Showdown behaves like markdown.pl in most
+// edge cases. So web applications can do client-side preview
+// in Javascript, and then build identical HTML on the server.
+//
+// This port needs the new RegExp functionality of ECMA 262,
+// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
+// should do fine. Even with the new regular expression features,
+// We do a lot of work to emulate Perl's regex functionality.
+// The tricky changes in this file mostly have the "attacklab:"
+// label. Major or self-explanatory changes don't.
+//
+// Smart diff tools like Araxis Merge will be able to match up
+// this file with markdown.pl in a useful way. A little tweaking
+// helps: in a copy of markdown.pl, replace "#" with "//" and
+// replace "$text" with "text". Be sure to ignore whitespace
+// and line endings.
+//
+
+
+//
+// Showdown usage:
+//
+// var text = "Markdown *rocks*.";
+//
+// var converter = new Attacklab.showdown.converter();
+// var html = converter.makeHtml(text);
+//
+// alert(html);
+//
+// Note: move the sample code to the bottom of this
+// file before uncommenting it.
+//
+
+
+//
+// Attacklab namespace
+//
+var Attacklab = Attacklab || {}
+
+//
+// Showdown namespace
+//
+Attacklab.showdown = Attacklab.showdown || {}
+
+//
+// converter
+//
+// Wraps all "globals" so that the only thing
+// exposed is makeHtml().
+//
+Attacklab.showdown.converter = function() {
+
+//
+// Globals:
+//
+
+// Global hashes, used by various utility routines
+var g_urls;
+var g_titles;
+var g_html_blocks;
+
+// Used to track when we're inside an ordered or unordered list
+// (see _ProcessListItems() for details):
+var g_list_level = 0;
+
+
+this.makeHtml = function(text) {
+//
+// Main function. The order in which other subs are called here is
+// essential. Link and image substitutions need to happen before
+// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
+// and <img> tags get encoded.
+//
+
+ // Clear the global hashes. If we don't clear these, you get conflicts
+ // from other articles when generating a page which contains more than
+ // one article (e.g. an index page that shows the N most recent
+ // articles):
+ g_urls = new Array();
+ g_titles = new Array();
+ g_html_blocks = new Array();
+
+ // attacklab: Replace ~ with ~T
+ // This lets us use tilde as an escape char to avoid md5 hashes
+ // The choice of character is arbitray; anything that isn't
+ // magic in Markdown will work.
+ text = text.replace(/~/g,"~T");
+
+ // attacklab: Replace $ with ~D
+ // RegExp interprets $ as a special character
+ // when it's in a replacement string
+ text = text.replace(/\$/g,"~D");
+
+ // Standardize line endings
+ text = text.replace(/\r\n/g,"\n"); // DOS to Unix
+ text = text.replace(/\r/g,"\n"); // Mac to Unix
+
+ // Make sure text begins and ends with a couple of newlines:
+ text = "\n\n" + text + "\n\n";
+
+ // Convert all tabs to spaces.
+ text = _Detab(text);
+
+ // Strip any lines consisting only of spaces and tabs.
+ // This makes subsequent regexen easier to write, because we can
+ // match consecutive blank lines with /\n+/ instead of something
+ // contorted like /[ \t]*\n+/ .
+ text = text.replace(/^[ \t]+$/mg,"");
+
+ // Turn block-level HTML blocks into hash entries
+ text = _HashHTMLBlocks(text);
+
+ // Strip link definitions, store in hashes.
+ text = _StripLinkDefinitions(text);
+
+ text = _RunBlockGamut(text);
+
+ text = _UnescapeSpecialChars(text);
+
+ // attacklab: Restore dollar signs
+ text = text.replace(/~D/g,"$$");
+
+ // attacklab: Restore tildes
+ text = text.replace(/~T/g,"~");
+
+ return text;
+}
+
+var _StripLinkDefinitions = function(text) {
+//
+// Strips link definitions from text, stores the URLs and titles in
+// hash references.
+//
+
+ // Link defs are in the form: ^[id]: url "optional title"
+
+ /*
+ var text = text.replace(/
+ ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
+ [ \t]*
+ \n? // maybe *one* newline
+ [ \t]*
+ <?(\S+?)>? // url = $2
+ [ \t]*
+ \n? // maybe one newline
+ [ \t]*
+ (?:
+ (\n*) // any lines skipped = $3 attacklab: lookbehind removed
+ ["(]
+ (.+?) // title = $4
+ [")]
+ [ \t]*
+ )? // title is optional
+ (?:\n+|$)
+ /gm,
+ function(){...});
+ */
+ var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
+ function (wholeMatch,m1,m2,m3,m4) {
+ m1 = m1.toLowerCase();
+ g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
+ if (m3) {
+ // Oops, found blank lines, so it's not a title.
+ // Put back the parenthetical statement we stole.
+ return m3+m4;
+ } else if (m4) {
+ g_titles[m1] = m4.replace(/"/g,"&quot;");
+ }
+
+ // Completely remove the definition from the text
+ return "";
+ }
+ );
+
+ return text;
+}
+
+var _HashHTMLBlocks = function(text) {
+ // attacklab: Double up blank lines to reduce lookaround
+ text = text.replace(/\n/g,"\n\n");
+
+ // Hashify HTML blocks:
+ // We only want to do this for block-level HTML tags, such as headers,
+ // lists, and tables. That's because we still want to wrap <p>s around
+ // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+ // phrase emphasis, and spans. The list of tags we're looking for is
+ // hard-coded:
+ var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
+ var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
+
+ // First, look for nested blocks, e.g.:
+ // <div>
+ // <div>
+ // tags for inner block must be indented.
+ // </div>
+ // </div>
+ //
+ // The outermost tags must start at the left margin for this to match, and
+ // the inner nested divs must be indented.
+ // We need to do this before the next, more liberal match, because the next
+ // match will start at the first `<div>` and stop at the first `</div>`.
+
+ // attacklab: This regex can be expensive when it fails.
+ /*
+ var text = text.replace(/
+ ( // save in $1
+ ^ // start of line (with /m)
+ <($block_tags_a) // start tag = $2
+ \b // word break
+ // attacklab: hack around khtml/pcre bug...
+ [^\r]*?\n // any number of lines, minimally matching
+ </\2> // the matching end tag
+ [ \t]* // trailing spaces/tabs
+ (?=\n+) // followed by a newline
+ ) // attacklab: there are sentinel newlines at end of document
+ /gm,function(){...}};
+ */
+ text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);
+
+ //
+ // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
+ //
+
+ /*
+ var text = text.replace(/
+ ( // save in $1
+ ^ // start of line (with /m)
+ <($block_tags_b) // start tag = $2
+ \b // word break
+ // attacklab: hack around khtml/pcre bug...
+ [^\r]*? // any number of lines, minimally matching
+ .*</\2> // the matching end tag
+ [ \t]* // trailing spaces/tabs
+ (?=\n+) // followed by a newline
+ ) // attacklab: there are sentinel newlines at end of document
+ /gm,function(){...}};
+ */
+ text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);
+
+ // Special case just for <hr />. It was easier to make a special case than
+ // to make the other regex more complicated.
+
+ /*
+ text = text.replace(/
+ ( // save in $1
+ \n\n // Starting after a blank line
+ [ ]{0,3}
+ (<(hr) // start tag = $2
+ \b // word break
+ ([^<>])*? //
+ \/?>) // the matching end tag
+ [ \t]*
+ (?=\n{2,}) // followed by a blank line
+ )
+ /g,hashElement);
+ */
+ text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
+
+ // Special case for standalone HTML comments:
+
+ /*
+ text = text.replace(/
+ ( // save in $1
+ \n\n // Starting after a blank line
+ [ ]{0,3} // attacklab: g_tab_width - 1
+ <!
+ (--[^\r]*?--\s*)+
+ >
+ [ \t]*
+ (?=\n{2,}) // followed by a blank line
+ )
+ /g,hashElement);
+ */
+ text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);
+
+ // PHP and ASP-style processor instructions (<?...?> and <%...%>)
+
+ /*
+ text = text.replace(/
+ (?:
+ \n\n // Starting after a blank line
+ )
+ ( // save in $1
+ [ ]{0,3} // attacklab: g_tab_width - 1
+ (?:
+ <([?%]) // $2
+ [^\r]*?
+ \2>
+ )
+ [ \t]*
+ (?=\n{2,}) // followed by a blank line
+ )
+ /g,hashElement);
+ */
+ text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
+
+ // attacklab: Undo double lines (see comment at top of this function)
+ text = text.replace(/\n\n/g,"\n");
+ return text;
+}
+
+var hashElement = function(wholeMatch,m1) {
+ var blockText = m1;
+
+ // Undo double lines
+ blockText = blockText.replace(/\n\n/g,"\n");
+ blockText = blockText.replace(/^\n/,"");
+
+ // strip trailing blank lines
+ blockText = blockText.replace(/\n+$/g,"");
+
+ // Replace the element text with a marker ("~KxK" where x is its key)
+ blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
+
+ return blockText;
+};
+
+var _RunBlockGamut = function(text) {
+//
+// These are all the transformations that form block-level
+// tags like paragraphs, headers, and list items.
+//
+ text = _DoHeaders(text);
+
+ // Do Horizontal Rules:
+ var key = hashBlock("<hr />");
+ text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
+ text = text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,key);
+ text = text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,key);
+
+ text = _DoLists(text);
+ text = _DoCodeBlocks(text);
+ text = _DoBlockQuotes(text);
+
+ // We already ran _HashHTMLBlocks() before, in Markdown(), but that
+ // was to escape raw HTML in the original Markdown source. This time,
+ // we're escaping the markup we've just created, so that we don't wrap
+ // <p> tags around block-level tags.
+ text = _HashHTMLBlocks(text);
+ text = _FormParagraphs(text);
+
+ return text;
+}
+
+
+var _RunSpanGamut = function(text) {
+//
+// These are all the transformations that occur *within* block-level
+// tags like paragraphs, headers, and list items.
+//
+
+ text = _DoCodeSpans(text);
+ text = _EscapeSpecialCharsWithinTagAttributes(text);
+ text = _EncodeBackslashEscapes(text);
+
+ // Process anchor and image tags. Images must come first,
+ // because ![foo][f] looks like an anchor.
+ text = _DoImages(text);
+ text = _DoAnchors(text);
+
+ // Make links out of things like `<http://example.com/>`
+ // Must come after _DoAnchors(), because you can use < and >
+ // delimiters in inline links like [this](<url>).
+ text = _DoAutoLinks(text);
+ text = _EncodeAmpsAndAngles(text);
+ text = _DoItalicsAndBold(text);
+
+ // Do hard breaks:
+ text = text.replace(/ +\n/g," <br />\n");
+
+ return text;
+}
+
+var _EscapeSpecialCharsWithinTagAttributes = function(text) {
+//
+// Within tags -- meaning between < and > -- encode [\ ` * _] so they
+// don't conflict with their use in Markdown for code, italics and strong.
+//
+
+ // Build a regex to find HTML tags and comments. See Friedl's
+ // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
+ var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
+
+ text = text.replace(regex, function(wholeMatch) {
+ var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
+ tag = escapeCharacters(tag,"\\`*_");
+ return tag;
+ });
+
+ return text;
+}
+
+var _DoAnchors = function(text) {
+//
+// Turn Markdown link shortcuts into XHTML <a> tags.
+//
+ //
+ // First, handle reference-style links: [link text] [id]
+ //
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ \[
+ (
+ (?:
+ \[[^\]]*\] // allow brackets nested one level
+ |
+ [^\[] // or anything else
+ )*
+ )
+ \]
+
+ [ ]? // one optional space
+ (?:\n[ ]*)? // one optional newline followed by spaces
+
+ \[
+ (.*?) // id = $3
+ \]
+ )()()()() // pad remaining backreferences
+ /g,_DoAnchors_callback);
+ */
+ text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
+
+ //
+ // Next, inline-style links: [link text](url "optional title")
+ //
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ \[
+ (
+ (?:
+ \[[^\]]*\] // allow brackets nested one level
+ |
+ [^\[\]] // or anything else
+ )
+ )
+ \]
+ \( // literal paren
+ [ \t]*
+ () // no id, so leave $3 empty
+ <?(.*?)>? // href = $4
+ [ \t]*
+ ( // $5
+ (['"]) // quote char = $6
+ (.*?) // Title = $7
+ \6 // matching quote
+ [ \t]* // ignore any spaces/tabs between closing quote and )
+ )? // title is optional
+ \)
+ )
+ /g,writeAnchorTag);
+ */
+ text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
+
+ //
+ // Last, handle reference-style shortcuts: [link text]
+ // These must come last in case you've also got [link test][1]
+ // or [link test](/foo)
+ //
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ \[
+ ([^\[\]]+) // link text = $2; can't contain '[' or ']'
+ \]
+ )()()()()() // pad rest of backreferences
+ /g, writeAnchorTag);
+ */
+ text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
+
+ return text;
+}
+
+var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
+ if (m7 == undefined) m7 = "";
+ var whole_match = m1;
+ var link_text = m2;
+ var link_id = m3.toLowerCase();
+ var url = m4;
+ var title = m7;
+
+ if (url == "") {
+ if (link_id == "") {
+ // lower-case and turn embedded newlines into spaces
+ link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
+ }
+ url = "#"+link_id;
+
+ if (g_urls[link_id] != undefined) {
+ url = g_urls[link_id];
+ if (g_titles[link_id] != undefined) {
+ title = g_titles[link_id];
+ }
+ }
+ else {
+ if (whole_match.search(/\(\s*\)$/m)>-1) {
+ // Special case for explicit empty url
+ url = "";
+ } else {
+ return whole_match;
+ }
+ }
+ }
+
+ url = escapeCharacters(url,"*_");
+ var result = "<a href=\"" + url + "\"";
+
+ if (title != "") {
+ title = title.replace(/"/g,"&quot;");
+ title = escapeCharacters(title,"*_");
+ result += " title=\"" + title + "\"";
+ }
+
+ result += ">" + link_text + "</a>";
+
+ return result;
+}
+
+
+var _DoImages = function(text) {
+//
+// Turn Markdown image shortcuts into <img> tags.
+//
+
+ //
+ // First, handle reference-style labeled images: ![alt text][id]
+ //
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ !\[
+ (.*?) // alt text = $2
+ \]
+
+ [ ]? // one optional space
+ (?:\n[ ]*)? // one optional newline followed by spaces
+
+ \[
+ (.*?) // id = $3
+ \]
+ )()()()() // pad rest of backreferences
+ /g,writeImageTag);
+ */
+ text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
+
+ //
+ // Next, handle inline images: ![alt text](url "optional title")
+ // Don't forget: encode * and _
+
+ /*
+ text = text.replace(/
+ ( // wrap whole match in $1
+ !\[
+ (.*?) // alt text = $2
+ \]
+ \s? // One optional whitespace character
+ \( // literal paren
+ [ \t]*
+ () // no id, so leave $3 empty
+ <?(\S+?)>? // src url = $4
+ [ \t]*
+ ( // $5
+ (['"]) // quote char = $6
+ (.*?) // title = $7
+ \6 // matching quote
+ [ \t]*
+ )? // title is optional
+ \)
+ )
+ /g,writeImageTag);
+ */
+ text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
+
+ return text;
+}
+
+var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
+ var whole_match = m1;
+ var alt_text = m2;
+ var link_id = m3.toLowerCase();
+ var url = m4;
+ var title = m7;
+
+ if (!title) title = "";
+
+ if (url == "") {
+ if (link_id == "") {
+ // lower-case and turn embedded newlines into spaces
+ link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
+ }
+ url = "#"+link_id;
+
+ if (g_urls[link_id] != undefined) {
+ url = g_urls[link_id];
+ if (g_titles[link_id] != undefined) {
+ title = g_titles[link_id];
+ }
+ }
+ else {
+ return whole_match;
+ }
+ }
+
+ alt_text = alt_text.replace(/"/g,"&quot;");
+ url = escapeCharacters(url,"*_");
+ var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
+
+ // attacklab: Markdown.pl adds empty title attributes to images.
+ // Replicate this bug.
+
+ //if (title != "") {
+ title = title.replace(/"/g,"&quot;");
+ title = escapeCharacters(title,"*_");
+ result += " title=\"" + title + "\"";
+ //}
+
+ result += " />";
+
+ return result;
+}
+
+
+var _DoHeaders = function(text) {
+
+ // Setext-style headers:
+ // Header 1
+ // ========
+ //
+ // Header 2
+ // --------
+ //
+ text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
+ function(wholeMatch,m1){return hashBlock("<h1>" + _RunSpanGamut(m1) + "</h1>");});
+
+ text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
+ function(matchFound,m1){return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>");});
+
+ // atx-style headers:
+ // # Header 1
+ // ## Header 2
+ // ## Header 2 with closing hashes ##
+ // ...
+ // ###### Header 6
+ //
+
+ /*
+ text = text.replace(/
+ ^(\#{1,6}) // $1 = string of #'s
+ [ \t]*
+ (.+?) // $2 = Header text
+ [ \t]*
+ \#* // optional closing #'s (not counted)
+ \n+
+ /gm, function() {...});
+ */
+
+ text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
+ function(wholeMatch,m1,m2) {
+ var h_level = m1.length;
+ return hashBlock("<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">");
+ });
+
+ return text;
+}
+
+// This declaration keeps Dojo compressor from outputting garbage:
+var _ProcessListItems;
+
+var _DoLists = function(text) {
+//
+// Form HTML ordered (numbered) and unordered (bulleted) lists.
+//
+
+ // attacklab: add sentinel to hack around khtml/safari bug:
+ // http://bugs.webkit.org/show_bug.cgi?id=11231
+ text += "~0";
+
+ // Re-usable pattern to match any entirel ul or ol list:
+
+ /*
+ var whole_list = /
+ ( // $1 = whole list
+ ( // $2
+ [ ]{0,3} // attacklab: g_tab_width - 1
+ ([*+-]|\d+[.]) // $3 = first list item marker
+ [ \t]+
+ )
+ [^\r]+?
+ ( // $4
+ ~0 // sentinel for workaround; should be $
+ |
+ \n{2,}
+ (?=\S)
+ (?! // Negative lookahead for another list item marker
+ [ \t]*
+ (?:[*+-]|\d+[.])[ \t]+
+ )
+ )
+ )/g
+ */
+ var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
+
+ if (g_list_level) {
+ text = text.replace(whole_list,function(wholeMatch,m1,m2) {
+ var list = m1;
+ var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
+
+ // Turn double returns into triple returns, so that we can make a
+ // paragraph for the last item in a list, if necessary:
+ list = list.replace(/\n{2,}/g,"\n\n\n");;
+ var result = _ProcessListItems(list);
+
+ // Trim any trailing whitespace, to put the closing `</$list_type>`
+ // up on the preceding line, to get it past the current stupid
+ // HTML block parser. This is a hack to work around the terrible
+ // hack that is the HTML block parser.
+ result = result.replace(/\s+$/,"");
+ result = "<"+list_type+">" + result + "</"+list_type+">\n";
+ return result;
+ });
+ } else {
+ whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
+ text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
+ var runup = m1;
+ var list = m2;
+
+ var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
+ // Turn double returns into triple returns, so that we can make a
+ // paragraph for the last item in a list, if necessary:
+ var list = list.replace(/\n{2,}/g,"\n\n\n");;
+ var result = _ProcessListItems(list);
+ result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";
+ return result;
+ });
+ }
+
+ // attacklab: strip sentinel
+ text = text.replace(/~0/,"");
+
+ return text;
+}
+
+_ProcessListItems = function(list_str) {
+//
+// Process the contents of a single ordered or unordered list, splitting it
+// into individual list items.
+//
+ // The $g_list_level global keeps track of when we're inside a list.
+ // Each time we enter a list, we increment it; when we leave a list,
+ // we decrement. If it's zero, we're not in a list anymore.
+ //
+ // We do this because when we're not inside a list, we want to treat
+ // something like this:
+ //
+ // I recommend upgrading to version
+ // 8. Oops, now this line is treated
+ // as a sub-list.
+ //
+ // As a single paragraph, despite the fact that the second line starts
+ // with a digit-period-space sequence.
+ //
+ // Whereas when we're inside a list (or sub-list), that line will be
+ // treated as the start of a sub-list. What a kludge, huh? This is
+ // an aspect of Markdown's syntax that's hard to parse perfectly
+ // without resorting to mind-reading. Perhaps the solution is to
+ // change the syntax rules such that sub-lists must start with a
+ // starting cardinal number; e.g. "1." or "a.".
+
+ g_list_level++;
+
+ // trim trailing blank lines:
+ list_str = list_str.replace(/\n{2,}$/,"\n");
+
+ // attacklab: add sentinel to emulate \z
+ list_str += "~0";
+
+ /*
+ list_str = list_str.replace(/
+ (\n)? // leading line = $1
+ (^[ \t]*) // leading whitespace = $2
+ ([*+-]|\d+[.]) [ \t]+ // list marker = $3
+ ([^\r]+? // list item text = $4
+ (\n{1,2}))
+ (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
+ /gm, function(){...});
+ */
+ list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
+ function(wholeMatch,m1,m2,m3,m4){
+ var item = m4;
+ var leading_line = m1;
+ var leading_space = m2;
+
+ if (leading_line || (item.search(/\n{2,}/)>-1)) {
+ item = _RunBlockGamut(_Outdent(item));
+ }
+ else {
+ // Recursion for sub-lists:
+ item = _DoLists(_Outdent(item));
+ item = item.replace(/\n$/,""); // chomp(item)
+ item = _RunSpanGamut(item);
+ }
+
+ return "<li>" + item + "</li>\n";
+ }
+ );
+
+ // attacklab: strip sentinel
+ list_str = list_str.replace(/~0/g,"");
+
+ g_list_level--;
+ return list_str;
+}
+
+
+var _DoCodeBlocks = function(text) {
+//
+// Process Markdown `<pre><code>` blocks.
+//
+
+ /*
+ text = text.replace(text,
+ /(?:\n\n|^)
+ ( // $1 = the code block -- one or more lines, starting with a space/tab
+ (?:
+ (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
+ .*\n+
+ )+
+ )
+ (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
+ /g,function(){...});
+ */
+
+ // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
+ text += "~0";
+
+ text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
+ function(wholeMatch,m1,m2) {
+ var codeblock = m1;
+ var nextChar = m2;
+
+ codeblock = _EncodeCode( _Outdent(codeblock));
+ codeblock = _Detab(codeblock);
+ codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
+ codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
+
+ codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
+
+ return hashBlock(codeblock) + nextChar;
+ }
+ );
+
+ // attacklab: strip sentinel
+ text = text.replace(/~0/,"");
+
+ return text;
+}
+
+var hashBlock = function(text) {
+ text = text.replace(/(^\n+|\n+$)/g,"");
+ return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
+}
+
+
+var _DoCodeSpans = function(text) {
+//
+// * Backtick quotes are used for <code></code> spans.
+//
+// * You can use multiple backticks as the delimiters if you want to
+// include literal backticks in the code span. So, this input:
+//
+// Just type ``foo `bar` baz`` at the prompt.
+//
+// Will translate to:
+//
+// <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+//
+// There's no arbitrary limit to the number of backticks you
+// can use as delimters. If you need three consecutive backticks
+// in your code, use four for delimiters, etc.
+//
+// * You can use spaces to get literal backticks at the edges:
+//
+// ... type `` `bar` `` ...
+//
+// Turns to:
+//
+// ... type <code>`bar`</code> ...
+//
+
+ /*
+ text = text.replace(/
+ (^|[^\\]) // Character before opening ` can't be a backslash
+ (`+) // $2 = Opening run of `
+ ( // $3 = The code block
+ [^\r]*?
+ [^`] // attacklab: work around lack of lookbehind
+ )
+ \2 // Matching closer
+ (?!`)
+ /gm, function(){...});
+ */
+
+ text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
+ function(wholeMatch,m1,m2,m3,m4) {
+ var c = m3;
+ c = c.replace(/^([ \t]*)/g,""); // leading whitespace
+ c = c.replace(/[ \t]*$/g,""); // trailing whitespace
+ c = _EncodeCode(c);
+ return m1+"<code>"+c+"</code>";
+ });
+
+ return text;
+}
+
+
+var _EncodeCode = function(text) {
+//
+// Encode/escape certain characters inside Markdown code runs.
+// The point is that in code, these characters are literals,
+// and lose their special Markdown meanings.
+//
+ // Encode all ampersands; HTML entities are not
+ // entities within a Markdown code span.
+ text = text.replace(/&/g,"&amp;");
+
+ // Do the angle bracket song and dance:
+ text = text.replace(/</g,"&lt;");
+ text = text.replace(/>/g,"&gt;");
+
+ // Now, escape characters that are magic in Markdown:
+ text = escapeCharacters(text,"\*_{}[]\\",false);
+
+// jj the line above breaks this:
+//---
+
+//* Item
+
+// 1. Subitem
+
+// special char: *
+//---
+
+ return text;
+}
+
+
+var _DoItalicsAndBold = function(text) {
+
+ // <strong> must go first:
+ text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\1/g,
+ "<strong>$2</strong>");
+
+ text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
+ "<em>$2</em>");
+
+ return text;
+}
+
+
+var _DoBlockQuotes = function(text) {
+
+ /*
+ text = text.replace(/
+ ( // Wrap whole match in $1
+ (
+ ^[ \t]*>[ \t]? // '>' at the start of a line
+ .+\n // rest of the first line
+ (.+\n)* // subsequent consecutive lines
+ \n* // blanks
+ )+
+ )
+ /gm, function(){...});
+ */
+
+ text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
+ function(wholeMatch,m1) {
+ var bq = m1;
+
+ // attacklab: hack around Konqueror 3.5.4 bug:
+ // "----------bug".replace(/^-/g,"") == "bug"
+
+ bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting
+
+ // attacklab: clean up hack
+ bq = bq.replace(/~0/g,"");
+
+ bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines
+ bq = _RunBlockGamut(bq); // recurse
+
+ bq = bq.replace(/(^|\n)/g,"$1 ");
+ // These leading spaces screw with <pre> content, so we need to fix that:
+ bq = bq.replace(
+ /(\s*<pre>[^\r]+?<\/pre>)/gm,
+ function(wholeMatch,m1) {
+ var pre = m1;
+ // attacklab: hack around Konqueror 3.5.4 bug:
+ pre = pre.replace(/^ /mg,"~0");
+ pre = pre.replace(/~0/g,"");
+ return pre;
+ });
+
+ return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
+ });
+ return text;
+}
+
+
+var _FormParagraphs = function(text) {
+//
+// Params:
+// $text - string to process with html <p> tags
+//
+
+ // Strip leading and trailing lines:
+ text = text.replace(/^\n+/g,"");
+ text = text.replace(/\n+$/g,"");
+
+ var grafs = text.split(/\n{2,}/g);
+ var grafsOut = new Array();
+
+ //
+ // Wrap <p> tags.
+ //
+ var end = grafs.length;
+ for (var i=0; i<end; i++) {
+ var str = grafs[i];
+
+ // if this is an HTML marker, copy it
+ if (str.search(/~K(\d+)K/g) >= 0) {
+ grafsOut.push(str);
+ }
+ else if (str.search(/\S/) >= 0) {
+ str = _RunSpanGamut(str);
+ str = str.replace(/^([ \t]*)/g,"<p>");
+ str += "</p>"
+ grafsOut.push(str);
+ }
+
+ }
+
+ //
+ // Unhashify HTML blocks
+ //
+ end = grafsOut.length;
+ for (var i=0; i<end; i++) {
+ // if this is a marker for an html block...
+ while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
+ var blockText = g_html_blocks[RegExp.$1];
+ blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
+ grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
+ }
+ }
+
+ return grafsOut.join("\n\n");
+}
+
+
+var _EncodeAmpsAndAngles = function(text) {
+// Smart processing for ampersands and angle brackets that need to be encoded.
+
+ // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+ // http://bumppo.net/projects/amputator/
+ text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
+
+ // Encode naked <'s
+ text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
+
+ return text;
+}
+
+
+var _EncodeBackslashEscapes = function(text) {
+//
+// Parameter: String.
+// Returns: The string, with after processing the following backslash
+// escape sequences.
+//
+
+ // attacklab: The polite way to do this is with the new
+ // escapeCharacters() function:
+ //
+ // text = escapeCharacters(text,"\\",true);
+ // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
+ //
+ // ...but we're sidestepping its use of the (slow) RegExp constructor
+ // as an optimization for Firefox. This function gets called a LOT.
+
+ text = text.replace(/\\(\\)/g,escapeCharacters_callback);
+ text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
+ return text;
+}
+
+
+var _DoAutoLinks = function(text) {
+
+ text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
+
+ // Email addresses: <address@domain.foo>
+
+ /*
+ text = text.replace(/
+ <
+ (?:mailto:)?
+ (
+ [-.\w]+
+ \@
+ [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
+ )
+ >
+ /gi, _DoAutoLinks_callback());
+ */
+ text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
+ function(wholeMatch,m1) {
+ return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
+ }
+ );
+
+ return text;
+}
+
+
+var _EncodeEmailAddress = function(addr) {
+//
+// Input: an email address, e.g. "foo@example.com"
+//
+// Output: the email address as a mailto link, with each character
+// of the address encoded as either a decimal or hex entity, in
+// the hopes of foiling most address harvesting spam bots. E.g.:
+//
+// <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
+// x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
+// &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
+//
+// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
+// mailing list: <http://tinyurl.com/yu7ue>
+//
+
+ // attacklab: why can't javascript speak hex?
+ function char2hex(ch) {
+ var hexDigits = '0123456789ABCDEF';
+ var dec = ch.charCodeAt(0);
+ return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
+ }
+
+ var encode = [
+ function(ch){return "&#"+ch.charCodeAt(0)+";";},
+ function(ch){return "&#x"+char2hex(ch)+";";},
+ function(ch){return ch;}
+ ];
+
+ addr = "mailto:" + addr;
+
+ addr = addr.replace(/./g, function(ch) {
+ if (ch == "@") {
+ // this *must* be encoded. I insist.
+ ch = encode[Math.floor(Math.random()*2)](ch);
+ } else if (ch !=":") {
+ // leave ':' alone (to spot mailto: later)
+ var r = Math.random();
+ // roughly 10% raw, 45% hex, 45% dec
+ ch = (
+ r > .9 ? encode[2](ch) :
+ r > .45 ? encode[1](ch) :
+ encode[0](ch)
+ );
+ }
+ return ch;
+ });
+
+ addr = "<a href=\"" + addr + "\">" + addr + "</a>";
+ addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
+
+ return addr;
+}
+
+
+var _UnescapeSpecialChars = function(text) {
+//
+// Swap back in all the special characters we've hidden.
+//
+ text = text.replace(/~E(\d+)E/g,
+ function(wholeMatch,m1) {
+ var charCodeToReplace = parseInt(m1);
+ return String.fromCharCode(charCodeToReplace);
+ }
+ );
+ return text;
+}
+
+
+var _Outdent = function(text) {
+//
+// Remove one level of line-leading tabs or spaces
+//
+
+ // attacklab: hack around Konqueror 3.5.4 bug:
+ // "----------bug".replace(/^-/g,"") == "bug"
+
+ text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
+
+ // attacklab: clean up hack
+ text = text.replace(/~0/g,"")
+
+ return text;
+}
+
+var _Detab = function(text) {
+// attacklab: Detab's completely rewritten for speed.
+// In perl we could fix it by anchoring the regexp with \G.
+// In javascript we're less fortunate.
+
+ // expand first n-1 tabs
+ text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width
+
+ // replace the nth with two sentinels
+ text = text.replace(/\t/g,"~A~B");
+
+ // use the sentinel to anchor our regex so it doesn't explode
+ text = text.replace(/~B(.+?)~A/g,
+ function(wholeMatch,m1,m2) {
+ var leadingText = m1;
+ var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width
+
+ // there *must* be a better way to do this:
+ for (var i=0; i<numSpaces; i++) leadingText+=" ";
+
+ return leadingText;
+ }
+ );
+
+ // clean up sentinels
+ text = text.replace(/~A/g," "); // attacklab: g_tab_width
+ text = text.replace(/~B/g,"");
+
+ return text;
+}
+
+
+//
+// attacklab: Utility functions
+//
+
+
+var escapeCharacters = function(text, charsToEscape, afterBackslash) {
+ // First we have to escape the escape characters so that
+ // we can build a character class out of them
+ var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";
+
+ if (afterBackslash) {
+ regexString = "\\\\" + regexString;
+ }
+
+ var regex = new RegExp(regexString,"g");
+ text = text.replace(regex,escapeCharacters_callback);
+
+ return text;
+}
+
+
+var escapeCharacters_callback = function(wholeMatch,m1) {
+ var charCodeToEscape = m1.charCodeAt(0);
+ return "~E"+charCodeToEscape+"E";
+}
+
+} // end of Attacklab.showdown.converter
+
+
+// Version 0.9 used the Showdown namespace instead of Attacklab.showdown
+// The old namespace is deprecated, but we'll support it for now:
+var Showdown = Attacklab.showdown;
+
+// If anyone's interested, tell the world that this file's been loaded
+if (Attacklab.fileLoaded) {
+ Attacklab.fileLoaded("showdown.js");
+} \ No newline at end of file
diff --git a/templates/content/js/wmd/wmd-min.js b/templates/content/js/wmd/wmd-min.js
new file mode 100644
index 00000000..85cbd907
--- /dev/null
+++ b/templates/content/js/wmd/wmd-min.js
@@ -0,0 +1 @@
+var Attacklab=Attacklab||{};Attacklab.wmdBase=function(){var y=top.Attacklab;var E=top.document;var s=top.RegExp;var l=top.navigator;y.Util={};y.Position={};y.Command={};y.Global={};var a=y.Util;var C=y.Position;var h=y.Command;var v=y.Global;v.isIE=/msie/.test(l.userAgent.toLowerCase());v.isIE_5or6=/msie 6/.test(l.userAgent.toLowerCase())||/msie 5/.test(l.userAgent.toLowerCase());v.isIE_7plus=v.isIE&&!v.isIE_5or6;v.isOpera=/opera/.test(l.userAgent.toLowerCase());v.isKonqueror=/konqueror/.test(l.userAgent.toLowerCase());var c="粗体 <strong> Ctrl-B";var f="斜体 <em> Ctrl-I";var z="超链接 <a> Ctrl-L";var u="引用 <blockquote> Ctrl-.";var e="代码 <pre><code> Ctrl-K";var d="图片 <img> Ctrl-G";var q="数字编号列表 <ol> Ctrl-O";var t="项目符号列表 <ul> Ctrl-U";var i="标题 <h1>/<h2> Ctrl-H";var p="水平线 <hr> Ctrl-R";var m="撤销 Ctrl-Z";var j="重做 Ctrl-Y";var B="<p style='margin-top: 0px'><b>输入图片地址</b></p><p>示例:<br />http://www.cnprog.com/images/temp.jpg \"我的截图\"</p>";var D="<p style='margin-top: 0px'><b>输入Web地址</b></p><p>示例:<br />http://www.cnprog.com/ \"我的网站\"</p>";var n='<div>或者上传本地图片:</div><input type="file" name="file-upload" id="file-upload" size="26" onchange="return ajaxFileUpload($(\'#image-url\'));"/><br><img id="loading" src="/content/images/indicator.gif" style="display:none;"/>';var b="http://";var g="http://";var o="images/";var A=500;var x=100;var k="http://wmd-editor.com/";var r="WMD website";var w="_blank";y.PanelCollection=function(){this.buttonBar=E.getElementById("wmd-button-bar");this.preview=E.getElementById("previewer");this.output=E.getElementById("wmd-output");this.input=E.getElementById("editor")};y.panels=undefined;y.ieCachedRange=null;y.ieRetardedClick=false;a.isVisible=function(F){if(window.getComputedStyle){return window.getComputedStyle(F,null).getPropertyValue("display")!=="none"}else{if(F.currentStyle){return F.currentStyle.display!=="none"}}};a.addEvent=function(G,F,H){if(G.attachEvent){G.attachEvent("on"+F,H)}else{G.addEventListener(F,H,false)}};a.removeEvent=function(G,F,H){if(G.detachEvent){G.detachEvent("on"+F,H)}else{G.removeEventListener(F,H,false)}};a.fixEolChars=function(F){F=F.replace(/\r\n/g,"\n");F=F.replace(/\r/g,"\n");return F};a.extendRegExp=function(H,J,G){if(J===null||J===undefined){J=""}if(G===null||G===undefined){G=""}var I=H.toString();var F;I=I.replace(/\/([gim]*)$/,"");F=s.$1;I=I.replace(/(^\/|\/$)/g,"");I=J+I+G;return new s(I,F)};a.createImage=function(F){var H=o+F;var G=E.createElement("img");G.className="wmd-button";G.src=H;return G};a.prompt=function(M,P,H){var I;var F;var K;var J=0;if(arguments.length==4){J=arguments[3]}if(P===undefined){P=""}var L=function(Q){var R=(Q.charCode||Q.keyCode);if(R===27){N(true)}};var N=function(Q){a.removeEvent(E.body,"keydown",L);var R=K.value;if(Q){R=null}else{R=R.replace("http://http://","http://");R=R.replace("http://https://","https://");R=R.replace("http://ftp://","ftp://");if(R.indexOf("http://")===-1&&R.indexOf("ftp://")===-1){R="http://"+R}}I.parentNode.removeChild(I);F.parentNode.removeChild(F);H(R);return false};var G=function(){F=E.createElement("div");F.className="wmd-prompt-background";style=F.style;style.position="absolute";style.top="0";style.zIndex="1000";if(v.isKonqueror){style.backgroundColor="transparent"}else{if(v.isIE){style.filter="alpha(opacity=50)"}else{style.opacity="0.5"}}var Q=C.getPageSize();style.height=Q[1]+"px";if(v.isIE){style.left=E.documentElement.scrollLeft;style.width=E.documentElement.clientWidth}else{style.left="0";style.width="100%"}E.body.appendChild(F)};var O=function(){I=E.createElement("div");I.className="wmd-prompt-dialog";I.style.padding="10px;";I.style.position="fixed";I.style.width="400px";I.style.zIndex="1001";var Q=E.createElement("div");Q.innerHTML=M;Q.style.padding="5px";I.appendChild(Q);var S=E.createElement("form");S.onsubmit=function(){return N(false)};style=S.style;style.padding="0";style.margin="0";style.cssFloat="left";style.width="100%";style.textAlign="center";style.position="relative";I.appendChild(S);K=E.createElement("input");if(J==1){K.id="image-url"}K.type="text";K.value=P;style=K.style;style.display="block";style.width="80%";style.marginLeft=style.marginRight="auto";S.appendChild(K);if(J==1){var R=E.createElement("div");R.innerHTML=n;R.style.padding="5px";S.appendChild(R)}var U=E.createElement("input");U.type="button";U.onclick=function(){return N(false)};U.value="OK";style=U.style;style.margin="10px";style.display="inline";style.width="7em";var T=E.createElement("input");T.type="button";T.onclick=function(){return N(true)};T.value="Cancel";style=T.style;style.margin="10px";style.display="inline";style.width="7em";if(/mac/.test(l.platform.toLowerCase())){S.appendChild(T);S.appendChild(U)}else{S.appendChild(U);S.appendChild(T)}a.addEvent(E.body,"keydown",L);I.style.top="50%";I.style.left="50%";I.style.display="block";if(v.isIE_5or6){I.style.position="absolute";I.style.top=E.documentElement.scrollTop+200+"px";I.style.left="50%"}E.body.appendChild(I);I.style.marginTop=-(C.getHeight(I)/2)+"px";I.style.marginLeft=-(C.getWidth(I)/2)+"px"};G();top.setTimeout(function(){O();var R=P.length;if(K.selectionStart!==undefined){K.selectionStart=0;K.selectionEnd=R}else{if(K.createTextRange){var Q=K.createTextRange();Q.collapse(false);Q.moveStart("character",-R);Q.moveEnd("character",R);Q.select()}}K.focus()},0)};C.getTop=function(H,G){var F=H.offsetTop;if(!G){while(H=H.offsetParent){F+=H.offsetTop}}return F};C.getHeight=function(F){return F.offsetHeight||F.scrollHeight};C.getWidth=function(F){return F.offsetWidth||F.scrollWidth};C.getPageSize=function(){var G,H;var F,K;if(self.innerHeight&&self.scrollMaxY){G=E.body.scrollWidth;H=self.innerHeight+self.scrollMaxY}else{if(E.body.scrollHeight>E.body.offsetHeight){G=E.body.scrollWidth;H=E.body.scrollHeight}else{G=E.body.offsetWidth;H=E.body.offsetHeight}}if(self.innerHeight){F=self.innerWidth;K=self.innerHeight}else{if(E.documentElement&&E.documentElement.clientHeight){F=E.documentElement.clientWidth;K=E.documentElement.clientHeight}else{if(E.body){F=E.body.clientWidth;K=E.body.clientHeight}}}var J=Math.max(G,F);var I=Math.max(H,K);return[J,I,F,K]};y.inputPoller=function(O,H){var F=this;var K=y.panels.input;var G;var I;var L;var J;this.tick=function(){if(!a.isVisible(K)){return}if(K.selectionStart||K.selectionStart===0){var Q=K.selectionStart;var P=K.selectionEnd;if(Q!=G||P!=I){G=Q;I=P;if(L!=K.value){L=K.value;return true}}}return false};var N=function(){if(!a.isVisible(K)){return}if(F.tick()){O()}};var M=function(){J=top.setInterval(N,H)};this.destroy=function(){top.clearInterval(J)};M()};y.undoManager=function(Q){var U=this;var O=[];var M=0;var L="none";var G;var R;var H;var K;var F=function(W,V){if(L!=W){L=W;if(!V){I()}}if(!v.isIE||L!="moving"){H=top.setTimeout(N,1)}else{K=null}};var N=function(){K=new y.TextareaState();R.tick();H=undefined};this.setCommandMode=function(){L="command";I();H=top.setTimeout(N,0)};this.canUndo=function(){return M>1};this.canRedo=function(){if(O[M+1]){return true}return false};this.undo=function(){if(U.canUndo()){if(G){G.restore();G=null}else{O[M]=new y.TextareaState();O[--M].restore();if(Q){Q()}}}L="none";y.panels.input.focus();N()};this.redo=function(){if(U.canRedo()){O[++M].restore();if(Q){Q()}}L="none";y.panels.input.focus();N()};var I=function(){var V=K||new y.TextareaState();if(!V){return false}if(L=="moving"){if(!G){G=V}return}if(G){if(O[M-1].text!=G.text){O[M++]=G}G=null}O[M++]=V;O[M+1]=null;if(Q){Q()}};var P=function(V){var X=false;if(V.ctrlKey||V.metaKey){var W=V.charCode||V.keyCode;var Y=String.fromCharCode(W);switch(Y){case"y":U.redo();X=true;break;case"z":if(!V.shiftKey){U.undo()}else{U.redo()}X=true;break}}if(X){if(V.preventDefault){V.preventDefault()}if(top.event){top.event.returnValue=false}return}};var T=function(V){if(!V.ctrlKey&&!V.metaKey){var W=V.keyCode;if((W>=33&&W<=40)||(W>=63232&&W<=63235)){F("moving")}else{if(W==8||W==46||W==127){F("deleting")}else{if(W==13){F("newlines")}else{if(W==27){F("escape")}else{if((W<16||W>20)&&W!=91){F("typing")}}}}}}};var J=function(){a.addEvent(y.panels.input,"keypress",function(W){if((W.ctrlKey||W.metaKey)&&(W.keyCode==89||W.keyCode==90)){W.preventDefault()}});var V=function(){if(v.isIE||(K&&K.text!=y.panels.input.value)){if(H==undefined){L="paste";I();N()}}};R=new y.inputPoller(V,x);a.addEvent(y.panels.input,"keydown",P);a.addEvent(y.panels.input,"keydown",T);a.addEvent(y.panels.input,"mousedown",function(){F("moving")});y.panels.input.onpaste=V;y.panels.input.ondrop=V};var S=function(){J();N();I()};this.destroy=function(){if(R){R.destroy()}};S()};y.editor=function(O){if(!O){O=function(){}}var L=y.panels.input;var I=0;var P=this;var K;var R;var G;var M;var N;var U=function(W){L.focus();if(W.textOp){if(N){N.setCommandMode()}var Y=new y.TextareaState();if(!Y){return}var Z=Y.getChunks();var V=function(){L.focus();if(Z){Y.setChunks(Z)}Y.restore();O()};var X=W.textOp(Z,V);if(!X){V()}}if(W.execute){W.execute(P)}};var S=function(){if(N){F(document.getElementById("wmd-undo-button"),N.canUndo());F(document.getElementById("wmd-redo-button"),N.canRedo())}};var F=function(V,X){var Y="0px";var Z="-20px";var W="-40px";if(X){V.style.backgroundPosition=V.XShift+" "+Y;V.onmouseover=function(){this.style.backgroundPosition=this.XShift+" "+W};V.onmouseout=function(){this.style.backgroundPosition=this.XShift+" "+Y};if(v.isIE){V.onmousedown=function(){y.ieRetardedClick=true;y.ieCachedRange=document.selection.createRange()}}if(!V.isHelp){V.onclick=function(){if(this.onmouseout){this.onmouseout()}U(this);return false}}}else{V.style.backgroundPosition=V.XShift+" "+Z;V.onmouseover=V.onmouseout=V.onclick=function(){}}};var J=function(){var Z=document.getElementById("wmd-button-bar");var W="0px";var Y="-20px";var ae="-40px";var ak=document.createElement("ul");ak.id="wmd-button-row";ak=Z.appendChild(ak);var ad=document.createElement("li");ad.className="wmd-button";ad.id="wmd-bold-button";ad.title=c;ad.XShift="0px";ad.textOp=h.doBold;F(ad,true);ak.appendChild(ad);var ac=document.createElement("li");ac.className="wmd-button";ac.id="wmd-italic-button";ac.title=f;ac.XShift="-20px";ac.textOp=h.doItalic;F(ac,true);ak.appendChild(ac);var ah=document.createElement("li");ah.className="wmd-spacer";ah.id="wmd-spacer1";ak.appendChild(ah);var ai=document.createElement("li");ai.className="wmd-button";ai.id="wmd-link-button";ai.title=z;ai.XShift="-40px";ai.textOp=function(ap,aq){return h.doLinkOrImage(ap,aq,false)};F(ai,true);ak.appendChild(ai);var al=document.createElement("li");al.className="wmd-button";al.id="wmd-quote-button";al.title=u;al.XShift="-60px";al.textOp=h.doBlockquote;F(al,true);ak.appendChild(al);var am=document.createElement("li");am.className="wmd-button";am.id="wmd-code-button";am.title=e;am.XShift="-80px";am.textOp=h.doCode;F(am,true);ak.appendChild(am);var aa=document.createElement("li");aa.className="wmd-button";aa.id="wmd-image-button";aa.title=d;aa.XShift="-100px";aa.textOp=function(ap,aq){return h.doLinkOrImage(ap,aq,true)};F(aa,true);ak.appendChild(aa);var ag=document.createElement("li");ag.className="wmd-spacer";ag.id="wmd-spacer2";ak.appendChild(ag);var ab=document.createElement("li");ab.className="wmd-button";ab.id="wmd-olist-button";ab.title=q;ab.XShift="-120px";ab.textOp=function(ap,aq){h.doList(ap,aq,true)};F(ab,true);ak.appendChild(ab);var ao=document.createElement("li");ao.className="wmd-button";ao.id="wmd-ulist-button";ao.title=t;ao.XShift="-140px";ao.textOp=function(ap,aq){h.doList(ap,aq,false)};F(ao,true);ak.appendChild(ao);var aj=document.createElement("li");aj.className="wmd-button";aj.id="wmd-heading-button";aj.title=i;aj.XShift="-160px";aj.textOp=h.doHeading;F(aj,true);ak.appendChild(aj);var X=document.createElement("li");X.className="wmd-button";X.id="wmd-hr-button";X.title=p;X.XShift="-180px";X.textOp=h.doHorizontalRule;F(X,true);ak.appendChild(X);var af=document.createElement("li");af.className="wmd-spacer";af.id="wmd-spacer3";ak.appendChild(af);var V=document.createElement("li");V.className="wmd-button";V.id="wmd-undo-button";V.title=m;V.XShift="-200px";V.execute=function(ap){ap.undo()};F(V,true);ak.appendChild(V);var an=document.createElement("li");an.className="wmd-button";an.id="wmd-redo-button";an.title=j;if(/win/.test(l.platform.toLowerCase())){an.title=j}else{an.title="重做 - Ctrl+Shift+Z"}an.XShift="-220px";an.execute=function(ap){ap.redo()};F(an,true);ak.appendChild(an);S()};var H=function(){if(/\?noundo/.test(E.location.href)){y.nativeUndo=true}if(!y.nativeUndo){N=new y.undoManager(function(){O();S()})}J();var W="keydown";if(v.isOpera){W="keypress"}a.addEvent(L,W,function(Y){if(Y.ctrlKey||Y.metaKey){var Z=Y.charCode||Y.keyCode;var X=String.fromCharCode(Z).toLowerCase();if(Z===46){X=""}if(Z===190){X="."}switch(X){case"b":U(document.getElementById("wmd-bold-button"));break;case"i":U(document.getElementById("wmd-italic-button"));break;case"l":U(document.getElementById("wmd-link-button"));break;case".":U(document.getElementById("wmd-quote-button"));break;case"k":U(document.getElementById("wmd-code-button"));break;case"g":U(document.getElementById("wmd-image-button"));break;case"o":U(document.getElementById("wmd-olist-button"));break;case"u":U(document.getElementById("wmd-ulist-button"));break;case"h":U(document.getElementById("wmd-heading-button"));break;case"r":U(document.getElementById("wmd-hr-button"));break;case"y":U(document.getElementById("wmd-redo-button"));break;case"z":if(Y.shiftKey){U(document.getElementById("wmd-redo-button"))}else{U(document.getElementById("wmd-undo-button"))}break;default:return}if(Y.preventDefault){Y.preventDefault()}if(top.event){top.event.returnValue=false}}});a.addEvent(L,"keyup",function(X){if(X.shiftKey&&!X.ctrlKey&&!X.metaKey){var Y=X.charCode||X.keyCode;if(Y===13){fakeButton={};fakeButton.textOp=h.doAutoindent;U(fakeButton)}}});if(L.form){var V=L.form.onsubmit;L.form.onsubmit=function(){Q();if(V){return V.apply(this,arguments)}}}};var Q=function(){if(y.showdown){var V=new y.showdown.converter()}var W=L.value;var X=function(){L.value=W};if(!/markdown/.test(y.wmd_env.output.toLowerCase())){if(V){L.value=V.makeHtml(W);top.setTimeout(X,0)}}return true};this.undo=function(){if(N){N.undo()}};this.redo=function(){if(N){N.redo()}};var T=function(){H()};this.destroy=function(){if(N){N.destroy()}if(G.parentNode){G.parentNode.removeChild(G)}if(L){L.style.marginTop=""}top.clearInterval(M)};T()};y.TextareaState=function(){var F=this;var G=y.panels.input;this.init=function(){if(!a.isVisible(G)){return}this.setInputAreaSelectionStartEnd();this.scrollTop=G.scrollTop;if(!this.text&&G.selectionStart||G.selectionStart===0){this.text=G.value}};this.setInputAreaSelection=function(){if(!a.isVisible(G)){return}if(G.selectionStart!==undefined&&!v.isOpera){G.focus();G.selectionStart=F.start;G.selectionEnd=F.end;G.scrollTop=F.scrollTop}else{if(E.selection){if(E.activeElement&&E.activeElement!==G){return}G.focus();var H=G.createTextRange();H.moveStart("character",-G.value.length);H.moveEnd("character",-G.value.length);H.moveEnd("character",F.end);H.moveStart("character",F.start);H.select()}}};this.setInputAreaSelectionStartEnd=function(){if(G.selectionStart||G.selectionStart===0){F.start=G.selectionStart;F.end=G.selectionEnd}else{if(E.selection){F.text=a.fixEolChars(G.value);var K;if(y.ieRetardedClick&&y.ieCachedRange){K=y.ieCachedRange;y.ieRetardedClick=false}else{K=E.selection.createRange()}var L=a.fixEolChars(K.text);var J="\x07";var I=J+L+J;K.text=I;var M=a.fixEolChars(G.value);K.moveStart("character",-I.length);K.text=L;F.start=M.indexOf(J);F.end=M.lastIndexOf(J)-J.length;var H=F.text.length-a.fixEolChars(G.value).length;if(H){K.moveStart("character",-L.length);while(H--){L+="\n";F.end+=1}K.text=L}this.setInputAreaSelection()}}};this.restore=function(){if(F.text!=undefined&&F.text!=G.value){G.value=F.text}this.setInputAreaSelection();G.scrollTop=F.scrollTop};this.getChunks=function(){var H=new y.Chunks();H.before=a.fixEolChars(F.text.substring(0,F.start));H.startTag="";H.selection=a.fixEolChars(F.text.substring(F.start,F.end));H.endTag="";H.after=a.fixEolChars(F.text.substring(F.end));H.scrollTop=F.scrollTop;return H};this.setChunks=function(H){H.before=H.before+H.startTag;H.after=H.endTag+H.after;if(v.isOpera){H.before=H.before.replace(/\n/g,"\r\n");H.selection=H.selection.replace(/\n/g,"\r\n");H.after=H.after.replace(/\n/g,"\r\n")}this.start=H.before.length;this.end=H.before.length+H.selection.length;this.text=H.before+H.selection+H.after;this.scrollTop=H.scrollTop};this.init()};y.Chunks=function(){};y.Chunks.prototype.findTags=function(G,I){var F=this;var H;if(G){H=a.extendRegExp(G,"","$");this.before=this.before.replace(H,function(J){F.startTag=F.startTag+J;return""});H=a.extendRegExp(G,"^","");this.selection=this.selection.replace(H,function(J){F.startTag=F.startTag+J;return""})}if(I){H=a.extendRegExp(I,"","$");this.selection=this.selection.replace(H,function(J){F.endTag=J+F.endTag;return""});H=a.extendRegExp(I,"^","");this.after=this.after.replace(H,function(J){F.endTag=J+F.endTag;return""})}};y.Chunks.prototype.trimWhitespace=function(F){this.selection=this.selection.replace(/^(\s*)/,"");if(!F){this.before+=s.$1}this.selection=this.selection.replace(/(\s*)$/,"");if(!F){this.after=s.$1+this.after}};y.Chunks.prototype.skipLines=function(H,G,F){if(H===undefined){H=1}if(G===undefined){G=1}H++;G++;var I;var J;this.selection=this.selection.replace(/(^\n*)/,"");this.startTag=this.startTag+s.$1;this.selection=this.selection.replace(/(\n*$)/,"");this.endTag=this.endTag+s.$1;this.startTag=this.startTag.replace(/(^\n*)/,"");this.before=this.before+s.$1;this.endTag=this.endTag.replace(/(\n*$)/,"");this.after=this.after+s.$1;if(this.before){I=J="";while(H--){I+="\\n?";J+="\n"}if(F){I="\\n*"}this.before=this.before.replace(new s(I+"$",""),J)}if(this.after){I=J="";while(G--){I+="\\n?";J+="\n"}if(F){I="\\n*"}this.after=this.after.replace(new s(I,""),J)}};h.prefixes="(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";h.unwrap=function(G){var F=new s("([^\\n])\\n(?!(\\n|"+h.prefixes+"))","g");G.selection=G.selection.replace(F,"$1 $2")};h.wrap=function(G,F){h.unwrap(G);var H=new s("(.{1,"+F+"})( +|$\\n?)","gm");G.selection=G.selection.replace(H,function(I,J){if(new s("^"+h.prefixes,"").test(I)){return I}return J+"\n"});G.selection=G.selection.replace(/\s+$/,"")};h.doBold=function(F,G){return h.doBorI(F,G,2,"strong text")};h.doItalic=function(F,G){return h.doBorI(F,G,1,"emphasized text")};h.doBorI=function(L,J,K,F){L.trimWhitespace();L.selection=L.selection.replace(/\n{2,}/g,"\n");L.before.search(/(\**$)/);var I=s.$1;L.after.search(/(^\**)/);var G=s.$1;var M=Math.min(I.length,G.length);if((M>=K)&&(M!=2||K!=1)){L.before=L.before.replace(s("[*]{"+K+"}$",""),"");L.after=L.after.replace(s("^[*]{"+K+"}",""),"")}else{if(!L.selection&&G){L.after=L.after.replace(/^([*_]*)/,"");L.before=L.before.replace(/(\s?)$/,"");var H=s.$1;L.before=L.before+G+H}else{if(!L.selection&&!G){L.selection=F}var N=K<=1?"*":"**";L.before=L.before+N;L.after=N+L.after}}return};h.stripLinkDefs=function(G,F){G=G.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,function(K,L,H,I,J){F[L]=K.replace(/\s*$/,"");if(I){F[L]=K.replace(/["(](.+?)[")]$/,"");return I+J}return""});return G};h.addLinkDef=function(M,I){var F=0;var H={};M.before=h.stripLinkDefs(M.before,H);M.selection=h.stripLinkDefs(M.selection,H);M.after=h.stripLinkDefs(M.after,H);var G="";var L=/(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;var K=function(O){F++;O=O.replace(/^[ ]{0,3}\[(\d+)\]:/," ["+F+"]:");G+="\n"+O};var J=function(P,Q,R,O){if(H[R]){K(H[R]);return Q+F+O}return P};M.before=M.before.replace(L,J);if(I){K(I)}else{M.selection=M.selection.replace(L,J)}var N=F;M.after=M.after.replace(L,J);if(M.after){M.after=M.after.replace(/\n*$/,"")}if(!M.after){M.selection=M.selection.replace(/\n*$/,"")}M.after+="\n\n"+G;return N};h.doLinkOrImage=function(F,G,I){F.trimWhitespace();F.findTags(/\s*!?\[/,/\][ ]?(?:\n[ ]*)?(\[.*?\])?/);if(F.endTag.length>1){F.startTag=F.startTag.replace(/!?\[/,"");F.endTag="";h.addLinkDef(F,null)}else{if(/\n\n/.test(F.selection)){h.addLinkDef(F,null);return}var H=function(L){if(L!==null){F.startTag=F.endTag="";var K=" [999]: "+L;var J=h.addLinkDef(F,K);F.startTag=I?"![":"[";F.endTag="]["+J+"]";if(!F.selection){if(I){F.selection="alt text"}else{F.selection="link text"}}}G()};if(I){a.prompt(B,b,H,1)}else{a.prompt(D,g,H)}return true}};a.makeAPI=function(){y.wmd={};y.wmd.editor=y.editor;y.wmd.previewManager=y.previewManager};a.startEditor=function(){if(y.wmd_env.autostart===false){a.makeAPI();return}var G;var F;var H=function(){y.panels=new y.PanelCollection();F=new y.previewManager();var I=F.refresh;G=new y.editor(I);F.refresh(true)};a.addEvent(top,"load",H)};y.previewManager=function(){var H=this;var V;var F;var N;var M;var S;var O;var I=3000;var P="delayed";var K=function(X,Y){a.addEvent(X,"input",Y);X.onpaste=Y;X.ondrop=Y;a.addEvent(X,"keypress",Y);a.addEvent(X,"keydown",Y);F=new y.inputPoller(Y,A)};var R=function(){var X=0;if(top.innerHeight){X=top.pageYOffset}else{if(E.documentElement&&E.documentElement.scrollTop){X=E.documentElement.scrollTop}else{if(E.body){X=E.body.scrollTop}}}return X};var L=function(){if(!y.panels.preview&&!y.panels.output){return}var Z=y.panels.input.value;if(Z&&Z==S){return}else{S=Z}var Y=new Date().getTime();if(!V&&y.showdown){V=new y.showdown.converter()}if(V){Z=V.makeHtml(Z)}var X=new Date().getTime();M=X-Y;G(Z);O=Z};var U=function(){if(N){top.clearTimeout(N);N=undefined}if(P!=="manual"){var X=0;if(P==="delayed"){X=M}if(X>I){X=I}N=top.setTimeout(L,X)}};var J=function(X){if(X.scrollHeight<=X.clientHeight){return 1}return X.scrollTop/(X.scrollHeight-X.clientHeight)};var W=function(){if(y.panels.preview){y.panels.preview.scrollTop=(y.panels.preview.scrollHeight-y.panels.preview.clientHeight)*J(y.panels.preview)}if(y.panels.output){y.panels.output.scrollTop=(y.panels.output.scrollHeight-y.panels.output.clientHeight)*J(y.panels.output)}};this.refresh=function(X){if(X){S="";L()}else{U()}};this.processingTime=function(){return M};this.output=function(){return O};this.setUpdateMode=function(X){P=X;H.refresh()};var Q=true;var G=function(aa){var X=C.getTop(y.panels.input)-R();if(y.panels.output){if(y.panels.output.value!==undefined){y.panels.output.value=aa;y.panels.output.readOnly=true}else{var Z=aa.replace(/&/g,"&amp;");Z=Z.replace(/</g,"&lt;");y.panels.output.innerHTML="<pre><code>"+Z+"</code></pre>"}}if(y.panels.preview){y.panels.preview.innerHTML=aa}W();if(Q){Q=false;return}var Y=C.getTop(y.panels.input)-R();if(v.isIE){top.setTimeout(function(){top.scrollBy(0,Y-X)},0)}else{top.scrollBy(0,Y-X)}};var T=function(){K(y.panels.input,U);L();if(y.panels.preview){y.panels.preview.scrollTop=0}if(y.panels.output){y.panels.output.scrollTop=0}};this.destroy=function(){if(F){F.destroy()}};T()};h.doAutoindent=function(F,G){F.before=F.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/,"\n\n");F.before=F.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/,"\n\n");F.before=F.before.replace(/(\n|^)[ \t]+\n$/,"\n\n");if(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(F.before)){if(h.doList){h.doList(F)}}if(/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(F.before)){if(h.doBlockquote){h.doBlockquote(F)}}if(/(\n|^)(\t|[ ]{4,}).*\n$/.test(F.before)){if(h.doCode){h.doCode(F)}}};h.doBlockquote=function(F,G){F.selection=F.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,function(L,K,J,I){F.before+=K;F.after=I+F.after;return J});F.before=F.before.replace(/(>[ \t]*)$/,function(J,I){F.selection=I+F.selection;return""});F.selection=F.selection.replace(/^(\s|>)+$/,"");F.selection=F.selection||"Blockquote";if(F.before){F.before=F.before.replace(/\n?$/,"\n")}if(F.after){F.after=F.after.replace(/^\n?/,"\n")}F.before=F.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,function(I){F.startTag=I;return""});F.after=F.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,function(I){F.endTag=I;return""});var H=function(J){var I=J?"> ":"";if(F.startTag){F.startTag=F.startTag.replace(/\n((>|\s)*)\n$/,function(L,K){return"\n"+K.replace(/^[ ]{0,3}>?[ \t]*$/gm,I)+"\n"})}if(F.endTag){F.endTag=F.endTag.replace(/^\n((>|\s)*)\n/,function(L,K){return"\n"+K.replace(/^[ ]{0,3}>?[ \t]*$/gm,I)+"\n"})}};if(/^(?![ ]{0,3}>)/m.test(F.selection)){h.wrap(F,y.wmd_env.lineLength-2);F.selection=F.selection.replace(/^/gm,"> ");H(true);F.skipLines()}else{F.selection=F.selection.replace(/^[ ]{0,3}> ?/gm,"");h.unwrap(F);H(false);if(!/^(\n|^)[ ]{0,3}>/.test(F.selection)&&F.startTag){F.startTag=F.startTag.replace(/\n{0,2}$/,"\n\n")}if(!/(\n|^)[ ]{0,3}>.*$/.test(F.selection)&&F.endTag){F.endTag=F.endTag.replace(/^\n{0,2}/,"\n\n")}}if(!/\n/.test(F.selection)){F.selection=F.selection.replace(/^(> *)/,function(I,J){F.startTag+=J;return""})}};h.doCode=function(F,G){var I=/\S[ ]*$/.test(F.before);var K=/^[ ]*\S/.test(F.after);if((!K&&!I)||/\n/.test(F.selection)){F.before=F.before.replace(/[ ]{4}$/,function(L){F.selection=L+F.selection;return""});var J=1;var H=1;if(/\n(\t|[ ]{4,}).*\n$/.test(F.before)){J=0}if(/^\n(\t|[ ]{4,})/.test(F.after)){H=0}F.skipLines(J,H);if(!F.selection){F.startTag=" ";F.selection="enter code here"}else{if(/^[ ]{0,3}\S/m.test(F.selection)){F.selection=F.selection.replace(/^/gm," ")}else{F.selection=F.selection.replace(/^[ ]{4}/gm,"")}}}else{F.trimWhitespace();F.findTags(/`/,/`/);if(!F.startTag&&!F.endTag){F.startTag=F.endTag="`";if(!F.selection){F.selection="enter code here"}}else{if(F.endTag&&!F.startTag){F.before+=F.endTag;F.endTag=""}else{F.startTag=F.endTag=""}}}};h.doList=function(Q,J,I){var S=/(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;var R=/^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;var F="-";var N=1;var L=function(){var T;if(I){T=" "+N+". ";N++}else{T=" "+F+" "}return T};var M=function(T){if(I===undefined){I=/^\s*\d/.test(T)}T=T.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,function(U){return L()});return T};Q.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/,null);if(Q.before&&!/\n$/.test(Q.before)&&!/^\n/.test(Q.startTag)){Q.before+=Q.startTag;Q.startTag=""}if(Q.startTag){var H=/\d+[.]/.test(Q.startTag);Q.startTag="";Q.selection=Q.selection.replace(/\n[ ]{4}/g,"\n");h.unwrap(Q);Q.skipLines();if(H){Q.after=Q.after.replace(R,M)}if(I==H){return}}var K=1;Q.before=Q.before.replace(S,function(T){if(/^\s*([*+-])/.test(T)){F=s.$1}K=/[^\n]\n\n[^\n]/.test(T)?1:0;return M(T)});if(!Q.selection){Q.selection="List item"}var O=L();var G=1;Q.after=Q.after.replace(R,function(T){G=/[^\n]\n\n[^\n]/.test(T)?1:0;return M(T)});Q.trimWhitespace(true);Q.skipLines(K,G,true);Q.startTag=O;var P=O.replace(/./g," ");h.wrap(Q,y.wmd_env.lineLength-P.length);Q.selection=Q.selection.replace(/\n/g,"\n"+P)};h.doHeading=function(H,I){H.selection=H.selection.replace(/\s+/g," ");H.selection=H.selection.replace(/(^\s+|\s+$)/g,"");if(!H.selection){H.startTag="## ";H.selection="Heading";H.endTag=" ##";return}var J=0;H.findTags(/#+[ ]*/,/[ ]*#+/);if(/#+/.test(H.startTag)){J=s.lastMatch.length}H.startTag=H.endTag="";H.findTags(null,/\s?(-+|=+)/);if(/=+/.test(H.endTag)){J=1}if(/-+/.test(H.endTag)){J=2}H.startTag=H.endTag="";H.skipLines(1,1);var K=J==0?2:J-1;if(K>0){var G=K>=2?"-":"=";var F=H.selection.length;if(F>y.wmd_env.lineLength){F=y.wmd_env.lineLength}H.endTag="\n";while(F--){H.endTag+=G}}};h.doHorizontalRule=function(F,G){F.startTag="----------\n";F.selection="";F.skipLines(2,1,true)}};Attacklab.wmd_env={};Attacklab.account_options={};Attacklab.wmd_defaults={version:1,output:"Markdown",lineLength:40,delayLoad:false};if(!Attacklab.wmd){Attacklab.wmd=function(){Attacklab.loadEnv=function(){var b=function(d){if(!d){return}for(var c in d){Attacklab.wmd_env[c]=d[c]}};b(Attacklab.wmd_defaults);b(Attacklab.account_options);b(top.wmd_options);Attacklab.full=true;var a="bold italic link blockquote code image ol ul heading hr";Attacklab.wmd_env.buttons=Attacklab.wmd_env.buttons||a};Attacklab.loadEnv()};Attacklab.wmd();Attacklab.wmdBase();Attacklab.Util.startEditor()}; \ No newline at end of file
diff --git a/templates/content/js/wmd/wmd-test.html b/templates/content/js/wmd/wmd-test.html
new file mode 100644
index 00000000..d748501a
--- /dev/null
+++ b/templates/content/js/wmd/wmd-test.html
@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html>
+
+ <head>
+ <title>Test WMD Page</title>
+
+ <link rel="stylesheet" type="text/css" href="wmd.css" />
+
+ <meta http-equiv="pragma" content="no-cache">
+ <meta http-equiv="cache-control" content="no-cache">
+ <meta http-equiv="pragma-directive" content="no-cache">
+ <meta http-equiv="cache-directive" content="no-cache">
+ <meta http-equiv="expires" content="0">
+
+ <script type="text/javascript" src="jQuery/jquery-1.2.6.js"></script>
+ <script type="text/javascript" src="showdown.js"></script>
+ </head>
+
+ <body>
+ <div id="wmd-button-bar" class="wmd-panel"></div>
+ <br/>
+ <textarea id="editor" class="wmd-panel"></textarea>
+ <br/>
+ <div id="previewer" class="wmd-panel"></div>
+ <br/>
+ <div id="wmd-output" class="wmd-panel"></div>
+
+ <p>To test that page up/down and arrow keys work, copy this above the WMD
+ control.</p>
+
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+ Scroll Down!<br/>
+
+ <script type="text/javascript" src="wmd.js"></script>
+ </body>
+</html> \ No newline at end of file
diff --git a/templates/content/js/wmd/wmd.css b/templates/content/js/wmd/wmd.css
new file mode 100644
index 00000000..80c226c8
--- /dev/null
+++ b/templates/content/js/wmd/wmd.css
@@ -0,0 +1,129 @@
+/*body
+{
+ background-color: White
+}
+*/
+.wmd-panel
+{
+}
+
+#wmd-button-bar
+{
+ background-color: White;
+}
+
+#wmd-input
+{
+ height: 500px;
+ background-color: Gainsboro;
+ border: 1px solid DarkGray;
+ margin-top: -20px;
+}
+
+#wmd-preview
+{
+ background-color: LightSkyBlue;
+}
+
+#wmd-output
+{
+ background-color: Pink;
+}
+
+#wmd-button-row
+{
+ position: relative;
+ margin-left: 5px;
+ margin-right: 5px;
+ margin-bottom: 5px;
+ margin-top: 10px;
+ padding: 0px;
+ height: 20px;
+}
+
+.wmd-spacer
+{
+ width: 1px;
+ height: 20px;
+ margin-left: 14px;
+
+ position: absolute;
+ background-color: Silver;
+ display: inline-block;
+ list-style: none;
+}
+
+.wmd-button
+{
+ width: 20px;
+ height: 20px;
+ margin-left: 5px;
+ margin-right: 5px;
+
+ position: absolute;
+ background-image: url(images/wmd-buttons.png);
+ background-repeat: no-repeat;
+ background-position: 0px 0px;
+ display: inline-block;
+ list-style: none;
+}
+
+.wmd-button > a
+{
+ width: 20px;
+ height: 20px;
+ margin-left: 5px;
+ margin-right: 5px;
+
+ position: absolute;
+ display: inline-block;
+}
+
+
+/* sprite button slicing style information */
+#wmd-button-bar #wmd-bold-button {left: 0px; background-position: 0px 0;}
+#wmd-button-bar #wmd-italic-button {left: 25px; background-position: -20px 0;}
+#wmd-button-bar #wmd-spacer1 {left: 50px;}
+#wmd-button-bar #wmd-link-button {left: 75px; background-position: -40px 0;}
+#wmd-button-bar #wmd-quote-button {left: 100px; background-position: -60px 0;}
+#wmd-button-bar #wmd-code-button {left: 125px; background-position: -80px 0;}
+#wmd-button-bar #wmd-image-button {left: 150px; background-position: -100px 0;}
+#wmd-button-bar #wmd-spacer2 {left: 175px;}
+#wmd-button-bar #wmd-olist-button {left: 200px; background-position: -120px 0;}
+#wmd-button-bar #wmd-ulist-button {left: 225px; background-position: -140px 0;}
+#wmd-button-bar #wmd-heading-button {left: 250px; background-position: -160px 0;}
+#wmd-button-bar #wmd-hr-button {left: 275px; background-position: -180px 0;}
+#wmd-button-bar #wmd-spacer3 {left: 300px;}
+#wmd-button-bar #wmd-undo-button {left: 325px; background-position: -200px 0;}
+#wmd-button-bar #wmd-redo-button {left: 350px; background-position: -220px 0;}
+#wmd-button-bar #wmd-help-button {right: 0px; background-position: -240px 0;}
+
+
+.wmd-prompt-background
+{
+ background-color: Black;
+}
+
+.wmd-prompt-dialog
+{
+ border: 1px solid #999999;
+ background-color: #F5F5F5;
+}
+
+.wmd-prompt-dialog > div {
+ font-size: 1em;
+ font-family: arial, helvetica, sans-serif;
+}
+
+
+.wmd-prompt-dialog > form > input[type="text"] {
+ border: 1px solid #999999;
+ color: black;
+}
+
+.wmd-prompt-dialog > form > input[type="button"]{
+ border: 1px solid #888888;
+ font-family: trebuchet MS, helvetica, sans-serif;
+ font-size: 1em;
+ font-weight: bold;
+}
diff --git a/templates/content/js/wmd/wmd.js b/templates/content/js/wmd/wmd.js
new file mode 100644
index 00000000..7b611dba
--- /dev/null
+++ b/templates/content/js/wmd/wmd.js
@@ -0,0 +1,2388 @@
+var Attacklab = Attacklab || {};
+
+Attacklab.wmdBase = function(){
+
+ // A few handy aliases for readability.
+ var wmd = top.Attacklab;
+ var doc = top.document;
+ var re = top.RegExp;
+ var nav = top.navigator;
+
+ // Some namespaces.
+ wmd.Util = {};
+ wmd.Position = {};
+ wmd.Command = {};
+ wmd.Global = {};
+
+ var util = wmd.Util;
+ var position = wmd.Position;
+ var command = wmd.Command;
+ var global = wmd.Global;
+
+
+ // Used to work around some browser bugs where we can't use feature testing.
+ global.isIE = /msie/.test(nav.userAgent.toLowerCase());
+ global.isIE_5or6 = /msie 6/.test(nav.userAgent.toLowerCase()) || /msie 5/.test(nav.userAgent.toLowerCase());
+ global.isIE_7plus = global.isIE && !global.isIE_5or6;
+ global.isOpera = /opera/.test(nav.userAgent.toLowerCase());
+ global.isKonqueror = /konqueror/.test(nav.userAgent.toLowerCase());
+
+ var toolbar_strong_label = $.i18n._('bold') + " <strong> Ctrl-B";
+ var toolbar_emphasis_label = $.i18n._('italic') + " <em> Ctrl-I";
+ var toolbar_hyperlink_label = $.i18n._('link') + " <a> Ctrl-L";
+ var toolbar_blockquote_label = $.i18n._('quote') + " <blockquote> Ctrl-.";
+ var toolbar_code_label = $.i18n._('preformatted text') + " <pre><code> Ctrl-K";
+ var toolbar_image_label = $.i18n._('image') + " <img> Ctrl-G";
+ var toolbar_numbered_label = $.i18n._('numbered list') + " <ol> Ctrl-O";
+ var toolbar_bulleted_label = $.i18n._('bulleted list') + " <ul> Ctrl-U";
+ var toolbar_heading_label = $.i18n._('heading') + " <h1>/<h2> Ctrl-H";
+ var toolbar_horizontal_label = $.i18n._('horizontal bar') + " <hr> Ctrl-R";
+ var toolbar_undo_label = $.i18n._('undo') + " Ctrl-Z";
+ var toolbar_redo_label = $.i18n._('redo') + " Ctrl-Y";
+
+ // -------------------------------------------------------------------
+ // YOUR CHANGES GO HERE
+ //
+ // I've tried to localize the things you are likely to change to
+ // this area.
+ // -------------------------------------------------------------------
+
+ // The text that appears on the upper part of the dialog box when
+ // entering links.
+ var imageDialogText = "<p style='margin-top: 0px'>" + $.i18n._('enter image url') + '</p>';
+ var linkDialogText = "<p style='margin-top: 0px'>" + $.i18n._('enter url') + '</p>';
+ var uploadImageHTML ="<div>" + $.i18n._('upload image') + "</div>" +
+ "<input type=\"file\" name=\"file-upload\" id=\"file-upload\" size=\"26\" "+
+ "onchange=\"return ajaxFileUpload($('#image-url'));\"/><br>" +
+ "<img id=\"loading\" src=\"/content/images/indicator.gif\" style=\"display:none;\"/>";
+
+ // The default text that appears in the dialog input box when entering
+ // links.
+ var imageDefaultText = "http://";
+ var linkDefaultText = "http://";
+
+ // The location of your button images relative to the base directory.
+ var imageDirectory = "images/";
+
+ // Some intervals in ms. These can be adjusted to reduce the control's load.
+ var previewPollInterval = 500;
+ var pastePollInterval = 100;
+
+ // The link and title for the help button
+ var helpLink = "http://wmd-editor.com/";
+ var helpHoverTitle = "WMD website";
+ var helpTarget = "_blank";
+
+ // -------------------------------------------------------------------
+ // END OF YOUR CHANGES
+ // -------------------------------------------------------------------
+
+ // A collection of the important regions on the page.
+ // Cached so we don't have to keep traversing the DOM.
+ wmd.PanelCollection = function(){
+ this.buttonBar = doc.getElementById("wmd-button-bar");
+ this.preview = doc.getElementById("previewer");
+ this.output = doc.getElementById("wmd-output");
+ this.input = doc.getElementById("editor");
+ };
+
+ // This PanelCollection object can't be filled until after the page
+ // has loaded.
+ wmd.panels = undefined;
+
+ // Internet explorer has problems with CSS sprite buttons that use HTML
+ // lists. When you click on the background image "button", IE will
+ // select the non-existent link text and discard the selection in the
+ // textarea. The solution to this is to cache the textarea selection
+ // on the button's mousedown event and set a flag. In the part of the
+ // code where we need to grab the selection, we check for the flag
+ // and, if it's set, use the cached area instead of querying the
+ // textarea.
+ //
+ // This ONLY affects Internet Explorer (tested on versions 6, 7
+ // and 8) and ONLY on button clicks. Keyboard shortcuts work
+ // normally since the focus never leaves the textarea.
+ wmd.ieCachedRange = null; // cached textarea selection
+ wmd.ieRetardedClick = false; // flag
+
+ // Returns true if the DOM element is visible, false if it's hidden.
+ // Checks if display is anything other than none.
+ util.isVisible = function (elem) {
+
+ if (window.getComputedStyle) {
+ // Most browsers
+ return window.getComputedStyle(elem, null).getPropertyValue("display") !== "none";
+ }
+ else if (elem.currentStyle) {
+ // IE
+ return elem.currentStyle["display"] !== "none";
+ }
+ };
+
+
+ // Adds a listener callback to a DOM element which is fired on a specified
+ // event.
+ util.addEvent = function(elem, event, listener){
+ if (elem.attachEvent) {
+ // IE only. The "on" is mandatory.
+ elem.attachEvent("on" + event, listener);
+ }
+ else {
+ // Other browsers.
+ elem.addEventListener(event, listener, false);
+ }
+ };
+
+
+ // Removes a listener callback from a DOM element which is fired on a specified
+ // event.
+ util.removeEvent = function(elem, event, listener){
+ if (elem.detachEvent) {
+ // IE only. The "on" is mandatory.
+ elem.detachEvent("on" + event, listener);
+ }
+ else {
+ // Other browsers.
+ elem.removeEventListener(event, listener, false);
+ }
+ };
+
+ // Converts \r\n and \r to \n.
+ util.fixEolChars = function(text){
+ text = text.replace(/\r\n/g, "\n");
+ text = text.replace(/\r/g, "\n");
+ return text;
+ };
+
+ // Extends a regular expression. Returns a new RegExp
+ // using pre + regex + post as the expression.
+ // Used in a few functions where we have a base
+ // expression and we want to pre- or append some
+ // conditions to it (e.g. adding "$" to the end).
+ // The flags are unchanged.
+ //
+ // regex is a RegExp, pre and post are strings.
+ util.extendRegExp = function(regex, pre, post){
+
+ if (pre === null || pre === undefined)
+ {
+ pre = "";
+ }
+ if(post === null || post === undefined)
+ {
+ post = "";
+ }
+
+ var pattern = regex.toString();
+ var flags;
+
+ // Replace the flags with empty space and store them.
+ pattern = pattern.replace(/\/([gim]*)$/, "");
+ flags = re.$1;
+
+ // Remove the slash delimiters on the regular expression.
+ pattern = pattern.replace(/(^\/|\/$)/g, "");
+ pattern = pre + pattern + post;
+
+ return new re(pattern, flags);
+ }
+
+
+ // Sets the image for a button passed to the WMD editor.
+ // Returns a new element with the image attached.
+ // Adds several style properties to the image.
+ util.createImage = function(img){
+
+ var imgPath = imageDirectory + img;
+
+ var elem = doc.createElement("img");
+ elem.className = "wmd-button";
+ elem.src = imgPath;
+
+ return elem;
+ };
+
+
+ // This simulates a modal dialog box and asks for the URL when you
+ // click the hyperlink or image buttons.
+ //
+ // text: The html for the input box.
+ // defaultInputText: The default value that appears in the input box.
+ // makeLinkMarkdown: The function which is executed when the prompt is dismissed, either via OK or Cancel
+ util.prompt = function(text, defaultInputText, makeLinkMarkdown){
+
+ // These variables need to be declared at this level since they are used
+ // in multiple functions.
+ var dialog; // The dialog box.
+ var background; // The background beind the dialog box.
+ var input; // The text box where you enter the hyperlink.
+ var type = 0;
+ // The dialog box type(0: Link, 1: Image)
+ if(arguments.length == 4)
+ type = arguments[3];
+
+ if (defaultInputText === undefined) {
+ defaultInputText = "";
+ }
+
+ // Used as a keydown event handler. Esc dismisses the prompt.
+ // Key code 27 is ESC.
+ var checkEscape = function(key){
+ var code = (key.charCode || key.keyCode);
+ if (code === 27) {
+ close(true);
+ }
+ };
+
+ // Dismisses the hyperlink input box.
+ // isCancel is true if we don't care about the input text.
+ // isCancel is false if we are going to keep the text.
+ var close = function(isCancel){
+ util.removeEvent(doc.body, "keydown", checkEscape);
+ var text = input.value;
+
+ if (isCancel){
+ text = null;
+ }
+ else{
+ // Fixes common pasting errors.
+ text = text.replace('http://http://', 'http://');
+ text = text.replace('http://https://', 'https://');
+ text = text.replace('http://ftp://', 'ftp://');
+
+ if (text.indexOf('http://') === -1 && text.indexOf('ftp://') === -1) {
+ text = 'http://' + text;
+ }
+ }
+
+ dialog.parentNode.removeChild(dialog);
+ background.parentNode.removeChild(background);
+ makeLinkMarkdown(text);
+ return false;
+ };
+
+ // Creates the background behind the hyperlink text entry box.
+ // Most of this has been moved to CSS but the div creation and
+ // browser-specific hacks remain here.
+ var createBackground = function(){
+
+ background = doc.createElement("div");
+ background.className = "wmd-prompt-background";
+ style = background.style;
+ style.position = "absolute";
+ style.top = "0";
+
+ style.zIndex = "1000";
+
+ // Some versions of Konqueror don't support transparent colors
+ // so we make the whole window transparent.
+ //
+ // Is this necessary on modern konqueror browsers?
+ if (global.isKonqueror){
+ style.backgroundColor = "transparent";
+ }
+ else if (global.isIE){
+ style.filter = "alpha(opacity=50)";
+ }
+ else {
+ style.opacity = "0.5";
+ }
+
+ var pageSize = position.getPageSize();
+ style.height = pageSize[1] + "px";
+
+ if(global.isIE){
+ style.left = doc.documentElement.scrollLeft;
+ style.width = doc.documentElement.clientWidth;
+ }
+ else {
+ style.left = "0";
+ style.width = "100%";
+ }
+
+ doc.body.appendChild(background);
+ };
+
+ // Create the text input box form/window.
+ var createDialog = function(){
+
+ // The main dialog box.
+ dialog = doc.createElement("div");
+ dialog.className = "wmd-prompt-dialog";
+ dialog.style.padding = "10px;";
+ dialog.style.position = "fixed";
+ dialog.style.width = "400px";
+ dialog.style.zIndex = "1001";
+
+ // The dialog text.
+ var question = doc.createElement("div");
+ question.innerHTML = text;
+ question.style.padding = "5px";
+ dialog.appendChild(question);
+
+ // The web form container for the text box and buttons.
+ var form = doc.createElement("form");
+ form.onsubmit = function(){ return close(false); };
+ style = form.style;
+ style.padding = "0";
+ style.margin = "0";
+ style.cssFloat = "left";
+ style.width = "100%";
+ style.textAlign = "center";
+ style.position = "relative";
+ dialog.appendChild(form);
+
+ // The input text box
+ input = doc.createElement("input");
+ if(type == 1)
+ input.id = "image-url";
+ input.type = "text";
+ input.value = defaultInputText;
+ style = input.style;
+ style.display = "block";
+ style.width = "80%";
+ style.marginLeft = style.marginRight = "auto";
+ form.appendChild(input);
+
+ // The upload file input
+ if(type == 1){
+ var upload = doc.createElement("div");
+ upload.innerHTML = uploadImageHTML;
+ upload.style.padding = "5px";
+ form.appendChild(upload);
+ }
+
+ // The ok button
+ var okButton = doc.createElement("input");
+ okButton.type = "button";
+ okButton.onclick = function(){ return close(false); };
+ okButton.value = "OK";
+ style = okButton.style;
+ style.margin = "10px";
+ style.display = "inline";
+ style.width = "7em";
+
+
+ // The cancel button
+ var cancelButton = doc.createElement("input");
+ cancelButton.type = "button";
+ cancelButton.onclick = function(){ return close(true); };
+ cancelButton.value = "Cancel";
+ style = cancelButton.style;
+ style.margin = "10px";
+ style.display = "inline";
+ style.width = "7em";
+
+ // The order of these buttons is different on macs.
+ if (/mac/.test(nav.platform.toLowerCase())) {
+ form.appendChild(cancelButton);
+ form.appendChild(okButton);
+ }
+ else {
+ form.appendChild(okButton);
+ form.appendChild(cancelButton);
+ }
+
+ util.addEvent(doc.body, "keydown", checkEscape);
+ dialog.style.top = "50%";
+ dialog.style.left = "50%";
+ dialog.style.display = "block";
+ if(global.isIE_5or6){
+ dialog.style.position = "absolute";
+ dialog.style.top = doc.documentElement.scrollTop + 200 + "px";
+ dialog.style.left = "50%";
+ }
+ doc.body.appendChild(dialog);
+
+ // This has to be done AFTER adding the dialog to the form if you
+ // want it to be centered.
+ dialog.style.marginTop = -(position.getHeight(dialog) / 2) + "px";
+ dialog.style.marginLeft = -(position.getWidth(dialog) / 2) + "px";
+
+ };
+
+ createBackground();
+
+ // Why is this in a zero-length timeout?
+ // Is it working around a browser bug?
+ top.setTimeout(function(){
+
+ createDialog();
+
+ var defTextLen = defaultInputText.length;
+ if (input.selectionStart !== undefined) {
+ input.selectionStart = 0;
+ input.selectionEnd = defTextLen;
+ }
+ else if (input.createTextRange) {
+ var range = input.createTextRange();
+ range.collapse(false);
+ range.moveStart("character", -defTextLen);
+ range.moveEnd("character", defTextLen);
+ range.select();
+ }
+
+ input.focus();
+ }, 0);
+ };
+
+
+ // UNFINISHED
+ // The assignment in the while loop makes jslint cranky.
+ // I'll change it to a better loop later.
+ position.getTop = function(elem, isInner){
+ var result = elem.offsetTop;
+ if (!isInner) {
+ while (elem = elem.offsetParent) {
+ result += elem.offsetTop;
+ }
+ }
+ return result;
+ };
+
+ position.getHeight = function (elem) {
+ return elem.offsetHeight || elem.scrollHeight;
+ };
+
+ position.getWidth = function (elem) {
+ return elem.offsetWidth || elem.scrollWidth;
+ };
+
+ position.getPageSize = function(){
+
+ var scrollWidth, scrollHeight;
+ var innerWidth, innerHeight;
+
+ // It's not very clear which blocks work with which browsers.
+ if(self.innerHeight && self.scrollMaxY){
+ scrollWidth = doc.body.scrollWidth;
+ scrollHeight = self.innerHeight + self.scrollMaxY;
+ }
+ else if(doc.body.scrollHeight > doc.body.offsetHeight){
+ scrollWidth = doc.body.scrollWidth;
+ scrollHeight = doc.body.scrollHeight;
+ }
+ else{
+ scrollWidth = doc.body.offsetWidth;
+ scrollHeight = doc.body.offsetHeight;
+ }
+
+ if(self.innerHeight){
+ // Non-IE browser
+ innerWidth = self.innerWidth;
+ innerHeight = self.innerHeight;
+ }
+ else if(doc.documentElement && doc.documentElement.clientHeight){
+ // Some versions of IE (IE 6 w/ a DOCTYPE declaration)
+ innerWidth = doc.documentElement.clientWidth;
+ innerHeight = doc.documentElement.clientHeight;
+ }
+ else if(doc.body){
+ // Other versions of IE
+ innerWidth = doc.body.clientWidth;
+ innerHeight = doc.body.clientHeight;
+ }
+
+ var maxWidth = Math.max(scrollWidth, innerWidth);
+ var maxHeight = Math.max(scrollHeight, innerHeight);
+ return [maxWidth, maxHeight, innerWidth, innerHeight];
+ };
+
+ // Watches the input textarea, polling at an interval and runs
+ // a callback function if anything has changed.
+ wmd.inputPoller = function(callback, interval){
+
+ var pollerObj = this;
+ var inputArea = wmd.panels.input;
+
+ // Stored start, end and text. Used to see if there are changes to the input.
+ var lastStart;
+ var lastEnd;
+ var markdown;
+
+ var killHandle; // Used to cancel monitoring on destruction.
+ // Checks to see if anything has changed in the textarea.
+ // If so, it runs the callback.
+ this.tick = function(){
+
+ if (!util.isVisible(inputArea)) {
+ return;
+ }
+
+ // Update the selection start and end, text.
+ if (inputArea.selectionStart || inputArea.selectionStart === 0) {
+ var start = inputArea.selectionStart;
+ var end = inputArea.selectionEnd;
+ if (start != lastStart || end != lastEnd) {
+ lastStart = start;
+ lastEnd = end;
+
+ if (markdown != inputArea.value) {
+ markdown = inputArea.value;
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+
+ var doTickCallback = function(){
+
+ if (!util.isVisible(inputArea)) {
+ return;
+ }
+
+ // If anything has changed, call the function.
+ if (pollerObj.tick()) {
+ callback();
+ }
+ };
+
+ // Set how often we poll the textarea for changes.
+ var assignInterval = function(){
+ // previewPollInterval is set at the top of the namespace.
+ killHandle = top.setInterval(doTickCallback, interval);
+ };
+
+ this.destroy = function(){
+ top.clearInterval(killHandle);
+ };
+
+ assignInterval();
+ };
+
+ // Handles pushing and popping TextareaStates for undo/redo commands.
+ // I should rename the stack variables to list.
+ wmd.undoManager = function(callback){
+
+ var undoObj = this;
+ var undoStack = []; // A stack of undo states
+ var stackPtr = 0; // The index of the current state
+ var mode = "none";
+ var lastState; // The last state
+ var poller;
+ var timer; // The setTimeout handle for cancelling the timer
+ var inputStateObj;
+
+ // Set the mode for later logic steps.
+ var setMode = function(newMode, noSave){
+
+ if (mode != newMode) {
+ mode = newMode;
+ if (!noSave) {
+ saveState();
+ }
+ }
+
+ if (!global.isIE || mode != "moving") {
+ timer = top.setTimeout(refreshState, 1);
+ }
+ else {
+ inputStateObj = null;
+ }
+ };
+
+ var refreshState = function(){
+ inputStateObj = new wmd.TextareaState();
+ poller.tick();
+ timer = undefined;
+ };
+
+ this.setCommandMode = function(){
+ mode = "command";
+ saveState();
+ timer = top.setTimeout(refreshState, 0);
+ };
+
+ this.canUndo = function(){
+ return stackPtr > 1;
+ };
+
+ this.canRedo = function(){
+ if (undoStack[stackPtr + 1]) {
+ return true;
+ }
+ return false;
+ };
+
+ // Removes the last state and restores it.
+ this.undo = function(){
+
+ if (undoObj.canUndo()) {
+ if (lastState) {
+ // What about setting state -1 to null or checking for undefined?
+ lastState.restore();
+ lastState = null;
+ }
+ else {
+ undoStack[stackPtr] = new wmd.TextareaState();
+ undoStack[--stackPtr].restore();
+
+ if (callback) {
+ callback();
+ }
+ }
+ }
+
+ mode = "none";
+ wmd.panels.input.focus();
+ refreshState();
+ };
+
+ // Redo an action.
+ this.redo = function(){
+
+ if (undoObj.canRedo()) {
+
+ undoStack[++stackPtr].restore();
+
+ if (callback) {
+ callback();
+ }
+ }
+
+ mode = "none";
+ wmd.panels.input.focus();
+ refreshState();
+ };
+
+ // Push the input area state to the stack.
+ var saveState = function(){
+
+ var currState = inputStateObj || new wmd.TextareaState();
+
+ if (!currState) {
+ return false;
+ }
+ if (mode == "moving") {
+ if (!lastState) {
+ lastState = currState;
+ }
+ return;
+ }
+ if (lastState) {
+ if (undoStack[stackPtr - 1].text != lastState.text) {
+ undoStack[stackPtr++] = lastState;
+ }
+ lastState = null;
+ }
+ undoStack[stackPtr++] = currState;
+ undoStack[stackPtr + 1] = null;
+ if (callback) {
+ callback();
+ }
+ };
+
+ var handleCtrlYZ = function(event){
+
+ var handled = false;
+
+ if (event.ctrlKey || event.metaKey) {
+
+ // IE and Opera do not support charCode.
+ var keyCode = event.charCode || event.keyCode;
+ var keyCodeChar = String.fromCharCode(keyCode);
+
+ switch (keyCodeChar) {
+
+ case "y":
+ undoObj.redo();
+ handled = true;
+ break;
+
+ case "z":
+ if (!event.shiftKey) {
+ undoObj.undo();
+ }
+ else {
+ undoObj.redo();
+ }
+ handled = true;
+ break;
+ }
+ }
+
+ if (handled) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ }
+ if (top.event) {
+ top.event.returnValue = false;
+ }
+ return;
+ }
+ };
+
+ // Set the mode depending on what is going on in the input area.
+ var handleModeChange = function(event){
+
+ if (!event.ctrlKey && !event.metaKey) {
+
+ var keyCode = event.keyCode;
+
+ if ((keyCode >= 33 && keyCode <= 40) || (keyCode >= 63232 && keyCode <= 63235)) {
+ // 33 - 40: page up/dn and arrow keys
+ // 63232 - 63235: page up/dn and arrow keys on safari
+ setMode("moving");
+ }
+ else if (keyCode == 8 || keyCode == 46 || keyCode == 127) {
+ // 8: backspace
+ // 46: delete
+ // 127: delete
+ setMode("deleting");
+ }
+ else if (keyCode == 13) {
+ // 13: Enter
+ setMode("newlines");
+ }
+ else if (keyCode == 27) {
+ // 27: escape
+ setMode("escape");
+ }
+ else if ((keyCode < 16 || keyCode > 20) && keyCode != 91) {
+ // 16-20 are shift, etc.
+ // 91: left window key
+ // I think this might be a little messed up since there are
+ // a lot of nonprinting keys above 20.
+ setMode("typing");
+ }
+ }
+ };
+
+ var setEventHandlers = function(){
+
+ util.addEvent(wmd.panels.input, "keypress", function(event){
+ // keyCode 89: y
+ // keyCode 90: z
+ if ((event.ctrlKey || event.metaKey) && (event.keyCode == 89 || event.keyCode == 90)) {
+ event.preventDefault();
+ }
+ });
+
+ var handlePaste = function(){
+ if (global.isIE || (inputStateObj && inputStateObj.text != wmd.panels.input.value)) {
+ if (timer == undefined) {
+ mode = "paste";
+ saveState();
+ refreshState();
+ }
+ }
+ };
+
+ // pastePollInterval is specified at the beginning of this namespace.
+ poller = new wmd.inputPoller(handlePaste, pastePollInterval);
+
+ util.addEvent(wmd.panels.input, "keydown", handleCtrlYZ);
+ util.addEvent(wmd.panels.input, "keydown", handleModeChange);
+
+ util.addEvent(wmd.panels.input, "mousedown", function(){
+ setMode("moving");
+ });
+ wmd.panels.input.onpaste = handlePaste;
+ wmd.panels.input.ondrop = handlePaste;
+ };
+
+ var init = function(){
+ setEventHandlers();
+ refreshState();
+ saveState();
+ };
+
+ this.destroy = function(){
+ if (poller) {
+ poller.destroy();
+ }
+ };
+
+ init();
+ };
+
+ // I think my understanding of how the buttons and callbacks are stored in the array is incomplete.
+ wmd.editor = function(previewRefreshCallback){
+
+ if (!previewRefreshCallback) {
+ previewRefreshCallback = function(){};
+ }
+
+ var inputBox = wmd.panels.input;
+
+ var offsetHeight = 0;
+
+ var editObj = this;
+
+ var mainDiv;
+ var mainSpan;
+
+ var div; // This name is pretty ambiguous. I should rename this.
+
+ // Used to cancel recurring events from setInterval.
+ var creationHandle;
+
+ var undoMgr; // The undo manager
+
+ // Perform the button's action.
+ var doClick = function(button){
+
+ inputBox.focus();
+
+ if (button.textOp) {
+
+ if (undoMgr) {
+ undoMgr.setCommandMode();
+ }
+
+ var state = new wmd.TextareaState();
+
+ if (!state) {
+ return;
+ }
+
+ var chunks = state.getChunks();
+
+ // Some commands launch a "modal" prompt dialog. Javascript
+ // can't really make a modal dialog box and the WMD code
+ // will continue to execute while the dialog is displayed.
+ // This prevents the dialog pattern I'm used to and means
+ // I can't do something like this:
+ //
+ // var link = CreateLinkDialog();
+ // makeMarkdownLink(link);
+ //
+ // Instead of this straightforward method of handling a
+ // dialog I have to pass any code which would execute
+ // after the dialog is dismissed (e.g. link creation)
+ // in a function parameter.
+ //
+ // Yes this is awkward and I think it sucks, but there's
+ // no real workaround. Only the image and link code
+ // create dialogs and require the function pointers.
+ var fixupInputArea = function(){
+
+ inputBox.focus();
+
+ if (chunks) {
+ state.setChunks(chunks);
+ }
+
+ state.restore();
+ previewRefreshCallback();
+ };
+
+ var noCleanup = button.textOp(chunks, fixupInputArea);
+
+ if(!noCleanup) {
+ fixupInputArea();
+ }
+
+ }
+
+ if (button.execute) {
+ button.execute(editObj);
+ }
+ };
+
+ var setUndoRedoButtonStates = function(){
+ if(undoMgr){
+ setupButton(document.getElementById("wmd-undo-button"), undoMgr.canUndo());
+ setupButton(document.getElementById("wmd-redo-button"), undoMgr.canRedo());
+ }
+ };
+
+ var setupButton = function(button, isEnabled) {
+
+ var normalYShift = "0px";
+ var disabledYShift = "-20px";
+ var highlightYShift = "-40px";
+
+ if(isEnabled) {
+ button.style.backgroundPosition = button.XShift + " " + normalYShift;
+ button.onmouseover = function(){
+ this.style.backgroundPosition = this.XShift + " " + highlightYShift;
+ };
+
+ button.onmouseout = function(){
+ this.style.backgroundPosition = this.XShift + " " + normalYShift;
+ };
+
+ // IE tries to select the background image "button" text (it's
+ // implemented in a list item) so we have to cache the selection
+ // on mousedown.
+ if(global.isIE) {
+ button.onmousedown = function() {
+ wmd.ieRetardedClick = true;
+ wmd.ieCachedRange = document.selection.createRange();
+ };
+ }
+
+ if (!button.isHelp)
+ {
+ button.onclick = function() {
+ if (this.onmouseout) {
+ this.onmouseout();
+ }
+ doClick(this);
+ return false;
+ }
+ }
+ }
+ else {
+ button.style.backgroundPosition = button.XShift + " " + disabledYShift;
+ button.onmouseover = button.onmouseout = button.onclick = function(){};
+ }
+ }
+
+ var makeSpritedButtonRow = function(){
+
+ var buttonBar = document.getElementById("wmd-button-bar");
+ var normalYShift = "0px";
+ var disabledYShift = "-20px";
+ var highlightYShift = "-40px";
+
+ var buttonRow = document.createElement("ul");
+ buttonRow.id = "wmd-button-row";
+ buttonRow = buttonBar.appendChild(buttonRow);
+
+
+ var boldButton = document.createElement("li");
+ boldButton.className = "wmd-button";
+ boldButton.id = "wmd-bold-button";
+ boldButton.title = toolbar_strong_label;
+ boldButton.XShift = "0px";
+ boldButton.textOp = command.doBold;
+ setupButton(boldButton, true);
+ buttonRow.appendChild(boldButton);
+
+ var italicButton = document.createElement("li");
+ italicButton.className = "wmd-button";
+ italicButton.id = "wmd-italic-button";
+ italicButton.title = toolbar_emphasis_label;
+ italicButton.XShift = "-20px";
+ italicButton.textOp = command.doItalic;
+ setupButton(italicButton, true);
+ buttonRow.appendChild(italicButton);
+
+ var spacer1 = document.createElement("li");
+ spacer1.className = "wmd-spacer";
+ spacer1.id = "wmd-spacer1";
+ buttonRow.appendChild(spacer1);
+
+ var linkButton = document.createElement("li");
+ linkButton.className = "wmd-button";
+ linkButton.id = "wmd-link-button";
+ linkButton.title = toolbar_hyperlink_label;
+ linkButton.XShift = "-40px";
+ linkButton.textOp = function(chunk, postProcessing){
+ return command.doLinkOrImage(chunk, postProcessing, false);
+ };
+ setupButton(linkButton, true);
+ buttonRow.appendChild(linkButton);
+
+ var quoteButton = document.createElement("li");
+ quoteButton.className = "wmd-button";
+ quoteButton.id = "wmd-quote-button";
+ quoteButton.title = toolbar_blockquote_label;
+ quoteButton.XShift = "-60px";
+ quoteButton.textOp = command.doBlockquote;
+ setupButton(quoteButton, true);
+ buttonRow.appendChild(quoteButton);
+
+ var codeButton = document.createElement("li");
+ codeButton.className = "wmd-button";
+ codeButton.id = "wmd-code-button";
+ codeButton.title = toolbar_code_label;
+ codeButton.XShift = "-80px";
+ codeButton.textOp = command.doCode;
+ setupButton(codeButton, true);
+ buttonRow.appendChild(codeButton);
+
+ var imageButton = document.createElement("li");
+ imageButton.className = "wmd-button";
+ imageButton.id = "wmd-image-button";
+ imageButton.title = toolbar_image_label;
+ imageButton.XShift = "-100px";
+ imageButton.textOp = function(chunk, postProcessing){
+ return command.doLinkOrImage(chunk, postProcessing, true);
+ };
+ setupButton(imageButton, true);
+ buttonRow.appendChild(imageButton);
+
+ var spacer2 = document.createElement("li");
+ spacer2.className = "wmd-spacer";
+ spacer2.id = "wmd-spacer2";
+ buttonRow.appendChild(spacer2);
+
+ var olistButton = document.createElement("li");
+ olistButton.className = "wmd-button";
+ olistButton.id = "wmd-olist-button";
+ olistButton.title = toolbar_numbered_label;
+ olistButton.XShift = "-120px";
+ olistButton.textOp = function(chunk, postProcessing){
+ command.doList(chunk, postProcessing, true);
+ };
+ setupButton(olistButton, true);
+ buttonRow.appendChild(olistButton);
+
+ var ulistButton = document.createElement("li");
+ ulistButton.className = "wmd-button";
+ ulistButton.id = "wmd-ulist-button";
+ ulistButton.title = toolbar_bulleted_label;
+ ulistButton.XShift = "-140px";
+ ulistButton.textOp = function(chunk, postProcessing){
+ command.doList(chunk, postProcessing, false);
+ };
+ setupButton(ulistButton, true);
+ buttonRow.appendChild(ulistButton);
+
+ var headingButton = document.createElement("li");
+ headingButton.className = "wmd-button";
+ headingButton.id = "wmd-heading-button";
+ headingButton.title = toolbar_heading_label;
+ headingButton.XShift = "-160px";
+ headingButton.textOp = command.doHeading;
+ setupButton(headingButton, true);
+ buttonRow.appendChild(headingButton);
+
+ var hrButton = document.createElement("li");
+ hrButton.className = "wmd-button";
+ hrButton.id = "wmd-hr-button";
+ hrButton.title = toolbar_horizontal_label;
+ hrButton.XShift = "-180px";
+ hrButton.textOp = command.doHorizontalRule;
+ setupButton(hrButton, true);
+ buttonRow.appendChild(hrButton);
+
+ var spacer3 = document.createElement("li");
+ spacer3.className = "wmd-spacer";
+ spacer3.id = "wmd-spacer3";
+ buttonRow.appendChild(spacer3);
+
+ var undoButton = document.createElement("li");
+ undoButton.className = "wmd-button";
+ undoButton.id = "wmd-undo-button";
+ undoButton.title = toolbar_undo_label;
+ undoButton.XShift = "-200px";
+ undoButton.execute = function(manager){
+ manager.undo();
+ };
+ setupButton(undoButton, true);
+ buttonRow.appendChild(undoButton);
+
+ var redoButton = document.createElement("li");
+ redoButton.className = "wmd-button";
+ redoButton.id = "wmd-redo-button";
+ redoButton.title = toolbar_redo_label;
+ if (/win/.test(nav.platform.toLowerCase())) {
+ redoButton.title = toolbar_redo_label;
+ }
+ else {
+ // mac and other non-Windows platforms
+ redoButton.title = $.i18n._('redo') + " - Ctrl+Shift+Z";
+ }
+ redoButton.XShift = "-220px";
+ redoButton.execute = function(manager){
+ manager.redo();
+ };
+ setupButton(redoButton, true);
+ buttonRow.appendChild(redoButton);
+ /*
+ var helpButton = document.createElement("li");
+ helpButton.className = "wmd-button";
+ helpButton.id = "wmd-help-button";
+ helpButton.XShift = "-240px";
+ helpButton.isHelp = true;
+
+ var helpAnchor = document.createElement("a");
+ helpAnchor.href = helpLink;
+ helpAnchor.target = helpTarget
+ helpAnchor.title = helpHoverTitle;
+ helpButton.appendChild(helpAnchor);
+
+ setupButton(helpButton, true);
+ buttonRow.appendChild(helpButton);
+ */
+ setUndoRedoButtonStates();
+ }
+
+ var setupEditor = function(){
+
+ if (/\?noundo/.test(doc.location.href)) {
+ wmd.nativeUndo = true;
+ }
+
+ if (!wmd.nativeUndo) {
+ undoMgr = new wmd.undoManager(function(){
+ previewRefreshCallback();
+ setUndoRedoButtonStates();
+ });
+ }
+
+ makeSpritedButtonRow();
+
+
+ var keyEvent = "keydown";
+ if (global.isOpera) {
+ keyEvent = "keypress";
+ }
+
+ util.addEvent(inputBox, keyEvent, function(key){
+
+ // Check to see if we have a button key and, if so execute the callback.
+ if (key.ctrlKey || key.metaKey) {
+
+ var keyCode = key.charCode || key.keyCode;
+ var keyCodeStr = String.fromCharCode(keyCode).toLowerCase();
+
+ // Bugfix for messed up DEL and .
+ if (keyCode === 46) {
+ keyCodeStr = "";
+ }
+ if (keyCode === 190) {
+ keyCodeStr = ".";
+ }
+
+ switch(keyCodeStr) {
+ case "b":
+ doClick(document.getElementById("wmd-bold-button"));
+ break;
+ case "i":
+ doClick(document.getElementById("wmd-italic-button"));
+ break;
+ case "l":
+ doClick(document.getElementById("wmd-link-button"));
+ break;
+ case ".":
+ doClick(document.getElementById("wmd-quote-button"));
+ break;
+ case "k":
+ doClick(document.getElementById("wmd-code-button"));
+ break;
+ case "g":
+ doClick(document.getElementById("wmd-image-button"));
+ break;
+ case "o":
+ doClick(document.getElementById("wmd-olist-button"));
+ break;
+ case "u":
+ doClick(document.getElementById("wmd-ulist-button"));
+ break;
+ case "h":
+ doClick(document.getElementById("wmd-heading-button"));
+ break;
+ case "r":
+ doClick(document.getElementById("wmd-hr-button"));
+ break;
+ case "y":
+ doClick(document.getElementById("wmd-redo-button"));
+ break;
+ case "z":
+ if(key.shiftKey) {
+ doClick(document.getElementById("wmd-redo-button"));
+ }
+ else {
+ doClick(document.getElementById("wmd-undo-button"));
+ }
+ break;
+ default:
+ return;
+ }
+
+
+ if (key.preventDefault) {
+ key.preventDefault();
+ }
+
+ if (top.event) {
+ top.event.returnValue = false;
+ }
+ }
+ });
+
+ // Auto-indent on shift-enter
+ util.addEvent(inputBox, "keyup", function(key){
+ if (key.shiftKey && !key.ctrlKey && !key.metaKey) {
+ var keyCode = key.charCode || key.keyCode;
+ // Character 13 is Enter
+ if (keyCode === 13) {
+ fakeButton = {};
+ fakeButton.textOp = command.doAutoindent;
+ doClick(fakeButton);
+ }
+ }
+ });
+
+ if (inputBox.form) {
+ var submitCallback = inputBox.form.onsubmit;
+ inputBox.form.onsubmit = function(){
+ convertToHtml();
+ if (submitCallback) {
+ return submitCallback.apply(this, arguments);
+ }
+ };
+ }
+ };
+
+ // Convert the contents of the input textarea to HTML in the output/preview panels.
+ var convertToHtml = function(){
+
+ if (wmd.showdown) {
+ var markdownConverter = new wmd.showdown.converter();
+ }
+ var text = inputBox.value;
+
+ var callback = function(){
+ inputBox.value = text;
+ };
+
+ if (!/markdown/.test(wmd.wmd_env.output.toLowerCase())) {
+ if (markdownConverter) {
+ inputBox.value = markdownConverter.makeHtml(text);
+ top.setTimeout(callback, 0);
+ }
+ }
+ return true;
+ };
+
+
+ this.undo = function(){
+ if (undoMgr) {
+ undoMgr.undo();
+ }
+ };
+
+ this.redo = function(){
+ if (undoMgr) {
+ undoMgr.redo();
+ }
+ };
+
+ // This is pretty useless. The setupEditor function contents
+ // should just be copied here.
+ var init = function(){
+ setupEditor();
+ };
+
+ this.destroy = function(){
+ if (undoMgr) {
+ undoMgr.destroy();
+ }
+ if (div.parentNode) {
+ div.parentNode.removeChild(div);
+ }
+ if (inputBox) {
+ inputBox.style.marginTop = "";
+ }
+ top.clearInterval(creationHandle);
+ };
+
+ init();
+ };
+
+ // The input textarea state/contents.
+ // This is used to implement undo/redo by the undo manager.
+ wmd.TextareaState = function(){
+
+ // Aliases
+ var stateObj = this;
+ var inputArea = wmd.panels.input;
+
+ this.init = function() {
+
+ if (!util.isVisible(inputArea)) {
+ return;
+ }
+
+ this.setInputAreaSelectionStartEnd();
+ this.scrollTop = inputArea.scrollTop;
+ if (!this.text && inputArea.selectionStart || inputArea.selectionStart === 0) {
+ this.text = inputArea.value;
+ }
+
+ }
+
+ // Sets the selected text in the input box after we've performed an
+ // operation.
+ this.setInputAreaSelection = function(){
+
+ if (!util.isVisible(inputArea)) {
+ return;
+ }
+
+ if (inputArea.selectionStart !== undefined && !global.isOpera) {
+
+ inputArea.focus();
+ inputArea.selectionStart = stateObj.start;
+ inputArea.selectionEnd = stateObj.end;
+ inputArea.scrollTop = stateObj.scrollTop;
+ }
+ else if (doc.selection) {
+
+ if (doc.activeElement && doc.activeElement !== inputArea) {
+ return;
+ }
+
+ inputArea.focus();
+ var range = inputArea.createTextRange();
+ range.moveStart("character", -inputArea.value.length);
+ range.moveEnd("character", -inputArea.value.length);
+ range.moveEnd("character", stateObj.end);
+ range.moveStart("character", stateObj.start);
+ range.select();
+ }
+ };
+
+ this.setInputAreaSelectionStartEnd = function(){
+
+ if (inputArea.selectionStart || inputArea.selectionStart === 0) {
+
+ stateObj.start = inputArea.selectionStart;
+ stateObj.end = inputArea.selectionEnd;
+ }
+ else if (doc.selection) {
+
+ stateObj.text = util.fixEolChars(inputArea.value);
+
+ // IE loses the selection in the textarea when buttons are
+ // clicked. On IE we cache the selection and set a flag
+ // which we check for here.
+ var range;
+ if(wmd.ieRetardedClick && wmd.ieCachedRange) {
+ range = wmd.ieCachedRange;
+ wmd.ieRetardedClick = false;
+ }
+ else {
+ range = doc.selection.createRange();
+ }
+
+ var fixedRange = util.fixEolChars(range.text);
+ var marker = "\x07";
+ var markedRange = marker + fixedRange + marker;
+ range.text = markedRange;
+ var inputText = util.fixEolChars(inputArea.value);
+
+ range.moveStart("character", -markedRange.length);
+ range.text = fixedRange;
+
+ stateObj.start = inputText.indexOf(marker);
+ stateObj.end = inputText.lastIndexOf(marker) - marker.length;
+
+ var len = stateObj.text.length - util.fixEolChars(inputArea.value).length;
+
+ if (len) {
+ range.moveStart("character", -fixedRange.length);
+ while (len--) {
+ fixedRange += "\n";
+ stateObj.end += 1;
+ }
+ range.text = fixedRange;
+ }
+
+ this.setInputAreaSelection();
+ }
+ };
+
+ // Restore this state into the input area.
+ this.restore = function(){
+
+ if (stateObj.text != undefined && stateObj.text != inputArea.value) {
+ inputArea.value = stateObj.text;
+ }
+ this.setInputAreaSelection();
+ inputArea.scrollTop = stateObj.scrollTop;
+ };
+
+ // Gets a collection of HTML chunks from the inptut textarea.
+ this.getChunks = function(){
+
+ var chunk = new wmd.Chunks();
+
+ chunk.before = util.fixEolChars(stateObj.text.substring(0, stateObj.start));
+ chunk.startTag = "";
+ chunk.selection = util.fixEolChars(stateObj.text.substring(stateObj.start, stateObj.end));
+ chunk.endTag = "";
+ chunk.after = util.fixEolChars(stateObj.text.substring(stateObj.end));
+ chunk.scrollTop = stateObj.scrollTop;
+
+ return chunk;
+ };
+
+ // Sets the TextareaState properties given a chunk of markdown.
+ this.setChunks = function(chunk){
+
+ chunk.before = chunk.before + chunk.startTag;
+ chunk.after = chunk.endTag + chunk.after;
+
+ if (global.isOpera) {
+ chunk.before = chunk.before.replace(/\n/g, "\r\n");
+ chunk.selection = chunk.selection.replace(/\n/g, "\r\n");
+ chunk.after = chunk.after.replace(/\n/g, "\r\n");
+ }
+
+ this.start = chunk.before.length;
+ this.end = chunk.before.length + chunk.selection.length;
+ this.text = chunk.before + chunk.selection + chunk.after;
+ this.scrollTop = chunk.scrollTop;
+ };
+
+ this.init();
+ };
+
+ // before: contains all the text in the input box BEFORE the selection.
+ // after: contains all the text in the input box AFTER the selection.
+ wmd.Chunks = function(){
+ };
+
+ // startRegex: a regular expression to find the start tag
+ // endRegex: a regular expresssion to find the end tag
+ wmd.Chunks.prototype.findTags = function(startRegex, endRegex){
+
+ var chunkObj = this;
+ var regex;
+
+ if (startRegex) {
+
+ regex = util.extendRegExp(startRegex, "", "$");
+
+ this.before = this.before.replace(regex,
+ function(match){
+ chunkObj.startTag = chunkObj.startTag + match;
+ return "";
+ });
+
+ regex = util.extendRegExp(startRegex, "^", "");
+
+ this.selection = this.selection.replace(regex,
+ function(match){
+ chunkObj.startTag = chunkObj.startTag + match;
+ return "";
+ });
+ }
+
+ if (endRegex) {
+
+ regex = util.extendRegExp(endRegex, "", "$");
+
+ this.selection = this.selection.replace(regex,
+ function(match){
+ chunkObj.endTag = match + chunkObj.endTag;
+ return "";
+ });
+
+ regex = util.extendRegExp(endRegex, "^", "");
+
+ this.after = this.after.replace(regex,
+ function(match){
+ chunkObj.endTag = match + chunkObj.endTag;
+ return "";
+ });
+ }
+ };
+
+ // If remove is false, the whitespace is transferred
+ // to the before/after regions.
+ //
+ // If remove is true, the whitespace disappears.
+ wmd.Chunks.prototype.trimWhitespace = function(remove){
+
+ this.selection = this.selection.replace(/^(\s*)/, "");
+
+ if (!remove) {
+ this.before += re.$1;
+ }
+
+ this.selection = this.selection.replace(/(\s*)$/, "");
+
+ if (!remove) {
+ this.after = re.$1 + this.after;
+ }
+ };
+
+
+ wmd.Chunks.prototype.skipLines = function(nLinesBefore, nLinesAfter, findExtraNewlines){
+
+ if (nLinesBefore === undefined) {
+ nLinesBefore = 1;
+ }
+
+ if (nLinesAfter === undefined) {
+ nLinesAfter = 1;
+ }
+
+ nLinesBefore++;
+ nLinesAfter++;
+
+ var regexText;
+ var replacementText;
+
+ this.selection = this.selection.replace(/(^\n*)/, "");
+ this.startTag = this.startTag + re.$1;
+ this.selection = this.selection.replace(/(\n*$)/, "");
+ this.endTag = this.endTag + re.$1;
+ this.startTag = this.startTag.replace(/(^\n*)/, "");
+ this.before = this.before + re.$1;
+ this.endTag = this.endTag.replace(/(\n*$)/, "");
+ this.after = this.after + re.$1;
+
+ if (this.before) {
+
+ regexText = replacementText = "";
+
+ while (nLinesBefore--) {
+ regexText += "\\n?";
+ replacementText += "\n";
+ }
+
+ if (findExtraNewlines) {
+ regexText = "\\n*";
+ }
+ this.before = this.before.replace(new re(regexText + "$", ""), replacementText);
+ }
+
+ if (this.after) {
+
+ regexText = replacementText = "";
+
+ while (nLinesAfter--) {
+ regexText += "\\n?";
+ replacementText += "\n";
+ }
+ if (findExtraNewlines) {
+ regexText = "\\n*";
+ }
+
+ this.after = this.after.replace(new re(regexText, ""), replacementText);
+ }
+ };
+
+ // The markdown symbols - 4 spaces = code, > = blockquote, etc.
+ command.prefixes = "(?:\\s{4,}|\\s*>|\\s*-\\s+|\\s*\\d+\\.|=|\\+|-|_|\\*|#|\\s*\\[[^\n]]+\\]:)";
+
+ // Remove markdown symbols from the chunk selection.
+ command.unwrap = function(chunk){
+ var txt = new re("([^\\n])\\n(?!(\\n|" + command.prefixes + "))", "g");
+ chunk.selection = chunk.selection.replace(txt, "$1 $2");
+ };
+
+ command.wrap = function(chunk, len){
+ command.unwrap(chunk);
+ var regex = new re("(.{1," + len + "})( +|$\\n?)", "gm");
+
+ chunk.selection = chunk.selection.replace(regex, function(line, marked){
+ if (new re("^" + command.prefixes, "").test(line)) {
+ return line;
+ }
+ return marked + "\n";
+ });
+
+ chunk.selection = chunk.selection.replace(/\s+$/, "");
+ };
+
+ command.doBold = function(chunk, postProcessing){
+ return command.doBorI(chunk, postProcessing, 2, "strong text");
+ };
+
+ command.doItalic = function(chunk, postProcessing){
+ return command.doBorI(chunk, postProcessing, 1, "emphasized text");
+ };
+
+ // chunk: The selected region that will be enclosed with */**
+ // nStars: 1 for italics, 2 for bold
+ // insertText: If you just click the button without highlighting text, this gets inserted
+ command.doBorI = function(chunk, postProcessing, nStars, insertText){
+
+ // Get rid of whitespace and fixup newlines.
+ chunk.trimWhitespace();
+ chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n");
+
+ // Look for stars before and after. Is the chunk already marked up?
+ chunk.before.search(/(\**$)/);
+ var starsBefore = re.$1;
+
+ chunk.after.search(/(^\**)/);
+ var starsAfter = re.$1;
+
+ var prevStars = Math.min(starsBefore.length, starsAfter.length);
+
+ // Remove stars if we have to since the button acts as a toggle.
+ if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) {
+ chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), "");
+ chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), "");
+ }
+ else if (!chunk.selection && starsAfter) {
+ // It's not really clear why this code is necessary. It just moves
+ // some arbitrary stuff around.
+ chunk.after = chunk.after.replace(/^([*_]*)/, "");
+ chunk.before = chunk.before.replace(/(\s?)$/, "");
+ var whitespace = re.$1;
+ chunk.before = chunk.before + starsAfter + whitespace;
+ }
+ else {
+
+ // In most cases, if you don't have any selected text and click the button
+ // you'll get a selected, marked up region with the default text inserted.
+ if (!chunk.selection && !starsAfter) {
+ chunk.selection = insertText;
+ }
+
+ // Add the true markup.
+ var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ?
+ chunk.before = chunk.before + markup;
+ chunk.after = markup + chunk.after;
+ }
+
+ return;
+ };
+
+ command.stripLinkDefs = function(text, defsToAdd){
+
+ text = text.replace(/^[ ]{0,3}\[(\d+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|$)/gm,
+ function(totalMatch, id, link, newlines, title){
+ defsToAdd[id] = totalMatch.replace(/\s*$/, "");
+ if (newlines) {
+ // Strip the title and return that separately.
+ defsToAdd[id] = totalMatch.replace(/["(](.+?)[")]$/, "");
+ return newlines + title;
+ }
+ return "";
+ });
+
+ return text;
+ };
+
+ command.addLinkDef = function(chunk, linkDef){
+
+ var refNumber = 0; // The current reference number
+ var defsToAdd = {}; //
+ // Start with a clean slate by removing all previous link definitions.
+ chunk.before = command.stripLinkDefs(chunk.before, defsToAdd);
+ chunk.selection = command.stripLinkDefs(chunk.selection, defsToAdd);
+ chunk.after = command.stripLinkDefs(chunk.after, defsToAdd);
+
+ var defs = "";
+ var regex = /(\[(?:\[[^\]]*\]|[^\[\]])*\][ ]?(?:\n[ ]*)?\[)(\d+)(\])/g;
+
+ var addDefNumber = function(def){
+ refNumber++;
+ def = def.replace(/^[ ]{0,3}\[(\d+)\]:/, " [" + refNumber + "]:");
+ defs += "\n" + def;
+ };
+
+ var getLink = function(wholeMatch, link, id, end){
+
+ if (defsToAdd[id]) {
+ addDefNumber(defsToAdd[id]);
+ return link + refNumber + end;
+
+ }
+ return wholeMatch;
+ };
+
+ chunk.before = chunk.before.replace(regex, getLink);
+
+ if (linkDef) {
+ addDefNumber(linkDef);
+ }
+ else {
+ chunk.selection = chunk.selection.replace(regex, getLink);
+ }
+
+ var refOut = refNumber;
+
+ chunk.after = chunk.after.replace(regex, getLink);
+
+ if (chunk.after) {
+ chunk.after = chunk.after.replace(/\n*$/, "");
+ }
+ if (!chunk.after) {
+ chunk.selection = chunk.selection.replace(/\n*$/, "");
+ }
+
+ chunk.after += "\n\n" + defs;
+
+ return refOut;
+ };
+
+ command.doLinkOrImage = function(chunk, postProcessing, isImage){
+
+ chunk.trimWhitespace();
+ chunk.findTags(/\s*!?\[/, /\][ ]?(?:\n[ ]*)?(\[.*?\])?/);
+
+ if (chunk.endTag.length > 1) {
+
+ chunk.startTag = chunk.startTag.replace(/!?\[/, "");
+ chunk.endTag = "";
+ command.addLinkDef(chunk, null);
+
+ }
+ else {
+
+ if (/\n\n/.test(chunk.selection)) {
+ command.addLinkDef(chunk, null);
+ return;
+ }
+
+ // The function to be executed when you enter a link and press OK or Cancel.
+ // Marks up the link and adds the ref.
+ var makeLinkMarkdown = function(link){
+
+ if (link !== null) {
+
+ chunk.startTag = chunk.endTag = "";
+ var linkDef = " [999]: " + link;
+
+ var num = command.addLinkDef(chunk, linkDef);
+ chunk.startTag = isImage ? "![" : "[";
+ chunk.endTag = "][" + num + "]";
+
+ if (!chunk.selection) {
+ if (isImage) {
+ chunk.selection = "alt text";
+ }
+ else {
+ chunk.selection = "link text";
+ }
+ }
+ }
+ postProcessing();
+ };
+
+ if (isImage) {
+ // add forth param to identify image window
+ util.prompt(imageDialogText, imageDefaultText, makeLinkMarkdown, 1);
+ }
+ else {
+ util.prompt(linkDialogText, linkDefaultText, makeLinkMarkdown);
+ }
+ return true;
+ }
+ };
+
+ util.makeAPI = function(){
+ wmd.wmd = {};
+ wmd.wmd.editor = wmd.editor;
+ wmd.wmd.previewManager = wmd.previewManager;
+ };
+
+ util.startEditor = function(){
+
+ if (wmd.wmd_env.autostart === false) {
+ util.makeAPI();
+ return;
+ }
+
+ var edit; // The editor (buttons + input + outputs) - the main object.
+ var previewMgr; // The preview manager.
+
+ // Fired after the page has fully loaded.
+ var loadListener = function(){
+
+ wmd.panels = new wmd.PanelCollection();
+
+ previewMgr = new wmd.previewManager();
+ var previewRefreshCallback = previewMgr.refresh;
+
+ edit = new wmd.editor(previewRefreshCallback);
+
+ previewMgr.refresh(true);
+
+ };
+
+ util.addEvent(top, "load", loadListener);
+ };
+
+ wmd.previewManager = function(){
+
+ var managerObj = this;
+ var converter;
+ var poller;
+ var timeout;
+ var elapsedTime;
+ var oldInputText;
+ var htmlOut;
+ var maxDelay = 3000;
+ var startType = "delayed"; // The other legal value is "manual"
+
+ // Adds event listeners to elements and creates the input poller.
+ var setupEvents = function(inputElem, listener){
+
+ util.addEvent(inputElem, "input", listener);
+ inputElem.onpaste = listener;
+ inputElem.ondrop = listener;
+
+ util.addEvent(inputElem, "keypress", listener);
+ util.addEvent(inputElem, "keydown", listener);
+ // previewPollInterval is set at the top of this file.
+ poller = new wmd.inputPoller(listener, previewPollInterval);
+ };
+
+ var getDocScrollTop = function(){
+
+ var result = 0;
+
+ if (top.innerHeight) {
+ result = top.pageYOffset;
+ }
+ else
+ if (doc.documentElement && doc.documentElement.scrollTop) {
+ result = doc.documentElement.scrollTop;
+ }
+ else
+ if (doc.body) {
+ result = doc.body.scrollTop;
+ }
+
+ return result;
+ };
+
+ var makePreviewHtml = function(){
+
+ // If there are no registered preview and output panels
+ // there is nothing to do.
+ if (!wmd.panels.preview && !wmd.panels.output) {
+ return;
+ }
+
+ var text = wmd.panels.input.value;
+ if (text && text == oldInputText) {
+ return; // Input text hasn't changed.
+ }
+ else {
+ oldInputText = text;
+ }
+
+ var prevTime = new Date().getTime();
+
+ if (!converter && wmd.showdown) {
+ converter = new wmd.showdown.converter();
+ }
+
+ if (converter) {
+ text = converter.makeHtml(text);
+ }
+
+ // Calculate the processing time of the HTML creation.
+ // It's used as the delay time in the event listener.
+ var currTime = new Date().getTime();
+ elapsedTime = currTime - prevTime;
+
+ pushPreviewHtml(text);
+ htmlOut = text;
+ };
+
+ // setTimeout is already used. Used as an event listener.
+ var applyTimeout = function(){
+
+ if (timeout) {
+ top.clearTimeout(timeout);
+ timeout = undefined;
+ }
+
+ if (startType !== "manual") {
+
+ var delay = 0;
+
+ if (startType === "delayed") {
+ delay = elapsedTime;
+ }
+
+ if (delay > maxDelay) {
+ delay = maxDelay;
+ }
+ timeout = top.setTimeout(makePreviewHtml, delay);
+ }
+ };
+
+ var getScaleFactor = function(panel){
+ if (panel.scrollHeight <= panel.clientHeight) {
+ return 1;
+ }
+ return panel.scrollTop / (panel.scrollHeight - panel.clientHeight);
+ };
+
+ var setPanelScrollTops = function(){
+
+ if (wmd.panels.preview) {
+ wmd.panels.preview.scrollTop = (wmd.panels.preview.scrollHeight - wmd.panels.preview.clientHeight) * getScaleFactor(wmd.panels.preview);
+ ;
+ }
+
+ if (wmd.panels.output) {
+ wmd.panels.output.scrollTop = (wmd.panels.output.scrollHeight - wmd.panels.output.clientHeight) * getScaleFactor(wmd.panels.output);
+ ;
+ }
+ };
+
+ this.refresh = function(requiresRefresh){
+
+ if (requiresRefresh) {
+ oldInputText = "";
+ makePreviewHtml();
+ }
+ else {
+ applyTimeout();
+ }
+ };
+
+ this.processingTime = function(){
+ return elapsedTime;
+ };
+
+ // The output HTML
+ this.output = function(){
+ return htmlOut;
+ };
+
+ // The mode can be "manual" or "delayed"
+ this.setUpdateMode = function(mode){
+ startType = mode;
+ managerObj.refresh();
+ };
+
+ var isFirstTimeFilled = true;
+
+ var pushPreviewHtml = function(text){
+
+ var emptyTop = position.getTop(wmd.panels.input) - getDocScrollTop();
+
+ // Send the encoded HTML to the output textarea/div.
+ if (wmd.panels.output) {
+ // The value property is only defined if the output is a textarea.
+ if (wmd.panels.output.value !== undefined) {
+ wmd.panels.output.value = text;
+ wmd.panels.output.readOnly = true;
+ }
+ // Otherwise we are just replacing the text in a div.
+ // Send the HTML wrapped in <pre><code>
+ else {
+ var newText = text.replace(/&/g, "&amp;");
+ newText = newText.replace(/</g, "&lt;");
+ wmd.panels.output.innerHTML = "<pre><code>" + newText + "</code></pre>";
+ }
+ }
+
+ if (wmd.panels.preview) {
+ wmd.panels.preview.innerHTML = text;
+ }
+
+ setPanelScrollTops();
+
+ if (isFirstTimeFilled) {
+ isFirstTimeFilled = false;
+ return;
+ }
+
+ var fullTop = position.getTop(wmd.panels.input) - getDocScrollTop();
+
+ if (global.isIE) {
+ top.setTimeout(function(){
+ top.scrollBy(0, fullTop - emptyTop);
+ }, 0);
+ }
+ else {
+ top.scrollBy(0, fullTop - emptyTop);
+ }
+ };
+
+ var init = function(){
+
+ setupEvents(wmd.panels.input, applyTimeout);
+ makePreviewHtml();
+
+ if (wmd.panels.preview) {
+ wmd.panels.preview.scrollTop = 0;
+ }
+ if (wmd.panels.output) {
+ wmd.panels.output.scrollTop = 0;
+ }
+ };
+
+ this.destroy = function(){
+ if (poller) {
+ poller.destroy();
+ }
+ };
+
+ init();
+ };
+
+ // When making a list, hitting shift-enter will put your cursor on the next line
+ // at the current indent level.
+ command.doAutoindent = function(chunk, postProcessing){
+
+ chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n");
+ chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n");
+ chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n");
+
+ if(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)){
+ if(command.doList){
+ command.doList(chunk);
+ }
+ }
+ if(/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)){
+ if(command.doBlockquote){
+ command.doBlockquote(chunk);
+ }
+ }
+ if(/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)){
+ if(command.doCode){
+ command.doCode(chunk);
+ }
+ }
+ };
+
+ command.doBlockquote = function(chunk, postProcessing){
+
+ chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,
+ function(totalMatch, newlinesBefore, text, newlinesAfter){
+ chunk.before += newlinesBefore;
+ chunk.after = newlinesAfter + chunk.after;
+ return text;
+ });
+
+ chunk.before = chunk.before.replace(/(>[ \t]*)$/,
+ function(totalMatch, blankLine){
+ chunk.selection = blankLine + chunk.selection;
+ return "";
+ });
+
+ chunk.selection = chunk.selection.replace(/^(\s|>)+$/ ,"");
+ chunk.selection = chunk.selection || "Blockquote";
+
+ if(chunk.before){
+ chunk.before = chunk.before.replace(/\n?$/,"\n");
+ }
+ if(chunk.after){
+ chunk.after = chunk.after.replace(/^\n?/,"\n");
+ }
+
+ chunk.before = chunk.before.replace(/(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*$)/,
+ function(totalMatch){
+ chunk.startTag = totalMatch;
+ return "";
+ });
+
+ chunk.after = chunk.after.replace(/^(((\n|^)(\n[ \t]*)*>(.+\n)*.*)+(\n[ \t]*)*)/,
+ function(totalMatch){
+ chunk.endTag = totalMatch;
+ return "";
+ });
+
+ var replaceBlanksInTags = function(useBracket){
+
+ var replacement = useBracket ? "> " : "";
+
+ if(chunk.startTag){
+ chunk.startTag = chunk.startTag.replace(/\n((>|\s)*)\n$/,
+ function(totalMatch, markdown){
+ return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
+ });
+ }
+ if(chunk.endTag){
+ chunk.endTag = chunk.endTag.replace(/^\n((>|\s)*)\n/,
+ function(totalMatch, markdown){
+ return "\n" + markdown.replace(/^[ ]{0,3}>?[ \t]*$/gm, replacement) + "\n";
+ });
+ }
+ };
+
+ if(/^(?![ ]{0,3}>)/m.test(chunk.selection)){
+ command.wrap(chunk, wmd.wmd_env.lineLength - 2);
+ chunk.selection = chunk.selection.replace(/^/gm, "> ");
+ replaceBlanksInTags(true);
+ chunk.skipLines();
+ }
+ else{
+ chunk.selection = chunk.selection.replace(/^[ ]{0,3}> ?/gm, "");
+ command.unwrap(chunk);
+ replaceBlanksInTags(false);
+
+ if(!/^(\n|^)[ ]{0,3}>/.test(chunk.selection) && chunk.startTag){
+ chunk.startTag = chunk.startTag.replace(/\n{0,2}$/, "\n\n");
+ }
+
+ if(!/(\n|^)[ ]{0,3}>.*$/.test(chunk.selection) && chunk.endTag){
+ chunk.endTag=chunk.endTag.replace(/^\n{0,2}/, "\n\n");
+ }
+ }
+
+ if(!/\n/.test(chunk.selection)){
+ chunk.selection = chunk.selection.replace(/^(> *)/,
+ function(wholeMatch, blanks){
+ chunk.startTag += blanks;
+ return "";
+ });
+ }
+ };
+
+ command.doCode = function(chunk, postProcessing){
+
+ var hasTextBefore = /\S[ ]*$/.test(chunk.before);
+ var hasTextAfter = /^[ ]*\S/.test(chunk.after);
+
+ // Use 'four space' markdown if the selection is on its own
+ // line or is multiline.
+ if((!hasTextAfter && !hasTextBefore) || /\n/.test(chunk.selection)){
+
+ chunk.before = chunk.before.replace(/[ ]{4}$/,
+ function(totalMatch){
+ chunk.selection = totalMatch + chunk.selection;
+ return "";
+ });
+
+ var nLinesBack = 1;
+ var nLinesForward = 1;
+
+ if(/\n(\t|[ ]{4,}).*\n$/.test(chunk.before)){
+ nLinesBack = 0;
+ }
+ if(/^\n(\t|[ ]{4,})/.test(chunk.after)){
+ nLinesForward = 0;
+ }
+
+ chunk.skipLines(nLinesBack, nLinesForward);
+
+ if(!chunk.selection){
+ chunk.startTag = " ";
+ chunk.selection = "enter code here";
+ }
+ else {
+ if(/^[ ]{0,3}\S/m.test(chunk.selection)){
+ chunk.selection = chunk.selection.replace(/^/gm, " ");
+ }
+ else{
+ chunk.selection = chunk.selection.replace(/^[ ]{4}/gm, "");
+ }
+ }
+ }
+ else{
+ // Use backticks (`) to delimit the code block.
+
+ chunk.trimWhitespace();
+ chunk.findTags(/`/, /`/);
+
+ if(!chunk.startTag && !chunk.endTag){
+ chunk.startTag = chunk.endTag="`";
+ if(!chunk.selection){
+ chunk.selection = "enter code here";
+ }
+ }
+ else if(chunk.endTag && !chunk.startTag){
+ chunk.before += chunk.endTag;
+ chunk.endTag = "";
+ }
+ else{
+ chunk.startTag = chunk.endTag="";
+ }
+ }
+ };
+
+ command.doList = function(chunk, postProcessing, isNumberedList){
+
+ // These are identical except at the very beginning and end.
+ // Should probably use the regex extension function to make this clearer.
+ var previousItemsRegex = /(\n|^)(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*$/;
+ var nextItemsRegex = /^\n*(([ ]{0,3}([*+-]|\d+[.])[ \t]+.*)(\n.+|\n{2,}([*+-].*|\d+[.])[ \t]+.*|\n{2,}[ \t]+\S.*)*)\n*/;
+
+ // The default bullet is a dash but others are possible.
+ // This has nothing to do with the particular HTML bullet,
+ // it's just a markdown bullet.
+ var bullet = "-";
+
+ // The number in a numbered list.
+ var num = 1;
+
+ // Get the item prefix - e.g. " 1. " for a numbered list, " - " for a bulleted list.
+ var getItemPrefix = function(){
+ var prefix;
+ if(isNumberedList){
+ prefix = " " + num + ". ";
+ num++;
+ }
+ else{
+ prefix = " " + bullet + " ";
+ }
+ return prefix;
+ };
+
+ // Fixes the prefixes of the other list items.
+ var getPrefixedItem = function(itemText){
+
+ // The numbering flag is unset when called by autoindent.
+ if(isNumberedList === undefined){
+ isNumberedList = /^\s*\d/.test(itemText);
+ }
+
+ // Renumber/bullet the list element.
+ itemText = itemText.replace(/^[ ]{0,3}([*+-]|\d+[.])\s/gm,
+ function( _ ){
+ return getItemPrefix();
+ });
+
+ return itemText;
+ };
+
+ chunk.findTags(/(\n|^)*[ ]{0,3}([*+-]|\d+[.])\s+/, null);
+
+ if(chunk.before && !/\n$/.test(chunk.before) && !/^\n/.test(chunk.startTag)){
+ chunk.before += chunk.startTag;
+ chunk.startTag = "";
+ }
+
+ if(chunk.startTag){
+
+ var hasDigits = /\d+[.]/.test(chunk.startTag);
+ chunk.startTag = "";
+ chunk.selection = chunk.selection.replace(/\n[ ]{4}/g, "\n");
+ command.unwrap(chunk);
+ chunk.skipLines();
+
+ if(hasDigits){
+ // Have to renumber the bullet points if this is a numbered list.
+ chunk.after = chunk.after.replace(nextItemsRegex, getPrefixedItem);
+ }
+ if(isNumberedList == hasDigits){
+ return;
+ }
+ }
+
+ var nLinesUp = 1;
+
+ chunk.before = chunk.before.replace(previousItemsRegex,
+ function(itemText){
+ if(/^\s*([*+-])/.test(itemText)){
+ bullet = re.$1;
+ }
+ nLinesUp = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
+ return getPrefixedItem(itemText);
+ });
+
+ if(!chunk.selection){
+ chunk.selection = "List item";
+ }
+
+ var prefix = getItemPrefix();
+
+ var nLinesDown = 1;
+
+ chunk.after = chunk.after.replace(nextItemsRegex,
+ function(itemText){
+ nLinesDown = /[^\n]\n\n[^\n]/.test(itemText) ? 1 : 0;
+ return getPrefixedItem(itemText);
+ });
+
+ chunk.trimWhitespace(true);
+ chunk.skipLines(nLinesUp, nLinesDown, true);
+ chunk.startTag = prefix;
+ var spaces = prefix.replace(/./g, " ");
+ command.wrap(chunk, wmd.wmd_env.lineLength - spaces.length);
+ chunk.selection = chunk.selection.replace(/\n/g, "\n" + spaces);
+
+ };
+
+ command.doHeading = function(chunk, postProcessing){
+
+ // Remove leading/trailing whitespace and reduce internal spaces to single spaces.
+ chunk.selection = chunk.selection.replace(/\s+/g, " ");
+ chunk.selection = chunk.selection.replace(/(^\s+|\s+$)/g, "");
+
+ // If we clicked the button with no selected text, we just
+ // make a level 2 hash header around some default text.
+ if(!chunk.selection){
+ chunk.startTag = "## ";
+ chunk.selection = "Heading";
+ chunk.endTag = " ##";
+ return;
+ }
+
+ var headerLevel = 0; // The existing header level of the selected text.
+
+ // Remove any existing hash heading markdown and save the header level.
+ chunk.findTags(/#+[ ]*/, /[ ]*#+/);
+ if(/#+/.test(chunk.startTag)){
+ headerLevel = re.lastMatch.length;
+ }
+ chunk.startTag = chunk.endTag = "";
+
+ // Try to get the current header level by looking for - and = in the line
+ // below the selection.
+ chunk.findTags(null, /\s?(-+|=+)/);
+ if(/=+/.test(chunk.endTag)){
+ headerLevel = 1;
+ }
+ if(/-+/.test(chunk.endTag)){
+ headerLevel = 2;
+ }
+
+ // Skip to the next line so we can create the header markdown.
+ chunk.startTag = chunk.endTag = "";
+ chunk.skipLines(1, 1);
+
+ // We make a level 2 header if there is no current header.
+ // If there is a header level, we substract one from the header level.
+ // If it's already a level 1 header, it's removed.
+ var headerLevelToCreate = headerLevel == 0 ? 2 : headerLevel - 1;
+
+ if(headerLevelToCreate > 0){
+
+ // The button only creates level 1 and 2 underline headers.
+ // Why not have it iterate over hash header levels? Wouldn't that be easier and cleaner?
+ var headerChar = headerLevelToCreate >= 2 ? "-" : "=";
+ var len = chunk.selection.length;
+ if(len > wmd.wmd_env.lineLength){
+ len = wmd.wmd_env.lineLength;
+ }
+ chunk.endTag = "\n";
+ while(len--){
+ chunk.endTag += headerChar;
+ }
+ }
+ };
+
+ command.doHorizontalRule = function(chunk, postProcessing){
+ chunk.startTag = "----------\n";
+ chunk.selection = "";
+ chunk.skipLines(2, 1, true);
+ }
+};
+
+
+Attacklab.wmd_env = {};
+Attacklab.account_options = {};
+Attacklab.wmd_defaults = {version:1, output:"Markdown", lineLength:40, delayLoad:false};
+
+if(!Attacklab.wmd)
+{
+ Attacklab.wmd = function()
+ {
+ Attacklab.loadEnv = function()
+ {
+ var mergeEnv = function(env)
+ {
+ if(!env)
+ {
+ return;
+ }
+
+ for(var key in env)
+ {
+ Attacklab.wmd_env[key] = env[key];
+ }
+ };
+
+ mergeEnv(Attacklab.wmd_defaults);
+ mergeEnv(Attacklab.account_options);
+ mergeEnv(top["wmd_options"]);
+ Attacklab.full = true;
+
+ var defaultButtons = "bold italic link blockquote code image ol ul heading hr";
+ Attacklab.wmd_env.buttons = Attacklab.wmd_env.buttons || defaultButtons;
+ };
+ Attacklab.loadEnv();
+
+ };
+
+ Attacklab.wmd();
+ Attacklab.wmdBase();
+ Attacklab.Util.startEditor();
+};
+
diff --git a/templates/content/js/yuicompressor-2.4.2.jar b/templates/content/js/yuicompressor-2.4.2.jar
new file mode 100644
index 00000000..c29470bd
--- /dev/null
+++ b/templates/content/js/yuicompressor-2.4.2.jar
Binary files differ
diff --git a/templates/content/style/default.css b/templates/content/style/default.css
new file mode 100644
index 00000000..0221cc03
--- /dev/null
+++ b/templates/content/style/default.css
@@ -0,0 +1,1753 @@
+/*
+Document : default
+Created on : Dec 9, 2008, 8:48:40 PM
+Author : Mike
+Description:
+Style sheet for cnprog.com
+All rights reserved. 2008 CNPROG.COM
+*/
+
+@import url(/content/style/jquery.autocomplete.css);
+@import url(/content/style/openid.css);
+@import url(/content/style/prettify.css);
+
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, form, label, table, caption, tbody, tfoot, thead, tr, th, td
+{
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ vertical-align: baseline;
+ background: transparent;
+}
+body{
+ font-family: "segoe ui",Helvetica,"Microsoft YaHei",,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
+ font-size: 80%;
+
+ margin:0;
+ padding:0;
+ text-align:center;
+ line-height: 1;
+}
+blockquote, q
+{
+ quotes: none;
+}
+blockquote:before, blockquote:after, q:before, q:after
+{
+ content: '';
+ content: none;
+}
+table
+{
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+input, select, button, textarea
+{
+ border: 1px solid #999;
+ font-size:100%;
+ font-family:"segoe ui",Helvetica,"Microsoft YaHei",,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
+}
+
+input
+{
+ /*
+ margin: 5px 0px 5px 0px;*/
+ padding:3px;
+
+}
+
+pre
+{
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size:100%;
+ margin-bottom: 10px;
+ overflow: auto;
+ width: 580px;
+ background-color: #F5F5F5;
+ padding-left:5px;
+ padding-top:5px;
+ padding-bottom: 20px !ie7;
+}
+
+code{
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size:100%;
+
+}
+
+blockquote
+{
+ margin-bottom: 10px;
+ margin-right: 15px;
+ padding: 10px 0px 1px 10px;
+ background-color: #F5F5F5;
+}
+
+b, strong
+{
+ font-weight: bold;
+}
+
+i, em
+{
+ font-style: italic;
+}
+
+ul
+{
+ list-style: disc;
+ margin-left: 30px;
+ margin-bottom: 1em;
+}
+
+p
+{
+ clear: both;
+ margin-bottom: 1em;
+}
+
+ol
+{
+ list-style: decimal;
+ margin-left: 30px;
+ margin-bottom: 1em;
+}
+
+a {color:#07c;text-decoration: none;}
+a:visited{color:#3E6D8E;text-decoration: none;}
+a:hover{text-decoration: underline;}
+a:active{text-decoration: none;}
+
+h1 {
+ font-family:Trebuchet MS,Helvetica,sans-serif;
+ font-size: 160%;
+}
+h2 {
+ font-family:Trebuchet MS,Helvetica,sans-serif;
+ font-size:140%;
+ font-weight:bold;
+ margin-bottom:7px;
+}
+h3 {display:block;font-size:120%;font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;font-weight:bold;}
+h3 a:visited{
+ color:#86B0E1;
+}
+h4 {display:block;font-size:90%; font-family:Verdana;color:#ccc;}
+
+#top{
+ width:100%;
+ border-bottom:solid 3px #ccc;
+ background-color:#eee;
+ margin:0px;
+}
+
+#container{
+ margin: 0px auto;
+ width:950px;
+ display:block;
+ text-align: left;
+}
+
+#header{
+ width:100%;
+ padding:3px 0 10px 0;
+}
+
+#header-links{
+
+}
+
+#header-links a{
+ color:darkred;
+ font-weight:400;
+ font-size:100%;
+ /*letter-spacing:1px;*/
+
+}
+
+#header-links a:hover{
+ text-decoration:none;
+}
+
+
+#content{
+ margin-top:5px;
+}
+
+#main{
+ float:left;
+ width:670px;
+}
+
+#container{
+ width:950px;
+}
+
+#main-bar{
+ border-bottom: 1px solid #666;
+ height: 34px;
+ width: 100%;
+ clear:both;
+ margin-bottom:5px;
+}
+
+#main-bar h3{
+ float: left;
+ font-size: 140%;
+ font-weight:700;
+ /*letter-spacing:3px;*/
+ margin-top:8px;
+ padding:5px 0 0 3px ;
+ height:20px;
+ width:200px;
+ color:#333;
+}
+
+#question-title{
+ float:left;
+ border-bottom: 1px solid #666;
+ width: 98%;
+ clear:both;
+ padding:13px 0px 5px 0;
+ margin-bottom:5px;
+
+}
+
+#question-title h3{
+ font-size: 130%;
+ font-weight:bold;
+ line-height:25px;
+}
+
+#question-title a{
+ color:#333;
+}
+
+#main-body{
+ float:left;
+ width:100%;
+}
+
+#main-bar .golden{
+ background:url(/content/images/bg_title_golden.gif) no-repeat;
+}
+
+#main-bar .pink{
+ background:url(/content/images/bg_title_red.gif) no-repeat;
+}
+
+#main-bar .orange{
+ background:url(/content/images/bg_title_orange.gif) no-repeat;
+}
+
+#main-bar .green{
+ background:url(/content/images/bg_title_green.gif) no-repeat;
+}
+
+#tab{
+ float:right;
+ position:relative;
+ display:block;
+ font-weight:bold;
+}
+
+#tab a
+{
+ background: #eee;
+ border: 1px solid #eee;
+ color: #777;
+ display: block;
+ float: left;
+ font-size: 90%;
+ height: 24px;
+ line-height: 22px;
+ margin: 8px 4px 0 0;
+ padding: 0 11px 0 11px;
+ text-decoration: none;
+}
+
+#tab a:hover
+{
+ background: #fff;
+ border: 1px solid #777;
+ border-bottom-color: #fff;
+ line-height: 20px;
+ margin-top: 9px;
+}
+
+#tab a.youarehere
+{
+ background: #fff;
+ color:#333;
+ border: 1px solid #777;
+ border-bottom-color: #fff;
+ font-size: 100%;
+ height: 30px;
+ line-height: 28px;
+ margin-top: 3px;
+ padding: 0px 11px 0px 11px;
+}
+
+#sidebar{
+ float:right;
+ width:260px;
+}
+
+#footer{
+ background:#777777 none repeat scroll 0 0;
+ border-top:7px solid #000000;
+ clear:both;
+ margin-top:30px;
+ overflow:hidden;
+ padding:15px;
+ text-align:center;
+}
+
+#footer a
+{
+ color: White;
+}
+
+#askform{
+ padding:5px;
+ line-height:20px;
+}
+#askform .title-desc{
+ color:#999;
+ font-size:90%;
+}
+
+#askform #description{
+ padding:20px 0 20px 0;
+}
+
+#askform .submit{
+ padding:5px 10px 5px 10px;
+ font-size:120%;
+ font-weight:bold;
+ width:120px;
+ /*letter-spacing:1px;*/
+ background-color:#D4D0C8;
+}
+.notify
+{
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ z-index: 100;
+ padding: 7px 0 5px 0;
+ text-align: center;
+ font-size: 130%;
+ font-weight: Bold;
+ color: #444;
+ background-color: #F4A83D;
+}
+
+.notify span
+{
+ float: left;
+ width: 95%;
+ text-align: center;
+}
+
+.close-notify
+{
+ float: right;
+ margin-right: 20px;
+ color: #735005;
+ text-decoration: none;
+ background-color: #FAD163;
+ border: 2px #735005 solid;
+ padding-left: 3px;
+ padding-right: 3px;
+ cursor:pointer;
+}
+.small{
+ font-size:90%;
+ color:#777;
+}
+.big{
+ font-size:120%;
+ font-weight:600;
+
+}
+
+.paragraph{
+ line-height:20px;
+ margin-bottom:10px;
+}
+
+.paragraph-extra{
+ line-height:30px;
+ margin-bottom:10px;
+}
+
+.darkred{
+ color:darkred;
+}
+
+.yellowbg{
+ background-color:yellow;
+}
+
+.header-table{
+ width:100%;
+ /*solve template header space problem*/
+ margin-top:-12px;
+}
+
+.header-table a{
+ font-weight:bold;
+}
+
+.link-separator{
+ color:#ccc;
+ padding:3px;
+}
+
+.action-link-separator{
+ color:#ccc;
+}
+
+.nav
+{
+ font-size: 120%;
+}
+
+.nav ul
+{
+ margin: 0;
+}
+
+.nav li
+{
+ background: #777;
+ display: block;
+ float: left;
+ margin-right: 7px;
+}
+
+.nav li.youarehere
+{
+ background: #f90;
+}
+
+.nav a
+{
+ color: #fff;
+ display: block;
+ font-weight: bold;
+ padding: 6px 15px 6px 15px;
+ text-decoration: none;
+}
+
+.nav li.youarehere a
+{
+ color: white;
+}
+
+.nav li:hover
+{
+ background-color:#ff9900;
+}
+
+.header-notification{
+ font-size:85%;
+ color:red;
+}
+
+.highlight-box{
+ /*letter-spacing:1px;*/
+ color:#735005;
+}
+
+.highlight-box p{
+ margin:5px 0 0px 0;
+}
+
+.highlight-box a{
+ color:#735005;
+ font-size:90%;
+ font-weight:bold;
+}
+
+.yellow-box{
+ background-color:#FFFFCC;
+ border:1px dotted #D8D2A9;
+ margin-bottom:20px;
+ line-height:20px;
+ padding:10px 10px 5px;
+
+}
+
+.yellow-box ul{
+ margin-left:0px;
+}
+
+.yellow-box h3{
+ font-size:120%;
+ padding-bottom:5px;
+ margin-bottom:10px;
+ border-bottom:1px dotted #D8D2A9;
+}
+
+.gray-box{
+ background-color:#eee;
+ border:1px dotted #D8D2A9;
+ margin-bottom:20px;
+ line-height:20px;
+ padding:10px 10px 5px;
+ overflow:hidden;
+}
+
+.gray-box ul{
+ margin-left:0px;
+}
+
+.gray-box h3{
+ font-size:120%;
+ padding-bottom:5px;
+ margin-bottom:10px;
+ border-bottom:1px dotted #D8D2A9;
+}
+
+.question-item{
+ width:100%;
+ float:left;
+ margin:5px 0 10px 0;
+ border-bottom:1px dotted #D8D2A9;
+}
+
+.boxes{
+ float:left;
+ text-align:center;
+ color:#666;
+}
+
+.boxes-cell{
+ padding-right:5px;
+ vertical-align:top;
+}
+
+.questions-summary-container{
+ float:left;
+ width:580px;
+
+}
+.questions-boxes-container{
+ float:left;
+}
+.questions-boxes{
+ float:left;
+ width: 70px;
+ text-align:center;
+ color:#666;
+
+}
+.questions-vote-number{
+ font-size:200%;
+ font-weight:bold;
+ font-family:Arial;
+}
+.questions-answer-number{
+ font-size:140%;
+ font-weight:bold;
+ font-family:Arial;
+}
+.questions-answerbox{
+ padding:5px;
+}
+
+.questions-votebox{
+ padding:5px;
+}
+.questions-viewbox{
+ padding:5px;
+}
+.qustions-summary{
+ line-height:20px;
+ margin-bottom:5px;
+}
+.question-body{
+ min-height:100px;
+}
+.question-body IMG{
+ max-width:600px;
+}
+.questions-mark{
+ float:right;
+ text-align:left;
+ font-size:90%;
+
+ line-height:20px;
+ width:160px;
+}
+
+.questions-boxicon{
+ width:15px;
+ vertical-align:top;
+}
+.questions-count{
+ font-family:arial;
+ font-size:350%;
+ font-weight:bolder;
+ margin:15px 0 15px 0;
+ color:#777;
+}
+#questions-answerbox-cell{
+ padding:5px;
+
+}
+
+.vote{
+ background-color:#eee;
+}
+
+.box-number{
+ font-size:160%;
+ font-weight:bold;
+ font-family:Arial;
+}
+
+.box-unit{
+ padding:3px;
+ font-size:90%;
+}
+
+.answer{
+ border-bottom:1px dotted #666666;
+ padding-bottom:20px;
+ padding-top:20px;
+ width: 100%;
+ margin-bottom:10px;
+}
+.answer-body{
+ min-height:80px;
+}
+
+.answer-body IMG{
+ max-width:600px;
+}
+
+.accepted-answer{
+ background-color:#E2FCE2;
+ border-bottom-color:#9BD59B;
+}
+
+.accepted-answer .comments-link{
+ background-color:#D9F5D9;
+}
+
+.accepted-answer .comments-container{
+ background-color:#D9F5D9;
+}
+
+.answered
+{
+ background: #75845C;
+ color: white;
+}
+
+.answered-accepted
+{
+ background: #75845C;
+ color: #E1E818;
+}
+
+.answered-by-owner
+{
+ background: #E0EAF1;
+}
+
+.answered-by-owner .comments-link
+{
+ background-color:#D6E4EE;
+}
+
+.answered-by-owner .comments-container
+{
+ background-color:#D6E4EE;
+}
+
+.answered-accepted strong
+{
+ color: #E1E818;
+}
+
+.unanswered
+{
+ background: #9A4444;
+ color: white;
+}
+
+.view{
+
+}
+
+.subtitle{
+ padding:15px 0 10px 0;
+}
+
+.qustion-title{
+ font-size:100%;
+ padding:0px;
+ line-height:20px;
+ margin-bottom:5px;
+}
+
+.question-tag{
+ float:left;
+ padding-bottom:3px;
+ width:250px;
+}
+
+.post-tag
+{
+ color: #3E6D8E;
+ background-color: #E0EAF1;
+ border-bottom: 1px solid #3E6D8E;
+ border-right: 1px solid #7F9FB6;
+ margin: 2px 3px 2px 0;
+ padding: 2px 4px 2px 4px;
+ text-decoration: none;
+ height:20px;
+ font-family:Microsoft Yahei,,Tahoma;
+ font-size: 90%;
+ line-height: 2.2;
+ white-space:nowrap;
+ vertical-align:baseline;
+}
+
+.post-tag:hover
+{
+ background-color: #3E6D8E;
+ color: #E0EAF1;
+ border-bottom: 1px solid #37607D;
+ border-right: 1px solid #37607D;
+ text-decoration: none;
+}
+
+.highlight-tag{
+ background-color: #3E6D8E;
+ color: #fff;
+ font-weight:bold;
+ border-bottom: 1px solid #3E6D8E;
+ border-right: 1px solid #7F9FB6;
+ margin: 2px 6px 2px 0;
+ padding: 3px 4px 3px 4px;
+ text-decoration: none;
+ font-size: 90%;
+}
+
+.question-started{
+ font-size:90%;
+ padding-top:10px;
+ float:right;
+ padding-bottom:3px;
+
+}
+
+.updatedtime{
+ font-weight:bold;
+ color:#999;
+}
+
+.badge
+{
+ color: #fff;
+ background-color: #333;
+ border: 1px solid #333;
+ margin: 0 3px 3px 0;
+ padding: 4px 8px 4px 3px;
+ color: white !important;
+ text-decoration: none;
+ line-height: 1.9;
+}
+
+.badge:hover
+{
+ border: 1px solid #555;
+ background-color: #555;
+ text-decoration: none;
+}
+
+.badge-item{
+ margin-bottom:10px;
+}
+
+.badge1
+{
+ margin-left:3px;
+ padding-right:2px;
+ font-size: 120%;
+ color: #FFCC00;
+}
+
+.badge2
+{
+ margin-left:3px;
+ padding-right:2px;
+ font-size: 120%;
+ color: #C0C0C0;
+}
+
+.badge3
+{
+ margin-left:3px;
+ padding-right:2px;
+ font-size: 120%;
+ color: #CC9966;
+}
+
+.badgecount
+{
+ color: #808185;
+}
+
+.reputation-score{
+ font-family:arial;
+ font-weight: bold;
+ color: #333;
+ font-size: 120%;
+ margin-right:2px;
+}
+
+#recent-tags .post-tag
+{
+ background-color: #eee;
+ border: none;
+ margin: 0;
+ font-size: 100%;
+ color: black !important;
+}
+
+#recent-tags .post-tag:hover
+{
+ background-color: #777;
+ color: White !important;
+ border: none;
+ margin: 0;
+ text-decoration: none;
+}
+
+#related-tags{
+ line-height:30px;
+}
+
+.list-item{
+ list-style:inside square
+
+}
+.list-item li{
+ margin-bottom:5px;
+}
+
+.list-item dt{
+ line-height:20px;
+}
+
+.list-item dd{
+ line-height:20px;
+ margin-bottom:10px;
+}
+
+#editor{
+ font-size:100%;
+ min-height:200px;
+ line-height: 18px;
+ width:595px;
+}
+
+.wmd-preview{
+ margin-top:10px;
+ padding:6px;
+ width:590px;
+ background-color:#F5F5F5;
+ min-height:20px;
+}
+
+/*adjustment for editor preview*/
+.wmd-preview pre{
+ background-color:#E7F1F8;
+
+}
+
+.wmd-preview blockquote
+{
+ background-color: #eee;
+}
+
+.wmd-preview IMG{
+ max-width:600px;
+}
+.preview-toggle{
+ font-size:90%;
+ font-weight:600;
+ margin-bottom:-10px;
+ width:600px;
+ color:#aaa;
+ /*letter-spacing:1px;*/
+
+}
+
+.preview-toggle span:hover{
+ cursor:pointer;
+}
+
+.edit-content-html{
+ border-top:1px dotted #D8D2A9;
+ border-bottom:1px dotted #D8D2A9;
+ margin:5px 0 5px 0;
+}
+
+/* 2 textarea resizer styles */
+div.grippie {
+ background:#EEEEEE url(/content/images/grippie.png) no-repeat scroll center 2px;
+ border-color:#DDDDDD;
+ border-style:solid;
+ border-width:0pt 1px 1px;
+ cursor:s-resize;
+ height:9px;
+ overflow:hidden;
+ width:600px;
+}
+.resizable-textarea textarea {
+ display:block;
+ margin-bottom:0pt;
+ width:95%;
+ height: 20%;
+}
+
+.openid-input{
+ background:url(/content/images/openid.gif) no-repeat;
+ padding-left:15px;
+ cursor:pointer;
+}
+
+.openid-login-input{
+ background-position:center left;
+ background:url(/content/images/openid.gif) no-repeat 0% 50%;
+ padding:5px 5px 5px 15px;
+ cursor:pointer;
+ font-family:Trebuchet MS;
+ font-weight:300;
+ font-size:150%;
+ width:500px;
+}
+
+.openid-login-submit{
+ padding:6px;
+ #padding:4px;
+ cursor:pointer;
+ font-weight:bold;
+ font-size:120%;
+ background-color:#D4D0C8;
+}
+
+.openid-samples{
+
+}
+
+.openid-samples .list, .list li{
+ font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
+ list-style:none !important;
+ margin-left:-30px !important;
+ line-height:20px !important;
+}
+
+.darkred{
+ color:darkred;
+}
+
+.gray{
+ color:gray;
+}
+
+.message{
+ padding:5px;
+ font-weight:bold;
+ background-color:#eee;
+ margin:10px 0 10px 0;
+}
+
+.warning{
+ color:red;
+}
+
+.vote-buttons{
+ float:left;
+ text-align:center;
+ border:solid 0px #ccc;
+}
+
+.vote-buttons img{
+ cursor:pointer;
+}
+
+.item-right{
+ float:left;
+
+}
+
+.vote-number{
+ font-family:Arial;
+ padding:0px 0 3px 0;
+ font-size:220%;
+ font-weight:bold;
+ color:#777;
+}
+.favorite-number{
+ font-size:100%;
+ font-family:Arial;
+ margin-top:-8px;
+ font-weight:bold;
+ color:#808185
+}
+
+.my-favorite-number{
+ color:#D4A849;
+}
+
+.tag-number{
+ font-size:120%;
+ font-family:Arial;
+ font-weight:bold;
+}
+
+#question-tags{
+ margin:5px 0 5px 0;
+}
+
+.question-tag-extra{
+ float:left;
+ padding-bottom:3px;
+ width:100%;
+}
+
+.question-mark{
+ background-color:#E0EAF1;
+ text-align:left;
+ font-size:90%;
+ padding:5px;
+ overflow:hidden;
+}
+
+.question-edit{
+ text-align:left;
+ font-size:90%;
+
+ overflow:hidden;
+}
+
+.question-status{
+ margin-top:10px;
+ padding: 20px;
+ background-color:#eee;
+ text-align:center;
+}
+
+.answer-mark{
+ text-align:left;
+ font-size:90%;
+ padding:5px;
+}
+
+.wiki-category{
+ margin-left:5px;
+ color:#999;
+ font-size:90%;
+}
+
+.offensive-flag a{
+ color:#777;
+ font-size:90%;
+ padding:3px;
+ cursor:pointer;
+}
+
+.offensive-flag a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+
+.linksopt a{
+ color:#777;
+ font-size:90%;
+ padding:3px;
+ cursor:pointer;
+}
+
+.linksopt a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+
+.action-link a{
+ color:#777;
+ font-size:90%;
+ padding:3px;
+ cursor:pointer;
+}
+
+.action-link a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+
+.dot-line{
+ border-bottom:1px dotted #D8D2A9;
+ width:670px; /*IE*/
+ /*
+ width:100% !important;
+ */
+ margin-bottom:20px;
+}
+
+.line{
+ border-bottom:1px solid #777;
+ width:650px; /*IE*/
+ /*
+ width:100% !important;
+ */
+ margin-bottom:20px
+}
+
+.taglist-td{
+ width:190px;
+ line-height:26px;
+}
+
+.list-table{
+ margin-left:15px;
+}
+
+.list-td{
+ width:185px;
+
+}
+
+.submit{
+ cursor:pointer;
+ #padding-bottom:0px;
+ #padding-top:0px
+ background-color:#D4D0C8;
+}
+
+form .error{
+ color:darkred;
+}
+
+form .errorlist li{
+ color: red;
+}
+
+.vote-notification
+{
+ z-index: 1;
+ cursor: pointer;
+ display: none;
+ position: absolute;
+ padding: 15px;
+ color: White;
+ background-color: #AE0000;
+}
+
+.vote-notification a
+{
+ color: White;
+ text-decoration:underline;
+}
+
+.center-container{
+ width:950px;
+}
+
+#revisions{
+ width:950px;
+}
+
+.revision{
+ margin:10px 0 10px 0;
+ width:100%;
+}
+
+.revision .header{
+ background-color:#eee;
+ padding:5px;
+ cursor:pointer;
+}
+
+.revision .author{
+ background-color:#E0EAF1;
+}
+
+.revision .summary{
+ padding: 5px 0 10px 0;
+}
+
+.revision .summary span{
+ background-color:yellow;
+ display:inline;
+}
+.revision h1{
+ font-size:130%;
+ font-weight:600;
+ padding:15px 0 15px 0;
+}
+
+.revision-mark{
+ width:200px;
+ text-align:left;
+ font-size:90%;
+ overflow:hidden;
+}
+
+.revision-number{
+ font-size:300%;
+ font-weight:bold;
+ font-family:arial;
+}
+
+.revision .body{
+ padding-left:10px;
+ margin-bottom:50px;
+}
+.revision .answerbody{
+ padding:10px 0 5px 10px;
+}
+
+/* Revision pages */
+del { color: #FF5F5F; }
+del .post-tag{
+color: #FF5F5F;
+};
+ins { background-color: #97ff97;}
+ins .post-tag{
+background-color: #97ff97;
+};
+
+/** PAGINATOR **/
+.paginator {
+ padding:5px 0 10px 0;
+ font:normal .8em arial;
+}
+
+.paginator .prev-na,
+.paginator .next-na {
+ padding:.3em;
+ font:bold .875em arial;
+}
+
+.paginator .prev-na,
+.paginator .next-na {
+ border:1px solid #ccc;
+ background-color:#f9f9f9;
+ color:#aaa;
+ font-weight:normal;
+}
+
+.paginator .prev a, .paginator .prev a:visited,
+.paginator .next a, .paginator .next a:visited {
+ border:1px solid #fff;
+ background-color:#fff;
+ color:#777;
+ padding:.3em;
+ font:bold 100% arial;
+}
+
+.paginator .prev, .paginator .prev-na { margin-right:.5em; }
+.paginator .next, .paginator .next-na { margin-left:.5em; }
+
+.paginator .page a, .paginator .page a:visited, .paginator .curr {
+ padding:.25em;
+ font:normal .875em verdana;
+ border:1px solid #ccc;
+ background-color:#fff;
+ margin:0em .25em;
+ color:#777;
+}
+
+.paginator .curr {
+ background-color:#777;
+ color:#fff;
+ border:1px solid #777;
+ font-weight:bold;
+ font-size:1em;
+}
+
+.paginator .page a:hover,
+.paginator .curr a:hover,
+.paginator .prev a:hover,
+.paginator .next a:hover {
+ color:#fff;
+ background-color:#777;
+ border:1px solid #777;
+ text-decoration:none;
+}
+
+.paginator .text{
+ color:#777;
+ padding:.3em;
+ font:bold 100% arial;
+}
+
+.paginator-container{
+ float:right;
+ padding:10px 0 10px 0;
+}
+
+.paginator-container-left{
+ padding:5px 0 10px 0;
+}
+
+.user-info {
+ clear:both;
+ height:45px;
+}
+
+.user-info .user-action-time {
+ margin-bottom:4px;
+ margin-top:2px;
+}
+
+.user-info .user-details {
+ float:left;
+ margin-left:5px;
+ overflow:hidden;
+ white-space:nowrap;
+ width:138px;
+}
+.user-details {
+ color:#888888;
+ line-height:17px;
+}
+
+table.user-details td {
+ color:Black;
+ padding:4px 0;
+}
+
+.user-info .user-gravatar32 {
+ float:left;
+ width:32px;
+}
+
+#mainbar-full {
+ width:950px;
+}
+
+#subheader {
+ border-bottom:1px solid #666666;
+ clear:both;
+ height:34px;
+ width:100%;
+}
+
+#tabs {
+ float:right;
+ font-weight:bold;
+ position:relative;
+}
+
+.summaryinfo {
+ color:#808185;
+ text-align:center;
+}
+
+.summarycount {
+ font-family:arial;
+ color:#808185;
+ font-size:350%;
+ font-weight:bold;
+ text-align:center;
+}
+.summarysubcount {
+ font-family:arial;
+ color:#808185;
+ font-size:250%;
+ font-weight:bold;
+ text-align:center;
+}
+
+
+#user-about-me {
+ background-color:#EEEEEE;
+ height:200px;
+ line-height:20px;
+ margin:10px;
+ overflow:auto;
+ padding:10px;
+ width:90%;
+}
+
+.favorites-count-off {
+ color:#919191;
+ float:left;
+ margin-top:13px;
+ text-align:center;
+}
+
+.favorites-count {
+ color:#D4A849;
+ float:left;
+ margin-top:13px;
+ text-align:center;
+}
+
+.question-summary {
+ border-bottom:1px dotted #999999;
+ float:left;
+ overflow:hidden;
+ padding:11px 0;
+ width:670px;
+}
+
+.user-stats-table .question-summary {
+ width:800px;
+}
+
+.narrow .stats {
+ background:transparent none repeat scroll 0 0;
+ float:left;
+ height:48px;
+ margin:0 0 0 7px;
+ padding:0;
+ width:auto;
+}
+
+.narrow .votes {
+ background:#EEEEEE none repeat scroll 0 0;
+ float:left;
+ height:38px;
+ margin:0 3px 0 0;
+ padding:5px;
+ width:38px;
+}
+
+.narrow .views {
+ float:left;
+ height:38px;
+ margin:0 7px 0 0;
+ padding:5px 0 5px 4px;
+ width:40px;
+}
+
+.narrow .status {
+ float:left;
+ height:38px;
+ margin:0 3px 0 0;
+ padding:5px;
+ width:48px;
+}
+
+.narrow .vote-count-post {
+ font-size:100%;
+ margin:0;
+}
+
+div.votebox {
+ text-align:center;
+ vertical-align:middle;
+}
+
+div.votebox span {
+ color:#808185;
+ display:block;
+ font-weight:bold;
+}
+.vote-count-post {
+ display:block;
+ font-size:240%;
+}
+
+.answered-accepted {
+ background:#75845C none repeat scroll 0 0;
+ color:#E1E818;
+}
+
+.status {
+ margin-top:-3px;
+ padding:4px 0;
+ text-align:center;
+}
+
+.views {
+ color:#999999;
+ padding-top:4px;
+ text-align:center;
+}
+
+.user-stats-table .summary {
+ width:600px;
+ line-height:22px;
+}
+
+.user-stats-table .summary h3{
+ margin:0px;
+ padding:0px;
+}
+
+div.tags {
+ float:left;
+ line-height:18px;
+}
+
+div.started {
+ color:#999999;
+ float:right;
+ line-height:18px;
+ width:185px;
+}
+
+.narrow div.started {
+ line-height:inherit;
+ padding-top:4px;
+ white-space:nowrap;
+ width:auto;
+}
+
+.relativetime {
+ font-weight:bold;
+ text-decoration:none;
+}
+
+div.started a {
+ font-weight:bold;
+}
+
+div.started .reputation-score {
+ margin-left:1px;
+}
+
+.answer-summary {
+ clear:both;
+ padding:3px;
+}
+
+.answer-votes {
+ background-color:#EEEEEE;
+ color:#555555;
+ float:left;
+ font-size:110%;
+ font-weight:bold;
+ height:15px;
+ padding:4px 4px 5px;
+ text-align:center;
+ text-decoration:none;
+ width:20px;
+}
+
+.answered-accepted {
+ background:#75845C none repeat scroll 0 0;
+ color:#E1E818;
+}
+
+.answer-link {
+ color:#555555;
+ float:left;
+ margin-top:5px;
+ padding-left:10px;
+ width:700px;
+}
+
+.answer-link a {
+ font-weight:bold;
+}
+
+.deleted{
+ background:#F4E7E7 none repeat scroll 0 0;
+}
+
+#mainbar-footer {
+ clear:both;
+ width:100%;
+}
+
+#feed-link {
+ float:right;
+ text-align:right;
+}
+
+#feed-link-image {
+ float:left;
+ margin-left:12px;
+ margin-right:5px;
+}
+
+#feed-link-text {
+ float:left;
+}
+
+.revision-comment {
+ background-color:#FFFE9B;
+ color:#333333;
+ line-height:1.4;
+ padding:3px;
+}
+
+.accept-answer-link {
+ color:#508850 !important;
+ font-weight:bold;
+}
+
+.accept-answer-link:hover {
+ background-color:#508850 !important;
+ color:#E1E818 !important;
+ text-decoration:none;
+}
+
+div.comments {
+ line-height:150%;
+ padding:10px 0;
+}
+
+div.post-comments{
+ width:585px
+}
+
+form.post-comments textarea {
+ height:6em;
+ margin-bottom:4px;
+}
+
+form.post-comments input {
+ margin-left:10px;
+ margin-top:1px;
+ vertical-align:top;
+ width:100px;
+}
+
+span.text-counter {
+ margin-right:20px;
+}
+
+span.form-error {
+ color:#990000;
+ font-size:90%;
+ font-weight:normal;
+ margin-left:5px;
+}
+
+div.comments-container, div.comments-container-accepted, div.comments-container-owner, div.comments-container-deleted {
+ display:none;
+ margin-top:-1px;
+ padding:0 5px 5px;
+}
+
+div.comments-container, a.comments-link {
+ background-color:#EEEEEE;
+}
+
+.post-comments a {
+ color:#888888;
+ padding:0 3px 2px;
+ font-size:90%;
+}
+
+a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-link-deleted {
+ color:black;
+ padding:2px;
+ cursor:pointer;
+}
+
+.post-comments a:hover {
+ background-color:#777777;
+ color:white;
+ text-decoration:none;
+}
+
+a.comment-user, a.comment-user:hover {
+ background-color:inherit;
+ color:#0077CC;
+ padding:0;
+}
+
+a.comment-user:hover {
+ text-decoration:underline;
+}
+
+.user-action{
+
+}
+.user-action-1{
+ font-weight:bold;
+ color:#333;
+}
+.user-action-2{
+ font-weight:bold;
+ color:#75845C;
+}
+.user-action-3{
+ color:#333;
+}
+.user-action-4{
+ color:#333;
+}
+.user-action-5{
+ color:darkred;
+}
+.user-action-6{
+ color:darkred;
+}
+.user-action-7{
+ color:#333;
+}
+.user-action-8{
+ padding:3px;
+ font-weight:bold;
+ background-color:#75845C;
+ color:#E1E818;
+}
+
+.revision-summary{
+ background-color:#FFFE9B;
+ padding:2px;
+}
+.question-title-link a{
+ font-weight:bold;
+ color:#0077CC;
+}
+.answer-title-link a{
+ color:#333;
+}
+
+.post-type-1 a {
+ font-weight:bold;
+ color:#0077CC;
+}
+.post-type-3 a {
+ font-weight:bold;
+ color:#0077CC;
+}
+.post-type-5 a {
+ font-weight:bold;
+ color:#0077CC;
+}
+.post-type-2 a{
+ color:#333;
+}
+.post-type-4 a{
+ color:#333;
+}
+.post-type-6 a{
+ color:#333;
+}
+.post-type-8 a{
+ color:#333;
+}
+
+/* openid styles */
+.form-row{line-height:25px;}
+.submit-row{line-height:30px;padding-top:10px;}
+.errors{line-height:20px;color:red;}
+.error{color:red;}
+.error-list li{padding:5px;}
+.login{margin-bottom:10px;}
+.fieldset{border:solid 1px #777;margin-top:10px;padding:10px;}
diff --git a/templates/content/style/jquery.autocomplete.css b/templates/content/style/jquery.autocomplete.css
new file mode 100644
index 00000000..7c3127d1
--- /dev/null
+++ b/templates/content/style/jquery.autocomplete.css
@@ -0,0 +1,49 @@
+.ac_results {
+ padding: 0px;
+ border: 1px solid black;
+ background-color: white;
+ overflow: hidden;
+ z-index: 99999;
+ text-align:left;
+}
+
+.ac_results ul {
+ width: 100%;
+ list-style-position: outside;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ac_results li {
+ margin: 0px;
+ padding: 2px 5px;
+ cursor: default;
+ display: block;
+ /*
+ if width will be 100% horizontal scrollbar will apear
+ when scroll mode will be used
+ */
+ /*width: 100%;*/
+ font: menu;
+ font-size: 12px;
+ /*
+ it is very important, if line-height not setted or setted
+ in relative units scroll will be broken in firefox
+ */
+ line-height: 16px;
+ overflow: hidden;
+}
+
+.ac_loading {
+ background: white url('/content/images/indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+ background-color: #E7F1F8;
+}
+
+.ac_over {
+ background-color: #0A246A;
+ color: white;
+}
diff --git a/templates/content/style/openid.css b/templates/content/style/openid.css
new file mode 100644
index 00000000..0d201df2
--- /dev/null
+++ b/templates/content/style/openid.css
@@ -0,0 +1,45 @@
+#openid_form {
+ width: 470px;
+}
+ #openid_form legend {
+ font-weight: bold;
+ }
+#openid_choice {
+ display: none;
+}
+#openid_input_area {
+ clear: both;
+ padding: 10px;
+}
+#openid_btns, #openid_btns br {
+ clear: both;
+}
+ #openid_highlight {
+ padding: 3px;
+ background-color: #FFFCC9;
+ float: left;
+ }
+ .openid_large_btn {
+ width: 100px;
+ height: 60px;
+ border: 1px solid #DDD;
+ margin: 3px;
+ float: left;
+ }
+ .openid_small_btn {
+ width: 24px;
+ height: 24px;
+ border: 1px solid #DDD;
+ margin: 3px;
+ float: left;
+ }
+ a.openid_large_btn:focus {
+ outline: none;
+ }
+ a.openid_large_btn:focus
+ {
+ -moz-outline-style: none;
+ }
+ .openid_selected {
+ border: 4px solid #DDD;
+ }
diff --git a/templates/content/style/prettify.css b/templates/content/style/prettify.css
new file mode 100644
index 00000000..10a37577
--- /dev/null
+++ b/templates/content/style/prettify.css
@@ -0,0 +1,27 @@
+/* Pretty printing styles. Used with prettify.js. */
+
+.str { color: #080; }
+.kwd { color: #008; }
+.com { color: #800; }
+.typ { color: #606; }
+.lit { color: #066; }
+.pun { color: #660; }
+.pln { color: #000; }
+.tag { color: #008; }
+.atn { color: #606; }
+.atv { color: #080; }
+.dec { color: #606; }
+pre.prettyprint { padding: 3px; border: 0px solid #888; }
+
+@media print {
+ .str { color: #060; }
+ .kwd { color: #006; font-weight: bold; }
+ .com { color: #600; font-style: italic; }
+ .typ { color: #404; font-weight: bold; }
+ .lit { color: #044; }
+ .pun { color: #440; }
+ .pln { color: #000; }
+ .tag { color: #006; font-weight: bold; }
+ .atn { color: #404; }
+ .atv { color: #060; }
+}
diff --git a/templates/content/style/style.css b/templates/content/style/style.css
new file mode 100644
index 00000000..165903ba
--- /dev/null
+++ b/templates/content/style/style.css
@@ -0,0 +1,1044 @@
+@import url(/content/style/jquery.autocomplete.css);
+@import url(/content/style/openid.css);
+@import url(/content/style/prettify.css);
+/* 公用 */
+body{background:#FFF; font-size:12px; line-height:150%; margin:0; padding:0; color:#000; font-family: "segoe ui",Helvetica,微软雅黑,宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
+}
+div{margin:0 auto; padding:0;}
+h1,h2,h3,h4,h5,h6,ul,li,dl,dt,dd,form,img,p{margin:0; padding:0; border:none; }
+input, select {font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;}
+p{margin-bottom:4px; font-size:13px; line-height:160%;}
+a {color:#333333; text-decoration:none;}
+a:hover {text-decoration:underline;}
+.block{width:960px; height:auto;}
+.fleft{float:left;}
+.fright{float:right;}
+.tleft{text-align:left;}
+.tcenter{text-align:center;}
+.tright{text-align:right;}
+.dis{display:block;}
+.inline{display:inline;}
+.none{display:none;}
+.red{color:#CC0000;}
+.b{font-weight:bold;}
+.f10{font-size:10px;}
+.f11{font-size:11px;}
+.f12{font-size:12px;}
+.f13{font-size:13px;}
+.f14{font-size:14px;}
+.white{color:#FFFFFF;}
+.u{text-decoration:underline;}
+.spacer1{height:6px; line-height:6px; clear:both; visibility:hidden;}
+.spacer3{height:30px; line-height:30px; clear:both; visibility:hidden;}
+h1{font-size:160%;padding:5px 0 5px 0;}
+h2{font-size:140%;padding:3px 0 3px 0;}
+h3{font-size:120%;padding:3px 0 3px 0;}
+ul
+{
+ list-style: disc;
+ margin-left: 20px;
+ padding-left:0px;
+ margin-bottom: 1em;
+}
+ol
+{
+ list-style: decimal;
+ margin-left: 30px;
+ margin-bottom: 1em;
+ padding-left:0px;
+}
+pre
+{
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size:100%;
+ margin-bottom: 10px;
+ overflow: auto;
+ width: 580px;
+ background-color: #F5F5F5;
+ padding-left:5px;
+ padding-top:5px;
+ padding-bottom: 20px !ie7;
+}
+
+code{
+ font-family: Consolas, Monaco, Liberation Mono, Lucida Console, Monospace;
+ font-size:100%;
+
+}
+
+blockquote
+{
+ margin-bottom: 10px;
+ margin-right: 15px;
+ padding: 10px 0px 1px 10px;
+ background-color: #F5F5F5;
+}
+
+/*页面布局*/
+#wrapper {width:960px;margin:auto;padding:0;}
+#roof {margin-top:0px;background:#FFF;}
+#room {padding-top:10px;background-color:#FFF;border-bottom:1px solid #777;}
+#CALeft{width:700px; float:left; position:relative;padding-left:5px}
+#CARight{width:240px; float:right; padding-right:5px}
+#CAFull{float:left;padding:0 5px 0 5px;width:950px;}
+#ground {width:100%;border-top:1px solid #000; padding-top:6px; padding-bottom:10px; text-align:center;background:#777;}
+/*#licenseLogo {top:10px;right:10px;}*/
+
+/*顶部及导航栏*/
+#top {height:20px; text-align:right; padding:3px;background-color:#ffffff;}
+#header {width:960px;}
+#top a {height:35px; text-align:right;
+ /*letter-spacing:1px; */
+ margin-left:20px;text-decoration:underline; font-size:12px; color:#333333;}
+#logo {padding:5px;}
+#navBar {float:clear;position:relative;display:block;width:960px;}
+#navBar .nav {margin:20px 0px 0px 16px;
+ /*letter-spacing:1px; */
+ }
+#navBar .nav a {color:#333; background-color:#F9F7ED;
+ border-bottom: none;
+ border-left: 1px solid #aaaaaa;
+ border-right: 1px solid #aaaaaa;
+ border-top: 1px solid #aaaaaa;
+ padding:0px 12px 3px 12px; height:25px; line-height:30px;margin-left:10px; font-size:15px; font-weight:400; text-decoration:none;display: block;float: left;}
+#navBar .nav a:hover {text-decoration:underline}
+#navBar .nav a.on {height:24px;line-height:28px;
+ border-top:1px solid #e66222;
+ border-bottom: 1px solid #d64000;
+ border-right:1px solid #e66222;
+ border-left:1px solid #e66222;
+ /*background:#A31E39; */
+ background:#d64000;
+ color:#FFF; font-weight:600; text-decoration:none}
+#navBar .nav a.special {font-size:15px; color:#B02B2C; font-weight:bold; text-decoration:none; }
+#navBar .nav a.special:hover {text-decoration:underline;}
+#navBar .nav div.focus {float:right; padding-right:0px;}
+/*搜索栏*/
+#searchBar {background-color:#9db2b1;padding:5px;} /* #B02B2C */
+#searchBar .content { }
+#searchBar .searchInput {font-size:13px; height:18px; width:400px;}
+#searchBar .searchBtn {font-size:14px; height:26px; width:80px;}
+#searchBar .options {padding-top:5px; font-size:100%;color:#EEE;
+ /*letter-spacing:1px;*/
+ }
+#searchBar .options INPUT {margin-left:15px;}
+#searchBar .options INPUT:hover {cursor:pointer}
+
+/*问题列表*/
+#listA {float:left; background-color:#FFF;padding:0 0px 0 0px; width:100%;}
+#listA .qstA {padding:3px 5px 0 5px; margin:0 0px 10px 0px; background:url(/content/images/quest-bg.gif) repeat-x top;}
+#listA .qstA thumb {float:left; }
+#listA .qstA H2 {font-size:15px; font-weight:800; margin:8px auto;padding:0px;}
+#listA .qstA H2 a {color:#663333; }
+#listA .qstA .stat {font-size:13px;
+ /*letter-spacing:1px;*/
+ float:right;}
+#listA .qstA .stat span {margin-right:5px;}
+#listA .qstA .stat td {min-width:40px;text-align:center;}
+#listA .qstA .stat .num {font-family:arial;color:#905213; font-size:20px; font-weight:800;}
+#listA .qstA .stat .unit {color:#777;}
+#listA .qstA .from {margin-top:5px; font-size:13px;}
+#listA .qstA .from .score {font-family:arial;}
+#listA .qstA .date {margin-left:20px; color:#777;}
+#listA .qstA .wiki {color:#663333;font-size:12px;}
+#listA .qstA .from a {}
+#listA .qstA .from IMG {vertical-align:middle;}
+#listA .qstA .author {font-weight:400; }
+#listA .qstA .author a{ }
+#listA .qstA .summary{margin-right:5px;}
+.evenMore {font-size:14px; font-weight:800;}
+.questions-count{font-size:32px;font-family:arial;font-weight:600;padding:5px;color:#777;}
+
+/*内容块*/
+.boxA {background:#777; padding:6px; margin-bottom:8px;}
+.boxA H3 {font-size:13px; font-weight:800; color:#FFF; margin:0; padding:0; margin-bottom:4px;}
+.boxA .body {border:1px solid #999; padding:8px; background:#FFF; font-size:13px;}
+.boxA .more {padding:2px; text-align:right; font-weight:800;}
+.boxB {background:#F9F7CD; padding:6px; margin-bottom:8px;}
+.boxB H3 {font-size:13px; font-weight:800; color:#000; margin:0; padding:0 0 0 15px; margin-bottom:4px; background:url(/content/images/dot-g.gif) no-repeat left center;}
+.boxB .body {border:1px solid #FFFF88; padding:8px; background:#FFF; font-size:13px; line-height:160%;}
+.boxB .more {padding:1px; text-align:right; font-weight:800;}
+.boxC {background:#F5F5F5; padding:6px; margin-bottom:8px;}
+/*分页*/
+.pager {margin-top:10px; margin-bottom:16px; float:left;}
+.pagesize {margin-top:10px; margin-bottom:16px; float:right;}
+
+/** PAGINATOR **/
+.paginator {
+ padding:5px 0 10px 0;
+ font:normal 12px arial;
+}
+
+.paginator .prev-na,
+.paginator .next-na {
+ padding:.3em;
+ font:bold .875em arial;
+}
+
+.paginator .prev-na,
+.paginator .next-na {
+ border:1px solid #ccc;
+ background-color:#f9f9f9;
+ color:#aaa;
+ font-weight:normal;
+}
+
+.paginator .prev a, .paginator .prev a:visited,
+.paginator .next a, .paginator .next a:visited {
+ border:1px solid #fff;
+ background-color:#fff;
+ color:#777;
+ padding:.3em;
+ font:bold 100% arial;
+}
+
+.paginator .prev, .paginator .prev-na { margin-right:.5em; }
+.paginator .next, .paginator .next-na { margin-left:.5em; }
+
+.paginator .page a, .paginator .page a:visited, .paginator .curr {
+ padding:.25em;
+ font:normal .875em verdana;
+ border:1px solid #ccc;
+ background-color:#fff;
+ margin:0em .25em;
+ color:#777;
+}
+
+.paginator .curr {
+ background-color:#777;
+ color:#fff;
+ border:1px solid #777;
+ font-weight:bold;
+}
+
+.paginator .page a:hover,
+.paginator .curr a:hover,
+.paginator .prev a:hover,
+.paginator .next a:hover {
+ color:#fff;
+ background-color:#777;
+ border:1px solid #777;
+ text-decoration:none;
+}
+
+.paginator .text{
+ color:#777;
+ padding:.3em;
+ font:bold 100% arial;
+}
+
+.paginator-container{
+ float:right;
+ padding:10px 0 10px 0;
+}
+
+.paginator-container-left{
+ padding:5px 0 10px 0;
+}
+
+/*标签*/
+.tag {font-size:100%; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
+.tags {font-family:arial; line-height:200%; display:block; margin-top:5px;}
+.tags a {font-size:100%; font-weight:normal; color:#333; text-decoration:none;background-color:#EEE; border-left:3px solid #777; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:1px 8px 1px 8px;}
+.tags a:hover {background-color:#fFF;color:#333;}
+.tag-number {font-weight:700;font-family:arial;}
+
+/*奖牌*/
+a.medal { font-size:14px; line-height:250%; font-weight:800; color:#333; text-decoration:none; background:url(/content/images/medala.gif) no-repeat; border-left:1px solid #EEE; border-top:1px solid #EEE; border-bottom:1px solid #CCC; border-right:1px solid #CCC; padding:4px 12px 4px 6px;}
+a:hover.medal {color:#333; text-decoration:none; background:url(/content/images/medala_on.gif) no-repeat; border-left:1px solid #E7E296; border-top:1px solid #E7E296; border-bottom:1px solid #D1CA3D; border-right:1px solid #D1CA3D;}
+
+/*Tab栏*/
+.tabBar{background-color:#FFF;border-bottom: 1px solid #666;height: 30px; width: 100%;clear:both; margin-bottom:3px;}
+.tabsA {background-color:#FFF;float:right;position:relative;display:block;font-weight:bold;height:20px;}
+.tabsB {background-color:#FFF;float:left;position:relative;display:block;font-weight:bold;height:20px;}
+.tabsA a.on, .tabsA a.on:hover,.tabsB a.on, .tabsB a.on:hover {background: #fff;
+ color:#333;
+ border: 1px solid #777;
+ border-bottom:2px solid #FFF;
+ height: 25px;
+ line-height: 26px;
+ margin-top: 3px;
+ padding: 0px 11px 0px 11px;}
+
+.tabsA a {background: #eee;
+ border: 1px solid #eee;
+ color: #777;
+ display: block;
+ float: left;
+ height: 22px;
+ line-height: 28px;
+ margin: 5px 4px 0 0;
+ padding: 0 11px 0 11px;
+ text-decoration: none;
+}
+.tabsB a {background: #eee;
+ border: 1px solid #eee;
+ color: #777;
+ display: block;
+ float: left;
+ height: 22px;
+ line-height: 28px;
+ margin: 5px 0px 0 4px;
+ padding: 0 11px 0 11px;
+ text-decoration: none;
+}
+.tabsA a:hover, .tabsB a:hover {background: #fff;border: 1px solid #777;border-bottom:3px solid #FFF;}
+.headlineA {font-size:13px; border-bottom:1px solid #777; padding-bottom:2px; font-weight:800; margin-bottom:12px; text-align:right; height:30px;}
+.headQuestions {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
+.headAnswers {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_answers.gif) left 2px no-repeat; padding-left:24px;}
+.headTags {float:left; padding:3px; font-size:18px; font-weight:800; background:url(/content/images/ico_tags.gif) no-repeat; padding-left:24px;}
+.headUsers {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
+.headMedals {float:left; height:23px; line-height:23px; margin:5px 0 0 5px;padding:0px 6px 0px 15px; font-size:15px; font-weight:700; border-bottom:0px solid #777; border-left:0px solid #darkred; background-color:#FFF;background:url(/content/images/dot-list.gif) no-repeat left center;}
+.headLogin {float:left; padding:3px; font-size:15px; font-weight:800; background:url(/content/images/ico_login.gif) no-repeat; padding-left:24px;}
+.headNormal {text-align:left;padding:3px; font-size:15px; margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
+.headUser {text-align:left;padding:5px; font-size:20px;
+ /*letter-spacing:1px;*/
+ margin-bottom:12px; font-weight:800;border-bottom:1px solid #777;}
+/*RSS订阅*/
+#feeds {margin:10px 0; }
+#feeds a {background:url(/content/images/feed-icon-small.png) no-repeat 0; padding-left:18px; font-weight:700; font-size:13px; }
+
+/*问题*/
+#question {margin-bottom:30px;}
+#question h1{font-size:15px;background:#CCC; padding:6px 8px;;}
+#question .body{background:#F7F7F7; padding:20px 10px;}
+.starter {padding:10px; background:#E0EAF1;}
+.vote {font-size:20px; color:#666; font-weight:800;}
+.questions-related{font-weight:700;word-wrap:break-word;}
+.questions-related p{line-height:20px; margin-bottom:10px;font-size:100%;}
+.question-status{
+ margin-top:10px;
+ padding: 20px;
+ background-color:#F5F5F5;
+ text-align:center;
+}
+.question-status h3{font-size:125%;}
+.question-body{
+ min-height:100px;
+ font-size:13px;
+ line-height:20px;
+}
+.question-body IMG{
+ max-width:600px;
+}
+.question-mark{
+ background-color:#E9E9FF;
+ text-align:left;
+ padding:5px;
+ overflow:hidden;
+}
+.question-edit{
+ text-align:left;
+ overflow:hidden;
+}
+.vote-buttons {float:left;text-align:center;}
+.vote-buttons IMG{cursor:pointer;}
+.vote-number{
+ font-family:Arial;
+ padding:0px 0 3px 0;
+ font-size:140%;
+ font-weight:bold;
+ color:#777;
+}
+.question-img-upvote:hover{background:url(/content/images/vote-arrow-up-on.png)}
+.question-img-downvote:hover{background:url(/content/images/vote-arrow-down-on.png)}
+.question-img-favorite:hover{background:url(/content/images/vote-favorite-on.png)}
+.favorite-number{padding:0px;font-size:100%; font-family:Arial;font-weight:bold;color:#777;}
+.vote-notification
+{
+ z-index: 1;
+ cursor: pointer;
+ display: none;
+ position: absolute;
+ padding: 15px;
+ color: White;
+ background-color: darkred;
+ text-align: center;
+}
+.vote-notification a
+{
+ color: White;
+ text-decoration:underline;
+}
+.offensive-flag a{
+ color:#777;
+ padding:3px;
+ cursor:pointer;
+}
+
+.offensive-flag a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+
+.linksopt a{
+ color:#777;
+ padding:3px;
+ cursor:pointer;
+}
+
+.linksopt a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+
+.action-link a{
+ color:#777;
+ padding:3px;
+ cursor:pointer;
+}
+
+.action-link a:hover{
+ background-color:#777;
+ text-decoration:none;
+ color:#fff;
+}
+.action-link-separator{
+ color:#ccc;
+}
+.wiki-category{
+ margin-left:5px;
+ color:#999;
+ font-size:90%;
+}
+
+div.comments {
+ line-height:150%;
+ padding:10px 0;
+}
+
+div.post-comments{
+ width:100%;
+}
+
+form.post-comments textarea {
+ height:6em;
+ margin-bottom:4px;
+}
+
+form.post-comments input {
+ margin-left:10px;
+ margin-top:1px;
+ vertical-align:top;
+ width:100px;
+}
+span.text-counter {
+ margin-right:20px;
+}
+
+span.form-error {
+ color:#990000;
+ font-weight:normal;
+ margin-left:5px;
+}
+
+div.comments-container, div.comments-container-accepted, div.comments-container-owner, div.comments-container-deleted {
+ display:none;
+ margin-top:-1px;
+ padding:0 5px 5px;
+}
+
+div.comments-container, a.comments-link {
+ background-color:#EEEEEE;
+}
+
+.post-comments a {
+ color:#888888;
+ padding:0 3px 2px;
+}
+
+a.comments-link, a.comments-link-accepted, a.comments-link-owner, a.comments-link-deleted {
+ color:black;
+ padding:2px;
+ cursor:pointer;
+}
+
+.post-comments a:hover {
+ background-color:#777777;
+ color:white;
+ text-decoration:none;
+}
+
+a.comment-user, a.comment-user:hover {
+ background-color:inherit;
+ color:#0077CC;
+ padding:0;
+}
+
+a.comment-user:hover {
+ text-decoration:underline;
+}
+/*回答*/
+#answers {}
+.answer{
+ border-bottom:1px dotted #666666;
+ padding-bottom:20px;
+ padding-top:20px;
+ width: 100%;
+ margin-bottom:10px;
+}
+.answer-body{
+ min-height:80px;
+ font-size:13px;
+ line-height:20px;
+}
+
+.answer-body IMG{
+ max-width:600px;
+}
+
+.accepted-answer{
+ background-color:#EBFFE6;
+ border-bottom-color:#9BD59B;
+}
+
+.accepted-answer .comments-link{
+ background-color:#CCFFBF;
+}
+
+.accepted-answer .comments-container{
+ background-color:#CCFFBF;
+}
+
+.answered
+{
+ background: #CCC;
+ color: #999;
+}
+
+.answered-accepted
+{
+ background: #CCC;
+ color: #663333;
+}
+
+.unanswered
+{
+ background: #777;
+ color: white;
+}
+
+.answered-by-owner
+{
+ background: #E9E9FF;
+}
+
+.answered-by-owner .comments-link
+{
+ background-color:#E6ECFF;
+}
+
+.answered-by-owner .comments-container
+{
+ background-color:#E6ECFF;
+}
+
+.answered-accepted strong
+{
+ color: #E1E818;
+}
+
+.answer-img-accept:hover{background:url(/content/images/vote-accepted-on.png)}
+
+.deleted{
+ background:#F4E7E7 none repeat scroll 0 0;
+}
+
+/*标签列表*/
+.tagsbox {}
+.tagsbox a {color:#000;line-height:30px;margin-right:10px;font-size:100%;background-color:#F9F7ED;padding:3px;}
+.tagsbox a:hover {text-decoration:none;background-color:#F9F7CD;color:#B02B2C;}
+.tagsList {margin:0; list-style-type:none;padding:0px;min-height:360px;}
+.tagsList li {width:235px; float:left;}
+.badge-list{margin:0; list-style-type:none;}
+/*登录*/
+.list-item{margin-left:15px;}
+.list-item LI{list-style-type:disc; font-size:13px; line-height:20px; margin-bottom:10px;}
+/* openid styles */
+.form-row{line-height:25px;}
+.submit-row{line-height:30px;padding-top:10px;}
+.errors{line-height:20px;color:red;}
+.error{color:red;}
+.error-list li{padding:5px;}
+.login{margin-bottom:10px;}
+.fieldset{border:solid 1px #777;margin-top:10px;padding:10px;}
+.openid-input{background:url(/content/images/openid.gif) no-repeat;padding-left:15px;cursor:pointer;}
+.openid-login-input{
+ background-position:center left;
+ background:url(/content/images/openid.gif) no-repeat 0% 50%;
+ padding:5px 5px 5px 15px;
+ cursor:pointer;
+ font-family:Trebuchet MS;
+ font-weight:300;
+ font-size:150%;
+ width:500px;
+}
+
+.openid-login-submit{
+ height:40px;
+ width:80px;
+ line-height:40px;
+ cursor:pointer;
+ border:1px solid #777;
+ font-weight:bold;
+ font-size:120%;
+}
+
+.openid-samples{
+
+}
+
+.openid-samples .list, .list li{
+ font-family:Trebuchet MS,"segoe ui",Helvetica,"Microsoft YaHei",宋体,Tahoma,Verdana,MingLiu,PMingLiu,Arial,sans-serif;
+ list-style:none !important;
+ margin-left:-30px !important;
+ line-height:20px !important;
+}
+
+/*表单相关*/
+span.form-error {
+ color:#990000;
+ font-size:90%;
+ font-weight:normal;
+ margin-left:5px;
+}
+.title-desc{
+ color:#999;
+ font-size:90%;
+}
+
+/*adjustment for editor preview*/
+#editor{
+ font-size:100%;
+ min-height:200px;
+ line-height: 18px;
+ width:100%;
+}
+
+.wmd-preview{
+ margin-top:10px;
+ padding:6px;
+ width:100%;
+ background-color:#F5F5F5;
+ min-height:20px;
+}
+.wmd-preview pre{
+ background-color:#E7F1F8;
+
+}
+
+.wmd-preview blockquote
+{
+ background-color: #eee;
+}
+
+.wmd-preview IMG{
+ max-width:600px;
+}
+.preview-toggle{
+ font-weight:600;
+ width:100%;
+ color:#aaa;
+ /*letter-spacing:1px;*/
+ text-align:left;
+}
+
+.preview-toggle span:hover{
+ cursor:pointer;
+}
+
+.edit-content-html{
+ border-top:1px dotted #D8D2A9;
+ border-bottom:1px dotted #D8D2A9;
+ margin:5px 0 5px 0;
+}
+
+/*修订记录*/
+
+#revisions{
+ width:950px;
+}
+
+.revision{
+ margin:10px 0 10px 0;
+ width:100%;
+ font-size:13px;
+}
+
+.revision .header{
+ background-color:#eee;
+ padding:5px;
+ cursor:pointer;
+}
+
+.revision .author{
+ background-color:#E9E9FF;
+}
+
+.revision .summary{
+ padding: 5px 0 10px 0;
+}
+
+.revision .summary span{
+ background-color:yellow;
+ display:inline;
+}
+.revision h1{
+ font-size:130%;
+ font-weight:600;
+ padding:15px 0 15px 0;
+}
+
+.revision-mark{
+ width:200px;
+ text-align:left;
+ font-size:90%;
+ overflow:hidden;
+}
+
+.revision-number{
+ font-size:300%;
+ font-weight:bold;
+ font-family:arial;
+}
+
+.revision .body{
+ padding-left:10px;
+ margin-bottom:50px;
+}
+.revision .answerbody{
+ padding:10px 0 5px 10px;
+}
+
+/* Revision pages */
+del { color: #FF5F5F; }
+del .post-tag{
+color: #FF5F5F;
+}
+ins { background-color: #97ff97;}
+ins .post-tag{
+background-color: #97ff97;
+}
+
+/*用户资料页面*/
+.count {font-family:Arial;font-size:200%;font-weight:700;color:#777}
+.scoreNumber{font-family:Arial;font-size:35px;font-weight:800;color:#777;line-height:40px;
+ /*letter-spacing:0px*/
+ }
+.user-details{font-size:13px;}
+.user-about{background-color:#EEEEEE;height:200px;line-height:20px; overflow:auto;padding:10px;width:90%;}
+.user-edit-link {background:url(/content/images/edit.png) no-repeat; padding-left:20px; font-weight:600;}
+.favorites-count-off {
+ color:#919191;
+ float:left;
+ padding:3px;
+ margin:10px 0 0 0 ;
+ text-align:center;
+}
+
+.favorites-count {
+ color:#D4A849;
+ float:left;
+ padding:3px;
+ margin:10px 0 0 0 ;
+ text-align:center;
+}
+.favorites-empty{
+ width: 32px; height: 45px; float: left;
+}
+.question-summary {
+ border-bottom:1px dotted #999999;
+ float:left;
+ overflow:hidden;
+ padding:11px 0;
+ width:670px;
+}
+
+.user-info-table{
+width:950;margin-bottom:10px;
+}
+
+.user-stats-table .question-summary {
+ width:800px;
+}
+
+.narrow .stats {
+ background:transparent none repeat scroll 0 0;
+ float:left;
+ height:48px;
+ margin:0 0 0 7px;
+ padding:0;
+ width:auto;
+ font-family:Arial;
+}
+
+.stats div {
+ font-size:11px;
+ text-align:center;
+}
+
+.narrow .votes {
+ background:#EEEEEE none repeat scroll 0 0;
+ float:left;
+ height:42px;
+ margin:0 3px 0 0;
+ padding:5px;
+ width:46px;
+ text-align:center;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+.narrow .summary {
+ width:620px;
+}
+
+.narrow .summary h3 {
+ padding:0px;
+ margin:0px;
+}
+
+.narrow .views {
+ float:left;
+ height:42px;
+ margin:0 7px 0 0;
+ /*padding:5px 0 5px 4px;*/
+ padding: 5px;
+ width:46px;
+ text-align:center;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ color:#777;
+}
+
+.narrow .status {
+ float:left;
+ height:42px;
+ margin:0 3px 0 0;
+ padding:5px;
+ width:46px;
+ text-align:center;
+ -moz-border-radius: 5px;
+ -khtml-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+.narrow .vote-count-post {
+ font-weight:800;
+ margin:0;
+ font-size: 190%; color:#555; line-height:20px;
+}
+.narrow .answer-count-post{font-weight:800;margin:0; font-size: 190%; }
+.narrow .views-count-post{font-weight:800;margin:0; font-size: 190%;}
+div.started {
+ color:#999999;
+ float:right;
+ line-height:18px;
+
+}
+
+.narrow div.started {
+ line-height:inherit;
+ padding-top:4px;
+ white-space:nowrap;
+ width:auto;
+}
+
+.relativetime {
+ font-weight:bold;
+ text-decoration:none;
+}
+
+div.started a {
+ font-weight:bold;
+}
+
+div.started .reputation-score {
+ margin-left:1px;
+}
+
+.narrow .tags{float:left;}
+
+.answer-summary {
+ clear:both;
+ padding:3px;
+}
+
+.answer-votes {
+ background-color:#EEEEEE;
+ color:#555555;
+ float:left;
+ font-family:Arial;
+ font-size:110%;
+ font-weight:bold;
+ height:15px;
+ padding:4px 4px 5px;
+ text-align:center;
+ text-decoration:none;
+ width:20px;
+ margin-right:10px;
+}
+.vote-count{font-family:Arial; font-size:160%; font-weight:700; color:#777;}
+.user-action{
+
+}
+.user-action-1{
+ font-weight:bold;
+ color:#333;
+}
+.user-action-2{
+ font-weight:bold;
+ color:#CCC;
+}
+.user-action-3{
+ color:#333;
+}
+.user-action-4{
+ color:#333;
+}
+.user-action-5{
+ color:darkred;
+}
+.user-action-6{
+ color:darkred;
+}
+.user-action-7{
+ color:#333;
+}
+.user-action-8{
+ padding:3px;
+ font-weight:bold;
+ background-color:#CCC;
+ color:#663333;
+}
+
+.revision-summary{
+ background-color:#FFFE9B;
+ padding:2px;
+}
+.question-title-link a{
+ font-weight:bold;
+ color:#0077CC;
+}
+.answer-title-link a{
+ color:#333;
+}
+
+.post-type-1 a {
+ font-weight:bold;
+
+}
+.post-type-3 a {
+ font-weight:bold;
+
+}
+.post-type-5 a {
+ font-weight:bold;
+ }
+.post-type-2 a{
+ color:#333;
+}
+.post-type-4 a{
+ color:#333;
+}
+.post-type-6 a{
+ color:#333;
+}
+.post-type-8 a{
+ color:#333;
+}
+
+
+/*读书频道*/
+.bookInfo {float:left; width:940px;padding:5px;}
+.bookCover {float:left; width:200px;}
+.bookCover img{border:1px solid #ccc;max-width:200px;}
+.bookSummary {float:left; font-size:13px;}
+.blogRss {float:right;margin:0 10px 0 0;width:460px;height:240px;background-color:#EEE; padding:5px;}
+.bookQuestions {margin-bottom:10px;}
+.bookFeed {float:right;}
+.bookAsk{
+ /*letter-spacing:1px; */
+ float:right;margin:-30px 10px 0 0; padding:3px 5px 3px 5px;}
+.bookAsk a {font-size:15px; color:#FFF; font-weight:bold; text-decoration:none;background-color:#EC7000;padding:3px 6px 3px 6px; }
+.bookAsk a:hover {text-decoration:underline;}
+
+
+/*其他全局样式*/
+.hilite { background-color: #ff0; }
+.hilite1 { background-color: #ff0; }
+.hilite2 { background-color: #f0f; }
+.hilite3 { background-color: #0ff; }
+.userStatus {margin-left:12px; color:#FFF; float:right;}
+.userStatus a {color:#FFF;}
+.gold, .badge1 {color:#FFCC00;}
+.silver, .badge2 {color:#CCCCCC;}
+.bronze, .badge3 {color:#CC9933;}
+.score {font-weight:800; color:#333;}
+.footerLinks {color:#EEE; font-size:13px;
+ /* letter-spacing:1px;*/
+ }
+.footerLinks a {color:#FFF; font-size:13px;}
+.subSearch {margin-bottom:12px; padding:4px;}
+a.comment {background:#EEE; color:#993300; padding:4px;}
+a.permLink {padding:2px;}
+a.offensive {color:#999;}
+ul.bulleta li {background:url(/content/images/bullet_green.gif) no-repeat 0px 2px; padding-left:16px; margin-bottom:4px;}
+.user {padding:5px; line-height:140%; width:170px;}
+.user ul {margin:0; list-style-type:none;}
+.user .thumb{clear:both;float:left; margin-right:4px; display:inline;}
+.yellowbg{background:yellow;}
+.subtitle{line-height:30px;font-size:15px; font-weight:700;}
+.message{padding:5px;font-weight:bold;background-color:#eee;margin:10px 0 10px 0;}
+.warning{color:red;}
+.darkred{color:darkred;}
+.submit{
+ cursor:pointer;
+ /*letter-spacing:1px;*/
+ background-color:#D4D0C8;
+ height:40px;
+ border:1px solid #777;
+/* width:100px; */
+ font-weight:bold;
+ font-size:120%;}
+.submit:hover{text-decoration:underline;}
+.ask-body{padding-right:10px;}
+.thousand{color:orange;}
+.notify
+{
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ z-index: 100;
+ padding: 7px 0 5px 0;
+ text-align: center;
+ font-size: 130%;
+ font-weight: Bold;
+ color: #444;
+ background-color: #F4A83D;
+}
+
+.notify span
+{
+ float: left;
+ width: 95%;
+ text-align: center;
+}
+
+.close-notify
+{
+ float: right;
+ margin-right: 20px;
+ color: #735005;
+ text-decoration: none;
+ background-color: #FAD163;
+ border: 2px #735005 solid;
+ padding-left: 3px;
+ padding-right: 3px;
+ cursor:pointer;
+}
diff --git a/templates/faq.html b/templates/faq.html
new file mode 100644
index 00000000..31ee444a
--- /dev/null
+++ b/templates/faq.html
@@ -0,0 +1,113 @@
+<!-- template faq.html -->
+{% extends "base_content.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% load i18n %}
+{% block title %}{% spaceless %}FAQ{% endspaceless %}{% endblock %}
+{% block forejs %}
+{% endblock %}
+{% block content %}
+<div class="headNormal">
+ {% trans "Frequently Asked Questions " %}(FAQ)
+</div>
+<div id="main-body" style="width:100%">
+
+ <h3 class="subtitle">{% trans "What kinds of questions can I ask here?" %}</h3>
+ <p>{% trans "Most importanly - questions should be <strong>relevant</strong> to this community." %}<br>
+ {% trans "Before asking the question - please make sure to use search to see whether your question has alredy been answered."%}<br>
+ </p><br>
+
+ <h3 class="subtitle">{% trans "What questions should I avoid asking?" %}</h3>
+ <p>{% trans "Please avoid asking questions that are not relevant to this community, too subjective and argumentative." %}</p>
+ </p><br>
+
+ <h3 class="subtitle">{% trans "What should I avoid in my answers?" %}</h3>
+ <p>{% trans "site title" %} {% trans "is a Q&A site, not a discussion group. Therefore - please avoid having discussions in your answers, comment facility allows some space for brief discussions." %}
+ </p><br>
+
+ <h3 class="subtitle">{% trans "Who moderates this community?" %}</h3>
+ <p>{% trans "The short answer is: <strong>you</strong>." %}<br>
+ {% trans "This website is moderated by the users." %}
+ {% trans "The reputation system allows users earn the authorization to perform a variety of moderation tasks." %}
+ </p><br>
+
+ <h3 class="subtitle">{% trans "How does reputation system work?" %}</h3>
+ <p>{% trans "Anyone can ask questions and give answers, points are not necessary for that." %}<br>
+ {% trans "As we've said before, users help running this site. Point system helps select users who can administer this community."%}
+ {% trans "Reputation points roughly measure how community trusts you. These points are given to you directly by other members of the community." %}
+ </p>
+ <p>
+ {% trans "For example, if you ask an interesting question or give a helpful answer, your input will be upvoted and you will gain more trust in the community." %}
+ {% trans "If on the other hand someone gives a misleading answer, the answer will be voted down and he/she loses some points." %}
+ {% trans "Each vote in favor will generate <strong>10</strong> points, each vote against will subtract <strong>2</strong> points." %}
+ {% trans "Through the votes of other people you can accumulate a maximum of <strong>200</strong> points." %}
+ {% trans "After accumulating certain number of points, you can do more:" %}
+ <table style="font-family:arial;" cellspacing="3" cellpadding="3">
+ <tr>
+ <th width="40px" style="text-align:right"></th>
+ <th width="300px"></th>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>15</strong></td>
+ <td>{% trans "upvote" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>15</strong></td>
+ <td>{% trans "use tags" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>50</strong></td>
+ <td>{% trans "add comments" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>100</strong></td>
+ <td>{% trans "downvote" %}</td>
+ </tr><tr>
+ <td style="text-align:right;padding-right:5px"><strong>250</strong></td>
+ <td>打开关闭自己的问题</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>500</strong></td>
+ <td>{% trans "retag questions" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>750</strong></td>
+ <td>{% trans "edit community wiki questions" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>2000</strong></td>
+ <td>{% trans "edit any answer" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>3000</strong></td>
+ <td>{% trans "open any closed question" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>5000</strong></td>
+ <td>{% trans "delete any comment" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>10000</strong></td>
+ <td>{% trans "delete any questions and answers and perform other moderation tasks" %}</td>
+ </tr>
+
+ </table>
+
+ </p><br>
+
+ <h3 class="subtitle">{% trans "To register, do I need to create new password?" %}</h3>
+ <p>{% trans "No, you don't have to. You can login through any service that supports OpenID, e.g. Google, Yahoo, AOL, etc." %}
+ <strong><a href="/account/signin">{% trans "Login now!" %}</a> »</strong>
+ </p><br>
+
+ <h3 class="subtitle">{% trans "Why other people can edit my questions/answers?" %}</h3>
+ <p> {% trans "Goal of this site is..." %} {% trans "So questions and answers can be edited like wiki pages by experienced users of this site and this improves the overall quality of the knowledge base content." %}
+ {% trans "If this approach is not for you, we respect your choice." %}
+ </p><br>
+ <h3 class="subtitle">{% trans "Still have questions?" %}</h3>
+ <p>{% trans "Please ask your question, help make our community better!" %} <a href="/tags/faq" class="big">{% trans "site title" %} {% trans "questions" %}</a>{% trans "." %}
+ </p>
+ <br><br>
+</div>
+{% endblock %}
+<!-- end template faq.html -->
diff --git a/templates/feeds/rss_description.html b/templates/feeds/rss_description.html
new file mode 100644
index 00000000..fa781907
--- /dev/null
+++ b/templates/feeds/rss_description.html
@@ -0,0 +1 @@
+{{ obj.html|safe }} \ No newline at end of file
diff --git a/templates/feeds/rss_title.html b/templates/feeds/rss_title.html
new file mode 100644
index 00000000..7899fce3
--- /dev/null
+++ b/templates/feeds/rss_title.html
@@ -0,0 +1 @@
+{{ obj.title }} \ No newline at end of file
diff --git a/templates/footer.html b/templates/footer.html
new file mode 100644
index 00000000..61e6e6bb
--- /dev/null
+++ b/templates/footer.html
@@ -0,0 +1,33 @@
+<!-- template footer.html -->
+{% load extra_tags %}
+{% load i18n %}
+<!-- 页面底部开始: -->
+ <div id="ground">
+ <div class="footerLinks" >
+ <a href="/about">{% trans "about" %}</a><span class="link-separator"> |</span>
+ <a href="/faq">{% trans "faq" %}</a><span class="link-separator"> |</span>
+ <a href="{{ blog_url }}">{% trans "blog" %}</a><span class="link-separator"> |</span>
+ <a href="{{ webmaster_email }}">{% trans "contact us" %}</a><span class="link-separator"> |</span>
+ <a href="/privacy">{% trans "privacy policy" %}</a><span class="link-separator"> |</span>
+ <a href="{{ feedback_url }}" target="_blank">{% trans "give feedback" %}</a>
+ </div>
+ <p style="margin-top:10px;">
+ <a href="http://code.google.com/p/cnprog/" target="_blank">
+ <img src="/content/images/djangomade124x25_grey.gif" border="0" alt="Made with Django." title="Made with Django." >
+ </a>
+ <!--<div style="font-size:90%;color:#333">{% trans "current revision" %}: R-0120-20090406</div>-->
+ </p>
+ <p id="licenseLogo"><img src="/content/images/cc-wiki.png" title="Creative Commons: Attribution - Share Alike" alt="cc-wiki" width="50" height="68" /></p>
+ </div>
+ <!-- 页面底部结束: -->
+ <script type="text/javascript">
+ var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+ document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+ </script>
+ <script type="text/javascript">
+ try {
+ var pageTracker = _gat._getTracker({{ google_analytics_key }});
+ pageTracker._trackPageview();
+ } catch(err) {}
+ </script>
+<!-- end template footer.html -->
diff --git a/templates/header.html b/templates/header.html
new file mode 100644
index 00000000..0aa02886
--- /dev/null
+++ b/templates/header.html
@@ -0,0 +1,70 @@
+<!-- template header.html -->
+{% load extra_tags %}
+{% load i18n %}
+ <div id="top">
+ <div id="header">
+ {% if request.user.is_authenticated %}
+ <a href="/users/{{ request.user.id }}/{{ request.user.username }}/">{{ request.user.username }}</a> {% get_score_badge request.user %}
+ <a href="/logout/">{% trans "logout" %}</a>
+ {% else %}
+ <a href="/account/signin">{% trans "login" %}</a>
+ {% endif %}
+ <a href="/about">{% trans "about" %}</a>
+ <a href="/faq">{% trans "faq" %}</a>
+ </div>
+ </div>
+ <div id="roof">
+ <div id="navBar">
+ <table width="100%" border="0" cellspacing="0" cellpadding="0">
+ <tr>
+ <td width="23%">
+ <div id="logo">
+ <a href="/">
+ <img src="/content/images/logo.png" title="{% trans "back to home page" %}" />
+ </a>
+ </div>
+ </td>
+ <td width="77%" valign="bottom">
+ <div class="nav">
+ <a id="nav_questions" href="/questions/" >{% trans "questions" %}</a>
+ <a id="nav_tags" href="/tags/">{% trans "tags" %}</a>
+ <a id="nav_users" href="/users/">{% trans "users" %}</a>
+ <!--<a id="nav_books" href="/books/">{% trans "books" %}</a>-->
+ <a id="nav_badges" href="/badges/">{% trans "badges" %}</a>
+ <a id="nav_unanswered" href="/questions/unanswered/">{% trans "unanswered questions" %}</a>
+
+ {% comment %}<!-- i think this needs to be removed -e.f. -->
+ {% if request.user.is_authenticated %}
+ <a id="nav_profile" href="/users/{{ request.user.id }}/{{ request.user.username }}/">{% trans "my profile" %}</a>
+ {% endif %}
+ {% endcomment %}
+ <div class="focus">
+ <a id="nav_ask" href="/questions/ask/" class="special">{% trans "ask a question" %}</a>
+ </div>
+ </div>
+
+ </td>
+ </tr>
+ </table>
+ </div>
+ <div id="searchBar">
+ <table width="100%" height="60" border="0" cellpadding="0" cellspacing="0" class="content">
+ <tr>
+ <td align="center" valign="middle">
+ <form action="/search/" method="GET">
+ <div>
+ <input type="text" class="searchInput" value="{{ keywords }}" name="q" id="keywords" >
+ <input type="submit" name="Submit" value="{% trans "search" %}" class="searchBtn" >
+ </div>
+ <div class="options">
+ <input id="type-question" type="radio" class="" value="question" name="t" checked >{% trans "questions" %}
+ <input id="type-tag" type="radio" class="" value="tag" name="t" >{% trans "tags" %}
+ <input id="type-user" type="radio" class="" value="user" name="t" >{% trans "users" %}
+ </div>
+ </form>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+<!-- end template header.html -->
diff --git a/templates/index.html b/templates/index.html
new file mode 100644
index 00000000..2ef66d4e
--- /dev/null
+++ b/templates/index.html
@@ -0,0 +1,123 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+{% load extra_filters %}
+{% block title %}{% spaceless %}{% trans "Home" %}{% endspaceless %}{% endblock %}
+{% block meta %}<meta name="keywords" content="{% trans "meta site keywords, comma separated" %}" />
+ <meta name="description" content="{% trans "meta site content" %}" />{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ var tab_id = "{{ tab_id }}";
+ $("#"+tab_id).attr('className',"on");
+ $("#nav_questions").attr('className',"on");
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<div class="tabBar">
+ <div class="headQuestions">{% trans "Questions" %}</div>
+ <div class="tabsA">
+ <a id="latest" href="?sort=latest" title="{% trans "last updated questions" %}" >{% trans "newest" %}</a>
+ <a id="hottest" href="?sort=hottest" title="{% trans "hottest questions" %}" >{% trans "hottest" %}</a>
+ <a id="mostvoted" href="?sort=mostvoted" title="{% trans "most voted questions" %}" >{% trans "most voted" %}</a>
+ <a id="all" href="/questions/" title="{% trans "all questions" %}" >{% trans "all questions" %}</a>
+ </div>
+</div>
+<!-- 问题列表 -->
+<div id="listA">
+ {% for question in questions %}
+ <div class="qstA">
+ <h2>
+ <a href="{{ question.get_absolute_url }}" title="{{ question.summary }}...">
+ {{ question.get_question_title }}
+ </a>
+ </h2>
+ <div class="stat">
+ <table>
+ <tr>
+ <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
+ <td><span class="num">{{ question.score|intcomma }}</span> </td>
+ <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
+ </tr>
+ <tr>
+ <td><span class="unit">{% trans "answers" %}</span></td>
+ <td><span class="unit">{% trans "votes" %}</span></td>
+ <td><span class="unit">{% trans "views" %}</span></td>
+ </tr>
+ </table>
+ </div>
+ <div class="summary">
+ {{ question.summary }}...
+ </div>
+ {% if question.wiki %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ </div>
+ {% endif %}
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+</div>
+{% endblock %}
+
+{% block sidebar %}
+{% if not request.user.is_authenticated %}
+<div class="boxA">
+ <h3>{% trans "welcome to website" %}</h3>
+ <div class="body">
+ <p>{% trans "what is this website" %}</p>
+ <p>{% trans "what can one do on this website" %}</p>
+ <div class="more"><a href="/about">{% trans "about" %} »</a></div>
+ <div class="more"><a href="/faq">{% trans "faq" %} »</a></div>
+ </div>
+</div>
+{% endif %}
+<div class="boxB">
+ <h3>{% trans "Recent tags" %}</h3>
+ <div class="body">
+ <p class="tagsbox">
+ {% for tag in tags %}
+ <a rel="tag"
+ title="{% blocktrans with tag.name as tagname %}see questions tagged '{{tagname}}'{% endblocktrans %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
+ {% endfor %}
+ </p>
+ <div class="more"><a href="/tags">{% trans "popular tags" %} »</a> </div>
+ </div>
+</div>
+<div class="boxB">
+ <h3>{% trans "Recent awards" %}</h3>
+ <div class="body">
+ <ul class="badge-list">
+ {% for award in awards %}
+ <li>
+ <a href="/badges/{{award.badge_id}}/{{award.badge_name}}" title="{{ award.badge_description }}" class="medal">
+ <span class="badge{{ award.badge_type }}">&#9679;</span>&nbsp;{{ award.badge_name }}</a> {% trans "given to" %}
+ <a href="/users/{{award.user_id}}/{{award.user_name}}">{{ award.user_name }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+ <div class="more"><a href="/badges/">{% trans "all awards" %} »</a> </div>
+ </div>
+</div>
+<div id="feeds">
+<a href="/feeds/rss" title="{% trans "subscribe to last 30 questions by RSS" %}">{% trans "subscribe to the questions feed" %}</a>
+</div>
+{% endblock %}
+{% block tail %}
+<div style="padding:5px 0 5px 5px;">
+<span class="evenMore">{% trans "Still looking for more? See" %} <a href="/questions/">{% trans "complete list of quesionts" %}</a>, {% trans "or" %} <a href="/tags/">{% trans "popular tags" %}</a>{% trans "." %} {% trans "Please help us answer" %} <a href="/questions/unanswered">{% trans "unanswered questions" %}</a>{% trans "." %}</span>
+</div>
+{% endblock %}
diff --git a/templates/logout.html b/templates/logout.html
new file mode 100644
index 00000000..b13e5e25
--- /dev/null
+++ b/templates/logout.html
@@ -0,0 +1,25 @@
+<!-- template logout.html -->
+{% extends "base_content.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Logout" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $('#btLogout').bind('click', function(){ window.location.href='/account/signout/?next={{ next }}'; });
+
+ });
+ </script>
+{% endblock %}
+{% block content %}
+<div class="headNormal">
+ {% trans "Logout" %}
+</div>
+<div id="main-body" style="width:100%">
+ <p>{% trans "As a registered user you can login with your OpenID, log out of the site or permanently remove your account." %}</p>
+ <input id="btLogout" type="button" class="submit" value="{% trans "Logout now" %}"><!-- style="width:150px">-->
+ <br><br>
+</div>
+{% endblock %}
+<!-- ent template logout.html -->
diff --git a/templates/okfiles b/templates/okfiles
new file mode 100644
index 00000000..3ca0191e
--- /dev/null
+++ b/templates/okfiles
@@ -0,0 +1,30 @@
+404.html
+500.html
+about.html
+ask.html
+badges.html
+base_content.html
+base.html
+book.html
+close.html
+faq.html
+footer.html
+header.html
+index.html
+logout.html
+pagesize.html
+paginator.html
+privacy.html
+question_edit_tips.html
+question.html
+questions.html
+revisions_answer.html
+tags.html
+unanswered.html
+user_edit.html
+user_info.html
+users.html
+users_questions.html
+user_stats.html
+user_tabs.html
+user_votes.html
diff --git a/templates/pagesize.html b/templates/pagesize.html
new file mode 100644
index 00000000..5037f1f6
--- /dev/null
+++ b/templates/pagesize.html
@@ -0,0 +1,27 @@
+<!-- template pagesize.html -->
+{% spaceless %}
+{% load i18n %}
+{% if is_paginated %}
+<div class="paginator">
+ <span class="text">{% trans "posts per page" %}</span>
+ {% ifequal pagesize 10 %}
+ <span class="curr">10</span>
+ {% else %}
+ <span class="page"><a href="{{base_url}}pagesize=10">10</a></span>
+ {% endifequal %}
+
+ {% ifequal pagesize 30 %}
+ <span class="curr">30</span>
+ {% else %}
+ <span class="page"><a href="{{base_url}}pagesize=30">30</a></span>
+ {% endifequal %}
+
+ {% ifequal pagesize 50 %}
+ <span class="curr">50</span>
+ {% else %}
+ <span class="page"><a href="{{base_url}}pagesize=50">50</a></span>
+ {% endifequal %}
+</div>
+{% endif %}
+{% endspaceless %}
+<!-- end template pagesize.html -->
diff --git a/templates/paginator.html b/templates/paginator.html
new file mode 100644
index 00000000..c5e51a81
--- /dev/null
+++ b/templates/paginator.html
@@ -0,0 +1,39 @@
+<!-- paginator.html -->
+{% spaceless %}
+{% load i18n %}
+{% if is_paginated %}
+<div class="paginator">
+{% if has_previous %}<span class="prev"><a href="{{base_url}}page={{ previous }}{{ extend_url }}" title="{% trans "previous" %}">
+&laquo; {% trans "previous" %}</a></span>{% endif %}
+
+{% if not in_leading_range %}
+ {% for num in pages_outside_trailing_range %}
+ <span class="page"><a href="{{base_url}}page={{ num }}{{ extend_url }}" >{{ num }}</a></span>
+ {% endfor %}
+ ...
+{% endif %}
+
+{% for num in page_numbers %}
+ {% ifequal num page %}
+ {% ifequal pages 1 %}
+ {% else %}
+ <span class="curr" title="{% trans "current page" %}">{{ num }}</span>
+ {% endifequal %}
+ {% else %}
+ <span class="page"><a href="{{base_url}}page={{ num }}{{ extend_url }}" title="{% trans "page number " %}{{ num }}{% trans "number - make blank in english" %}">{{ num }}</a></span>
+ {% endifequal %}
+{% endfor %}
+
+{% if not in_trailing_range %}
+ ...
+ {% for num in pages_outside_leading_range reversed %}
+ <span class="page"><a href="{{base_url}}page={{ num }}{{ extend_url }}" title="{% trans "page number " %}{{ num }}{% trans "number - make blank in english" %}">{{ num }}</a></span>
+ {% endfor %}
+{% endif %}
+
+{% if has_next %}<span class="next"><a href="{{base_url}}page={{ next }}{{ extend_url }}" title="{% trans "next page" %}">{% trans "next page" %} &raquo;</a></span>{% endif %}
+</div>
+{% endif %}
+
+{% endspaceless %}
+<!-- end paginator.html -->
diff --git a/templates/privacy.html b/templates/privacy.html
new file mode 100644
index 00000000..335aba92
--- /dev/null
+++ b/templates/privacy.html
@@ -0,0 +1,43 @@
+<!-- privacy.html -->
+{% extends "base_content.html" %}
+{% load extra_tags %}
+{% load i18n %}
+{% load humanize %}
+{% block title %}{% spaceless %}{% trans "Privacy policy" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+{% endblock %}
+{% block content %}
+<div class="headNormal">
+ {% trans "Privacy policy" %}
+</div>
+<div id="main-body" style="width:100%">
+ <p>
+ {% trans "general message about privacy" %}
+ </p>
+
+ <h3 class="subtitle">{% trans "Site Visitors" %}</h3>
+ <p>
+ {% trans "what technical information is collected about visitors" %}
+ </p>
+
+ <h3 class="subtitle">{% trans "Personal Information" %}</h3>
+ <p>
+ {% trans "details on personal information policies" %}
+ </p>
+
+ <h3 class="subtitle">{% trans "Other Services" %}</h3>
+ <p>
+ {% trans "details on sharing data with third parties" %}
+ </p>
+
+ <h3 class="subtitle">Cookies</h3>
+ <p>
+ {% trans "cookie policy details" %}
+ </p>
+ <h3 class="subtitle">{% trans "Policy Changes" %}</h3>
+ <p>{% trans "how privacy policies can be changed" %}
+ </p>
+ <br><br>
+</div>
+{% endblock %}
+<!-- end privacy.html -->
diff --git a/templates/question.html b/templates/question.html
new file mode 100644
index 00000000..5ba08b63
--- /dev/null
+++ b/templates/question.html
@@ -0,0 +1,510 @@
+<!-- question.html -->
+{% extends "base.html" %}{% load extra_tags %}{% load extra_filters %}{% load humanize %}
+{% load i18n %}
+{% block title %}{% spaceless %}{{ question.get_question_title }}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ {% if not question.closed and request.user.is_authenticated %}
+ <script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
+ <script type='text/javascript' src='/content/js/wmd/showdown.js'></script>
+ <script type='text/javascript' src='/content/js/wmd/wmd.js'></script>
+ <link rel="stylesheet" type="text/css" href="/content/js/wmd/wmd.css" />
+ {% endif %}
+ <script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
+ <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type="text/javascript">
+ // define reputation needs for comments
+ var repNeededForComments = 50;
+ $().ready(function(){
+ $("#nav_questions").attr('className',"on");
+ var answer_sort_tab = "{{ tab_id }}";
+ $("#" + answer_sort_tab).attr('className',"on");
+
+ Vote.init({{ question.id }}, '{{ question.author.id }}','{{ request.user.id }}');
+
+ {% if not question.closed and request.user.is_authenticated %}initEditor();{% endif %}
+
+ lanai.highlightSyntax();
+ $('#btLogin').bind('click', function(){window.location.href='/account/signin/'; } )
+ });
+
+ function initEditor(){
+ $('#editor').TextAreaResizer();
+ //highlight code synctax when editor has new text
+ $("#editor").typeWatch({highlight: false, wait: 3000,
+ captureLength: 5, callback: lanai.highlightSyntax});
+
+ var display = true;
+ var txt = "[{% trans "hide preview" %}]";
+ $('#pre-collapse').text(txt);
+ $('#pre-collapse').bind('click', function(){
+ txt = display ? "[{% trans "show preview" %}]" : "[{% trans "hide preview" %}]";
+ display = !display;
+ $('#previewer').toggle();
+ $('#pre-collapse').text(txt);
+ });
+
+ setupFormValidation("#fmanswer", CPValidator.getQuestionFormRules(), CPValidator.getQuestionFormMessages());
+ }
+
+ </script>
+{% endblock %}
+
+{% block content %}
+<div class="headNormal">
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+</div>
+<div id="main-body" class="">
+ <div id="askform">
+ <form id="fmanswer" action="{% url answer question.id %}" method="post">
+ <table style="width:100%;" id="question-table" {% if question.deleted %}class="deleted"{%endif%}>
+ <tr>
+ <td style="width:30px;vertical-align:top">
+ <div class="vote-buttons">
+ {% if question_vote %}
+ <img id="question-img-upvote-{{ question.id }}" class="question-img-upvote"
+ src="/content/images/vote-arrow-up{% if question_vote.is_upvote %}-on{% endif %}.png"
+ title="{% trans "i like this post (click again to cancel)" %}" >
+ <div id="question-vote-number-{{ question.id }}" class="vote-number"
+ title="{% trans "current number of votes" %}">
+ {{ question.score }}
+ </div>
+ <img id="question-img-downvote-{{ question.id }}" class="question-img-downvote"
+ src="/content/images/vote-arrow-down{% if question_vote.is_downvote %}-on{% endif %}.png"
+ title="{% trans "i dont like this post (click again to cancel)" %}" >
+
+ {% else %}
+ <img id="question-img-upvote-{{ question.id }}" class="question-img-upvote"
+ src="/content/images/vote-arrow-up.png"
+ title="{% trans "i like this post (click again to cancel)" %}" >
+ <div id="question-vote-number-{{ question.id }}" class="vote-number"
+ title="{% trans "current number of votes" %}">
+ {{ question.score }}
+ </div>
+ <img id="question-img-downvote-{{ question.id }}" class="question-img-downvote"
+ src="/content/images/vote-arrow-down.png" title="{% trans "i dont like this post (click again to cancel)" %}" >
+
+ {% endif %}
+ <br><br>
+ {% if favorited %}
+ <img class="question-img-favorite" src="/content/images/vote-favorite-on.png"
+ title="{% trans "mark this question as favorite (click again to cancel)" %}" >
+ <div id="favorite-number" class="favorite-number my-favorite-number">
+ {{ question.favourite_count }}
+ </div>
+ {% else %}
+ <img class="question-img-favorite" src="/content/images/vote-favorite-off.png"
+ title="{% trans "remove favorite mark from this question (click again to restore mark)" %}" >
+ <div id="favorite-number" class="favorite-number">{% ifnotequal question.favourite_count 0 %}{{ question.favourite_count }}{% endifnotequal %}
+ </div>
+
+ {% endif %}
+
+ </div>
+ </td>
+ <td>
+ <div id="item-right">
+ <div class="question-body">
+ {{ question.html|safe }}
+ </div>
+ <div id="question-tags" class="tags" >
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" class="post-tag"
+ title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ <div id="question-controls" style="clear:both;">
+ <table width="100%">
+ <tr>
+ <td width="210px" style="vertical-align:top">
+
+ {% if request.user|can_edit_post:question %}
+ <span class="action-link"><a href="{% url edit_question question.id %}">{% trans 'edit' %}</a></span>
+ <span class="action-link-separator">|</span>
+ {% endif %}
+ {% if request.user|can_delete_post:question %}
+ <span class="action-link"><a id="question-delete-link-{{question.id}}">{% trans "delete" %}</a></span>
+ <span class="action-link-separator">|</span>
+ {% endif %}
+ {% if question.closed %}
+ {% if request.user|can_reopen_question:question %}
+ <span class="action-link"><a href="{% url reopen question.id %}">{% trans "reopen" %}</a></span>
+ <span class="action-link-separator">|</span>
+ {% endif %}
+ {% else %}
+ {% if request.user|can_close_question:question %}
+ <span class="action-link"><a href="{% url close question.id %}">{% trans "close" %}</a></span>
+ <span class="action-link-separator">|</span>
+ {% endif %}
+ {% endif %}
+
+ <span id="question-offensive-flag-{{ question.id }}" class="offensive-flag"
+ title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
+ <a>{% trans "flag offensive" %}</a>
+ <span class="darkred">{% if request.user|can_view_offensive_flags %}
+ {% if question.offensive_flag_count %}({{ question.offensive_flag_count }}){% endif %}{% endif %}</span>
+ </span>
+
+ </td>
+ <td width="210px" style="vertical-align:top">
+ {% if question.last_edited_by %}
+ <div class="question-edit" >
+ <table width="200px" >
+ <tr>
+ <td colspan="2">
+ {% trans "updated" %} <a href="{% url question_revisions question.id %}"><strong title="{{question.last_edited_at }}">{% diff_date question.last_edited_at %}</strong></a>
+ </td>
+
+ </tr>
+ {% if question.wiki %}
+ <tr>
+ <td style="width:40px;vertical-align:bottom">
+ {% gravatar question.last_edited_by 32 %}
+ </td>
+ <td style="width:160px; vertical-align:top">
+ <a href="/users/{{ question.last_edited_by.id }}/{{ question.last_edited_by.username }}">{{ question.last_edited_by.username }}</a>
+ </td>
+ </tr>
+ {% else %}
+ {% ifequal question.last_edited_by question.author %}
+ <tr>
+ <td> </td>
+ <td> </td>
+ </tr>
+ {% else %}
+ <tr>
+ <td style="width:40px;vertical-align:bottom">
+ {% gravatar question.last_edited_by 32 %}
+ </td>
+ <td style="width:160px; vertical-align:top">
+ <div><a href="/users/{{ question.last_edited_by.id }}/{{ question.last_edited_by.username }}">{{ question.last_edited_by.username }}</a></div>
+
+ <div>
+ {% get_score_badge question.last_edited_by %}
+ </div>
+
+ </td>
+ </tr>
+ {% endifequal %}
+ {% endif %}
+ </table>
+ </div>
+ {% endif %}
+
+ </td>
+ <td style="vertical-align:top">
+ {% if question.wiki %}
+ <span class="wiki-category">{% trans "community wiki" %}</span>
+ <div style="margin-bottom:10px"></div>
+ {% else %}
+ <div class="question-mark">
+ <table width="200px">
+ <tr>
+ <td colspan="2">
+ {% trans "asked" %} <strong title="{{ question.added_at }}">{% diff_date question.added_at %}</strong>
+ </td>
+
+ </tr>
+
+ <tr>
+ <td style="width:40px; vertical-align:bottom">
+ {% gravatar question.author 32 %}
+ </td>
+ <td align="left" style="width:160px;vertical-align:top">
+ <div><a href="/users/{{ question.author.id }}/{{ question.author }}">{{ question.author }}</a></div>
+ <div>
+ {% get_score_badge question.author %}
+ </div>
+ </td>
+ </tr>
+
+ </table>
+ </div>
+ {% endif %}
+
+ </td>
+ </tr>
+ </table>
+
+ </div>
+
+ <div class="post-comments" style="margin-bottom:20px">
+ <input id="can-post-comments-question-{{question.id}}" type="hidden" value="{{ request.user|can_add_comments }}"/>
+ <a id="comments-link-question-{{question.id}}" class="comments-link">
+ {% if question.comment_count %}{% trans "comments" %} <strong>({{question.comment_count}})</strong>
+ {% else %}{% trans "add comment" %}
+ {% endif %}</a>
+ <div id="comments-question-{{question.id}}" class="comments-container">
+ <div class="comments"/></div>
+ </div>
+
+ </div>
+
+ </td>
+ </tr>
+ </table>
+ {% if question.closed %}
+ <div class="question-status" style="margin-bottom:15px">
+ <h3>{% blocktrans %}The question has been closed for the following reason "{{ question.get_close_reason_display }}" by{% endblocktrans %}
+ <a href="{{ question.closed_by.get_profile_url }}">{{ question.closed_by.username }}</a>
+ {% blocktrans %}close date {{question.closed_at}}{% endblocktrans %}</h3>
+ </div>
+ {% endif %}
+
+ {% ifnotequal question.answer_count 0 %}
+ <div class="tabBar">
+ <a name="sort-top"></a>
+ <div class="headQuestions">{{ question.answer_count }}{% trans "Answers" %}:</div>
+ <div class="tabsA">
+ <a id="oldest" href="?sort=oldest#sort-top" title="{% trans "oldest answers will be shown first" %}">{% trans "oldest answers" %}</a>
+ <a id="latest" href="?sort=latest#sort-top" title="{% trans "newest answers will be shown first" %}">{% trans "newest answers" %}</a>
+ <a id="votes" href="?sort=votes#sort-top" title="{% trans "most voted answers will be shown first" %}">{% trans "popular answers" %}</a>
+ </div>
+ </div>
+ {% cnprog_paginator context %}
+
+ {% for answer in answers %}
+ <a name="{{ answer.id }}"></a>
+ <div id="answer-container-{{ answer.id }}" class="answer {% if answer.accepted %}accepted-answer{% endif %} {% ifequal answer.author_id question.author_id %} answered-by-owner{% endifequal %} {% if answer.deleted %}deleted{% endif %}">
+ <table style="width:100%;">
+ <tr>
+ <td style="width:30px;vertical-align:top">
+ <div class="vote-buttons">
+ <img id="answer-img-upvote-{{ answer.id }}" class="answer-img-upvote" src="/content/images/vote-arrow-up{% get_user_vote_image user_answer_votes answer.id 1 %}.png" title="{% trans "i like this answer (click again to cancel)" %}">
+ <div id="answer-vote-number-{{ answer.id }}" class="vote-number" title="{% trans "current number of votes" %}">
+ {{ answer.score }}
+ </div>
+ <img id="answer-img-downvote-{{ answer.id }}" class="answer-img-downvote"
+ src="/content/images/vote-arrow-down{% get_user_vote_image user_answer_votes answer.id -1 %}.png"
+ title="{% trans "i dont like this answer (click again to cancel)" %}" >
+
+ <br><br>
+ {% ifequal request.user question.author %}
+ <img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
+ src="/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png"
+ title="{% trans "mark this answer as favorite (click again to undo)" %}" >
+ {% else %}
+ {% if answer.accepted %}
+ <img id="answer-img-accept-{{ answer.id }}" class="answer-img-accept"
+ src="/content/images/vote-accepted{% if answer.accepted %}-on{% endif %}.png"
+ title="{% trans "the author of the question has selected this answer as correct" %}" >
+ {% endif %}
+ {% endifequal %}
+ </div>
+ </td>
+ <td>
+ <div class="item-right">
+ <div class="answer-body">
+ {{ answer.html|safe }}
+ </div>
+ <div class="answer-controls" style="clear:both;">
+ <table width="100%">
+ <tr>
+ <td width="400px" style="vertical-align:top">
+ {% if request.user|can_edit_post:answer %}
+ <span class="action-link"><a href="{% url edit_answer answer.id %}">{% trans "edit" %}</a></span>
+ <span class="action-link-separator">|</span>
+ {% endif %}
+ {% if request.user|can_delete_post:answer %}
+ <span class="action-link">
+ <a id="answer-delete-link-{{answer.id}}">
+ {% if answer.deleted %}
+ {% trans "undelete" %}
+ {% endif %}
+ {% if not answer.deleted %}
+ {% trans "delete" %}
+ {% endif %}
+ </a>
+ </span>
+ <span class="action-link-separator">|</span>
+ {% endif %}
+ <span class="linksopt">
+ <a href="#{{ answer.id }}" title="{% trans "answer permanent link" %}">
+ {% trans "permanent link" %}
+ </a>
+ </span>
+ <span class="action-link-separator">|</span>
+ <span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag"
+ title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}">
+ <a>{% trans "flag offensive" %}</a>
+ <span class="darkred">{% if request.user|can_view_offensive_flags %}{% if answer.offensive_flag_count %}({{ answer.offensive_flag_count }}){% endif %}{% endif %}</span></span>
+ </td>
+ <td width="110px" style="vertical-align:top">
+ {% if answer.last_edited_by %}
+ <div class="question-edit" >
+ <table width="200px" >
+ <tr>
+ <td colspan="2">
+ {% trans "updated" %}<a href="{% url answer_revisions answer.id %}"><strong title="{{answer.last_edited_at }}">{% diff_date answer.last_edited_at %}</strong></a>
+ </td>
+ </tr>
+ {% if answer.wiki %}
+ <tr>
+ <td width="40px" style="vertical-align:bottom">
+ {% gravatar answer.last_edited_by 32 %}
+ </td>
+ <td style="width:160px; vertical-align:top">
+ <div><a href="/users/{{ answer.last_edited_by.id }}/{{ answer.last_edited_by.username }}">{{ answer.last_edited_by.username }}</a></div>
+
+ </td>
+ </tr>
+ {% else %}
+ {% ifequal answer.last_edited_by answer.author %}
+ <tr>
+ <td> </td>
+ <td> </td>
+ </tr>
+ {% else %}
+ <tr>
+ <td width="40px" style="vertical-align:bottom">
+ {% gravatar answer.last_edited_by 32 %}
+ </td>
+ <td style="width:160px; vertical-align:top">
+ <div><a href="/users/{{ answer.last_edited_by.id }}/{{ answer.last_edited_by.username }}">{{ answer.last_edited_by.username }}</a></div>
+ <div>
+ {% get_score_badge answer.last_edited_by %}
+ </div>
+ </td>
+ </tr>
+ {% endifequal %}
+ {% endif %}
+ </table>
+ </div>
+ {% endif %}
+
+ </td>
+ <td style="vertical-align:top">
+ {% if answer.wiki %}
+ <span class="wiki-category">{% trans "community wiki" %}</span>
+ <div style="margin-bottom:10px"></div>
+ {% else %}
+ <div class="answer-mark">
+ <table width="200px">
+ <tr>
+ <td colspan="2">
+ {% trans "asked" %} <strong title="{{answer.added_at}}">{% diff_date answer.added_at %}</strong>
+ </td>
+ </tr>
+ <tr>
+ <td width="40px" style="vertical-align:bottom">
+ {% gravatar answer.author 32 %}
+ </td>
+ <td style="width:160px; vertical-align:top">
+ <div><a href="/users/{{ answer.author.id }}/{{ answer.author.username }}">{{ answer.author }}</a></div>
+ <div>
+ {% get_score_badge answer.author %}
+ </div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ {% endif %}
+
+ </td>
+ </tr>
+
+ </table>
+
+ </div>
+ <div id="comment-{{ answer.id }}" class="post-comments" >
+ <input id="can-post-comments-answer-{{answer.id}}" type="hidden" value="{{ request.user|can_add_comments }}"/>
+ <a id="comments-link-answer-{{answer.id}}" class="comments-link">
+ {% if answer.comment_count %}{% trans "comments" %}
+ <strong>({{answer.comment_count}})</strong>{% else %}{% trans "add comment" %}{% endif %}</a>
+ <div id="comments-answer-{{answer.id}}" class="comments-container">
+ <div class="comments"/></div>
+ </div>
+
+ </div>
+
+ </td>
+ </tr>
+ </table>
+ </div>
+ {% endfor %}
+ <div class="paginator-container-left">
+ {% cnprog_paginator context %}
+ </div>
+ {% else %}
+ <div class="line"></div>
+ {% endifnotequal %}
+ <div style="clear:both">
+ </div>
+
+ {% if not question.closed %}
+ {% if request.user.is_authenticated %}
+ <div style="padding:10px 0 0 0;">
+ <div class="headNormal">{% trans "Your answer" %}:</div>
+ </div>
+
+ <div id="description" class="" >
+ <div id="wmd-button-bar" class="wmd-panel"></div>
+ {{ answer.text }}
+ <div class="preview-toggle">
+ <table width="100%">
+ <tr>
+ <td>
+ <span id="pre-collapse"
+ title="{% trans "Toggle the real time Markdown editor preview" %}">{% trans "toggle preview" %}</span>
+ </td>
+ <td style="text-align:right;">
+ {{ answer.wiki }} <span style="font-weight:normal;cursor:help" title="{{answer.wiki.help_text}}">{{ answer.wiki.label_tag }} </span>
+ </td>
+ </tr>
+
+ </table>
+ </div>
+ <div id="previewer" class="wmd-preview"></div>
+ {{ answer.text.errors }}
+ </div>
+ <br>
+ <input type="submit" value="{% trans "Answer the question" %}" class="submit"><span class="form-error"></span>
+ {% else %}
+ <input id="btLogin" type="button" class="submit" style="width:200px" value="{% trans "Login to answer" %}">
+ {% endif %}
+ {% endif %}
+ <br><br>
+ </form>
+ </div>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <p>
+ {% trans "Question tags" %}:
+ </p>
+ <p class="tags" >
+ {% for tag in tags %}
+ <a href="{% url forum.views.tag tag.name|urlencode %}"
+ title="{% trans "see questions tagged"%}'{{tag.name}}'{% trans "using tags" %}"
+ rel="tag">{{ tag.name }}</a> <span class="tag-number">× {{ tag.used_count|intcomma }}</span><br>
+ {% endfor %}
+ </p>
+ <p>
+ {% trans "question asked" %}: <strong title="{{ question.added_at }}">{{ question.added_at|timesince }} {% trans "ago" %}</strong>
+ </p>
+ <p>
+ {% trans "question was seen" %}: <strong>{{ question.view_count|intcomma }} {% trans "times" %}</strong>
+ </p>
+ <p>
+ {% trans "last updated" %}: <strong title="{{ question.last_activity_at }}">{{ question.last_activity_at|timesince }} {% trans "ago" %}</strong>
+ </p>
+</div>
+
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Related questions" %}</h3>
+ <div class="questions-related">
+ {% for question in similar_questions %}
+ <p>
+ <a href="/questions/{{question.id}}/{{ question.get_question_title }}">{{ question.get_question_title }}</a>
+ </p>
+ {% endfor %}
+ </div>
+ <br>
+</div>
+
+{% endblock %}
+
+{% block endjs %}
+{% endblock %}
+<!-- end question.html -->
diff --git a/templates/question_edit.html b/templates/question_edit.html
new file mode 100644
index 00000000..6fe8bb41
--- /dev/null
+++ b/templates/question_edit.html
@@ -0,0 +1,134 @@
+<!-- question_edit.html -->
+{% extends "base.html" %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Edit question" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
+ <script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
+ <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type='text/javascript' src='/content/js/wmd/showdown.js'></script>
+ <script type='text/javascript' src='/content/js/wmd/wmd.js'></script>
+ <link rel="stylesheet" type="text/css" href="/content/js/wmd/wmd.css" />
+ <script type="text/javascript">
+ //todo move javascript out
+ $().ready(function(){
+ $("#nav_questions").attr('className',"on");
+ $('#editor').TextAreaResizer();
+
+ //highlight code synctax when editor has new text
+ $("#editor").typeWatch({highlight: false, wait: 3000,
+ captureLength: 5, callback: lanai.highlightSyntax});
+
+ //toggle preview of editor
+ var display = true;
+ var txt = "[{% trans "hide preview"}%]";
+ $('#pre-collapse').text(txt);
+ $('#pre-collapse').bind('click', function(){
+ txt = display ? "[{% trans "show preview" %}]" : "[{% trans "hide preview" %}]";
+ display = !display;
+ $('#previewer').toggle();
+ $('#pre-collapse').text(txt);
+ });
+
+ //Tags autocomplete action
+ var tags = {{ tags|safe }};
+ $("#id_tags").autocomplete(tags, {
+ matchContains: true,
+ max: 20,
+ multiple: true,
+ multipleSeparator: " ",
+ highlightItem: true,
+ scroll: true,
+ scrollHeight: 300,
+ formatItem: function(row, i, max) {
+ return row.n + " ("+ row.c +")";
+ },
+ formatResult: function(row, i, max){
+ return row.n;
+ }
+
+ });
+
+ setupFormValidation("#fmedit", CPValidator.getQuestionFormRules(), CPValidator.getQuestionFormMessages());
+
+ $('#id_revision').unbind().change(function(){
+ $("#select_revision").click();
+ });
+ lanai.highlightSyntax();
+
+ });
+ </script>
+{% endblock %}
+
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Edit question" %} [<a href="{{ question.get_absolute_url }}">{% trans "back" %}</a>]
+</div>
+<div id="main-body" class="ask-body">
+ <div id="askform">
+ <form id="fmedit" action="{% url edit_question question.id %}" method="post" >
+ <label for="id_revision" ><strong>{% trans "revision" %}:</strong></label> <br>
+ {% if revision_form.revision.errors %}{{ revision_form.revision.errors.as_ul }}{% endif %}
+ <div style="vertical-align:middle">
+ {{ revision_form.revision }} <input type="submit" style="display:none" id="select_revision" name="select_revision"
+ value="{% trans "select revision"%}">
+ </div><br>
+ <div class="form-item">
+ <label for="id_title" ><strong>{{ form.title.label_tag }}:</strong></label> <span class="form-error"></span><br>
+ {{ form.title }} {{ form.title.errors }}
+ <div class="title-desc">
+ {{ form.title.help_text }}
+ </div>
+ </div>
+ <div class="form-item">
+ <div id="wmd-button-bar" class="wmd-panel"></div>
+ {{ form.text }}
+ <span class="form-error"></span>
+ <div class="preview-toggle">
+ <table width="100%">
+ <tr>
+ <td>
+ <span id="pre-collapse" title="{% trans "Toggle the real time Markdown editor preview" %}">{% trans "toggle preview" %}</span>
+ </td>
+ <td style="text-align:right;">
+ {{ form.wiki }} <span style="color:#000;cursor:help" title="{{form.wiki.help_text}}">{{ form.wiki.label_tag }} </span>
+ </td>
+ </tr>
+
+ </table>
+ </div>
+ <div id="previewer" class="wmd-preview"></div>
+ <br>
+ </div>
+ <div class="form-item">
+ <strong>{{ form.tags.label_tag }}:</strong> <span class="form-error"></span><br>
+ {{ form.tags }} {{ form.tags.errors }}
+ <div class="title-desc">
+ {{ form.tags.help_text }}
+ </div>
+ </div>
+ <br>
+ <strong>{{ form.summary.label_tag }}</strong> <br>
+ {{ form.summary }} {{ form.summary.errors }}
+ <div class="title-desc">
+ {{ form.summary.help_text }}
+ </div>
+ <br>
+
+ <div class="error" ></div>
+ <input type="submit" value="{% trans "Save edit" %}" class="submit" />
+ <input type="button" value="{% trans "Cancel" %}" class="submit" onclick="history.back(-1);" />
+ <br>
+ <br>
+ </form>
+ </div>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+{% include "question_edit_tips.html" %}
+{% endblock %}
+
+{% block endjs %}
+{% endblock %}
+<!-- end question_edit.html -->
diff --git a/templates/question_edit_tips.html b/templates/question_edit_tips.html
new file mode 100644
index 00000000..4ac666a6
--- /dev/null
+++ b/templates/question_edit_tips.html
@@ -0,0 +1,51 @@
+<!-- question_edit_tips.html -->
+{% load i18n %}
+<div class="boxC">
+ <p class="subtitle darkred">{% trans "question tips" %}</p>
+ <div>
+ <ul class="list-item">
+ <li> <b>{% trans "please ask a relevant question" %}</b>
+ </li>
+ <li>
+ {% trans "please try provide enough details" %}
+ </li>
+ <li>
+ {% trans "be clear and concise" %}
+ </li>
+ </ul>
+ <a href="/faq/" target="_blank" title="{% trans "see frequently asked questions" %}" style="float:right;position:relative">faq »</a>
+ <br>
+ </div>
+</div>
+
+<div class="boxC">
+ <p class="subtitle">{% trans "Markdown tips" %}</p>
+ <ul class="list-item">
+ <li>
+ {% trans "*italic* or __italic__" %}
+ </li>
+ <li>
+ {% trans "**bold** or __bold__" %}
+ </li>
+ <li>
+ <b>{% trans "link" %}</b>:[{% trans "text" %}](http://url.com/ "{% trans "title" %}")
+
+ </li>
+
+ <li>
+ <b>{% trans "image" %}</b>:![alt {% trans "text" %}](/path/img.jpg "{% trans "title" %}")
+
+ </li>
+ <li>
+ {% trans "numbered list:" %}
+ 1. Foo
+ 2. Bar
+ </li>
+ <li>
+ {% trans "basic HTML tags are also supported" %}
+ </li>
+ </ul>
+ <a href="http://en.wikipedia.org/wiki/Markdown" target="_blank" style="float:right;position:relative">{% trans "learn more about Markdown" %} »</a>
+ <br>
+</div>
+<!-- end question_edit_tips.html -->
diff --git a/templates/questions.html b/templates/questions.html
new file mode 100644
index 00000000..21ac18ce
--- /dev/null
+++ b/templates/questions.html
@@ -0,0 +1,158 @@
+<!-- questions.html -->
+{% extends "base.html" %}
+{% load extra_tags %}
+{% load i18n %}
+{% load humanize %}
+{% load extra_filters %}
+{% block title %}{% spaceless %}{% trans "Questions" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ var tab_id = "{{ tab_id }}";
+ $("#"+tab_id).attr('className',"on");
+ $("#nav_questions").attr('className',"on");
+ Hilite.exact = false;
+ Hilite.elementid = "listA";
+ Hilite.debug_referrer = location.href;
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<div class="tabBar">
+ <div class="headQuestions">{% if searchtag %}{% trans "Found by tags" %}{% else %}{% if searchtitle %}{% trans "Found by title" %}{% else %}{% trans "All questions" %}{% endif %}{% endif %}</div>
+ <div class="tabsA">
+ <a id="latest" href="?sort=latest" class="off" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a>
+ <a id="active" href="?sort=active" class="off" title="{% trans "most recently updated questions" %}">{% trans "active" %}</a>
+ <a id="hottest" href="?sort=hottest" class="off" title="{% trans "hottest questions" %}">{% trans "hottest" %}</a>
+ <a id="mostvoted" href="?sort=mostvoted" class="off" title="{% trans "most voted questions" %}">{% trans "most voted" %}</a>
+ </div>
+</div>
+<div id="listA">
+ {% for question in questions.object_list %}
+ <div class="qstA">
+ <h2>
+ <a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a>
+ </h2>
+ <div class="stat">
+ <table>
+ <tr>
+ <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
+ <td><span class="num">{{ question.score|intcomma }}</span> </td>
+ <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
+ </tr>
+ <tr>
+ <td><span class="unit">{% trans "answers" %}</span></td>
+ <td><span class="unit">{% trans "votes" %}</span></td>
+ <td><span class="unit">{% trans "views" %}</span></td>
+ </tr>
+ </table>
+ </div>
+
+ <div class="summary">
+ {{ question.summary }}...
+ </div>
+
+ {% ifequal tab_id 'active'%}
+ {% if question.wiki %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ </div>
+ {% endif %}
+ {% else %}
+ {% if question.wiki %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.author 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ </div>
+ {% endif %}
+ {% endifequal %}
+
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+</div>
+
+{% endblock %}
+
+{% block tail %}
+
+ <div class="pager">
+ {% cnprog_paginator context %}
+
+ </div>
+ <div class="pagesize">
+ {% cnprog_pagesize context %}
+ </div>
+
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <p>
+ {% if searchtag %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num and searchtag as tagname %}
+ have total {{q_num}} questions tagged {{tagname}}
+ {% plural %}
+ have total {{q_num}} questions tagged {{tagname}}
+ {% endblocktrans %}
+ {% endif %}
+ {% if searchtitle %}
+ {% blocktrans count questions_count as cnt with questions_count|intcomma as q_num %}
+ have total {{q_num}} questions containing {{searchtitle}}
+ {% plural %}
+ have total {{q_num}} questions containing {{searchtitle}}
+ {% endblocktrans %}
+ {% endif %}
+
+ {% ifequal tab_id "latest" %}
+ <p>{% trans "latest questions info" %}</p>
+ {% endifequal %}
+
+ {% ifequal tab_id "active" %}
+ <p>{% trans "Questions are sorted by the <strong>time of last update</strong>." %}
+ {% trans "Most recently answered ones are shown first." %}</p>
+ {% endifequal %}
+
+ {% ifequal tab_id "hottest" %}
+ <p>{% trans "Questions sorted by <strong>number of responses</strong>." %}
+ {% trans "Most answered questions are shown first." %}</p>
+ {% endifequal %}
+
+ {% ifequal tab_id "mostvoted" %}
+ <p>{% trans "Questions are sorted by the <strong>number of votes</strong>." %}
+ {% trans "Most voted questions are shown first." %}</p>
+ {% endifequal %}
+
+
+ </p>
+</div>
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Related tags" %}</h3>
+ <div class="tags">
+ {% for tag in tags %}
+ <a rel="tag" title="{% trans "see questions tagged" %}'{{ tag.name }}'{% trans "using tags" %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
+ <span class="tag-number">× {{ tag.used_count|intcomma }}</span>
+ <br>
+ {% endfor %}
+ <br>
+ </div>
+</div>
+
+{% endblock %}
+<!-- end questions.html -->
diff --git a/templates/reopen.html b/templates/reopen.html
new file mode 100644
index 00000000..7ab59421
--- /dev/null
+++ b/templates/reopen.html
@@ -0,0 +1,40 @@
+<!-- reopen.html -->
+{% extends "base_content.html" %}
+{% load extra_tags %}
+{% load i18n %}
+{% load humanize %}
+{% block title %}{% spaceless %}{% trans "Reopen question" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $('#btBack').bind('click', function(){ history.back(); });
+ });
+ </script>
+{% endblock %}
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Reopen question" %}
+</div>
+<div id="main-body" style="width:100%">
+ <p>{% trans "Open the previously closed question" %}: <a href="{{ question.get_absolute_url }}"><span class="big">{{ question.get_question_title }}</span></a>
+
+ </p>
+ <p><strong>{% trans "The question was closed for the following reason " %}"{{ question.get_close_reason_display }}"{% trans "reason - leave blank in english" %} <a href="{{ question.closed_by.get_profile_url }}">{{ question.closed_by.username }}</a> {% trans "on "%} {% diff_date question.closed_at %}<font class="darkred">{% trans "date closed" %}</font>
+ </strong>
+ </p>
+
+ <form id="fmclose" action="{% url reopen question.id %}" method="post" >
+
+ <div id="" style="padding:20px 0 20px 0">
+ <input type="submit" value="{% trans "Reopen this question" %}" class="submit" />
+ <input id="btBack" type="button" value="{% trans "Cancel" %}" class="submit" />
+
+ </div>
+
+ </form>
+</div>
+{% endblock %}
+
+
+
+<!-- end reopen.html -->
diff --git a/templates/revisions_answer.html b/templates/revisions_answer.html
new file mode 100644
index 00000000..5ab12fbf
--- /dev/null
+++ b/templates/revisions_answer.html
@@ -0,0 +1,102 @@
+<!-- revisions_answer.html -->
+{% extends "base_content.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load extra_filters %}
+{% load humanize %}
+{% block title %}{% spaceless %}{% trans "Revision history" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
+ <script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
+ <script type="text/javascript">
+ //todo - take this out into .js file
+ $().ready(function(){
+ $("#nav_questions").attr('className',"on");
+ $('div.revision div[id^=rev-header-]').bind('click', function(){
+ var revId = this.id.substr(11);
+ toggleRev(revId);
+
+ });
+ lanai.highlightSyntax();
+ });
+
+ function toggleRev(id) {
+ var arrow = $("#rev-arrow-" + id);
+ var visible = arrow.attr("src").indexOf("hide") > -1;
+
+ arrow.attr("src", "/content/images/expander-arrow-" + (visible ? "show" : "hide") + ".gif");
+ $("#rev-body-" + id).slideToggle("fast");
+ }
+
+ </script>
+{% endblock %}
+
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Revision history" %} [<a href="{{ post.get_absolute_url }}">{% trans "back" %}</a>]
+</div>
+<div id="main-body" class="">
+ <div id="revisions">
+ {% for revision in revisions %}
+ <div class="revision">
+ <div id="rev-header-{{ revision.revision }}" class="header {% ifequal post.author_id revision.author_id %}author{% endifequal %}">
+ <div class="header-controls">
+ <table width="100%">
+ <tr>
+ <td width="20" style="vertical-align:middle"><img id="rev-arrow-{{ revision.revision }}" src="/content/images/expander-arrow-show.gif"></td>
+ <td width="30px" style="vertical-align:middle"><span class="revision-number" title="{% trans "revision" %} {{ revision.revision }}">{{ revision.revision }}</span></td>
+ <td width="200px" style="vertical-align:middle">
+ {% if revision.summary %}
+ <div class="summary"><span>{{ revision.summary }}<span></div>
+ {% endif %}
+ {% if request.user|can_edit_post:post %}
+ <a href="{% url edit_answer post.id %}?revision={{ revision.revision }}">{% trans "edit" %}</a>
+ {% endif %}
+
+ </td>
+ <td align="right">
+ <div class="revision-mark" >
+ <table width="100%" style="text-align:left" >
+ <tr >
+ <td colspan="2" style="padding:3px 0 3px 0">
+ {% ifequal revision.revision 1 %}
+ {% trans "asked" %} <strong title="{{ post.added_at }}">{% diff_date post.added_at %}</strong>
+ {% else %}
+ {% trans "updated" %} <strong title="{{ post.last_edited_at }}">{% diff_date revision.revised_at %}</strong>
+ {% endifequal %}
+ </td>
+
+ </tr>
+ <tr>
+ <td width="35px" style="vertical-align:bottom; ">
+ {% gravatar revision.author 32 %}
+ </td>
+ <td style="width:120px; vertical-align:top">
+ <div style="height:18px">
+ <a href="/users/{{ revision.author.id }}/{{ revision.author.username }}">{{ revision.author.username }}</a></div>
+ <div>
+ {% get_score_badge revision.author %}
+ </div>
+ </td>
+ </tr>
+
+ </table>
+ </div>
+ </td>
+ </tr>
+
+ </table>
+ </div>
+ </div>
+ <div id="rev-body-{{ revision.revision }}" class="answerbody">
+ {{ revision.diff|safe }}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+</div>
+{% endblock %}
+
+{% block endjs %}
+{% endblock %}
+<!-- end revisions_answer.html -->
diff --git a/templates/revisions_question.html b/templates/revisions_question.html
new file mode 100644
index 00000000..77a421bb
--- /dev/null
+++ b/templates/revisions_question.html
@@ -0,0 +1,103 @@
+<!-- revisions_question.html -->
+{% extends "base_content.html" %}
+<!--somehow very similar to revisions_answer.html-->
+{% load extra_tags %}
+{% load i18n %}
+{% load extra_filters %}
+{% load humanize %}
+{% block title %}{% spaceless %}{% trans "Revision history" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
+ <script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
+ <script type="text/javascript">
+ //todo - take this out into .js file
+ $().ready(function(){
+ $("#nav_questions").attr('className',"on");
+ $('div.revision div[id^=rev-header-]').bind('click', function(){
+ var revId = this.id.substr(11);
+ toggleRev(revId);
+
+ });
+ lanai.highlightSyntax();
+ });
+
+ function toggleRev(id) {
+ var arrow = $("#rev-arrow-" + id);
+ var visible = arrow.attr("src").indexOf("hide") > -1;
+
+ arrow.attr("src", "/content/images/expander-arrow-" + (visible ? "show" : "hide") + ".gif");
+ $("#rev-body-" + id).slideToggle("fast");
+ }
+ </script>
+{% endblock %}
+
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Revision history" %}[<a href="{{ post.get_absolute_url }}">{% trans "back" %}</a>]
+</div>
+<div id="main-body" class="">
+ <div id="revisions">
+ {% for revision in revisions %}
+ <div class="revision">
+ <div id="rev-header-{{ revision.revision }}" class="header {% ifequal post.author_id revision.author_id %}author{% endifequal %}">
+ <div class="header-controls">
+ <table width="100%">
+ <tr>
+ <td width="20" style="vertical-align:middle"><img id="rev-arrow-{{ revision.revision }}" src="/content/images/expander-arrow-show.gif"></td>
+ <td width="30px" style="vertical-align:middle"><span class="revision-number" title="{% trans "revision" %} {{ revision.revision }}">{{ revision.revision }}</span></td>
+ <td width="200px" style="vertical-align:middle">
+ {% if revision.summary %}
+ <div class="summary"><span>{{ revision.summary }}<span></div>
+ {% endif %}
+ {% if request.user|can_edit_post:post %}
+ <a href="{% url edit_question post.id %}?revision={{ revision.revision }}">{% trans "edit" %}</a>
+ {% endif %}
+
+ </td>
+ <td align="right">
+ <div class="revision-mark" >
+ <table width="100%" style="text-align:left" >
+ <tr >
+ <td colspan="2" style="padding:3px 0 3px 0">
+ {% ifequal revision.revision 1 %}
+ {% trans "asked" %}<strong title="{{ post.added_at }}">{% diff_date post.added_at %}</strong>
+ {% else %}
+ {% trans "updated" %}<strong title="{{ post.last_edited_at }}">{% diff_date revision.revised_at %}</strong>
+ {% endifequal %}
+ </td>
+
+ </tr>
+ <tr>
+ <td width="35px" style="vertical-align:bottom; ">
+ {% gravatar revision.author 32 %}
+ </td>
+ <td style="width:120px; vertical-align:top">
+ <div style="height:18px">
+ <a href="/users/{{ revision.author.id }}/{{ revision.author.username }}">{{ revision.author.username }}</a></div>
+ <div>
+ {% get_score_badge revision.author %}
+ </div>
+ </td>
+ </tr>
+
+ </table>
+ </div>
+ </td>
+ </tr>
+
+ </table>
+ </div>
+ </div>
+ <div id="rev-body-{{ revision.revision }}" class="diff body">
+ {{ revision.diff|safe }}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+</div>
+{% endblock %}
+
+{% block endjs %}
+{% endblock %}
+
+<!-- end revisions_question.html -->
diff --git a/templates/tags.html b/templates/tags.html
new file mode 100644
index 00000000..cb499eed
--- /dev/null
+++ b/templates/tags.html
@@ -0,0 +1,64 @@
+<!-- tags.html -->
+{% extends "base_content.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+{% block title %}{% spaceless %}{% trans "Tag list" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#nav_tags").attr('className',"on");
+ $("#ipSearchTag").focus();
+
+ var orderby = "{{ tab_id }}";
+ if(orderby != "used" && orderby != "name")
+ orderby = "used";
+ $("#sort_" + orderby).attr('className',"on");
+ $("#type-tag").attr('checked',true);
+
+ Hilite.exact = false;
+ Hilite.elementid = "searchtags";
+ Hilite.debug_referrer = location.href;
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<!-- Tabs -->
+<div class="tabBar">
+ <div class="headQuestions">{% trans "Tag list" %}</div>
+ <div class="tabsA">
+ <a id="sort_name" href="/tags/?sort=name" class="off" title="{% trans "sorted alphabetically" %}">{% trans "by name" %}</a>
+ <a id="sort_used" href="/tags/?sort=used" class="off" title="{% trans "sorted by frequency of tag use" %}">{% trans "by popularity" %}</a>
+ </div>
+</div>
+<div id="searchtags">
+<p>
+{% if stag %}
+ {% trans "All tags matching query" %} '<span class="darkred"><strong>{{ stag }}</strong></span>' {% trans "all tags - make this empty in english" %}:
+{% endif %}
+{% if not tags.object_list %}
+ <span>{% trans "Nothing found" %}</span>
+{% endif %}
+</p>
+<ul class="tagsList tags">
+{% for tag in tags.object_list %}
+ <li>
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">
+ {{ tag }}
+ </a>&nbsp;
+ <span class="tag-number">× {{ tag.used_count|intcomma }}</span>
+ <br>
+
+ </li>
+{% endfor %}
+</ul>
+</div>
+
+{% endblock %}
+{% block tail %}
+<div class="pager">
+ {% cnprog_paginator context %}
+</div>
+{% endblock %}
+<!-- end tags.html -->
diff --git a/templates/template.list b/templates/template.list
new file mode 100644
index 00000000..d4d4ec2d
--- /dev/null
+++ b/templates/template.list
@@ -0,0 +1,18 @@
+close.html
+book.html
+base_content.html
+badges.html
+badge.html
+ask.html
+answer_edit_tips.html
+answer_edit.html
+about.html
+500.html
+404.html
+pagesize.html
+logout.html
+header.html
+footer.html
+faq.html
+base.html
+question.html
diff --git a/templates/tough/faq.html b/templates/tough/faq.html
new file mode 100644
index 00000000..9b43a9ca
--- /dev/null
+++ b/templates/tough/faq.html
@@ -0,0 +1,110 @@
+{% extends "base_content.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% block title %}{% spaceless %}FAQ{% endspaceless %}{% endblock %}
+{% block forejs %}
+{% endblock %}
+{% block content %}
+<div class="headNormal">
+ {% trans "Frequently Asked Questions " %}(FAQ)
+</div>
+<div id="main-body" style="width:100%">
+
+ <h3 class="subtitle">{% trans "What kinds of questions can I ask here?" %}</h3>
+ <p>{% trans "Most importanly - questions should be <strong>relevant</strong> to this community." %}<br>
+ {% trans "Before asking the question - please make sure to use search to see whether your question has alredy been answered."%}<br>
+ </p><br>
+
+ <h3 class="subtitle">{% trans "What questions should I avoid asking?" %}</h3>
+ <p>{% trans "Please avoid asking questions that are not relevant to this community, too subjective and argumentative." %}</p>
+ </p><br>
+
+ <h3 class="subtitle">{% trans "What should I avoid in my answers?" %}</h3>
+ <p>{% trans "site title" %} {% trans "is a Q&A site, not a discussion group. Therefore - please avoid having discussions in your answers, comment facility allows some space for brief discussions." %}
+ </p><br>
+
+ <h3 class="subtitle">{% trans "Who moderates this community?" %}</h3>
+ <p>{% trans "The short answer is: <strong>you</strong>." %}<br>
+ {% trans "This website is moderated by the users." %}
+ {% trans "The reputation system allows users earn the authorization to perform a variety of moderation tasks." %}
+ </p><br>
+
+ <h3 class="subtitle">{% trans "How does reputation system work?" %}</h3>
+ <p>{% trans "Anyone can ask questions and give answers, points are not necessary for that." %}<br>
+ {% trans "As we've said before, users help running this site. Point system helps select users who can administer this community."%}
+ {% trans "Reputation points roughly measure how community trusts you. These points are given to you directly by other members of the community." %}
+ </p>
+ <p>
+ {% trans "For example, if you ask an interesting question or give a helpful answer, your input will be upvoted and you will gain more trust in the community." %}
+ {% trans "If on the other hand someone gives a misleading answer, the answer will be voted down and he/she loses some points." %}
+ {% trans "Each vote in favor will generate <strong>10</strong> points, each vote against will subtract <strong>2</strong> points." trans %}
+ {% trans "Through the votes of other people you can accumulate a maximum of <strong>200</strong> points." %}
+ {% "After accumulating certain number of points, you can do more:" %}
+ <table style="font-family:arial;" cellspacing="3" cellpadding="3">
+ <tr>
+ <th width="40px" style="text-align:right"></th>
+ <th width="300px"></th>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>15</strong></td>
+ <td>{% trans "upvote" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>15</strong></td>
+ <td>{% trans "use tags" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>50</strong></td>
+ <td>{% trans "add comments" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>100</strong></td>
+ <td>{% trans "downvote" %}</td>
+ </tr><tr>
+ <td style="text-align:right;padding-right:5px"><strong>250</strong></td>
+ <td>打开关闭自己的问题</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>500</strong></td>
+ <td>{% trans "retag questions" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>750</strong></td>
+ <td>{% trans "edit community wiki questions" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>2000</strong></td>
+ <td>{% trans "edit any answer" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>3000</strong></td>
+ <td>{% trans "open any closed question" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>5000</strong></td>
+ <td>{% trans "delete any comment" %}</td>
+ </tr>
+ <tr>
+ <td style="text-align:right;padding-right:5px"><strong>10000</strong></td>
+ <td>{% trans "delete any questions and answers and perform other moderation tasks" %}</td>
+ </tr>
+
+ </table>
+
+ </p><br>
+
+ <h3 class="subtitle">{% trans "To register, do I need to create new password?"</h3>
+ <p>{% trans "No, you don't have to. You can login through any service that supports OpenID, e.g. Google, Yahoo, AOL, etc." %}
+ <strong><a href="/account/signin">{% trans "Login now!" %}</a> »</strong>
+ </p><br>
+
+ <h3 class="subtitle">{% trans "Why other people can edit my questions/answers?" %}</h3>
+ <p> {% trans "Goal of this site is..." %} {% trans "So questions and answers can be edited like wiki pages by experienced users of this site and this improves the overall quality of the knowledge base content." %}
+ {% trans "If this approach is not for you, we respect your choice." %}
+ </p><br>
+ <h3 class="subtitle">{% trans "Still have questions?" %}</h3>
+ <p>{% "Please ask your question, help make our community better!" %} <a href="/tags/faq" class="big">{% trans "site title" %} {% trans "questions" %}</a>{% trans "." %}
+ </p>
+ <br><br>
+</div>
+{% endblock %}
diff --git a/templates/tough/question_retag.html b/templates/tough/question_retag.html
new file mode 100644
index 00000000..105428f4
--- /dev/null
+++ b/templates/tough/question_retag.html
@@ -0,0 +1,107 @@
+{% extends "base.html" %}
+{% block title %}{% spaceless %}{% trans "Revise tags" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type='text/javascript' src='/content/js/com.cnprog.editor.js'></script>
+ <script type='text/javascript' src='/content/js/com.cnprog.post.js'></script>
+ <script type='text/javascript' src='/content/js/jquery.validate.pack.js'></script>
+ <script type="text/javascript">
+
+ $().ready(function(){
+ $("#nav_questions").attr('className',"on");
+ //Tags autocomplete action
+ var tags = {{ tags|safe }};
+ $("#id_tags").autocomplete(tags, {
+ minChars: 1,
+ matchContains: true,
+ max: 20,
+ multiple: true,
+ multipleSeparator: " ",
+ formatItem: function(row, i, max) {
+ return row.n + " ("+ row.c +")";
+ },
+ formatResult: function(row, i, max){
+ return row.n;
+ }
+
+ });
+
+ $("#fmretag").validate({
+ rules: {
+ tags: {
+ required: true,
+ maxength: 105
+ }
+ },
+ messages: {
+ tags: {
+ required: ' ' + {% trans "tags are requried" %},
+ maxlength: ' ' + {% trans "up to 5 tags, less than 20 characters each" %},
+ }
+ }
+
+ });
+ lanai.highlightSyntax();
+
+ });
+ </script>
+{% endblock %}
+
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {% trans "Change tags" %} [<a href="{{ question.get_absolute_url }}">{% trans "back" %}</a>]
+</div>
+<div id="main-body" class="ask-body">
+ <div id="askform">
+ <form id="fmretag" action="{% url edit_question question.id %}" method="post" >
+ <h3>
+ {{ question.get_question_title }}
+ </h3>
+ <div id="description" class="edit-content-html">
+ {{ question.html|safe }}
+ </div>
+
+
+ <div class="form-item">
+ <strong>{{ form.tags.label_tag }}:</strong> <span class="form-error"></span><br>
+ {{ form.tags }} {{ form.tags.errors }}
+ <div class="title-desc">
+ {{ form.tags.help_text }}
+ </div>
+ </div>
+ <br>
+
+ <div class="error" ></div>
+ <input type="submit" value="{% trans "Change now" %}" class="submit" />
+ <input type="button" value="{% trans "Cancel" %}" class="submit" onclick="history.back(-1);" />
+ <br>
+ <br>
+ </form>
+ </div>
+</div>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <p class="subtitle">{% trans "Why use and modify tags?" %}</p>
+ <ul class="list-item">
+
+ <li>
+ {% trans "site title" %} {% trans "uses tags for the classification of questions %}
+ </li>
+ <li>
+ 修改完整问题需要用户的积分达到一定条件(比如:积分 >= 3000分,自己发布的问题除外),而用户积分达到比较低的时候,就可以修改问题的标签(比如:积分 >= 500, 这里指所有问题的标签)。
+
+ </li>
+ <li>
+ {% trans "tag editors receive special awards from the community" %}
+ </li>
+ </ul>
+ <a href="{% url faq %}" style="float:right;position:relative">faq »</a>
+ <br>
+</div>
+
+{% endblock %}
+
+{% block endjs %}
+{% endblock %}
+
diff --git a/templates/tough/unanswered.html b/templates/tough/unanswered.html
new file mode 100644
index 00000000..94e778db
--- /dev/null
+++ b/templates/tough/unanswered.html
@@ -0,0 +1,115 @@
+{% extends "base.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% load extra_filters %}
+{% block title %}{% spaceless %}{% trans "Unanswered questions" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#nav_unanswered").attr('className',"on");
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<div class="tabBar">
+ <span class="headQuestions">{% trans "Unanswered questions" %}</span>
+ <div class="tabsA">
+ <a id="latest" href="?sort=latest" class="on" title="{% trans "most recently asked questions" %}>{% trans "newest" %}</a>
+ </div>
+</div>
+
+<div id="listA">
+ {% for question in questions.object_list %}
+ <div class="qstA">
+ <h2><a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a></h2>
+ <div class="stat">
+ <table>
+ <tr>
+ <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
+ <td><span class="num">{{ question.score|intcomma }}</span> </td>
+ <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
+ </tr>
+ <tr>
+ <td><span class="unit">{% trans "answers" %}</span></td>
+ <td><span class="unit">{% trans "votes" %}</span></td>
+ <td><span class="unit">{% trans "views" %}</span></td>
+ </tr>
+ </table>
+ </div>
+ <div class="summary">
+ {{ question.summary }}...
+ </div>
+
+ {% ifequal tab_id 'active'%}
+ {% if question.wiki %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ </div>
+ {% endif %}
+ {% else %}
+ {% if question.wiki %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.author 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ </div>
+ {% endif %}
+ {% endifequal %}
+
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+</div>
+{% endblock %}
+
+{% block tail %}
+ <div class="pager">
+ {% cnprog_paginator context %}
+
+ </div>
+ <div class="pagesize">
+ {% cnprog_pagesize context %}
+ </div>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <p>
+ <!--todo: move this to blocktrans -->
+ {% tans "Have a total of" %}<br><div class="questions-count">{{ questions_count|intcomma }}</div>
+ <p>{% trans "number of <strong>unanswered</strong> questions" %}</p>
+ <p>问题按 <strong>问题创建时间</strong> 排序。最新加入的问题将显示在最前面。</p>
+
+ </p>
+</div>
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Related tags" %}</h3>
+ <div class="body">
+ <div class="tags">
+ {% for tag in tags %}
+ <a rel="tag" title="{% trans "see questions tagged"%}'{{ tag.name }}'{% trans "using tags" %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
+ <span class="tag-number">× {{ tag.used_count|intcomma }}</span>
+ <br>
+ {% endfor %}
+ <br>
+ </div>
+ </div>
+</div>
+
+{% endblock %}
+
diff --git a/templates/unanswered.html b/templates/unanswered.html
new file mode 100644
index 00000000..926f2ffd
--- /dev/null
+++ b/templates/unanswered.html
@@ -0,0 +1,120 @@
+<!-- unanswered.html -->
+{% extends "base.html" %}
+{% load extra_tags %}
+{% load i18n %}
+{% load humanize %}
+{% load extra_filters %}
+{% block title %}{% spaceless %}{% trans "Unanswered questions" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#nav_unanswered").attr('className',"on");
+ });
+
+ </script>
+{% endblock %}
+{% block content %}
+<div class="tabBar">
+ <span class="headQuestions">{% trans "Unanswered questions" %}</span>
+ <div class="tabsA">
+ <a id="latest" href="?sort=latest" class="on" title="{% trans "most recently asked questions" %}">{% trans "newest" %}</a>
+ </div>
+</div>
+
+<div id="listA">
+ {% for question in questions.object_list %}
+ <div class="qstA">
+ <h2><a href="{{ question.get_absolute_url }}">{{ question.get_question_title }}</a></h2>
+ <div class="stat">
+ <table>
+ <tr>
+ <td><span class="num">{{ question.answer_count|intcomma }}</span> </td>
+ <td><span class="num">{{ question.score|intcomma }}</span> </td>
+ <td><span class="num">{{ question.view_count|cnprog_intword|safe }}</span> </td>
+ </tr>
+ <tr>
+ <td><span class="unit">{% trans "answers" %}</span></td>
+ <td><span class="unit">{% trans "votes" %}</span></td>
+ <td><span class="unit">{% trans "views" %}</span></td>
+ </tr>
+ </table>
+ </div>
+ <div class="summary">
+ {{ question.summary }}...
+ </div>
+
+ {% ifequal tab_id 'active'%}
+ {% if question.wiki %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.last_activity_by 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.last_activity_by.get_profile_url }}">{{ question.last_activity_by }}</a></span>
+ <span class="score">{% get_score_badge question.last_activity_by %} </span>
+ <span class="date" title="{{ question.last_activity_at }}">{% diff_date question.last_activity_at %}</span>
+ </div>
+ {% endif %}
+ {% else %}
+ {% if question.wiki %}
+ <span class="from wiki">{% trans "community wiki" %}</span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ {% else %}
+ <div class="from">
+ {% comment %}{% gravatar question.author 24 %}{% endcomment %}
+ <span class="author"><a href="{{ question.author.get_profile_url }}">{{ question.author }}</a></span>
+ <span class="score">{% get_score_badge question.author %} </span>
+ <span class="date" title="{{ question.added_at }}">{% diff_date question.added_at %}</span>
+ </div>
+ {% endif %}
+ {% endifequal %}
+
+ <div class="tags">
+ {% for tag in question.tagname_list %}
+ <a href="{% url forum.views.tag tag|urlencode %}"
+ title="{% trans "see questions tagged" %}'{{ tag }}'{% trans "using tags" %}"
+ rel="tag">{{ tag }}
+ </a>
+ {% endfor %}
+ </div>
+ </div>
+ {% endfor %}
+</div>
+{% endblock %}
+
+{% block tail %}
+ <div class="pager">
+ {% cnprog_paginator context %}
+ </div>
+ <div class="pagesize">
+ {% cnprog_pagesize context %}
+ </div>
+{% endblock %}
+
+{% block sidebar %}
+<div class="boxC">
+ <p>
+ {% blocktrans with questions_count|intcomma as num_q %}have {{num_q}} unanswered questions{% endblocktrans %}
+ {% comment %}
+ {% trans "Have a total of" %}<br><div class="questions-count">{{ questions_count|intcomma }}</div>
+ {% endcomment %}
+ <!---<p>问题按 <strong>问题创建时间</strong> 排序。最新加入的问题将显示在最前面。</p>-->
+ </p>
+</div>
+<div class="boxC">
+ <h3 class="subtitle">{% trans "Related tags" %}</h3>
+ <div class="body">
+ <div class="tags">
+ {% for tag in tags %}
+ <a rel="tag" title="{% trans "see questions tagged"%}'{{ tag.name }}'{% trans "using tags" %}" href="{% url forum.views.tag tag.name|urlencode %}">{{ tag.name }}</a>
+ <span class="tag-number">× {{ tag.used_count|intcomma }}</span>
+ <br>
+ {% endfor %}
+ <br>
+ </div>
+ </div>
+</div>
+
+{% endblock %}
+
+<!-- end unanswered.html -->
diff --git a/templates/upfiles/1245715031297631.png b/templates/upfiles/1245715031297631.png
new file mode 100755
index 00000000..89a6aed4
--- /dev/null
+++ b/templates/upfiles/1245715031297631.png
Binary files differ
diff --git a/templates/upfiles/12457157052552259.png b/templates/upfiles/12457157052552259.png
new file mode 100755
index 00000000..89a6aed4
--- /dev/null
+++ b/templates/upfiles/12457157052552259.png
Binary files differ
diff --git a/templates/user.html b/templates/user.html
new file mode 100644
index 00000000..53a30dc0
--- /dev/null
+++ b/templates/user.html
@@ -0,0 +1,35 @@
+<!-- user.html -->
+{% extends "base_content.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% block title %}{% spaceless %}{{ page_title }}{% endspaceless %}{% endblock %}
+{% block forestyle%}
+ <style type="text/css">
+ .history-table td { padding: 5px; }
+ .user-stats-table { margin-left:50px; }
+ </style>
+{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ {% ifequal view_user request.user%}
+ $("#nav_profile").attr('className',"on");
+ {% else %}
+ $("#nav_users").attr('className',"on");
+
+ {% endifequal %}
+ });
+ </script>
+ {% block userjs %}
+ {% endblock %}
+{% endblock %}
+
+{% block content %}
+ <div id="mainbar-full">
+ {% include "user_info.html" %}
+ {% include "user_tabs.html" %}
+ {% block usercontent %}
+ {% endblock %}
+ {% include "user_footer.html" %}
+ </div>
+{% endblock %}<!-- end user.html -->
diff --git a/templates/user_edit.html b/templates/user_edit.html
new file mode 100644
index 00000000..0f927374
--- /dev/null
+++ b/templates/user_edit.html
@@ -0,0 +1,91 @@
+<!-- user_edit.html -->
+{% extends "base_content.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Edit user profile" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ $().ready(function(){
+ $("#nav_profile").attr('className',"on");
+ $("#cancel").bind('click', function(){history.go(-1);})
+ });
+ </script>
+ {% block userjs %}
+ {% endblock %}
+{% endblock %}
+{% block content %}
+<div id="main-bar" class="headNormal">
+ {{ request.user.username }} - {% trans "edit profile" %}
+</div>
+<div id="main-body" style="width:100%;padding-top:10px">
+ <form name="" action="{% url edit_user request.user.id %}" method="post">
+ <div id="left" style="float:left;width:180px">
+ {% if request.user.email %}
+ {% gravatar request.user 128 %}
+ {% else %}
+ <img src="/content/images/nophoto.png">
+ {% endif %}
+ <div style="padding:20px 0 0 20px;font-weight:bold;font-size:150%">
+ <a href="http://www.gravatar.com/" target="_blank"
+ title="gravatar {% trans "image associated with your email address" %}">{% trans "avatar" %}</a>
+ </div>
+ </div>
+
+ <div id="askform" style="float:right;width:750px;text-align:left;">
+ <h2>{% trans "Registered user" %}</h2>
+ <table class="user-details">
+ <tr>
+ <th width="100px"></th>
+ <th></th>
+ </tr>
+
+ <tr style="height:35px">
+ <td>{{ form.email.label_tag }}:</td>
+ <td>{{ form.email }} <span class="form-error"></span> {{ form.email.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td></td>
+ <td class="title-desc">{{ form.email.help_text }}</td>
+ </tr>
+ <tr style="height:35px">
+ <td>{{ form.realname.label_tag }}:</td>
+ <td>{{ form.realname }} <span class="form-error"></span> {{ form.realname.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td>{{ form.website.label_tag }}:</td>
+ <td>{{ form.website }} <span class="form-error"></span> {{ form.website.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td>{{ form.city.label_tag }}:</td>
+ <td>{{ form.city }} <span class="form-error"></span> {{ form.city.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td>{{ form.birthday.label_tag }}:</td>
+ <td>{{ form.birthday }} <span class="form-error"></span> {{ form.birthday.errors }} </td>
+ </tr>
+ <tr style="height:35px">
+ <td></td>
+ <td class="title-desc">{{ form.birthday.help_text }}</td>
+ </tr>
+ <tr style="height:10px">
+ <td colspan="2">
+ </td>
+ </tr>
+ <tr>
+ <td style="vertical-align:top">{{ form.about.label_tag }}:</td>
+ <td>{{ form.about }} <span class="form-error"></span> {{ form.about.errors }} </td>
+ </tr>
+
+ </table>
+ <div style="margin:30px 0 60px 0">
+ <input type="submit" value="{% trans "Update" %}" class="submit" >
+ <input id="cancel" type="button" value="{% trans "Cancel" %}" class="submit" >
+
+ </div>
+ </div>
+ </form>
+
+</div>
+{% endblock %}
+<!-- end user_edit.html -->
diff --git a/templates/user_favorites.html b/templates/user_favorites.html
new file mode 100644
index 00000000..d47670bd
--- /dev/null
+++ b/templates/user_favorites.html
@@ -0,0 +1,9 @@
+<!-- user_favorites.html -->
+{% extends "user.html" %}
+{% load extra_tags %}
+{% load humanize %}
+
+{% block usercontent %}
+ {% include "users_questions.html" %}
+{% endblock %}
+<!-- end user_favorites.html -->
diff --git a/templates/user_footer.html b/templates/user_footer.html
new file mode 100644
index 00000000..ee347742
--- /dev/null
+++ b/templates/user_footer.html
@@ -0,0 +1,4 @@
+<!-- user_footer.html -->
+<div id="mainbar-footer">
+
+</div><!-- end user_footer.html -->
diff --git a/templates/user_info.html b/templates/user_info.html
new file mode 100644
index 00000000..8e6dca84
--- /dev/null
+++ b/templates/user_info.html
@@ -0,0 +1,90 @@
+<!-- user_info.html -->
+{% load extra_tags %}
+{% load extra_filters %}
+{% load humanize %}
+{% load i18n %}
+<div id="subheader" class="headUser">
+ {{view_user.username}}
+
+</div>
+
+<table class="user-info-table">
+ <tr>
+ <td width="180" style="vertical-align:middle;text-align:center;">
+ <table width="100%">
+ <tr>
+ <td>
+ {% gravatar view_user 128 %}
+ </td>
+ </tr>
+ <tr>
+ <td align="center">
+ <div class="scoreNumber">{{view_user.reputation|intcomma}}</div>
+ <p><b style="color:#777;">{% trans "reputation" %}</b></p>
+ </td>
+ </tr>
+ </table>
+ </td>
+ <td width="360" style="vertical-align: top;">
+ <table class="user-details">
+ <tr height="30px">
+ <th width="130" align="left"><strong>{% trans "Registered user" %}</strong></th>
+ <th width="230" align="right">
+ {% if request.user|can_view_user_edit:view_user %}
+ <span class="user-edit-link"><a href="/users/{{ view_user.id }}/edit/">{% trans "update profile" %}</a></span>
+ {% endif %}
+ </th>
+ </tr>
+ {% if view_user.real_name %}
+ <tr>
+ <td>{% trans "real name" %}</td>
+ <td><b>{{view_user.real_name}}</b></td>
+ </tr>
+ {% endif %}
+ <tr>
+ <td>{% trans "member for" %}</td>
+ <td>{{ view_user.date_joined|timesince }}</td>
+ </tr>
+ {% if view_user.last_seen %}
+ <tr>
+ <td>{% trans "last seen" %}</td>
+ <td><strong title="{{ view_user.last_seen }}">{{ view_user.last_seen|timesince }} {% trans "ago" %}</strong></td>
+ </tr>
+ {% endif %}
+ {% if view_user.website %}
+ <tr>
+ <td>{% trans "user website" %}</td>
+ <td><a rel="nofollow" target="_blank" href="{{view_user.website}}">{{view_user.website}}</a></td>
+ </tr>
+ {% endif %}
+ {% if view_user.location %}
+ <tr>
+ <td>{% trans "location" %}</td>
+ <td>{{view_user.location}}</td>
+ </tr>
+ {% endif %}
+ {% if view_user.date_of_birth%}
+ <tr>
+ <!--todo - redo this with blocktrans -->
+ <td>{% trans "age" %}</td>
+ <td>{% get_age view_user.date_of_birth %} {% trans "age unit" %}</td>
+ </tr>
+ {% endif %}
+ {% if votes_today_left %}
+ <tr>
+ <td>{% trans "todays unused votes" %}</td>
+ <td><strong class="darkred">{{ votes_today_left }}</strong> {% trans "votes left" %}</td>
+ </tr>
+ {% endif %}
+ </table>
+ </td>
+ <td width="380">
+ <div class="user-about">
+ {% if view_user.about %}
+ {{view_user.about|safe|linebreaks}}
+ {% endif %}
+ </div>
+ </td>
+ </tr>
+</table>
+<!-- end user_info.html -->
diff --git a/templates/user_preferences.html b/templates/user_preferences.html
new file mode 100644
index 00000000..246a834e
--- /dev/null
+++ b/templates/user_preferences.html
@@ -0,0 +1,11 @@
+<!-- user_preferences.html -->
+{% extends "user.html" %}
+{% load extra_tags %}
+{% load humanize %}
+
+{% block usercontent %}
+ <div style="padding:5px;">
+ <h1>Surprise will be here soon ;-)</h1>
+ </div>
+{% endblock %}
+<!-- end user_preferences.html -->
diff --git a/templates/user_recent.html b/templates/user_recent.html
new file mode 100644
index 00000000..70c074ad
--- /dev/null
+++ b/templates/user_recent.html
@@ -0,0 +1,27 @@
+<!-- user_recent.html -->
+{% extends "user.html" %}
+{% load extra_tags %}
+{% load humanize %}
+
+{% block usercontent %}
+ <div style="padding-top:5px;font-size:13px;">
+ {% for act in activities %}
+ <div style="clear:both;line-height:20px" >
+ <div style="width:180px;float:left">{% diff_date act.time 3 %}</div>
+ <div style="width:150px;float:left">
+ <span class="user-action-{{ act.type_id }}">{{ act.type }}</span>
+ </div>
+ <div style="float:left;overflow:hidden;">
+ {% ifequal act.type_id 7 %}
+ <a href="{{act.badge.get_absolute_url}}" title="{{ act.badge.get_type_display }} : {{ act.badge.description }}" class="medal"><span class="badge{{ act.badge.type }}">&#9679;</span>&nbsp;{{ act.badge.name }}</a>
+ {% else %}
+ <span class="post-type-{{ act.type_id }}"><a href="{{ act.title_link }}">{{ act.title }}</a></span>
+ {% if act.summary %}<span class="revision-summary">{{ act.summary }}</span>{% endif %}
+ {% endifequal %}
+ <div style="height:5px"></div>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+{% endblock %}
+<!-- end user_recent.html -->
diff --git a/templates/user_reputation.html b/templates/user_reputation.html
new file mode 100644
index 00000000..7a1c7366
--- /dev/null
+++ b/templates/user_reputation.html
@@ -0,0 +1,41 @@
+<!-- user_reputation.html -->
+{% extends "user.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% block userjs %}
+ <script type='text/javascript' src='/content/js/excanvas.pack.js'></script>
+ <script type='text/javascript' src='/content/js/jquery.flot.pack.js'></script>
+
+ <script type="text/javascript">
+ $().ready(function(){
+ var d = {{ reps }};
+ var placeholder = $("#diagram");
+
+ var plot = $.plot(placeholder, [d], {
+ xaxis: { mode: "time" },
+ points: { show: true },
+ lines: { show: true }
+ });
+ });
+
+ </script>
+{% endblock %}
+{% block usercontent %}
+ <div style="padding:5px;font-size:13px;">
+ <div id="diagram" style="width:550px;height:250px;float:left">
+
+ </div>
+ <div style="float:right; width:385px; height:300px; overflow-y:auto;word-wrap:break-word;" >
+ {% for rep in reputation %}
+ <p style="clear:both">
+ <div style="float:left;text-align:right;font-family:arial;font-weight:bold;background:#eee;padding:2px 10px 2px 10px;margin-right:5px">
+ <div style="float:left;width:20px;color:green;padding-right:5px">{{ rep.positive }}</div>
+ <div style="float:left;width:20px;color:red">{{ rep.negative }}</div>
+
+ </div>
+ <a href="/questions/{{ rep.question_id }}/{{ rep.title }}">{{ rep.title }}</a> <span class="small">({{ rep.reputed_at }})</span>
+ </p>
+ {% endfor %}
+ </div>
+ </div>
+{% endblock %}<!-- end user_reputation.html -->
diff --git a/templates/user_responses.html b/templates/user_responses.html
new file mode 100644
index 00000000..45aab21b
--- /dev/null
+++ b/templates/user_responses.html
@@ -0,0 +1,23 @@
+<!-- user_responses.html -->
+{% extends "user.html" %}
+{% load extra_tags %}
+{% load humanize %}
+
+{% block usercontent %}
+ <div style="padding-top:5px;font-size:13px;">
+ {% for response in responses %}
+ <div style="clear:both;line-height:18px">
+ <div style="width:150px;float:left">{% diff_date response.time 3 %}</div>
+ <div style="width:100px;float:left"><a href="{{ response.userlink }}">{{ response.username }}</a></div>
+ <div style="float:left;overflow:hidden;width:680px">
+ <strong {% ifequal response.type "question_answered" %}class="user-action-2"{% endifequal %}{% ifequal response.type "answer_accepted" %}class="user-action-8"{% endifequal %}>{{ response.type }}</strong>:
+ <a href="{{ response.titlelink }}">{{ response.title }}</a><br>
+ {{ response.content|safe }}
+ <div style="height:10px"></div>
+ </div>
+
+ </div>
+ {% endfor %}
+ </div>
+{% endblock %}
+<!-- end user_responses.html -->
diff --git a/templates/user_stats.html b/templates/user_stats.html
new file mode 100644
index 00000000..a5be1a77
--- /dev/null
+++ b/templates/user_stats.html
@@ -0,0 +1,134 @@
+<!-- user_stats.html -->
+{% extends "user.html" %}
+{% load i18n %}
+{% load extra_tags %}
+{% load humanize %}
+ {% block usercontent %}
+
+ <a name="questions"></a>
+ <table>
+ <tr>
+ <td>
+ <div style="text-align: right;" class="count">{{questions|length}}</div>
+ </td>
+ <td>
+ <h2>{% trans "User questions" %}</h2>
+ </td>
+ </tr>
+ </table>
+ {% include "users_questions.html" %}
+ <p>&nbsp;</p>
+ <a name="answers"></a>
+ <table>
+ <tr>
+ <td>
+ <div style="text-align: right;" class="count">{{answered_questions|length}}</div>
+ </td>
+ <td >
+ <h2>{% trans "Answers" %}</h2>
+ </td>
+ </tr>
+ </table>
+ <div class="user-stats-table">
+ {% for answered_question in answered_questions %}
+ <div class="answer-summary">
+ <a title="{{answered_question.summary}}" href="/questions/{{answered_question.id}}/{{answered_question.title}}#{{answered_question.answer_id}}">
+ <div class="answer-votes {% if answered_question.accepted %}answered-accepted{% endif %}"
+ title="{% blocktrans with answered_question.vote_count as vote_count %}the answer has been voted for {{ vote_count }} times{% endblocktrans %}&#13;{% if answered_question.accepted %}{% trans "this answer has been selected as correct" %}{%endif%}">
+ {{ answered_question.vote_count }}
+ </div>
+ </a>
+ <div class="answer-link">
+ <a href="/questions/{{answered_question.id}}/{{answered_question.title}}#{{answered_question.answer_id}}">{{answered_question.title}}</a> {% if answered_question.comment_count %}<span
+ title="{% blocktrans %}the answer has been commented {{ answered_question.comment_count }} times{% endblocktrans %}">({{answered_question.comment_count}})</span>{% endif %}
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ <p>&nbsp;</p>
+ <a name="votes"></a>
+ <table>
+ <tr>
+ <td>
+ <div style="text-align: right;" class="count">{{total_votes}}</div>
+ </td>
+ <td >
+ <h2>{% trans "votes total" %}</h2>
+ </td>
+ </tr>
+ </table>
+ <div class="user-stats-table">
+ <table height="50px">
+ <tr>
+ <td width="60">
+ <img style="cursor: default;" align="absmiddle" src="/content/images/vote-arrow-up-on.png"/>
+ <span title="{% trans "user has voted up this many times" %}" class="vote-count">{{up_votes}}</span>
+
+ </td>
+ <td width="60">
+ <img style="cursor: default;" align="absmiddle" src="/content/images/vote-arrow-down-on.png"/>
+ <span title="{% trans "user voted down this many times" %}" class="vote-count">{{down_votes}}</span>
+
+ </td>
+ </tr>
+ </table>
+ </div>
+ <p>&nbsp;</p>
+ <a name="tags"></a>
+ <table>
+ <tr>
+ <td>
+ <div style="text-align: right;" class="count">{{tags|length}}</div>
+ </td>
+ <td >
+ <h2>{% trans "Tags" %}</h2>
+ </td>
+ </tr>
+ </table>
+ <div class="user-stats-table">
+ <table class="tags">
+ <tr>
+ <td width="180" valign="top">
+ {% for tag in tags%}
+ <a rel="tag"
+ title="{% blocktrans %}see other questions tagged '{{ tag }}' {% endblocktrans %}"
+ href="{% url forum.views.tag tag|urlencode %}">{{tag.name}}</a><span class="tag-number"> × {{ tag.used_count|intcomma }}</span><br>
+ {% if forloop.counter|divisibleby:"10" %}
+ </td>
+ <td width="180" valign="top">
+ {% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ </table>
+ </div>
+ <p>&nbsp;</p>
+ <a name="badges"></a>
+ <table>
+ <tr>
+ <td>
+ <div style="text-align: right;" class="count">{{total_awards}}</div>
+ </td>
+ <td >
+ <h2>{% trans "Badges" %}</h2>
+ </td>
+ </tr>
+ </table>
+ <div class="user-stats-table">
+ <table>
+ <tr>
+ <td width="180" style="line-height:35px">
+ {% for award in awards %}
+ <a href="/badges/{{award.id}}/{{award.name}}" title="{{ award.description }}" class="medal"><span class="badge{{ award.type }}">&#9679;</span>&nbsp;{{ award.name }}</a><span class="tag-number"> × {{ award.count|intcomma }}</span><br>
+ {% if forloop.counter|divisibleby:"6" %}
+ </td>
+ <td width="180" style="line-height:35px">
+ {% endif %}
+ {% endfor %}
+ </td>
+ </tr>
+ </table>
+ </div>
+
+{% endblock %}
+<!-- end user_stats.html -->
diff --git a/templates/user_tabs.html b/templates/user_tabs.html
new file mode 100644
index 00000000..03a9d111
--- /dev/null
+++ b/templates/user_tabs.html
@@ -0,0 +1,34 @@
+<!-- user_tabs.html -->
+{% load extra_filters %}
+{% load i18n %}
+<div class="tabBar">
+ <div class="tabsA">
+ <a id="stats" {% ifequal tab_name "stats" %}class="on"{% endifequal %}
+ title="{% trans "User profile" %}" href="/users/{{view_user.id}}/{{view_user.username}}?sort=stats">{% trans "overview" %}</a>
+ <a id="recent" {% ifequal tab_name "recent" %}class="on"{% endifequal %}
+ title="{% trans "recent activity" %}" href="/users/{{view_user.id}}/{{view_user.username}}?sort=recent">{% trans "recent activity" %}</a>
+ {% if request.user|is_user_self:view_user %}
+ <a id="responses" {% ifequal tab_name "responses" %}class="on"{% endifequal %}
+ title="{% trans "comments and answers to others questions" %}"
+ href="/users/{{view_user.id}}/{{view_user.username}}?sort=responses">{% trans "responses" %}</a>
+ {% endif %}
+ <a id="reputation" {% ifequal tab_name "reputation" %}class="on"{% endifequal %}
+ title="{% trans "graph of user reputation" %}"
+ href="/users/{{view_user.id}}/{{view_user.username}}?sort=reputation">{% trans "reputation history" %}</a>
+ {% if request.user|can_view_user_votes:view_user %}
+ <a id="votes" {% ifequal tab_name "votes" %}class="on"{% endifequal %}
+ title="{% trans "user vote record" %}" href="/users/{{view_user.id}}/{{view_user.username}}?sort=votes">{% trans "casted votes" %}</a>
+ {% endif %}
+ <a id="favorites" {% ifequal tab_name "favorites" %}class="on"{% endifequal %}
+ title="{% trans "questions that user selected as his/her favorite"
+ href="/users/{{view_user.id}}/{{view_user.username}}?sort=favorites">{% trans "favorites" %}</a>
+ <!--
+ {% if request.user|can_view_user_preferences:view_user %}
+ <a id="preferences" {% ifequal tab_name "preferences" %}class="on"{% endifequal %}
+ title="{% trans "user preference settings" %}"
+ href="/users/{{view_user.id}}/{{view_user.username}}?sort=preferences">{% trans "settings" %}</a>
+ {% endif %}
+ -->
+ </div>
+</div>
+<!-- end user_tabs.html -->
diff --git a/templates/user_votes.html b/templates/user_votes.html
new file mode 100644
index 00000000..ec967704
--- /dev/null
+++ b/templates/user_votes.html
@@ -0,0 +1,31 @@
+<!-- user_votes.html -->
+{% extends "user.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% load i18n %}
+
+{% block usercontent %}
+ <div style="padding-top:5px;font-size:13px;">
+ {% for vote in votes %}
+ <div style="clear:both;line-height:20px" >
+ <div style="width:150px;float:left">{% diff_date vote.voted_at 3 %}</div>
+ <div style="width:30px;float:left">
+ {% ifequal vote.vote 1 %}
+ <img src="/content/images/vote-arrow-up-on.png" title="{% trans "upvote" %}">
+ {% else %}
+ <img src="/content/images/vote-arrow-down-on.png" title="{% trans "downvote" %}">
+ {% endifequal %}
+ </div>
+ <div style="float:left;overflow:hidden;width:750px">
+ {% ifequal vote.answer_id 0 %}
+ <span class="question-title-link"><a href="/questions/{{ vote.question_id }}/{{ vote.title }}">{{ vote.title }}</a></span>
+ {% else %}
+ <span class="answer-title-link" ><a href="/questions/{{ vote.question_id }}/{{ vote.title }}#{{ vote.answer_id }}">{{ vote.title }}</a></span>
+ {% endifequal %}
+ <div style="height:5px"></div>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+{% endblock %}
+<!-- end user_votes.html -->
diff --git a/templates/users.html b/templates/users.html
new file mode 100644
index 00000000..701dbaa2
--- /dev/null
+++ b/templates/users.html
@@ -0,0 +1,73 @@
+<!-- users.html -->
+{% extends "base_content.html" %}
+{% load extra_tags %}
+{% load humanize %}
+{% load i18n %}
+{% block title %}{% spaceless %}{% trans "Users" %}{% endspaceless %}{% endblock %}
+{% block forejs %}
+ <script type="text/javascript">
+ //todo move javascript out
+ $().ready(function(){
+ $("#nav_users").attr('className',"on");
+ $("#type-user").attr('checked',true);
+ var orderby = "{{ tab_id }}";
+ $("#sort_" + orderby).attr('className',"on");
+
+ Hilite.exact = false;
+ Hilite.elementid = "main-body";
+ Hilite.debug_referrer = location.href;
+ });
+ </script>
+{% endblock %}
+{% block content %}
+<div class="tabBar">
+ <div class="headUsers">{% trans "Users" %}</div>
+ <div class="tabsA">
+ <a id="sort_reputation" href="?sort=reputation" class="off" title="{% trans "reputation" %}">{% trans "reputation" %}</a>
+ <a id="sort_newest" href="?sort=newest" class="off" title="{% trans "recent" %}">{% trans "recent" %}</a>
+ <a id="sort_last" href="?sort=last" class="off" title="{% trans "oldest" %}">{% trans "oldest" %}</a>
+ <a id="sort_user" href="?sort=user" class="off" title="{% trans "by username" %}">{% trans "by username" %}</a>
+ </div>
+</div>
+<div id="main-body" style="width:100%">
+ <p>
+ {% if suser %}
+ {% blocktrans %}users matching query {{suser}}:{% endblocktrans %}
+ {% endif %}
+
+ {% if not users.object_list %}
+ <span>{% trans "Nothing found." %}</span>
+ {% endif %}
+ </p>
+ <div class="userList">
+ <table class="list-table">
+ <tr>
+ <td class="list-td">
+ {% for user in users.object_list %}
+
+ <div class="user">
+ <ul>
+ <li class="thumb"><a href="{{ user.get_profile_url }}">{% gravatar user 32 %}</a></li>
+ <li><a href="{{ user.get_profile_url }}">{{user.username}}</a></li>
+ <li>{% get_score_badge user %}</li>
+ </ul>
+ </div>
+
+ {% if forloop.counter|divisibleby:"7" %}
+ </td>
+ <td>
+ {% endif %}
+
+ {% endfor %}
+ </td>
+ </tr>
+ </table>
+ </div>
+</div>
+{% endblock %}
+{% block tail %}
+<div class="pager">
+ {% cnprog_paginator context %}
+</div>
+{% endblock %}
+<!-- end users.html -->
diff --git a/templates/users_questions.html b/templates/users_questions.html
new file mode 100644
index 00000000..dfb87c8f
--- /dev/null
+++ b/templates/users_questions.html
@@ -0,0 +1,66 @@
+<!-- users_questions.html -->
+{% load extra_tags %}
+{% load extra_filters %}
+{% load humanize %}
+{% load i18n %}
+<div class="user-stats-table">
+ {% for question in questions%}
+ {% if question.favourite_count %}
+ {% if question.favorited_myself %}
+ <div class="favorites-count">
+ <img title="{% trans "this questions was selected as favorite" %} {{question.favourite_count}} {% trans "number of times" %}"
+ src="/content/images/vote-favorite-on.png">
+ <div><b>{{question.favourite_count|intcomma}}</b></div>
+ </div>
+ {% else %}
+ <div class="favorites-count-off">
+ <img title="{% trans "this question was selected as favorite" %}{{question.favourite_count}} {% trans "number of times" %}"
+ src="/content/images/vote-favorite-off.png">
+ <div><b>{{question.favourite_count|intcomma}}</b></div>
+ </div>
+ {% endif %}
+ {% else %}
+ <div class="favorites-empty"> </div>
+ {% endif %}
+ <div id="question-summary-{{question.id}}" class="question-summary narrow">
+ <a style="text-decoration: none;" href="/questions/{{question.id}}/{{question.get_question_title}}">
+ <div class="stats">
+ <div class="votes">
+ <div class="vote-count-post">{{question.vote_count|intcomma}}</div>
+ {% trans "votes" %}
+
+ </div>
+ <div title="{% if question.answer_accepted %}{% trans "this answer has been accepted to be correct" %}{% endif %}" class="status {% if question.answer_accepted %}answered-accepted{% endif %} {% ifequal question.answer_count 0 %}unanswered{% endifequal %}{% ifnotequal question.answer_count 0 %}answered{% endifnotequal %}">
+ <div class="answer-count-post">{{question.answer_count|intcomma}}</div>
+ {% trans "answers" %}
+
+ </div>
+ <div class="views">
+ <div class="views-count-post">{{question.view_count|cnprog_intword|safe}}</div>
+ {% trans "views" %}
+ </div>
+ </div>
+ </a>
+ <div class="summary">
+ <h3>
+ <a title="{{question.summary}}" href="/questions/{{question.id}}/{{question.title}}">{{question.title}}</a>
+ </h3>
+ <div class="tags">
+ {% convert2tagname_list question %}
+ {% for tag in question.tagnames %}
+ <!--todo - move trans below to blocktrans -->
+ <a href="{% url forum.views.tag tag|urlencode %}" title="{% trans "see questions tagged" %} '{{ tag }}' {% trans "using tags" %}" rel="tag">{{ tag }}</a>
+ {% endfor %}
+ </div>
+ <div class="started">
+ <span class="relativetime" title="{{question.last_activity_at}}">{% diff_date question.last_activity_at %}</span>
+ {% if question.la_username %}
+ <a href="/users/{{question.la_user_id}}/{{question.la_username}}">{{question.la_username}}</a> {% get_score_badge_by_details question.la_user_reputation question.la_user_gold question.la_user_silver question.la_user_bronze%}
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ <br clear="both"/>
+ {% endfor %}
+</div>
+<!-- end users_questions.html -->
diff --git a/urls.py b/urls.py
new file mode 100644
index 00000000..89b5127c
--- /dev/null
+++ b/urls.py
@@ -0,0 +1,63 @@
+import os.path
+from django.conf.urls.defaults import *
+from django.contrib import admin
+from forum.views import index
+from forum import views as app
+from forum.feed import RssLastestQuestionsFeed
+
+admin.autodiscover()
+feeds = {
+ 'rss': RssLastestQuestionsFeed
+}
+
+APP_PATH = os.path.dirname(__file__)
+urlpatterns = patterns('',
+ (r'^$', index),
+ (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.ico'}),
+ (r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.gif'}),
+ (r'^content/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': os.path.join(APP_PATH, 'templates/content').replace('\\','/')}
+ ),
+ (r'^upfiles/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': os.path.join(APP_PATH, 'templates/upfiles').replace('\\','/')}
+ ),
+ (r'^account/', include('django_authopenid.urls')),
+ (r'^signin/$', 'django_authopenid.views.signin'),
+ url(r'^about/$', app.about, name='about'),
+ url(r'^faq/$', app.faq, name='faq'),
+ url(r'^privacy/$', app.privacy, name='privacy'),
+ url(r'^logout/$', app.logout, name='logout'),
+ url(r'^answers/(?P<id>\d+)/comments/$', app.answer_comments, name='answer_comments'),
+ url(r'^answers/(?P<id>\d+)/edit/$', app.edit_answer, name='edit_answer'),
+ url(r'^answers/(?P<id>\d+)/revisions/$', app.answer_revisions, name='answer_revisions'),
+ url(r'^questions/$', app.questions, name='questions'),
+ url(r'^questions/ask/$', app.ask, name='ask'),
+ url(r'^questions/unanswered/$', app.unanswered, name='unanswered'),
+ url(r'^questions/(?P<id>\d+)/edit/$', app.edit_question, name='edit_question'),
+ url(r'^questions/(?P<id>\d+)/close/$', app.close, name='close'),
+ url(r'^questions/(?P<id>\d+)/reopen/$', app.reopen, name='reopen'),
+ url(r'^questions/(?P<id>\d+)/answer/$', app.answer, name='answer'),
+ url(r'^questions/(?P<id>\d+)/vote/$', app.vote, name='vote'),
+ url(r'^questions/(?P<id>\d+)/revisions/$', app.question_revisions, name='question_revisions'),
+ url(r'^questions/(?P<id>\d+)/comments/$', app.question_comments, name='question_comments'),
+ url(r'^questions/(?P<question_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_question_comment, name='delete_question_comment'),
+ url(r'^answers/(?P<answer_id>\d+)/comments/(?P<comment_id>\d+)/delete/$', app.delete_answer_comment, name='delete_answer_comment'),
+ #place general question item in the end of other operations
+ url(r'^questions/(?P<id>\d+)//*', app.question, name='question'),
+ (r'^tags/$', app.tags),
+ (r'^tags/(?P<tag>[^/]+)/$', app.tag),
+ (r'^users/$',app.users),
+ url(r'^users/(?P<id>\d+)/edit/$', app.edit_user, name='edit_user'),
+ url(r'^users/(?P<id>\d+)//*', app.user, name='user'),
+ url(r'^badges/$',app.badges, name='badges'),
+ url(r'^badges/(?P<id>\d+)//*', app.badge, name='badge'),
+ url(r'^messages/markread/$',app.read_message, name='read_message'),
+ # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ (r'^nimda/(.*)', admin.site.root),
+ (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}),
+ (r'^upload/$', app.upload),
+ url(r'^books/$', app.books, name='books'),
+ url(r'^books/ask/(?P<short_name>[^/]+)/$', app.ask_book, name='ask_book'),
+ url(r'^books/(?P<short_name>[^/]+)/$', app.book, name='book'),
+ url(r'^search/$', app.search, name='search'),
+)
diff --git a/utils/__init__.py b/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/utils/__init__.py
diff --git a/utils/cache.py b/utils/cache.py
new file mode 100644
index 00000000..bc1cb1af
--- /dev/null
+++ b/utils/cache.py
@@ -0,0 +1,92 @@
+"""Utilities for working with Django Models."""
+import itertools
+
+from django.contrib.contenttypes.models import ContentType
+
+from lanai.utils.lists import flatten
+
+def fetch_model_dict(model, ids, fields=None):
+ """
+ Fetches a dict of model details for model instances with the given
+ ids, keyed by their id.
+
+ If a fields list is given, a dict of details will be retrieved for
+ each model, otherwise complete model instances will be retrieved.
+
+ Any fields list given shouldn't contain the primary key attribute for
+ the model, as this can be determined from its Options.
+ """
+ if fields is None:
+ return model._default_manager.in_bulk(ids)
+ else:
+ id_attr = model._meta.pk.attname
+ return dict((obj[id_attr], obj) for obj
+ in model._default_manager.filter(id__in=ids).values(
+ *itertools.chain((id_attr,), fields)))
+
+def populate_foreign_key_caches(model, objects_to_populate, fields=None):
+ """
+ Populates caches for the given related Model in instances of objects
+ which have a ForeignKey relationship to it, specified as a list of
+ (object list, related attribute name list) two-tuples.
+
+ If a list of field names is given, only the given fields will be
+ looked up and related object caches will be populated with a dict of
+ the specified fields. Otherwise, complete model instances will be
+ retrieved.
+ """
+ # Get all related object ids for the appropriate fields
+ related_object_ids = []
+ for objects, attrs in objects_to_populate:
+ related_object_ids.append(tuple(tuple(getattr(obj, '%s_id' % attr)
+ for attr in attrs)
+ for obj in objects))
+ unique_ids = tuple(set(pk for pk in flatten(related_object_ids) if pk))
+ related_objects = fetch_model_dict(model, unique_ids, fields)
+
+ # Fill related object caches
+ for (objects, attrs), related_ids in itertools.izip(objects_to_populate,
+ related_object_ids):
+ for obj, related_ids_for_obj in itertools.izip(objects,
+ related_ids):
+ for attr, related_object in itertools.izip(attrs, (related_objects.get(pk, None)
+ for pk in related_ids_for_obj)):
+ setattr(obj, '_%s_cache' % attr, related_object)
+
+def populate_content_object_caches(generic_related_objects, model_fields=None):
+ """
+ Retrieves ``ContentType`` and content objects for the given list of
+ items which use a generic relation, grouping the retrieval of content
+ objects by model to reduce the number of queries executed.
+
+ This results in ``number_of_content_types + 1`` queries rather than
+ the ``number_of_generic_reL_objects * 2`` queries you'd get by
+ iterating over the list and accessing each item's object attribute.
+
+ If a dict mapping model classes to field names is given, only the
+ given fields will be looked up for each model specified and the
+ object cache will be populated with a dict of the specified fields.
+ Otherwise, complete model instances will be retrieved.
+ """
+ if model_fields is None:
+ model_fields = {}
+
+ # Group content object ids by their content type ids
+ ids_by_content_type = {}
+ for obj in generic_related_objects:
+ ids_by_content_type.setdefault(obj.content_type_id,
+ []).append(obj.object_id)
+
+ # Retrieve content types and content objects in bulk
+ content_types = ContentType.objects.in_bulk(ids_by_content_type.keys())
+ for content_type_id, ids in ids_by_content_type.iteritems():
+ model = content_types[content_type_id].model_class()
+ objects[content_type_id] = fetch_model_dict(
+ model, tuple(set(ids)), model_fields.get(model, None))
+
+ # Set content types and content objects in the appropriate cache
+ # attributes, so accessing the 'content_type' and 'object' attributes
+ # on each object won't result in further database hits.
+ for obj in generic_related_objects:
+ obj._object_cache = objects[obj.content_type_id][obj.object_id]
+ obj._content_type_cache = content_types[obj.content_type_id]
diff --git a/utils/html.py b/utils/html.py
new file mode 100644
index 00000000..602e1a76
--- /dev/null
+++ b/utils/html.py
@@ -0,0 +1,51 @@
+"""Utilities for working with HTML."""
+import html5lib
+from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers
+
+class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin):
+ acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big',
+ 'blockquote', 'br', 'caption', 'center', 'cite', 'code', 'col',
+ 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'font',
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd',
+ 'li', 'ol', 'p', 'pre', 'q', 's', 'samp', 'small', 'span', 'strike',
+ 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead',
+ 'tr', 'tt', 'u', 'ul', 'var')
+
+ acceptable_attributes = ('abbr', 'align', 'alt', 'axis', 'border',
+ 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'cite',
+ 'cols', 'colspan', 'datetime', 'dir', 'frame', 'headers', 'height',
+ 'href', 'hreflang', 'hspace', 'lang', 'longdesc', 'name', 'nohref',
+ 'noshade', 'nowrap', 'rel', 'rev', 'rows', 'rowspan', 'rules', 'scope',
+ 'span', 'src', 'start', 'summary', 'title', 'type', 'valign', 'vspace',
+ 'width')
+
+ allowed_elements = acceptable_elements
+ allowed_attributes = acceptable_attributes
+ allowed_css_properties = ()
+ allowed_css_keywords = ()
+ allowed_svg_properties = ()
+
+class HTMLSanitizer(tokenizer.HTMLTokenizer, HTMLSanitizerMixin):
+ def __init__(self, stream, encoding=None, parseMeta=True, useChardet=True,
+ lowercaseElementName=True, lowercaseAttrName=True):
+ tokenizer.HTMLTokenizer.__init__(self, stream, encoding, parseMeta,
+ useChardet, lowercaseElementName,
+ lowercaseAttrName)
+
+ def __iter__(self):
+ for token in tokenizer.HTMLTokenizer.__iter__(self):
+ token = self.sanitize_token(token)
+ if token:
+ yield token
+
+def sanitize_html(html):
+ """Sanitizes an HTML fragment."""
+ p = html5lib.HTMLParser(tokenizer=HTMLSanitizer,
+ tree=treebuilders.getTreeBuilder("dom"))
+ dom_tree = p.parseFragment(html)
+ walker = treewalkers.getTreeWalker("dom")
+ stream = walker(dom_tree)
+ s = serializer.HTMLSerializer(omit_optional_tags=False,
+ quote_attr_values=True)
+ output_generator = s.serialize(stream)
+ return u''.join(output_generator)
diff --git a/utils/lists.py b/utils/lists.py
new file mode 100644
index 00000000..426d9cd3
--- /dev/null
+++ b/utils/lists.py
@@ -0,0 +1,86 @@
+"""Utilities for working with lists and sequences."""
+
+def flatten(x):
+ """
+ Returns a single, flat list which contains all elements retrieved
+ from the sequence and all recursively contained sub-sequences
+ (iterables).
+
+ Examples:
+ >>> [1, 2, [3, 4], (5, 6)]
+ [1, 2, [3, 4], (5, 6)]
+
+ From http://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks
+ """
+ result = []
+ for el in x:
+ if hasattr(el, '__iter__') and not isinstance(el, basestring):
+ result.extend(flatten(el))
+ else:
+ result.append(el)
+ return result
+
+def batch_size(items, size):
+ """
+ Retrieves items in batches of the given size.
+
+ >>> l = range(1, 11)
+ >>> batch_size(l, 3)
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
+ >>> batch_size(l, 5)
+ [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
+ """
+ return [items[i:i+size] for i in xrange(0, len(items), size)]
+
+def batches(items, number):
+ """
+ Retrieves items in the given number of batches.
+
+ >>> l = range(1, 11)
+ >>> batches(l, 1)
+ [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
+ >>> batches(l, 2)
+ [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
+ >>> batches(l, 3)
+ [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
+ >>> batches(l, 4)
+ [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
+ >>> batches(l, 5)
+ [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
+
+ Initial batches will contain as many items as possible in cases where
+ there are not enough items to be distributed evenly.
+
+ >>> batches(l, 6)
+ [[1, 2], [3, 4], [5, 6], [7, 8], [9], [10]]
+ >>> batches(l, 7)
+ [[1, 2], [3, 4], [5, 6], [7], [8], [9], [10]]
+ >>> batches(l, 8)
+ [[1, 2], [3, 4], [5], [6], [7], [8], [9], [10]]
+ >>> batches(l, 9)
+ [[1, 2], [3], [4], [5], [6], [7], [8], [9], [10]]
+ >>> batches(l, 10)
+ [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]]
+
+ If there are more batches than items, empty batches will be appended
+ to the batch list.
+
+ >>> batches(l, 11)
+ [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], []]
+ >>> batches(l, 12)
+ [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [], []]
+ """
+ div, mod= divmod(len(items), number)
+ if div > 1:
+ if mod:
+ div += 1
+ return batch_size(items, div)
+ else:
+ if not div:
+ return [[item] for item in items] + [[]] * (number - mod)
+ elif div == 1 and not mod:
+ return [[item] for item in items]
+ else:
+ # mod now tells you how many lists of 2 you can fit in
+ return ([items[i*2:(i*2)+2] for i in xrange(0, mod)] +
+ [[item] for item in items[mod*2:]])