├── .gitignore ├── README.md ├── account ├── __init__.py ├── admin.py ├── locale │ └── zh_CN │ │ └── LC_MESSAGES │ │ └── django.po ├── models.py ├── templates │ └── account │ │ ├── change-password.html │ │ ├── login.html │ │ ├── reg.html │ │ ├── reset-password-confirm.html │ │ ├── reset-password-email.html │ │ ├── reset-password-subject.txt │ │ ├── reset-password.html │ │ ├── user-avatar.html │ │ ├── user-info.html │ │ ├── user-mention.html │ │ └── user-setting.html ├── tests.py ├── urls.py └── views.py ├── fairy ├── __init__.py ├── conf.py ├── middleware.py ├── settings.py ├── urls.py └── wsgi.py ├── forum ├── __init__.py ├── admin.py ├── api.py ├── locale │ └── zh_CN │ │ └── LC_MESSAGES │ │ └── django.po ├── models.py ├── templates │ └── forum │ │ ├── append.html │ │ ├── create-topic.html │ │ ├── edit-topic.html │ │ ├── index.html │ │ ├── node-all.html │ │ ├── node-view.html │ │ └── topic.html ├── templatetags │ ├── __init__.py │ └── settingsvalue.py ├── tests.py ├── urls.py └── views.py ├── locale └── zh_CN │ └── LC_MESSAGES │ └── django.po ├── manage.py ├── panel ├── __init__.py ├── admin.py ├── locale │ └── zh_CN │ │ └── LC_MESSAGES │ │ └── django.po ├── models.py ├── templates │ └── panel │ │ ├── base.html │ │ ├── index.html │ │ ├── login.html │ │ ├── node-create.html │ │ ├── node-edit.html │ │ ├── node-manage.html │ │ ├── notifications.html │ │ ├── panels-wells.html │ │ ├── tables.html │ │ ├── topic-edit.html │ │ ├── topic-manage.html │ │ ├── user-edit.html │ │ └── user-manage.html ├── tests.py ├── urls.py └── views.py ├── requirements.txt ├── static ├── css │ ├── base.css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ └── codehilite.css ├── fonts │ ├── .directory │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── image │ ├── Connect_logo_7.png │ ├── bg.png │ └── pixels.png ├── js │ ├── bootstrap.min.js │ ├── html5shiv.js │ ├── jquery.min.js │ ├── mention.js │ ├── previewer.js │ ├── respond.min.js │ └── search.js └── panel │ ├── css │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── plugins │ │ ├── dataTables │ │ │ └── dataTables.bootstrap.css │ │ ├── morris │ │ │ └── morris-0.4.3.min.css │ │ ├── social-buttons │ │ │ └── social-buttons.css │ │ └── timeline │ │ │ └── timeline.css │ └── sb-admin.css │ ├── font-awesome │ ├── css │ │ ├── font-awesome.css │ │ └── font-awesome.min.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── less │ │ ├── bordered-pulled.less │ │ ├── core.less │ │ ├── fixed-width.less │ │ ├── font-awesome.less │ │ ├── icons.less │ │ ├── larger.less │ │ ├── list.less │ │ ├── mixins.less │ │ ├── path.less │ │ ├── rotated-flipped.less │ │ ├── spinning.less │ │ ├── stacked.less │ │ └── variables.less │ └── scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _spinning.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ └── font-awesome.scss │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff │ └── js │ ├── autocomplete.js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── demo │ ├── dashboard-demo.js │ ├── flot-demo.js │ └── morris-demo.js │ ├── jquery-1.10.2.js │ ├── plugins │ ├── dataTables │ │ ├── dataTables.bootstrap.js │ │ └── jquery.dataTables.js │ ├── flot │ │ ├── excanvas.min.js │ │ ├── jquery.flot.js │ │ ├── jquery.flot.pie.js │ │ ├── jquery.flot.resize.js │ │ └── jquery.flot.tooltip.min.js │ ├── metisMenu │ │ └── jquery.metisMenu.js │ └── morris │ │ ├── morris.js │ │ └── raphael-2.1.0.min.js │ ├── sb-admin.js │ └── tabletools.js └── template ├── common ├── 403.html ├── 404.html ├── 500.html ├── base-with-sidebar.html ├── base.html └── error.html ├── pagination └── pagination.html └── widget ├── links.html ├── node-info.html ├── node.html ├── stat.html ├── topic-list.html └── user-panel.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *.swp 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | bin/ 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | venv/ 19 | var/ 20 | static/upload/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | *.sublime* 25 | 26 | # Installer logs 27 | pip-log.txt 28 | pip-delete-this-directory.txt 29 | 30 | # Unit test / coverage reports 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | .settings 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | db.sqlite3 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTICE 2 | 3 | This project is disconinued due to low code quality. 4 | 5 | A refactored version is available at [https://github.com/ericls/niji](https://github.com/ericls/niji) 6 | -------------------------------------------------------------------------------- /account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/account/__init__.py -------------------------------------------------------------------------------- /account/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from account.models import profile, social 3 | # Register your models here. 4 | admin.site.register(profile) 5 | admin.site.register(social) 6 | -------------------------------------------------------------------------------- /account/locale/zh_CN/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , 2014. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2014-07-15 14:08+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: templates/account/change-password.html:6 21 | #: templates/account/user-setting.html:29 views.py:148 22 | msgid "change password" 23 | msgstr "修改密码" 24 | 25 | #: templates/account/change-password.html:20 26 | msgid "current password" 27 | msgstr "原始密码" 28 | 29 | #: templates/account/change-password.html:26 30 | msgid "new password" 31 | msgstr "新密码" 32 | 33 | #: templates/account/change-password.html:33 34 | msgid "new password again" 35 | msgstr "重复" 36 | 37 | #: templates/account/change-password.html:42 38 | #: templates/account/user-avatar.html:29 39 | #: templates/account/user-setting.html:27 40 | msgid "update" 41 | msgstr "更新" 42 | 43 | #: templates/account/login.html:6 templates/account/login.html.py:29 44 | #: views.py:91 45 | msgid "sign in" 46 | msgstr "登陆" 47 | 48 | #: templates/account/login.html:18 templates/account/reg.html:18 49 | msgid "username" 50 | msgstr "用户名" 51 | 52 | #: templates/account/login.html:23 templates/account/reg.html:28 53 | msgid "password" 54 | msgstr "密码" 55 | 56 | #: templates/account/login.html:31 57 | msgid "reg?" 58 | msgstr "注册?" 59 | 60 | #: templates/account/login.html:32 61 | msgid "forget password?" 62 | msgstr "忘记密码?" 63 | 64 | #: templates/account/reg.html:6 views.py:58 65 | msgid "register" 66 | msgstr "注册" 67 | 68 | #: templates/account/reg.html:18 templates/account/reg.html.py:19 69 | msgid "letters,digits,underscore" 70 | msgstr "字母,数字,下划线" 71 | 72 | #: templates/account/reg.html:23 73 | msgid "Email" 74 | msgstr "Email" 75 | 76 | #: templates/account/reg.html:33 77 | msgid "repeat password" 78 | msgstr "重复密码" 79 | 80 | #: templates/account/reg.html:39 81 | #: templates/account/reset-password-confirm.html:44 82 | #: templates/account/reset-password.html:43 83 | msgid "submit" 84 | msgstr "提交" 85 | 86 | #: templates/account/reg.html:41 87 | msgid "sign in?" 88 | msgstr "已有账户?" 89 | 90 | #: templates/account/reset-password-confirm.html:5 91 | #: templates/account/reset-password-confirm.html:35 92 | #: templates/account/reset-password.html:5 93 | #: templates/account/reset-password.html:35 94 | msgid "reset password" 95 | msgstr "密码重置" 96 | 97 | #: templates/account/user-avatar.html:6 templates/account/user-setting.html:30 98 | msgid "avatar settings" 99 | msgstr "头像设置" 100 | 101 | #: templates/account/user-avatar.html:11 102 | msgid "upload new avatar(leave blank will not change your current avatar)" 103 | msgstr "上传新头像(留空不修改)" 104 | 105 | #: templates/account/user-avatar.html:13 106 | msgid "maximum 512K, png,jpg and gif only" 107 | msgstr "最大512k,png,jpg和gif格式" 108 | 109 | #: templates/account/user-avatar.html:15 110 | msgid "Use Gravatar" 111 | msgstr "使用Gravatar" 112 | 113 | #: templates/account/user-setting.html:6 114 | msgid "user settings" 115 | msgstr "用户设置" 116 | 117 | #: templates/account/user-setting.html:11 118 | msgid "website" 119 | msgstr "网站" 120 | 121 | #: views.py:49 122 | msgid "user info" 123 | msgstr "用户信息" 124 | 125 | #: views.py:52 126 | #, python-format 127 | msgid "%s's topics" 128 | msgstr "%s的话题" 129 | 130 | #: views.py:69 131 | msgid "username can only contain letters digits and underscore" 132 | msgstr "用户名只能包含字母,数字和下划线" 133 | 134 | #: views.py:73 135 | msgid "username already exists" 136 | msgstr "用户名已经存在" 137 | 138 | #: views.py:77 views.py:155 139 | msgid "passwords don't match, or are blank" 140 | msgstr "密码不匹配或为空" 141 | 142 | #: views.py:99 143 | msgid "username does not exist" 144 | msgstr "用户名不存在" 145 | 146 | #: views.py:103 147 | msgid "password is invalid" 148 | msgstr "密码不正确" 149 | 150 | #: views.py:114 views.py:123 151 | msgid "settings" 152 | msgstr "设置" 153 | 154 | #: views.py:138 155 | msgid "mentions" 156 | msgstr "提醒" 157 | 158 | #: views.py:161 159 | msgid "password updated successfully" 160 | msgstr "密码修改成功" 161 | 162 | #: views.py:164 163 | msgid "unable to change your password, the current password may be invalid" 164 | msgstr "错误,可能是原始密码不对" 165 | 166 | #: views.py:171 167 | #| msgid "avatar settings" 168 | msgid "avatar setting" 169 | msgstr "头像设置" 170 | 171 | #: views.py:181 172 | msgid "file too big" 173 | msgstr "文件太大" 174 | 175 | #: views.py:183 176 | msgid "file type not permitted" 177 | msgstr "文件类型不允许" 178 | -------------------------------------------------------------------------------- /account/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | import urllib 4 | import hashlib 5 | 6 | 7 | class profile(models.Model): 8 | user = models.OneToOneField(User) 9 | nickname = models.CharField(max_length=12, blank=True, null=True) 10 | use_gravatar = models.BooleanField(default=True) 11 | location = models.CharField(max_length=20, blank=True, null=True) 12 | avatar_url = models.URLField(blank=True, null=True) 13 | website = models.URLField(blank=True, null=True) 14 | 15 | def unread_mention(self): 16 | return self.user.received_mentions.filter(read=False) 17 | 18 | def old_mention(self): 19 | return self.user.received_mentions.filter(read=True)[0:5] 20 | 21 | def username(self): 22 | if self.nickname: 23 | return self.nickname 24 | else: 25 | return self.user.username 26 | 27 | def latest_activity(self): 28 | d = {} 29 | d['topic'] = self.user.topics.all().filter(deleted=False).order_by('-time_created')[0:10] 30 | d['post'] = self.user.posts.all().filter(deleted=False).order_by('-time_created')[0:10] 31 | return d 32 | 33 | def __unicode__(self): 34 | return self.user.username 35 | 36 | def avatar(self): 37 | da = '' # default avatar 38 | dic = {} 39 | if self.use_gravatar: 40 | mail = self.user.email.lower() 41 | gravatar_url = "http://www.gravatar.com/avatar/" 42 | base_url = gravatar_url + hashlib.md5(mail).hexdigest() + "?" 43 | dic['small'] = base_url + urllib.urlencode({'d': da, 's': '40'}) 44 | dic['middle'] = base_url + urllib.urlencode({'d': da, 's': '48'}) 45 | dic['large'] = base_url + urllib.urlencode({'d': da, 's': '80'}) 46 | return dic 47 | elif self.avatar_url: 48 | dic['small'] = self.avatar_url 49 | dic['middle'] = self.avatar_url 50 | dic['large'] = self.avatar_url 51 | return dic 52 | 53 | 54 | class social(models.Model): 55 | user = models.OneToOneField(User) 56 | access_token = models.CharField(max_length=255) 57 | openid = models.CharField(max_length=255) 58 | avatar = models.URLField() 59 | 60 | def __unicode__(self): 61 | return self.user.username 62 | 63 | -------------------------------------------------------------------------------- /account/templates/account/change-password.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% block left %} 4 |
5 |
6 |

{% trans 'change password' %}

7 |
8 |
9 | {% csrf_token %} 10 | {% if messages %} 11 |
12 | {% for message in messages %} 13 |

14 | {{ message }} 15 |

16 | {% endfor %} 17 |
18 | {% endif %} 19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 44 |
45 |
46 |
47 |
48 | {% endblock %} 49 | {% block right %} 50 | {% include 'user-panel.html' %} 51 | {% include 'node.html' %} 52 | {% include 'links.html' %} 53 | {% endblock %} 54 | -------------------------------------------------------------------------------- /account/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | {% block inside_row %} 4 |
5 |
6 |

{% trans 'sign in' %}

7 | {% csrf_token %} 8 | {% if messages %} 9 |
10 | {% for message in messages %} 11 |

12 | {{ message }} 13 |

14 | {% endfor %} 15 |
16 | {% endif %} 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 31 | {% trans 'reg?' %} 32 | {% trans 'forget password?' %} 33 |
34 |
35 |
36 | {% endblock %} -------------------------------------------------------------------------------- /account/templates/account/reg.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | {% block inside_row %} 4 |
5 |
6 |

{% trans 'register' %}

7 | {% csrf_token %} 8 | {% if messages %} 9 |
10 | {% for message in messages %} 11 |

12 | {{ message }} 13 |

14 | {% endfor %} 15 |
16 | {% endif %} 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 |
36 | 37 |
38 | 41 | {% trans 'sign in?' %} 42 |
43 |
44 |
45 | {% endblock %} -------------------------------------------------------------------------------- /account/templates/account/reset-password-confirm.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% load settingsvalue %} 4 | {% block title %} 5 | {% trans 'reset password'%}-{% conf_value "sitename" %} 6 | {% endblock %} 7 | {% block left %} 8 | 33 |
34 |
35 |

{% trans 'reset password'%}

36 |
37 | {% if validlink %} 38 |
39 | {% csrf_token %} 40 | {{ form.as_p }} 41 |
42 |
43 | 46 |
47 |
48 |
49 | {% else %} 50 |

This reset link is no longer valid!

51 | {% endif %} 52 |
53 | {% endblock %} 54 | {% block right %} 55 | {% include 'user-panel.html' %} 56 | {% endblock %} -------------------------------------------------------------------------------- /account/templates/account/reset-password-email.html: -------------------------------------------------------------------------------- 1 | password reset for email {{ email }}. Follow the link below: 2 | {{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} -------------------------------------------------------------------------------- /account/templates/account/reset-password-subject.txt: -------------------------------------------------------------------------------- 1 | Password Reset 2 | -------------------------------------------------------------------------------- /account/templates/account/reset-password.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% block title %} 3 | {% load i18n %} 4 | {% load settingsvalue %} 5 | {% trans 'reset password'%}-{% conf_value "sitename" %} 6 | {% endblock %} 7 | {% block left %} 8 | 33 |
34 |
35 |

{% trans 'reset password'%}

36 |
37 |
38 | {% csrf_token %} 39 | {{ form.as_p }} 40 |
41 |
42 | 45 |
46 |
47 |
48 |
49 | {% endblock %} 50 | {% block right %} 51 | {% include 'user-panel.html' %} 52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /account/templates/account/user-avatar.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% block left %} 4 |
5 |
6 |

{% trans 'avatar settings' %}

7 |
8 |
9 | {% csrf_token %} 10 |
11 | 12 | 13 |

{% trans 'maximum 512K, png,jpg and gif only' %}

14 |
15 | {% trans "Use Gravatar" as use_gravatar %} 16 |
17 | 24 |
25 |
26 |
27 |
28 | 31 |
32 |
33 |
34 |
35 | {% endblock %} 36 | {% block right %} 37 | {% include 'user-panel.html' %} 38 | {% include 'node.html' %} 39 | {% include 'links.html' %} 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /account/templates/account/user-info.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load humanize %} 3 | {% block left %} 4 |
5 |
6 |
7 | 8 |
9 |
10 |

{{user.profile.username}}

11 |

#{{user.id}} @ {{user.date_joined|naturaltime}}

12 | {% if user.profile.website %} 13 |

{{user.profile.website}}

14 | {% endif %} 15 |
16 |
17 |
18 | {% include 'topic-list.html' %} 19 | {% endblock %} 20 | {% block right %} 21 | {% include 'user-panel.html' %} 22 | {% include 'node.html' %} 23 | {% include 'links.html' %} 24 | {% endblock %} -------------------------------------------------------------------------------- /account/templates/account/user-mention.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% block left %} 3 | {% load humanize %} 4 | 5 |
6 |
7 |

8 | Unread Mentions 9 |

10 |
11 | 36 |
37 | 38 |
39 |
40 |

41 | Old Mentions 42 |

43 |
44 | 69 |
70 | 71 | 72 | {% endblock %} 73 | {% block right %} 74 | {% include 'user-panel.html' %} 75 | {% include 'node.html' %} 76 | {% include 'links.html' %} 77 | {% endblock %} 78 | 79 | -------------------------------------------------------------------------------- /account/templates/account/user-setting.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% block left %} 4 |
5 |
6 |

{% trans 'user settings' %}

7 |
8 |
9 | {% csrf_token %} 10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 29 | {% trans 'change password' %} 30 | {% trans 'avatar settings' %} 31 |
32 |
33 |
34 |
35 | {% endblock %} 36 | {% block right %} 37 | {% include 'user-panel.html' %} 38 | {% include 'node.html' %} 39 | {% include 'links.html' %} 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /account/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /account/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | urlpatterns = patterns('account.views', 4 | url(r'^(?P\d+)/info/$', 'user_info', name='user_info'), 5 | url(r'^reg/$', 'reg', name='reg'), 6 | url(r'^signin/$', 'user_login', name='signin'), 7 | url(r'^setting/$', 'setting', name='user_setting'), 8 | url(r'^signout/$', 'user_logout', name='signout'), 9 | url(r'^mention/$', 'view_mention', name='mention'), 10 | url(r'^oauth/qq/$', 'qq_oauth', name='qq_oauth'), 11 | url(r'password/$', 'change_password', name='change_password'), 12 | url(r'^avatar/$', 'user_avatar', name='user_avatar'), 13 | url(r'^reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', 14 | 'reset_confirm', name='password_reset_confirm'), 15 | url(r'^reset/$', 'reset', name='password_reset'), 16 | # url(r'^set_lang/$', 'set_lang', name='set_lang'), 17 | ) 18 | -------------------------------------------------------------------------------- /fairy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/fairy/__init__.py -------------------------------------------------------------------------------- /fairy/conf.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | from account.models import profile 3 | from fairy import settings 4 | from forum.models import node, topic, post 5 | import os 6 | sitename = u'FairyBBS' 7 | logoname = u'FairyBBS' 8 | 9 | links = { 10 | #'description': 'url', 11 | } 12 | nodes = node.objects.all() 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | UPLOAD_PATH = os.path.join(BASE_DIR, 'static/upload') 15 | user_count = profile.objects.count() 16 | topic_count = topic.objects.filter(deleted=False).count() 17 | post_count = post.objects.filter(deleted=False).count() 18 | 19 | site_off = False 20 | -------------------------------------------------------------------------------- /fairy/middleware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from fairy.conf import site_off 3 | from forum.views import error 4 | from django.core.urlresolvers import reverse 5 | 6 | class SiteOff(object): 7 | 8 | def process_request(self, request): 9 | if (site_off) and (request.path != reverse('signin')) and (not request.user.is_superuser): 10 | return error(request, 'down for maintenace') 11 | -------------------------------------------------------------------------------- /fairy/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Django settings for fairy project. 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = 'SECRET_KEY!!!!!!!!!!' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [''] 28 | 29 | 30 | # Application definition 31 | 32 | INSTALLED_APPS = ( 33 | 'account', 34 | 'forum', 35 | 'pagination', 36 | 'panel', 37 | 'django.contrib.humanize', 38 | 'django.contrib.admin', 39 | 'django.contrib.auth', 40 | 'django.contrib.contenttypes', 41 | 'django.contrib.sessions', 42 | 'django.contrib.messages', 43 | 'django.contrib.staticfiles', 44 | #'south', 45 | ) 46 | 47 | MIDDLEWARE_CLASSES = ( 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'django.middleware.common.CommonMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.middleware.locale.LocaleMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'fairy.middleware.SiteOff', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | 'pagination.middleware.PaginationMiddleware', 57 | ) 58 | 59 | ROOT_URLCONF = 'fairy.urls' 60 | 61 | WSGI_APPLICATION = 'fairy.wsgi.application' 62 | 63 | 64 | # Database 65 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 66 | 67 | DATABASES = { 68 | 'default': { 69 | 'ENGINE': 'django.db.backends.sqlite3', 70 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 71 | 'USER': '', 72 | 'PASSWORD': '', 73 | } 74 | } 75 | 76 | # Internationalization 77 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 78 | 79 | LANGUAGE_CODE = 'en-us' 80 | 81 | TIME_ZONE = 'Asia/Shanghai' 82 | 83 | USE_I18N = True 84 | 85 | USE_L10N = True 86 | 87 | USE_TZ = True 88 | 89 | 90 | # Static files (CSS, JavaScript, Images) 91 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 92 | 93 | STATIC_URL = '/static/' 94 | 95 | STATIC_ROOT = os.path.join(BASE_DIR, '/static/') 96 | 97 | STATICFILES_DIRS = ( 98 | os.path.join(BASE_DIR, "static"), 99 | ) 100 | 101 | TEMPLATE_DIRS = ( 102 | os.path.join(BASE_DIR, 'template'), 103 | os.path.join(BASE_DIR, 'template/common'), 104 | os.path.join(BASE_DIR, 'template/widget'), 105 | ) 106 | 107 | MAX_UPLOAD_SIZE = "524288" 108 | 109 | EMAIL_USE_TLS = True 110 | EMAIL_HOST = '' 111 | EMAIL_PORT = 465 112 | EMAIL_HOST_USER = '' 113 | EMAIL_HOST_PASSWORD = '' 114 | 115 | DEFAULT_FROM_EMAIL = EMAIL_HOST_USER 116 | SERVER_EMAIL = EMAIL_HOST_USER 117 | 118 | LOCALE_PATHS = (os.path.join(BASE_DIR, 'locale'),) 119 | -------------------------------------------------------------------------------- /fairy/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from fairy import settings 3 | from django.contrib import admin 4 | from django.conf.urls.static import static 5 | import forum 6 | 7 | admin.autodiscover() 8 | 9 | urlpatterns = patterns('', 10 | # Examples: 11 | # url(r'^$', 'fairy.views.home', name='home'), 12 | # url(r'^blog/', include('blog.urls')), 13 | 14 | url(r'^admin/', include(admin.site.urls)), 15 | url(r'', include('forum.urls')), 16 | url(r'^user/', include('account.urls')), 17 | url(r'^api/forum/', include(forum.urls.api_urlpatterns)), 18 | url(r'^panel/', include('panel.urls', namespace='panel', app_name='panel')), 19 | ) 20 | 21 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 22 | -------------------------------------------------------------------------------- /fairy/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for fairy project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fairy.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /forum/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/forum/__init__.py -------------------------------------------------------------------------------- /forum/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from forum.models import topic, mention, notification, post, node 3 | # Register your models here. 4 | 5 | 6 | class post_inline(admin.StackedInline): 7 | model = post 8 | exclude = ['user', 'content_rendered'] 9 | extra = 0 10 | 11 | 12 | class topic_admin(admin.ModelAdmin): 13 | list_display = ('title', 'time_created', 'user', 'click', 'last_replied') 14 | list_filter = ('time_created',) 15 | search_fields = ['title', 'user__username'] 16 | inlines = [post_inline] 17 | 18 | admin.site.register(topic, topic_admin) 19 | admin.site.register(node) 20 | -------------------------------------------------------------------------------- /forum/api.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.core.files.storage import FileSystemStorage 3 | from fairy.settings import BASE_DIR 4 | from forum.models import topic, post, node 5 | import json 6 | import os 7 | 8 | UPLOAD_TO = os.path.join(BASE_DIR, 'static/upload') 9 | storage = FileSystemStorage( 10 | location=UPLOAD_TO, 11 | base_url='static/upload' 12 | ) 13 | 14 | 15 | def topic_data(topic_id): 16 | t = topic.objects.get(id=topic_id) 17 | data=dict(id=t.id, 18 | title=t.title, 19 | content=t.content, 20 | create_time=t.time_created.isoformat(), 21 | user=dict(id=t.user.id, 22 | username=t.user.username), 23 | reply_count=t.reply_count, 24 | node_id=t.node.id, 25 | node_name=t.node.title, 26 | reply_id=[p.id for p in t.post_set.all()]) 27 | return data 28 | 29 | 30 | def post_api(request ,post_id): 31 | p = post.objects.get(id=post_id) 32 | data = dict(id=p.id, 33 | content=p.content, 34 | topic_id=p.topic.id, 35 | user=dict(id=p.user.id, 36 | username=p.user.username), 37 | create_time=p.time_created.isoformat()) 38 | return HttpResponse(json.dumps(data)) 39 | 40 | 41 | def topics_api(request): 42 | data = {} 43 | for t in topic.objects.all()[:30]: 44 | data[str(t.id)] = topic_data(t.id) 45 | return HttpResponse(json.dumps(data)) 46 | 47 | 48 | def topic_api(request, topic_id): 49 | data = topic_data(topic_id) 50 | return HttpResponse(json.dumps(data)) 51 | 52 | 53 | def simditor_upload(request): 54 | f = request.FILES['upload_file'] 55 | name = storage.save(storage.get_available_name(f.name), f) 56 | url = storage.url(name) 57 | data = {} 58 | data['file_path'] = url 59 | return HttpResponse(json.dumps(data), content_type="application/json") 60 | -------------------------------------------------------------------------------- /forum/locale/zh_CN/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , 2014. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2014-07-06 13:28+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: templates/forum/append.html:7 21 | #, python-format 22 | msgid "" 23 | "\n" 24 | "\t\t\tcreate appendix to topic:%(topic)s \n" 26 | "\t\t\t" 27 | msgstr "" 28 | "\n" 29 | "\t\t\t创建主题 %(topic)s 的附言 9 | {% endblocktrans %} 10 |

11 | 12 |
13 | {% if messages %} 14 |
15 | {% for message in messages %} 16 |

17 | {{ message }} 18 |

19 | {% endfor %} 20 |
21 | {% endif %} 22 |
23 | {% csrf_token %} 24 |
25 | 26 | 27 |
28 |
29 | {% trans 'preview' %} 30 | 33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 | {% endblock %} 41 | {% block right %} 42 | {% include 'node-info.html' %} 43 | {% include 'user-panel.html' %} 44 | {% include 'node.html' %} 45 | {% include 'links.html' %} 46 | {% endblock %} 47 | {% block footer_ext %} 48 | 49 | {% endblock %} -------------------------------------------------------------------------------- /forum/templates/forum/create-topic.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% block left %} 4 |
5 |
6 | {% trans 'home' %} 7 | {{node.title}} 8 | {% trans 'new topic' %} 9 |
10 |
11 | {% if messages %} 12 |
13 | {% for message in messages %} 14 |

15 | {{ message }} 16 |

17 | {% endfor %} 18 |
19 | {% endif %} 20 | {% if request.user.is_authenticated %} 21 |
22 | {% csrf_token %} 23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | {% trans 'preview' %} 34 | 35 | 38 |
39 |
40 |
41 | 42 |
43 | {% else %} 44 |
45 | {% trans 'please sign in before post ' %} 46 |
47 | {% trans 'sign in' %} 48 | {% endif %} 49 |
50 |
51 | {% endblock %} 52 | {% block right %} 53 | {% include 'node-info.html' %} 54 | {% include 'user-panel.html' %} 55 | {% include 'node.html' %} 56 | {% include 'links.html' %} 57 | {% endblock %} 58 | {% block footer_ext %} 59 | 60 | {% endblock %} -------------------------------------------------------------------------------- /forum/templates/forum/edit-topic.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% block left %} 4 |
5 |
6 |

7 | {% trans 'edit topic' %}-{{topic.title}} 8 |

9 |
10 |
11 | {% if messages %} 12 |
13 | {% for message in messages %} 14 |

15 | {{ message }} 16 |

17 | {% endfor %} 18 |
19 | {% endif %} 20 | {% if request.user.is_authenticated %} 21 |
22 | {% csrf_token %} 23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | {% trans 'preview' %} 34 | 35 | 38 |
39 |
40 | 41 |
42 |
43 | {% endif %} 44 |
45 |
46 | {% endblock %} 47 | {% block right %} 48 | {% include 'user-panel.html' %} 49 | {% include 'links.html' %} 50 | {% endblock %} 51 | {% block footer_ext %} 52 | 53 | {% endblock %} -------------------------------------------------------------------------------- /forum/templates/forum/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% block header_ext%} 3 | 4 | 5 | {% endblock %} 6 | {% block left %} 7 | {% include 'topic-list.html' %} 8 | {% endblock %} 9 | {% block right %} 10 | {% include 'user-panel.html' %} 11 | {% include 'node.html' %} 12 | {% include 'links.html' %} 13 | {% include 'stat.html' %} 14 | {% endblock %} -------------------------------------------------------------------------------- /forum/templates/forum/node-all.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% block left %} 4 |
5 |
6 |

{% trans 'node navigation' %}

7 |
8 |
9 |
10 | {% for category,node_list in nodes.items %} 11 | 12 | 13 | 14 | 15 | 24 | 25 | 26 |
{{category}} 16 | {% for node in node_list %} 17 | 18 | 19 | {{ node.title }} 20 | 21 | 22 | {% endfor %} 23 |
27 | {% endfor %} 28 |
29 |
30 |
31 | {% endblock %} 32 | {% block right %} 33 | {% include 'user-panel.html' %} 34 | {% include 'node.html' %} 35 | {% include 'links.html' %} 36 | {% endblock %} 37 | {% block footer_ext %} 38 | 39 | 40 | 41 | {% endblock %} -------------------------------------------------------------------------------- /forum/templates/forum/node-view.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% block left %} 3 | {% include 'topic-list.html' %} 4 | {% endblock %} 5 | {% block right %} 6 | {% include 'node-info.html' %} 7 | {% include 'user-panel.html' %} 8 | {% include 'node.html' %} 9 | {% include 'links.html' %} 10 | {% endblock %} 11 | {% block footer_ext %} 12 | 13 | 21 | {% endblock %} -------------------------------------------------------------------------------- /forum/templates/forum/topic.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% block left %} 4 | {% load humanize %} 5 | {% load pagination_tags %} 6 | {% load settingsvalue %} 7 |
8 |
9 |
10 | 11 |
12 |

13 | {% conf_value "logoname" %}{{topic.node.title}} 14 |

15 |

{{topic.title}}

16 |

17 | {{topic.node.title}} 18 | {{topic.click}} 19 | {{topic.time_created | naturaltime}} 20 | {{topic.user.profile.username}} 21 | {% if request.user == topic.user%} 22 | {% trans 'add appendix' %} 23 | {% endif %} 24 | {% if request.user.is_superuser or topic.user == request.user %} 25 | {% trans 'edit' %} 26 | {% trans 'delete' %} 27 | {% endif %} 28 |

29 |
30 |
31 |
32 |
33 | {{topic.content_rendered | safe}} 34 |
35 | {% if topic.appendix_set.all %} 36 |
    37 | {% for a in topic.appendix_set.all %} 38 |
    39 |

    40 | {% blocktrans with number=forloop.counter %}appendix {{number}}{% endblocktrans %} {{a.time_created|naturaltime}} 41 |

    42 | {{a.content_rendered | safe}} 43 |
    44 | {% endfor %} 45 |
46 | {% endif %} 47 |
48 | 49 |
50 |
51 |

52 | {% if topic.reply_count %} 53 | {% blocktrans count reply_count=topic.reply_count %} 54 | {{reply_count}} reply 55 | {% plural %} 56 | {{reply_count}} replies 57 | {% endblocktrans %} 58 | {% else %} 59 | {% trans 'no replies yet' %} 60 | {% endif %} 61 |

62 |
63 | {% if topic.reply_count != 0 %} 64 |
    65 | {% autopaginate posts 30 %} 66 | {% for r in posts %} 67 |
    68 |
    69 | 70 |
    71 |
    72 |
    73 | {{r.user.profile.username}} 74 | {{r.time_created | naturaltime}} 75 | {% if request.user.is_superuser %} 76 | {% trans 'delete' %} 77 | {% endif %} 78 |
    79 |
    80 | {% if request.user.is_authenticated %} 81 | reply 82 | {% endif %} 83 |
    84 |
    85 |
    86 |
    87 | {{r.content_rendered | safe}} 88 |
    89 |
    90 |
    91 |
    92 |
    93 | {% endfor %} 94 |
95 | 98 | {% else %} 99 |
100 |
101 | {% trans 'no replies yet' %} 102 |
103 |
104 | {% endif %} 105 |
106 | 107 |
108 |
109 |

110 | {% trans 'reply' %} 111 |

112 |
113 |
114 | {% if messages %} 115 |
116 | {% for message in messages %} 117 |

118 | {{ message }} 119 |

120 | {% endfor %} 121 |
122 | {% endif %} 123 | {% if request.user.is_authenticated %} 124 |
125 | {% csrf_token %} 126 |
127 | 128 | 129 |
130 | 131 |
132 | {% trans 'preview' %} 133 | 136 |
137 |
138 |
139 | 140 |
141 | {% else %} 142 |
143 | {% trans 'please sign in before reply' %} 144 |
145 | {% trans 'sign in' %} 146 | {% endif %} 147 |
148 | 149 |
150 | {% endblock %} 151 | {% block right %} 152 | {% include 'node-info.html' %} 153 | {% include 'user-panel.html' %} 154 | {% include 'node.html' %} 155 | {% include 'links.html' %} 156 | {% endblock %} 157 | {% block footer_ext %} 158 | 159 | 160 | {% endblock %} 161 | -------------------------------------------------------------------------------- /forum/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/forum/templatetags/__init__.py -------------------------------------------------------------------------------- /forum/templatetags/settingsvalue.py: -------------------------------------------------------------------------------- 1 | __author__ = 'TianShuo' 2 | from django import template 3 | from fairy import settings 4 | from fairy import conf 5 | 6 | register = template.Library() 7 | 8 | 9 | ''' 10 | usage: 11 | first 12 | {% load settingsvalue %} 13 | then 14 | {% settings_value "MAX_UPLOAD_SIZE" %} 15 | or 16 | {% settings_value "logoname" %} 17 | 18 | ''' 19 | 20 | @register.simple_tag 21 | def settings_value(name): 22 | return getattr(settings, name, "") 23 | 24 | @register.simple_tag 25 | def conf_value(name): 26 | return getattr(conf, name, "") 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /forum/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /forum/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | 4 | api_urlpatterns = patterns('forum.api', 5 | url(r'^topic/(?P\d+)/$', 'topic_api', name='topic_api'), 6 | url(r'^topics/$', 'topics_api', name='topics_api'), 7 | url(r'^post/(?P\d+)/$', 'post_api', name='post_api'), 8 | url(r'^/simditor-upload/$', 'simditor_upload', name='simditor_upload'), 9 | ) 10 | 11 | 12 | urlpatterns = patterns('forum.views', 13 | url(r'^$', 'index', name='index'), 14 | 15 | url(r'^topic/(?P\d+)/$', 'topic_view', name='topic_view'), 16 | url(r'^topic/(?P\d+)/reply/$', 'create_reply', name='create_reply'), 17 | url(r'^topic/(?P\d+)/append/$', 'add_appendix', name='add_appendix'), 18 | url(r'^topic/(?P\d+)/delete/$', 'del_topic', name='delete_topic'), 19 | url(r'^topic/(?P\d+)/edit/$', 'edit_topic', name='edit_topic'), 20 | 21 | url(r'^post/(?P\d+)/delete/$', 'del_reply', name='delete_post'), 22 | 23 | url(r'^node/$', 'node_all', name='node_all'), 24 | url(r'^node/(?P\d+)/$', 'node_view', name='node_view'), 25 | url(r'^node/(?P\d+)/create/$', 'create_topic', name='create_topic'), 26 | 27 | url(r'^search/(?P.*?)/$', 'search', name='search'), 28 | 29 | url(r'^recent/$', 'recent', name='recent'), 30 | 31 | url(r'^previewer/$', 'previewer', name='previewer'), 32 | ) 33 | -------------------------------------------------------------------------------- /forum/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from account.models import profile 3 | from django.contrib.admin.views.decorators import staff_member_required 4 | from django.contrib.auth.models import User 5 | from django.contrib import messages 6 | from django.core.context_processors import csrf 7 | from django.utils.translation import ugettext as _ 8 | from django.core.urlresolvers import reverse 9 | from django.db.models import Q 10 | from django.http import HttpResponseRedirect, HttpResponse 11 | from django.shortcuts import render_to_response 12 | from django.template import RequestContext 13 | from fairy import conf 14 | from forum.models import topic, post, node, appendix 15 | import json 16 | import markdown 17 | import operator 18 | # Create your views here. 19 | 20 | 21 | def error(request, msg, back=None): 22 | return render_to_response('error.html', {'conf': conf, 'title': _('notice'), 23 | 'msg': msg, 24 | 'back': back, 25 | 'request': request, }) 26 | 27 | 28 | def previewer(request): 29 | c = request.REQUEST['content'] 30 | md = {} 31 | md['marked'] = markdown.markdown(c, ['codehilite'], safe_mode='escape') 32 | return HttpResponse(json.dumps(md)) 33 | 34 | 35 | def index(request): 36 | conf.nodes = node.objects.all() 37 | conf.user_count = profile.objects.count() 38 | conf.topic_count = topic.objects.count() 39 | conf.post_count = post.objects.count() 40 | topics = topic.objects.all().filter(deleted=False).order_by('-last_replied')[0:30] 41 | post_list_title = _('latest topics') 42 | return render_to_response('forum/index.html', {'topics': topics, 'title': _('home'), 43 | 'request': request, 44 | 'post_list_title': post_list_title, 45 | 'conf': conf}) 46 | 47 | 48 | def topic_view(request, topic_id): 49 | t = topic.objects.get(id=topic_id) 50 | t.click += 1 51 | t.save() 52 | n = t.node 53 | posts = t.post_set.filter(deleted=False) 54 | try: 55 | page = request.GET['page'] 56 | except: 57 | page = None 58 | if page == '1': 59 | page = None 60 | return render_to_response('forum/topic.html', {'conf': conf, 'title': t.title, 61 | 'request': request, 62 | 'topic': t, 63 | 'node': n, 64 | 'pager': page, 65 | 'posts': posts 66 | }, 67 | context_instance=RequestContext(request)) 68 | 69 | 70 | def create_reply(request, topic_id): 71 | if request.method == 'POST': 72 | t = topic.objects.get(id=topic_id) 73 | r = post() 74 | r.topic = t 75 | if request.POST['content']: 76 | r.content = request.POST['content'] 77 | else: 78 | messages.add_message(request, messages.WARNING, _('content cannot be empty')) 79 | return HttpResponseRedirect(reverse('topic_view', kwargs={'topic_id':topic_id})) 80 | r.user = request.user 81 | r.save() 82 | return HttpResponseRedirect(reverse('topic_view', kwargs={'topic_id': t.id})) 83 | elif request.method == 'GET': 84 | return error(request, 'don\'t get') 85 | 86 | 87 | def node_view(request, node_id): 88 | try: 89 | page = request.GET['page'] 90 | except: 91 | page = None 92 | if page == '1': 93 | page = None 94 | n = node.objects.get(id=node_id) 95 | topics = topic.objects.filter(node=n,deleted=False) 96 | return render_to_response('forum/node-view.html', {'request': request, 'title': n.title, 97 | 'conf': conf, 98 | 'topics': topics, 99 | 'node': n, 100 | 'node_view': True, 101 | 'pager': page,}) 102 | 103 | 104 | def create_topic(request, node_id): 105 | n = node.objects.get(id=node_id) 106 | if request.method == 'GET': 107 | return render_to_response('forum/create-topic.html', {'node': n, 'title': _('create topic'), 108 | 'request': request, 109 | 'conf': conf}, 110 | context_instance=RequestContext(request)) 111 | elif request.method == 'POST': 112 | t = topic() 113 | t.content = request.POST.get('content') or '' 114 | t.node = n 115 | t.title = request.POST['title'] 116 | if not t.title: 117 | messages.add_message(request, messages.WARNING, _('title cannot be empty')) 118 | return HttpResponseRedirect(reverse('create_topic', kwargs={'node_id':node_id})) 119 | if not request.user.is_authenticated(): 120 | return error(request, '请登陆', reverse('signin')) 121 | t.user = request.user 122 | t.save() 123 | return HttpResponseRedirect(reverse('topic_view', 124 | kwargs={'topic_id': t.id})) 125 | 126 | 127 | def search(request, keyword): 128 | keys = keyword.split(' ') 129 | condition = reduce(operator.and_, 130 | (Q(title__contains=x) for x in keys)) 131 | topics = topic.objects.filter(condition) 132 | try: 133 | page = request.GET['page'] 134 | except: 135 | page = None 136 | if page == '1': 137 | page = None 138 | return render_to_response('forum/index.html', {'request': request, 'title': _('%s-search result') % (keyword), 139 | 'conf': conf, 'pager': page, 140 | 'topics': topics, 141 | 'post_list_title': _('search %s') % (keyword), }) 142 | 143 | 144 | def recent(request): 145 | try: 146 | page = request.GET['page'] 147 | except: 148 | page = None 149 | if page == '1': 150 | page = None 151 | topics = topic.objects.all().filter(deleted=False) 152 | return render_to_response('forum/index.html', {'request': request, 'title': _('latest topics'), 153 | 'conf': conf, 154 | 'topics': topics, 155 | 'recent': 'reccent', 156 | 'pager': page, 157 | 'post_list_title': _('latest posted topics'), }) 158 | 159 | 160 | @staff_member_required 161 | def del_reply(request, post_id): 162 | p = post.objects.get(id=post_id) 163 | t_id = p.topic.id 164 | p.deleted = True 165 | p.save() 166 | p.topic.save() 167 | return HttpResponseRedirect(reverse('topic_view', kwargs={'topic_id': t_id})) 168 | 169 | 170 | def del_topic(request, topic_id): 171 | t = topic.objects.get(id=topic_id) 172 | if request.user != t.user and (not request.user.is_superuser): 173 | return HttpResponseRedirect(reverse('topic_view', kwargs={'topic_id': t.id})) 174 | n_id = t.node.id 175 | t.deleted = True 176 | t.save() 177 | return HttpResponseRedirect(reverse('node_view', kwargs={'node_id': n_id})) 178 | 179 | 180 | def edit_topic(request, topic_id): 181 | t = topic.objects.get(id=topic_id) 182 | if request.user != t.user and (not request.user.is_superuser): 183 | return HttpResponseRedirect(reverse('topic_view', kwargs={'topic_id': t.id})) 184 | if request.method == 'GET': 185 | return render_to_response('forum/edit-topic.html',{'request': request, 'conf': conf, 186 | 'topic': t, 187 | 'title': _('topic edit')}, 188 | context_instance=RequestContext(request)) 189 | elif request.method == 'POST': 190 | t.title = request.POST['title'] 191 | t.content = request.POST['content'] 192 | if not t.title: 193 | messages.add_message(request, messages.WARNING, _('title cannot be empty')) 194 | return HttpResponseRedirect(reverse('edit_topic', kwargs={'topic_id': t.id})) 195 | t.save() 196 | return HttpResponseRedirect(reverse('topic_view', kwargs={'topic_id': t.id})) 197 | 198 | 199 | def add_appendix(request, topic_id): 200 | t = topic.objects.get(id=topic_id) 201 | n = t.node 202 | if request.user != t.user: 203 | return error(request, _('you cannot add appendix to other people\'s topic' )) 204 | if request.method == 'GET': 205 | return render_to_response('forum/append.html', {'request': request, 'title': _('add appendix'), 206 | 'node': n, 'conf': conf, 207 | 'topic': t, }, 208 | context_instance=RequestContext(request)) 209 | elif request.method == 'POST': 210 | a = appendix() 211 | a.content = request.POST['content'] 212 | if not a.content: 213 | messages.add_message(request, messages.WARNING, _('content cannot be empty')) 214 | return HttpResponseRedirect(reverse('add_appendix', kwargs={'topic_id': t.id})) 215 | a.topic = t 216 | a.save() 217 | return HttpResponseRedirect(reverse('topic_view', kwargs={'topic_id': t.id})) 218 | 219 | 220 | def node_all(request): 221 | nodes = {} 222 | nodes[u'分类1'] = list(node.objects.filter(id__in=[1]).all()) 223 | return render_to_response('forum/node-all.html', {'request': request, 'title': _('all nodes'), 224 | 'conf': conf, 225 | 'nodes': nodes, }) 226 | -------------------------------------------------------------------------------- /locale/zh_CN/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2014-07-05 17:14+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: template/common/base.html:17 21 | #, python-format 22 | msgid "page %(pager)s" 23 | msgstr "第%(pager)s页" 24 | 25 | #: template/common/base.html:49 26 | msgid "recent post" 27 | msgstr "最近主题" 28 | 29 | #: template/common/base.html:50 30 | msgid "all nodes" 31 | msgstr "所有节点" 32 | 33 | #: template/common/base.html:55 34 | msgid "enter key words" 35 | msgstr "输入关键词" 36 | 37 | #: template/common/base.html:58 38 | msgid "Search" 39 | msgstr "搜索" 40 | 41 | #: template/widget/links.html:5 42 | msgid "links" 43 | msgstr "链接" 44 | 45 | #: template/widget/node.html:5 46 | msgid "nodes" 47 | msgstr "节点" 48 | 49 | #: template/widget/stat.html:5 50 | msgid "site stat" 51 | msgstr "站点统计" 52 | 53 | #: template/widget/stat.html:10 54 | msgid "number of users" 55 | msgstr "用户数量" 56 | 57 | #: template/widget/stat.html:13 58 | msgid "number of topics" 59 | msgstr "话题数量" 60 | 61 | #: template/widget/stat.html:16 62 | msgid "number of replies" 63 | msgstr "回复数目" 64 | 65 | #: template/widget/topic-list.html:11 66 | msgid "home" 67 | msgstr "主页" 68 | 69 | #: template/widget/topic-list.html:15 70 | msgid "new topic" 71 | msgstr "新话题" 72 | 73 | #: template/widget/topic-list.html:21 74 | msgid "sorry, but we can't find the topics you need" 75 | msgstr "抱歉,没有找到你想要的内容" 76 | 77 | #: template/widget/topic-list.html:36 78 | #, python-format 79 | msgid "%(clicks)s clicks" 80 | msgstr "%(clicks)s次点击" 81 | 82 | #: template/widget/user-panel.html:16 83 | msgid "info" 84 | msgstr "信息" 85 | 86 | #: template/widget/user-panel.html:19 87 | msgid "settings" 88 | msgstr "设置" 89 | 90 | #: template/widget/user-panel.html:22 91 | msgid "log out" 92 | msgstr "登出" 93 | 94 | #: template/widget/user-panel.html:33 95 | msgid "sign in" 96 | msgstr "登陆" 97 | 98 | #: template/widget/user-panel.html:36 99 | msgid "reg" 100 | msgstr "注册" 101 | 102 | #: template/widget/user-panel.html:54 103 | msgid "new messages" 104 | msgstr "未读消息" 105 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fairy.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) -------------------------------------------------------------------------------- /panel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/panel/__init__.py -------------------------------------------------------------------------------- /panel/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /panel/locale/zh_CN/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , 2014. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2014-07-05 17:14+0800\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: panel/templates/panel/base.html:11 panel/templates/panel/base.html.py:39 21 | msgid "admin" 22 | msgstr "后台" 23 | 24 | #: panel/templates/panel/base.html:55 panel/templates/panel/user-manage.html:6 25 | #: panel/views.py:22 26 | msgid "user management" 27 | msgstr "用户管理" 28 | 29 | #: panel/templates/panel/base.html:58 30 | #: panel/templates/panel/user-manage.html:14 31 | msgid "all users" 32 | msgstr "所有用户" 33 | 34 | #: panel/templates/panel/base.html:65 panel/templates/panel/node-manage.html:6 35 | #: panel/templates/panel/node-manage.html:14 panel/views.py:108 36 | msgid "node management" 37 | msgstr "节点管理" 38 | 39 | #: panel/templates/panel/base.html:68 40 | msgid "all nodes" 41 | msgstr "所有节点" 42 | 43 | #: panel/templates/panel/base.html:72 panel/templates/panel/node-create.html:7 44 | #: panel/views.py:167 45 | msgid "create node" 46 | msgstr "创建节点" 47 | 48 | #: panel/templates/panel/base.html:79 49 | msgid "content management" 50 | msgstr "内容管理" 51 | 52 | #: panel/templates/panel/base.html:82 53 | msgid "all topics" 54 | msgstr "所有话题" 55 | 56 | #: panel/templates/panel/node-create.html:16 57 | msgid "new node" 58 | msgstr "新节点" 59 | 60 | #: panel/templates/panel/node-create.html:24 61 | #: panel/templates/panel/node-edit.html:24 62 | #: panel/templates/panel/topic-manage.html:24 63 | msgid "node title" 64 | msgstr "节点标题" 65 | 66 | #: panel/templates/panel/node-create.html:28 67 | #: panel/templates/panel/node-edit.html:28 68 | msgid "node description" 69 | msgstr "节点描述" 70 | 71 | #: panel/templates/panel/node-create.html:37 72 | #: panel/templates/panel/node-edit.html:37 73 | #: panel/templates/panel/topic-edit.html:49 74 | #: panel/templates/panel/user-edit.html:98 75 | msgid "submit" 76 | msgstr "提交" 77 | 78 | #: panel/templates/panel/node-edit.html:7 panel/views.py:152 79 | msgid "edit node" 80 | msgstr "编辑节点" 81 | 82 | #: panel/templates/panel/node-manage.html:22 83 | #: panel/templates/panel/topic-edit.html:24 84 | #: panel/templates/panel/topic-manage.html:22 85 | msgid "title" 86 | msgstr "标题" 87 | 88 | #: panel/templates/panel/node-manage.html:23 89 | #: panel/templates/panel/topic-manage.html:25 90 | #: panel/templates/panel/user-manage.html:24 panel/views.py:65 91 | #: panel/views.py:141 panel/views.py:238 92 | msgid "edit" 93 | msgstr "编辑" 94 | 95 | #: panel/templates/panel/topic-edit.html:7 panel/views.py:191 96 | msgid "edit topic" 97 | msgstr "编辑话题" 98 | 99 | #: panel/templates/panel/topic-edit.html:28 100 | msgid "author" 101 | msgstr "作者" 102 | 103 | #: panel/templates/panel/topic-edit.html:32 104 | msgid "node" 105 | msgstr "节点" 106 | 107 | #: panel/templates/panel/topic-edit.html:36 108 | msgid "content" 109 | msgstr "内容" 110 | 111 | #: panel/templates/panel/topic-edit.html:40 112 | msgid "order(0-99,default:99)" 113 | msgstr "排序" 114 | 115 | #: panel/templates/panel/topic-manage.html:6 116 | #: panel/templates/panel/topic-manage.html:14 panel/views.py:182 117 | msgid "topic management" 118 | msgstr "话题管理" 119 | 120 | #: panel/templates/panel/topic-manage.html:23 121 | #: panel/templates/panel/user-edit.html:25 122 | #: panel/templates/panel/user-manage.html:22 123 | msgid "username" 124 | msgstr "用户名" 125 | 126 | #: panel/templates/panel/topic-manage.html:32 127 | msgid "delete selected" 128 | msgstr "删除选中项" 129 | 130 | #: panel/templates/panel/user-edit.html:7 panel/views.py:77 131 | msgid "edit user" 132 | msgstr "编辑用户" 133 | 134 | #: panel/templates/panel/user-edit.html:23 135 | msgid "identity" 136 | msgstr "身份信息" 137 | 138 | #: panel/templates/panel/user-edit.html:29 139 | msgid "password" 140 | msgstr "密码" 141 | 142 | #: panel/templates/panel/user-edit.html:30 143 | msgid "leave empty will not change the current password" 144 | msgstr "留空不修改" 145 | 146 | #: panel/templates/panel/user-edit.html:33 147 | #: panel/templates/panel/user-manage.html:23 148 | msgid "email" 149 | msgstr "Email" 150 | 151 | #: panel/templates/panel/user-edit.html:40 152 | msgid "user status" 153 | msgstr "用户状态" 154 | 155 | #: panel/templates/panel/user-edit.html:44 156 | #: panel/templates/panel/user-edit.html:46 157 | msgid "active" 158 | msgstr "激活的" 159 | 160 | #: panel/templates/panel/user-edit.html:53 161 | #: panel/templates/panel/user-edit.html:55 162 | msgid "superuser" 163 | msgstr "超级用户" 164 | 165 | #: panel/templates/panel/user-edit.html:63 166 | msgid "user profile" 167 | msgstr "用户资料" 168 | 169 | #: panel/templates/panel/user-edit.html:65 170 | msgid "nickname" 171 | msgstr "昵称" 172 | 173 | #: panel/templates/panel/user-edit.html:69 174 | msgid "location" 175 | msgstr "位置" 176 | 177 | #: panel/templates/panel/user-edit.html:73 178 | msgid "website" 179 | msgstr "网站" 180 | 181 | #: panel/templates/panel/user-edit.html:83 182 | #: panel/templates/panel/user-edit.html:87 183 | #: panel/templates/panel/user-edit.html:89 184 | msgid "use Gravatar" 185 | msgstr "使用Gravatar" 186 | 187 | #: panel/views.py:17 188 | msgid "home" 189 | msgstr "主页" 190 | -------------------------------------------------------------------------------- /panel/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /panel/templates/panel/base.html: -------------------------------------------------------------------------------- 1 | {% load settingsvalue %} 2 | {% load i18n %} 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{title}}-{% trans 'admin' %}-{% conf_value "sitename" %} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 95 | 96 |
97 | {% block main %} 98 | {% endblock %} 99 |
100 | 101 | 102 |
103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | {% block footer_ext %}{% endblock %} 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /panel/templates/panel/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | 3 | -------------------------------------------------------------------------------- /panel/templates/panel/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Start Bootstrap - SB Admin Version 2.0 Demo 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 |
24 |
25 | 49 |
50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /panel/templates/panel/node-create.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block main %} 5 |
6 |
7 |

{% trans 'create node' %}

8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 | {% trans 'new node' %} 17 |
18 |
19 |
20 | {% csrf_token %} 21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 |
36 | 37 | 38 |
39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /panel/templates/panel/node-edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | 3 | {% load i18n %} 4 | {% block main %} 5 |
6 |
7 |

{% trans 'edit node' %}

8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 | {{node.title}} 17 |
18 |
19 |
20 | {% csrf_token %} 21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 |
36 | 37 | 38 |
39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /panel/templates/panel/node-manage.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | {% load i18n %} 3 | {% block main %} 4 |
5 |
6 |

{% trans 'node management' %}

7 |
8 |
9 | 10 |
11 |
12 |
13 |
14 | {% trans 'node management' %} 15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
id{% trans 'title' %}{% trans 'edit' %}
29 |
30 |
31 |
32 |
33 |
34 | {% endblock %} 35 | 36 | {% block footer_ext %} 37 | 38 | 39 | 40 | 41 | 70 | {% endblock %} 71 | -------------------------------------------------------------------------------- /panel/templates/panel/topic-edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block main %} 5 |
6 |
7 |

{% trans 'edit topic' %}

8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 | {{topic.title}} 17 |
18 |
19 |
20 | {% csrf_token %} 21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 |
48 | 49 | 50 |
51 |
52 | 53 |
54 | 55 |
56 | 57 |
58 | {% endblock %} 59 | 60 | {% block footer_ext %} 61 | 67 | 82 | {% endblock %} 83 | -------------------------------------------------------------------------------- /panel/templates/panel/topic-manage.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | {% load i18n %} 3 | {% block main %} 4 |
5 |
6 |

{% trans 'topic management' %}

7 |
8 |
9 | 10 |
11 |
12 |
13 |
14 | {% trans 'topic management' %} 15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
id{% trans 'title' %}{% trans 'username' %}{% trans 'node title' %}{% trans 'edit' %}
31 |
32 | {% trans 'delete selected' %} 33 |
34 |
35 |
36 |
37 | {% endblock %} 38 | 39 | {% block footer_ext %} 40 | 41 | 42 | 43 | 44 | 92 | {% endblock %} 93 | -------------------------------------------------------------------------------- /panel/templates/panel/user-edit.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | {% load i18n %} 3 | 4 | {% block main %} 5 |
6 |
7 |

{% trans 'edit user' %}

8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 | {{user.username}} 17 |
18 |
19 |
20 | {% csrf_token %} 21 |
22 |
23 |

{% trans 'identity' %}

24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | 37 | 38 | 39 |
40 | 41 |
42 | 49 |
50 |
51 | 58 |
59 |
60 |
61 | 62 |
63 |

{% trans 'user profile' %}

64 |
65 | 66 | 67 |
68 |
69 | 70 | 71 |
72 |
73 | 74 | 75 |
76 | 77 | 81 | 82 |
83 | 84 |
85 | 92 |
93 |
94 |
95 | 96 |
97 | 98 | 99 |
100 |
101 | 102 |
103 | 104 |
105 | 106 |
107 | {% endblock %} 108 | -------------------------------------------------------------------------------- /panel/templates/panel/user-manage.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | {% load i18n %} 3 | {% block main %} 4 |
5 |
6 |

{% trans 'user management' %}

7 |
8 |
9 | 10 |
11 |
12 |
13 |
14 | {% trans 'all users' %} 15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
id{% trans 'username' %}{% trans 'email' %}{% trans 'edit' %}
30 |
31 |
32 |
33 |
34 |
35 | {% endblock %} 36 | 37 | {% block footer_ext %} 38 | 39 | 40 | 41 | 42 | 72 | {% endblock %} 73 | -------------------------------------------------------------------------------- /panel/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /panel/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | 3 | urlpatterns = patterns('panel.views', 4 | url(r'^$', 'index', name='index'), 5 | 6 | url(r'^user/all/$', 'user_manage', name="user_manage"), 7 | url(r'^user/(?P\d+)/edit/$', 'user_edit', name="user_edit"), 8 | url(r'^user/all/data-ss/$', 'user_table_ss', name="user_table_ss"), 9 | 10 | url(r'^node/all/$', 'node_manage', name="node_manage"), 11 | url(r'^node/create/$', 'node_create', name="node_create"), 12 | url(r'^node/all/data-ss/$', 'node_table_ss', name="node_table_ss"), 13 | url(r'^node/(?P\d+)/edit/$', 'node_edit', name="node_edit"), 14 | 15 | url(r'^topic/all/$', 'topic_manage', name="topic_manage"), 16 | #url(r'^topic/(?P\d+)/edit/$', 'topic_edit', name="topic_edit"), 17 | url(r'^topic/all/data-ss/$', 'topic_table_ss', name="topic_table_ss"), 18 | url(r'^topic/(?P\d+)/$', 'topic_edit', name="topic_edit"), 19 | 20 | url(r'^ajax/node/$', 'node_title_ajax', name="node_title_ajax"), 21 | url(r'^ajax/topic/bulk-delete/$', 'topic_bulk_delete', name="topic_bulk_delete"), 22 | ) 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Markdown==2.4 2 | Pygments==1.6 3 | django-pagination==1.0.7 4 | pillow==2.4.0 -------------------------------------------------------------------------------- /static/css/base.css: -------------------------------------------------------------------------------- 1 | a { 2 | text-decoration: none !important; 3 | } 4 | 5 | .list-post-title, .list-meta { 6 | margin: 0px; 7 | } 8 | 9 | .list-post-title { 10 | font-size: 120%; 11 | padding-top: 5px; 12 | } 13 | 14 | .avatar-middle { 15 | width: 48px; 16 | height: 48px; 17 | } 18 | 19 | .avatar-large { 20 | width: 80px; 21 | height: 80px; 22 | } 23 | 24 | .list-reply-count { 25 | margin-top: 15px; 26 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 27 | } 28 | 29 | /*.label {*/ 30 | /*border: 1px solid #000;*/ 31 | /*}*/ 32 | 33 | .label a { 34 | color: #F5F5F5; 35 | } 36 | 37 | .label-node { 38 | display: inline-block; 39 | line-height: 1.6; 40 | background: #999; 41 | margin-bottom: 10px; 42 | } 43 | 44 | h3.list-post-title a { 45 | text-decoration: none; 46 | color: #333; 47 | } 48 | 49 | .list-topic-item:hover { 50 | background-color: #f5f5f5; 51 | } 52 | 53 | .list-count::before, .list-time::before, .list-username::before, .list-reply-count-xs::before { 54 | content: '\2022'; 55 | margin-right: 5px; 56 | } 57 | 58 | .list-username a { 59 | color: #666; 60 | } 61 | 62 | .list-meta { 63 | color: #aaa; 64 | } 65 | 66 | footer { 67 | padding-top: 20px; 68 | } 69 | 70 | #wrap { 71 | /**background-image: url("/static/image/bg.png");**/ 72 | background-color: #E0E0E0; 73 | padding-top: 70px; 74 | } 75 | 76 | ul.pager { 77 | margin: 0px; 78 | } 79 | 80 | .padtop { 81 | margin-top: 5px; 82 | } 83 | 84 | .list-time { 85 | color: #aaa 86 | } 87 | 88 | /* Sticky footer styles 89 | -------------------------------------------------- */ 90 | 91 | html, body { 92 | height: 100%; 93 | /* The html and body elements cannot have any padding or margin. */ 94 | } 95 | 96 | /* Wrapper for page content to push down footer */ 97 | #wrap { 98 | min-height: 100%; 99 | height: auto; 100 | /* Negative indent footer by its height */ 101 | margin-bottom: -50px; 102 | /* Pad bottom by footer height */ 103 | padding-bottom: 50px; 104 | } 105 | 106 | /* Set the fixed height of the footer here */ 107 | footer { 108 | height: 50px; 109 | background-color: #FEFEFE; 110 | } 111 | 112 | #content { 113 | height: 120px; 114 | } 115 | 116 | .pagination { 117 | margin: 0px; 118 | } 119 | 120 | .appendix-meta { 121 | font-size: 12px; 122 | line-height: 120%; 123 | text-align: left; 124 | color: #ccc; 125 | } 126 | 127 | .list-appendix-item { 128 | background: #FFFFF9; 129 | border-left: 3px solid #FFFBC1 !important; 130 | } 131 | 132 | .panel-body { 133 | padding: 15px; 134 | } 135 | 136 | .panel-body:before, .panel-body:after { 137 | display: table; 138 | content: " "; 139 | } 140 | 141 | .panel-body:after { 142 | clear: both; 143 | } 144 | 145 | .panel-body:before, .panel-body:after { 146 | display: table; 147 | content: " "; 148 | } 149 | 150 | .panel-body:after { 151 | clear: both; 152 | } 153 | 154 | .list-meta-node { 155 | background-color: #F5F5F5; 156 | } 157 | 158 | .list-meta-node a { 159 | color: #AAA; 160 | } 161 | 162 | .list-reply-count { 163 | background-color: #C0C0C0; 164 | } 165 | 166 | .topic-heading { 167 | background-color: #FFF !important; 168 | } 169 | 170 | .list-post-title-in-topic { 171 | font-size: 180%; 172 | padding-top: 5px; 173 | padding-bottom: 5px; 174 | margin-top: 0px; 175 | } 176 | 177 | @media (max-width: 768px) { 178 | .list-reply-count-xs { 179 | display: inline-block !important; 180 | } 181 | .list-username { 182 | display: none; 183 | } 184 | } 185 | 186 | .topic-breadcrumb, .topic-breadcrumb a { 187 | margin: 0px; 188 | color: #AAAAAA; 189 | } 190 | 191 | body { 192 | font-family: "WenQuanYi Micro Hei", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; 193 | } 194 | 195 | html, html a { 196 | -webkit-font-smoothing: antialiased; 197 | text-shadow: 1px 1px 1px rgba(0,0,0,0.004); 198 | } 199 | 200 | .title-breadcrumb a { 201 | color: #428bca; 202 | } 203 | 204 | .title-breadcrumb span:before { 205 | content: "/\00a0"; 206 | padding: 0 5px; 207 | color: #ccc; 208 | } 209 | 210 | a.add-new-btn { 211 | line-height: 12px; 212 | color: #FFF; 213 | } 214 | 215 | p > img { 216 | max-width: 100%; 217 | } 218 | -------------------------------------------------------------------------------- /static/css/codehilite.css: -------------------------------------------------------------------------------- 1 | .hll { background-color: #ffffcc } 2 | .c { color: #408080; font-style: italic } /* Comment */ 3 | .err { border: 1px solid #FF0000 } /* Error */ 4 | .k { color: #008000; font-weight: bold } /* Keyword */ 5 | .o { color: #666666 } /* Operator */ 6 | .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 7 | .cp { color: #BC7A00 } /* Comment.Preproc */ 8 | .c1 { color: #408080; font-style: italic } /* Comment.Single */ 9 | .cs { color: #408080; font-style: italic } /* Comment.Special */ 10 | .gd { color: #A00000 } /* Generic.Deleted */ 11 | .ge { font-style: italic } /* Generic.Emph */ 12 | .gr { color: #FF0000 } /* Generic.Error */ 13 | .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 14 | .gi { color: #00A000 } /* Generic.Inserted */ 15 | .go { color: #888888 } /* Generic.Output */ 16 | .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 17 | .gs { font-weight: bold } /* Generic.Strong */ 18 | .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 19 | .gt { color: #0044DD } /* Generic.Traceback */ 20 | .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 21 | .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 22 | .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 23 | .kp { color: #008000 } /* Keyword.Pseudo */ 24 | .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 25 | .kt { color: #B00040 } /* Keyword.Type */ 26 | .m { color: #666666 } /* Literal.Number */ 27 | .s { color: #BA2121 } /* Literal.String */ 28 | .na { color: #7D9029 } /* Name.Attribute */ 29 | .nb { color: #008000 } /* Name.Builtin */ 30 | .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 31 | .no { color: #880000 } /* Name.Constant */ 32 | .nd { color: #AA22FF } /* Name.Decorator */ 33 | .ni { color: #999999; font-weight: bold } /* Name.Entity */ 34 | .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 35 | .nf { color: #0000FF } /* Name.Function */ 36 | .nl { color: #A0A000 } /* Name.Label */ 37 | .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 38 | .nt { color: #008000; font-weight: bold } /* Name.Tag */ 39 | .nv { color: #19177C } /* Name.Variable */ 40 | .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 41 | .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .mf { color: #666666 } /* Literal.Number.Float */ 43 | .mh { color: #666666 } /* Literal.Number.Hex */ 44 | .mi { color: #666666 } /* Literal.Number.Integer */ 45 | .mo { color: #666666 } /* Literal.Number.Oct */ 46 | .sb { color: #BA2121 } /* Literal.String.Backtick */ 47 | .sc { color: #BA2121 } /* Literal.String.Char */ 48 | .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 49 | .s2 { color: #BA2121 } /* Literal.String.Double */ 50 | .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 51 | .sh { color: #BA2121 } /* Literal.String.Heredoc */ 52 | .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 53 | .sx { color: #008000 } /* Literal.String.Other */ 54 | .sr { color: #BB6688 } /* Literal.String.Regex */ 55 | .s1 { color: #BA2121 } /* Literal.String.Single */ 56 | .ss { color: #19177C } /* Literal.String.Symbol */ 57 | .bp { color: #008000 } /* Name.Builtin.Pseudo */ 58 | .vc { color: #19177C } /* Name.Variable.Class */ 59 | .vg { color: #19177C } /* Name.Variable.Global */ 60 | .vi { color: #19177C } /* Name.Variable.Instance */ 61 | .il { color: #666666 } /* Literal.Number.Integer.Long */ 62 | -------------------------------------------------------------------------------- /static/fonts/.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | PreviewsShown=true 3 | Timestamp=2014,2,19,23,27,30 4 | Version=3 5 | -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/image/Connect_logo_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/image/Connect_logo_7.png -------------------------------------------------------------------------------- /static/image/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/image/bg.png -------------------------------------------------------------------------------- /static/image/pixels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/image/pixels.png -------------------------------------------------------------------------------- /static/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f); 8 | if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b li { 19 | position: relative; 20 | margin-bottom: 20px; 21 | } 22 | 23 | .timeline > li:before, 24 | .timeline > li:after { 25 | content: " "; 26 | display: table; 27 | } 28 | 29 | .timeline > li:after { 30 | clear: both; 31 | } 32 | 33 | .timeline > li:before, 34 | .timeline > li:after { 35 | content: " "; 36 | display: table; 37 | } 38 | 39 | .timeline > li:after { 40 | clear: both; 41 | } 42 | 43 | .timeline > li > .timeline-panel { 44 | float: left; 45 | position: relative; 46 | width: 46%; 47 | padding: 20px; 48 | border: 1px solid #d4d4d4; 49 | border-radius: 2px; 50 | -webkit-box-shadow: 0 1px 6px rgba(0,0,0,0.175); 51 | box-shadow: 0 1px 6px rgba(0,0,0,0.175); 52 | } 53 | 54 | .timeline > li > .timeline-panel:before { 55 | content: " "; 56 | display: inline-block; 57 | position: absolute; 58 | top: 26px; 59 | right: -15px; 60 | border-top: 15px solid transparent; 61 | border-right: 0 solid #ccc; 62 | border-bottom: 15px solid transparent; 63 | border-left: 15px solid #ccc; 64 | } 65 | 66 | .timeline > li > .timeline-panel:after { 67 | content: " "; 68 | display: inline-block; 69 | position: absolute; 70 | top: 27px; 71 | right: -14px; 72 | border-top: 14px solid transparent; 73 | border-right: 0 solid #fff; 74 | border-bottom: 14px solid transparent; 75 | border-left: 14px solid #fff; 76 | } 77 | 78 | .timeline > li > .timeline-badge { 79 | z-index: 100; 80 | position: absolute; 81 | top: 16px; 82 | left: 50%; 83 | width: 50px; 84 | height: 50px; 85 | margin-left: -25px; 86 | border-radius: 50% 50% 50% 50%; 87 | text-align: center; 88 | font-size: 1.4em; 89 | line-height: 50px; 90 | color: #fff; 91 | background-color: #999999; 92 | } 93 | 94 | .timeline > li.timeline-inverted > .timeline-panel { 95 | float: right; 96 | } 97 | 98 | .timeline > li.timeline-inverted > .timeline-panel:before { 99 | right: auto; 100 | left: -15px; 101 | border-right-width: 15px; 102 | border-left-width: 0; 103 | } 104 | 105 | .timeline > li.timeline-inverted > .timeline-panel:after { 106 | right: auto; 107 | left: -14px; 108 | border-right-width: 14px; 109 | border-left-width: 0; 110 | } 111 | 112 | .timeline-badge.primary { 113 | background-color: #2e6da4 !important; 114 | } 115 | 116 | .timeline-badge.success { 117 | background-color: #3f903f !important; 118 | } 119 | 120 | .timeline-badge.warning { 121 | background-color: #f0ad4e !important; 122 | } 123 | 124 | .timeline-badge.danger { 125 | background-color: #d9534f !important; 126 | } 127 | 128 | .timeline-badge.info { 129 | background-color: #5bc0de !important; 130 | } 131 | 132 | .timeline-title { 133 | margin-top: 0; 134 | color: inherit; 135 | } 136 | 137 | .timeline-body > p, 138 | .timeline-body > ul { 139 | margin-bottom: 0; 140 | } 141 | 142 | .timeline-body > p + p { 143 | margin-top: 5px; 144 | } -------------------------------------------------------------------------------- /static/panel/css/sb-admin.css: -------------------------------------------------------------------------------- 1 | /* Global Styles */ 2 | 3 | /* ------------------------------- */ 4 | 5 | body { 6 | background-color: #f8f8f8; 7 | padding-top: 100px; 8 | } 9 | 10 | @media(min-width:768px) { 11 | body { 12 | padding-top: 50px; 13 | } 14 | } 15 | 16 | /* Wrappers */ 17 | 18 | /* ------------------------------- */ 19 | 20 | #wrapper { 21 | width: 100%; 22 | } 23 | 24 | #page-wrapper { 25 | padding: 0 15px; 26 | min-height: 568px; 27 | background-color: #fff; 28 | } 29 | 30 | @media(min-width:768px) { 31 | #page-wrapper { 32 | position: inherit; 33 | margin: 0 0 0 250px; 34 | padding: 0 30px; 35 | min-height: 1300px; 36 | border-left: 1px solid #e7e7e7; 37 | } 38 | } 39 | 40 | .navbar-static-side ul li { 41 | border-bottom: 1px solid #e7e7e7; 42 | } 43 | 44 | /* Navigation */ 45 | 46 | /* ------------------------------- */ 47 | 48 | /* Top Right Navigation Dropdown Styles */ 49 | 50 | .navbar-top-links li { 51 | display: inline-block; 52 | } 53 | 54 | .navbar-top-links li:last-child { 55 | margin-right: 15px; 56 | } 57 | 58 | .navbar-top-links li a { 59 | padding: 15px; 60 | min-height: 50px; 61 | } 62 | 63 | .navbar-top-links .dropdown-menu li { 64 | display: block; 65 | } 66 | 67 | .navbar-top-links .dropdown-menu li:last-child { 68 | margin-right: 0; 69 | } 70 | 71 | .navbar-top-links .dropdown-menu li a { 72 | padding: 3px 20px; 73 | min-height: 0; 74 | } 75 | 76 | .navbar-top-links .dropdown-menu li a div { 77 | white-space: normal; 78 | } 79 | 80 | .navbar-top-links .dropdown-messages, 81 | .navbar-top-links .dropdown-tasks, 82 | .navbar-top-links .dropdown-alerts { 83 | width: 310px; 84 | min-width: 0; 85 | } 86 | 87 | .navbar-top-links .dropdown-messages { 88 | margin-left: 5px; 89 | } 90 | 91 | .navbar-top-links .dropdown-tasks { 92 | margin-left: -59px; 93 | } 94 | 95 | .navbar-top-links .dropdown-alerts { 96 | margin-left: -123px; 97 | } 98 | 99 | .navbar-top-links .dropdown-user { 100 | right: 0; 101 | left: auto; 102 | } 103 | 104 | /* Sidebar Menu Styles */ 105 | 106 | .sidebar-search { 107 | padding: 15px; 108 | } 109 | 110 | .arrow { 111 | float: right; 112 | } 113 | 114 | .fa.arrow:before { 115 | content: "\f104"; 116 | } 117 | 118 | .active > a > .fa.arrow:before { 119 | content: "\f107"; 120 | } 121 | 122 | .nav-second-level li, 123 | .nav-third-level li { 124 | border-bottom: none !important; 125 | } 126 | 127 | .nav-second-level li a { 128 | padding-left: 37px; 129 | } 130 | 131 | .nav-third-level li a { 132 | padding-left: 52px; 133 | } 134 | 135 | @media(min-width:768px) { 136 | .navbar-static-side { 137 | z-index: 1; 138 | position: absolute; 139 | width: 250px; 140 | margin-top: 51px; 141 | } 142 | 143 | .navbar-top-links .dropdown-messages, 144 | .navbar-top-links .dropdown-tasks, 145 | .navbar-top-links .dropdown-alerts { 146 | margin-left: auto; 147 | } 148 | } 149 | 150 | /* Buttons */ 151 | 152 | /* ------------------------------- */ 153 | 154 | .btn-outline { 155 | color: inherit; 156 | background-color: transparent; 157 | transition: all .5s; 158 | } 159 | 160 | .btn-primary.btn-outline { 161 | color: #428bca; 162 | } 163 | 164 | .btn-success.btn-outline { 165 | color: #5cb85c; 166 | } 167 | 168 | .btn-info.btn-outline { 169 | color: #5bc0de; 170 | } 171 | 172 | .btn-warning.btn-outline { 173 | color: #f0ad4e; 174 | } 175 | 176 | .btn-danger.btn-outline { 177 | color: #d9534f; 178 | } 179 | 180 | .btn-primary.btn-outline:hover, 181 | .btn-success.btn-outline:hover, 182 | .btn-info.btn-outline:hover, 183 | .btn-warning.btn-outline:hover, 184 | .btn-danger.btn-outline:hover { 185 | color: #fff; 186 | } 187 | 188 | /* Pages */ 189 | 190 | /* ------------------------------- */ 191 | 192 | /* Dashboard Chat */ 193 | 194 | .chat { 195 | margin: 0; 196 | padding: 0; 197 | list-style: none; 198 | } 199 | 200 | .chat li { 201 | margin-bottom: 10px; 202 | padding-bottom: 5px; 203 | border-bottom: 1px dotted #B3A9A9; 204 | } 205 | 206 | .chat li.left .chat-body { 207 | margin-left: 60px; 208 | } 209 | 210 | .chat li.right .chat-body { 211 | margin-right: 60px; 212 | } 213 | 214 | .chat li .chat-body p { 215 | margin: 0; 216 | color: #777777; 217 | } 218 | 219 | .panel .slidedown .glyphicon, 220 | .chat .glyphicon { 221 | margin-right: 5px; 222 | } 223 | 224 | .chat-panel .panel-body { 225 | height: 350px; 226 | overflow-y: scroll; 227 | } 228 | 229 | /* Login Page */ 230 | 231 | .login-panel { 232 | margin-top: 25%; 233 | } 234 | 235 | /* Flot Chart Containers */ 236 | 237 | .flot-chart { 238 | display: block; 239 | height: 400px; 240 | } 241 | 242 | .flot-chart-content { 243 | width: 100%; 244 | height: 100%; 245 | } 246 | 247 | /* DataTables Overrides */ 248 | 249 | table.dataTable thead .sorting, 250 | table.dataTable thead .sorting_asc, 251 | table.dataTable thead .sorting_desc, 252 | table.dataTable thead .sorting_asc_disabled, 253 | table.dataTable thead .sorting_desc_disabled { 254 | background: transparent; 255 | } 256 | 257 | table.dataTable thead .sorting_asc:after { 258 | content: "\f0de"; 259 | float: right; 260 | font-family: fontawesome; 261 | } 262 | 263 | table.dataTable thead .sorting_desc:after { 264 | content: "\f0dd"; 265 | float: right; 266 | font-family: fontawesome; 267 | } 268 | 269 | table.dataTable thead .sorting:after { 270 | content: "\f0dc"; 271 | float: right; 272 | font-family: fontawesome; 273 | color: rgba(50,50,50,.5); 274 | } 275 | 276 | /* Circle Buttons */ 277 | 278 | .btn-circle { 279 | width: 30px; 280 | height: 30px; 281 | padding: 6px 0; 282 | border-radius: 15px; 283 | text-align: center; 284 | font-size: 12px; 285 | line-height: 1.428571429; 286 | } 287 | 288 | .btn-circle.btn-lg { 289 | width: 50px; 290 | height: 50px; 291 | padding: 10px 16px; 292 | border-radius: 25px; 293 | font-size: 18px; 294 | line-height: 1.33; 295 | } 296 | 297 | .btn-circle.btn-xl { 298 | width: 70px; 299 | height: 70px; 300 | padding: 10px 16px; 301 | border-radius: 35px; 302 | font-size: 24px; 303 | line-height: 1.33; 304 | } 305 | 306 | .show-grid [class^="col-"] { 307 | padding-top: 10px; 308 | padding-bottom: 10px; 309 | border: 1px solid #ddd; 310 | background-color: #eee !important; 311 | } 312 | 313 | .show-grid { 314 | margin: 15px 0; 315 | } -------------------------------------------------------------------------------- /static/panel/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/panel/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/panel/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/panel/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/panel/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/panel/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/panel/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/panel/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/panel/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .@{fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font-family: FontAwesome; 7 | font-style: normal; 8 | font-weight: normal; 9 | line-height: 1; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "spinning"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: -@fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon-rotate(@degrees, @rotation) { 5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 6 | -webkit-transform: rotate(@degrees); 7 | -moz-transform: rotate(@degrees); 8 | -ms-transform: rotate(@degrees); 9 | -o-transform: rotate(@degrees); 10 | transform: rotate(@degrees); 11 | } 12 | 13 | .fa-icon-flip(@horiz, @vert, @rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 15 | -webkit-transform: scale(@horiz, @vert); 16 | -moz-transform: scale(@horiz, @vert); 17 | -ms-transform: scale(@horiz, @vert); 18 | -o-transform: scale(@horiz, @vert); 19 | transform: scale(@horiz, @vert); 20 | } 21 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 9 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 10 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 11 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/spinning.less: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: spin 2s infinite linear; 6 | -moz-animation: spin 2s infinite linear; 7 | -o-animation: spin 2s infinite linear; 8 | animation: spin 2s infinite linear; 9 | } 10 | 11 | @-moz-keyframes spin { 12 | 0% { -moz-transform: rotate(0deg); } 13 | 100% { -moz-transform: rotate(359deg); } 14 | } 15 | @-webkit-keyframes spin { 16 | 0% { -webkit-transform: rotate(0deg); } 17 | 100% { -webkit-transform: rotate(359deg); } 18 | } 19 | @-o-keyframes spin { 20 | 0% { -o-transform: rotate(0deg); } 21 | 100% { -o-transform: rotate(359deg); } 22 | } 23 | @-ms-keyframes spin { 24 | 0% { -ms-transform: rotate(0deg); } 25 | 100% { -ms-transform: rotate(359deg); } 26 | } 27 | @keyframes spin { 28 | 0% { transform: rotate(0deg); } 29 | 100% { transform: rotate(359deg); } 30 | } 31 | -------------------------------------------------------------------------------- /static/panel/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font-family: FontAwesome; 7 | font-style: normal; 8 | font-weight: normal; 9 | line-height: 1; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon-rotate($degrees, $rotation) { 5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation); 6 | -webkit-transform: rotate($degrees); 7 | -moz-transform: rotate($degrees); 8 | -ms-transform: rotate($degrees); 9 | -o-transform: rotate($degrees); 10 | transform: rotate($degrees); 11 | } 12 | 13 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation); 15 | -webkit-transform: scale($horiz, $vert); 16 | -moz-transform: scale($horiz, $vert); 17 | -ms-transform: scale($horiz, $vert); 18 | -o-transform: scale($horiz, $vert); 19 | transform: scale($horiz, $vert); 20 | } 21 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 9 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 10 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 11 | //src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_spinning.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: spin 2s infinite linear; 6 | -moz-animation: spin 2s infinite linear; 7 | -o-animation: spin 2s infinite linear; 8 | animation: spin 2s infinite linear; 9 | } 10 | 11 | @-moz-keyframes spin { 12 | 0% { -moz-transform: rotate(0deg); } 13 | 100% { -moz-transform: rotate(359deg); } 14 | } 15 | @-webkit-keyframes spin { 16 | 0% { -webkit-transform: rotate(0deg); } 17 | 100% { -webkit-transform: rotate(359deg); } 18 | } 19 | @-o-keyframes spin { 20 | 0% { -o-transform: rotate(0deg); } 21 | 100% { -o-transform: rotate(359deg); } 22 | } 23 | @-ms-keyframes spin { 24 | 0% { -ms-transform: rotate(0deg); } 25 | 100% { -ms-transform: rotate(359deg); } 26 | } 27 | @keyframes spin { 28 | 0% { transform: rotate(0deg); } 29 | 100% { transform: rotate(359deg); } 30 | } 31 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /static/panel/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "spinning"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /static/panel/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/panel/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/panel/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/panel/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/panel/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/27480168d6598d6768110df7a443ed58195ff855/static/panel/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/panel/js/autocomplete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ajax Autocomplete for jQuery, version 1.2.9 3 | * (c) 2013 Tomas Kirda 4 | * 5 | * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. 6 | * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete 7 | * 8 | */ 9 | (function(d){"function"===typeof define&&define.amd?define(["jquery"],d):d(jQuery)})(function(d){function g(a,b){var c=function(){},c={autoSelectFirst:!1,appendTo:"body",serviceUrl:null,lookup:null,onSelect:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:g.formatResult,delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:c,onSearchComplete:c,onSearchError:c,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",currentRequest:null,triggerSelectOnValidInput:!0, 10 | lookupFilter:function(a,b,c){return-1!==a.value.toLowerCase().indexOf(c)},paramName:"query",transformResult:function(a){return"string"===typeof a?d.parseJSON(a):a}};this.element=a;this.el=d(a);this.suggestions=[];this.badQueries=[];this.selectedIndex=-1;this.currentValue=this.element.value;this.intervalId=0;this.cachedResponse={};this.onChange=this.onChangeInterval=null;this.isLocal=!1;this.suggestionsContainer=null;this.options=d.extend({},c,b);this.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"}; 11 | this.hint=null;this.hintValue="";this.selection=null;this.initialize();this.setOptions(b)}var k=function(){return{escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},createNode:function(a){var b=document.createElement("div");b.className=a;b.style.position="absolute";b.style.display="none";return b}}}();g.utils=k;d.Autocomplete=g;g.formatResult=function(a,b){var c="("+k.escapeRegExChars(b)+")";return a.value.replace(RegExp(c,"gi"),"$1")};g.prototype= 12 | {killerFn:null,initialize:function(){var a=this,b="."+a.classes.suggestion,c=a.classes.selected,e=a.options,f;a.element.setAttribute("autocomplete","off");a.killerFn=function(b){0===d(b.target).closest("."+a.options.containerClass).length&&(a.killSuggestions(),a.disableKillerFn())};a.suggestionsContainer=g.utils.createNode(e.containerClass);f=d(a.suggestionsContainer);f.appendTo(e.appendTo);"auto"!==e.width&&f.width(e.width);f.on("mouseover.autocomplete",b,function(){a.activate(d(this).data("index"))}); 13 | f.on("mouseout.autocomplete",function(){a.selectedIndex=-1;f.children("."+c).removeClass(c)});f.on("click.autocomplete",b,function(){a.select(d(this).data("index"))});a.fixPosition();a.fixPositionCapture=function(){a.visible&&a.fixPosition()};d(window).on("resize.autocomplete",a.fixPositionCapture);a.el.on("keydown.autocomplete",function(b){a.onKeyPress(b)});a.el.on("keyup.autocomplete",function(b){a.onKeyUp(b)});a.el.on("blur.autocomplete",function(){a.onBlur()});a.el.on("focus.autocomplete",function(){a.onFocus()}); 14 | a.el.on("change.autocomplete",function(b){a.onKeyUp(b)})},onFocus:function(){this.fixPosition();if(this.options.minChars<=this.el.val().length)this.onValueChange()},onBlur:function(){this.enableKillerFn()},setOptions:function(a){var b=this.options;d.extend(b,a);if(this.isLocal=d.isArray(b.lookup))b.lookup=this.verifySuggestionsFormat(b.lookup);d(this.suggestionsContainer).css({"max-height":b.maxHeight+"px",width:b.width+"px","z-index":b.zIndex})},clearCache:function(){this.cachedResponse={};this.badQueries= 15 | []},clear:function(){this.clearCache();this.currentValue="";this.suggestions=[]},disable:function(){this.disabled=!0;this.currentRequest&&this.currentRequest.abort()},enable:function(){this.disabled=!1},fixPosition:function(){var a;"body"===this.options.appendTo&&(a=this.el.offset(),a={top:a.top+this.el.outerHeight()+"px",left:a.left+"px"},"auto"===this.options.width&&(a.width=this.el.outerWidth()-2+"px"),d(this.suggestionsContainer).css(a))},enableKillerFn:function(){d(document).on("click.autocomplete", 16 | this.killerFn)},disableKillerFn:function(){d(document).off("click.autocomplete",this.killerFn)},killSuggestions:function(){var a=this;a.stopKillSuggestions();a.intervalId=window.setInterval(function(){a.hide();a.stopKillSuggestions()},50)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},isCursorAtEnd:function(){var a=this.el.val().length,b=this.element.selectionStart;return"number"===typeof b?b===a:document.selection?(b=document.selection.createRange(),b.moveStart("character", 17 | -a),a===b.text.length):!0},onKeyPress:function(a){if(!this.disabled&&!this.visible&&40===a.which&&this.currentValue)this.suggest();else if(!this.disabled&&this.visible){switch(a.which){case 27:this.el.val(this.currentValue);this.hide();break;case 39:if(this.hint&&this.options.onHint&&this.isCursorAtEnd()){this.selectHint();break}return;case 9:if(this.hint&&this.options.onHint){this.selectHint();return}case 13:if(-1===this.selectedIndex){this.hide();return}this.select(this.selectedIndex);if(9===a.which&& 18 | !1===this.options.tabDisabled)return;break;case 38:this.moveUp();break;case 40:this.moveDown();break;default:return}a.stopImmediatePropagation();a.preventDefault()}},onKeyUp:function(a){var b=this;if(!b.disabled){switch(a.which){case 38:case 40:return}clearInterval(b.onChangeInterval);if(b.currentValue!==b.el.val())if(b.findBestHint(),0f&&(b.suggestions=b.suggestions.slice(0,f));return b},getSuggestions:function(a){var b,c=this,e=c.options,f=e.serviceUrl,l,g;e.params[e.paramName]=a;l=e.ignoreParams?null:e.params; 21 | c.isLocal?b=c.getSuggestionsLocal(a):(d.isFunction(f)&&(f=f.call(c.element,a)),g=f+"?"+d.param(l||{}),b=c.cachedResponse[g]);b&&d.isArray(b.suggestions)?(c.suggestions=b.suggestions,c.suggest()):c.isBadQuery(a)||!1===e.onSearchStart.call(c.element,e.params)||(c.currentRequest&&c.currentRequest.abort(),c.currentRequest=d.ajax({url:f,data:l,type:e.type,dataType:e.dataType}).done(function(b){c.currentRequest=null;c.processResponse(b,a,g);e.onSearchComplete.call(c.element,a)}).fail(function(b,d,f){e.onSearchError.call(c.element, 22 | a,b,d,f)}))},isBadQuery:function(a){for(var b=this.badQueries,c=b.length;c--;)if(0===a.indexOf(b[c]))return!0;return!1},hide:function(){this.visible=!1;this.selectedIndex=-1;d(this.suggestionsContainer).hide();this.signalHint(null)},suggest:function(){if(0===this.suggestions.length)this.hide();else{var a=this.options,b=a.formatResult,c=this.getQuery(this.currentValue),e=this.classes.suggestion,f=this.classes.selected,g=d(this.suggestionsContainer),k=a.beforeRender,m="",h;if(a.triggerSelectOnValidInput&& 23 | (h=this.findSuggestionIndex(c),-1!==h)){this.select(h);return}d.each(this.suggestions,function(a,d){m+='
'+b(d,c)+"
"});"auto"===a.width&&(h=this.el.outerWidth()-2,g.width(0this.selectedIndex?(a=e.get(this.selectedIndex),d(a).addClass(b),a):null},selectHint:function(){var a=d.inArray(this.hint,this.suggestions); 26 | this.select(a)},select:function(a){this.hide();this.onSelect(a)},moveUp:function(){-1!==this.selectedIndex&&(0===this.selectedIndex?(d(this.suggestionsContainer).children().first().removeClass(this.classes.selected),this.selectedIndex=-1,this.el.val(this.currentValue),this.findBestHint()):this.adjustScroll(this.selectedIndex-1))},moveDown:function(){this.selectedIndex!==this.suggestions.length-1&&this.adjustScroll(this.selectedIndex+1)},adjustScroll:function(a){var b=this.activate(a),c,e;b&&(b=b.offsetTop, 27 | c=d(this.suggestionsContainer).scrollTop(),e=c+this.options.maxHeight-25,be&&d(this.suggestionsContainer).scrollTop(b-this.options.maxHeight+25),this.el.val(this.getValue(this.suggestions[a].value)),this.signalHint(null))},onSelect:function(a){var b=this.options.onSelect;a=this.suggestions[a];this.currentValue=this.getValue(a.value);this.el.val(this.currentValue);this.signalHint(null);this.suggestions=[];this.selection=a;d.isFunction(b)&&b.call(this.element, 28 | a)},getValue:function(a){var b=this.options.delimiter,c;if(!b)return a;c=this.currentValue;b=c.split(b);return 1===b.length?a:c.substr(0,c.length-b[b.length-1].length)+a},dispose:function(){this.el.off(".autocomplete").removeData("autocomplete");this.disableKillerFn();d(window).off("resize.autocomplete",this.fixPositionCapture);d(this.suggestionsContainer).remove()}};d.fn.autocomplete=function(a,b){return 0===arguments.length?this.first().data("autocomplete"):this.each(function(){var c=d(this),e= 29 | c.data("autocomplete");if("string"===typeof a){if(e&&"function"===typeof e[a])e[a](b)}else e&&e.dispose&&e.dispose(),e=new g(this,a),c.data("autocomplete",e)})}}); -------------------------------------------------------------------------------- /static/panel/js/demo/dashboard-demo.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | Morris.Area({ 4 | element: 'morris-area-chart', 5 | data: [{ 6 | period: '2010 Q1', 7 | iphone: 2666, 8 | ipad: null, 9 | itouch: 2647 10 | }, { 11 | period: '2010 Q2', 12 | iphone: 2778, 13 | ipad: 2294, 14 | itouch: 2441 15 | }, { 16 | period: '2010 Q3', 17 | iphone: 4912, 18 | ipad: 1969, 19 | itouch: 2501 20 | }, { 21 | period: '2010 Q4', 22 | iphone: 3767, 23 | ipad: 3597, 24 | itouch: 5689 25 | }, { 26 | period: '2011 Q1', 27 | iphone: 6810, 28 | ipad: 1914, 29 | itouch: 2293 30 | }, { 31 | period: '2011 Q2', 32 | iphone: 5670, 33 | ipad: 4293, 34 | itouch: 1881 35 | }, { 36 | period: '2011 Q3', 37 | iphone: 4820, 38 | ipad: 3795, 39 | itouch: 1588 40 | }, { 41 | period: '2011 Q4', 42 | iphone: 15073, 43 | ipad: 5967, 44 | itouch: 5175 45 | }, { 46 | period: '2012 Q1', 47 | iphone: 10687, 48 | ipad: 4460, 49 | itouch: 2028 50 | }, { 51 | period: '2012 Q2', 52 | iphone: 8432, 53 | ipad: 5713, 54 | itouch: 1791 55 | }], 56 | xkey: 'period', 57 | ykeys: ['iphone', 'ipad', 'itouch'], 58 | labels: ['iPhone', 'iPad', 'iPod Touch'], 59 | pointSize: 2, 60 | hideHover: 'auto', 61 | resize: true 62 | }); 63 | 64 | Morris.Donut({ 65 | element: 'morris-donut-chart', 66 | data: [{ 67 | label: "Download Sales", 68 | value: 12 69 | }, { 70 | label: "In-Store Sales", 71 | value: 30 72 | }, { 73 | label: "Mail-Order Sales", 74 | value: 20 75 | }], 76 | resize: true 77 | }); 78 | 79 | Morris.Bar({ 80 | element: 'morris-bar-chart', 81 | data: [{ 82 | y: '2006', 83 | a: 100, 84 | b: 90 85 | }, { 86 | y: '2007', 87 | a: 75, 88 | b: 65 89 | }, { 90 | y: '2008', 91 | a: 50, 92 | b: 40 93 | }, { 94 | y: '2009', 95 | a: 75, 96 | b: 65 97 | }, { 98 | y: '2010', 99 | a: 50, 100 | b: 40 101 | }, { 102 | y: '2011', 103 | a: 75, 104 | b: 65 105 | }, { 106 | y: '2012', 107 | a: 100, 108 | b: 90 109 | }], 110 | xkey: 'y', 111 | ykeys: ['a', 'b'], 112 | labels: ['Series A', 'Series B'], 113 | hideHover: 'auto', 114 | resize: true 115 | }); 116 | 117 | }); 118 | -------------------------------------------------------------------------------- /static/panel/js/demo/morris-demo.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | Morris.Area({ 4 | element: 'morris-area-chart', 5 | data: [{ 6 | period: '2010 Q1', 7 | iphone: 2666, 8 | ipad: null, 9 | itouch: 2647 10 | }, { 11 | period: '2010 Q2', 12 | iphone: 2778, 13 | ipad: 2294, 14 | itouch: 2441 15 | }, { 16 | period: '2010 Q3', 17 | iphone: 4912, 18 | ipad: 1969, 19 | itouch: 2501 20 | }, { 21 | period: '2010 Q4', 22 | iphone: 3767, 23 | ipad: 3597, 24 | itouch: 5689 25 | }, { 26 | period: '2011 Q1', 27 | iphone: 6810, 28 | ipad: 1914, 29 | itouch: 2293 30 | }, { 31 | period: '2011 Q2', 32 | iphone: 5670, 33 | ipad: 4293, 34 | itouch: 1881 35 | }, { 36 | period: '2011 Q3', 37 | iphone: 4820, 38 | ipad: 3795, 39 | itouch: 1588 40 | }, { 41 | period: '2011 Q4', 42 | iphone: 15073, 43 | ipad: 5967, 44 | itouch: 5175 45 | }, { 46 | period: '2012 Q1', 47 | iphone: 10687, 48 | ipad: 4460, 49 | itouch: 2028 50 | }, { 51 | period: '2012 Q2', 52 | iphone: 8432, 53 | ipad: 5713, 54 | itouch: 1791 55 | }], 56 | xkey: 'period', 57 | ykeys: ['iphone', 'ipad', 'itouch'], 58 | labels: ['iPhone', 'iPad', 'iPod Touch'], 59 | pointSize: 2, 60 | hideHover: 'auto', 61 | resize: true 62 | }); 63 | 64 | Morris.Donut({ 65 | element: 'morris-donut-chart', 66 | data: [{ 67 | label: "Download Sales", 68 | value: 12 69 | }, { 70 | label: "In-Store Sales", 71 | value: 30 72 | }, { 73 | label: "Mail-Order Sales", 74 | value: 20 75 | }], 76 | resize: true 77 | }); 78 | 79 | Morris.Bar({ 80 | element: 'morris-bar-chart', 81 | data: [{ 82 | y: '2006', 83 | a: 100, 84 | b: 90 85 | }, { 86 | y: '2007', 87 | a: 75, 88 | b: 65 89 | }, { 90 | y: '2008', 91 | a: 50, 92 | b: 40 93 | }, { 94 | y: '2009', 95 | a: 75, 96 | b: 65 97 | }, { 98 | y: '2010', 99 | a: 50, 100 | b: 40 101 | }, { 102 | y: '2011', 103 | a: 75, 104 | b: 65 105 | }, { 106 | y: '2012', 107 | a: 100, 108 | b: 90 109 | }], 110 | xkey: 'y', 111 | ykeys: ['a', 'b'], 112 | labels: ['Series A', 'Series B'], 113 | hideHover: 'auto', 114 | resize: true 115 | }); 116 | 117 | Morris.Line({ 118 | element: 'morris-line-chart', 119 | data: [{ 120 | y: '2006', 121 | a: 100, 122 | b: 90 123 | }, { 124 | y: '2007', 125 | a: 75, 126 | b: 65 127 | }, { 128 | y: '2008', 129 | a: 50, 130 | b: 40 131 | }, { 132 | y: '2009', 133 | a: 75, 134 | b: 65 135 | }, { 136 | y: '2010', 137 | a: 50, 138 | b: 40 139 | }, { 140 | y: '2011', 141 | a: 75, 142 | b: 65 143 | }, { 144 | y: '2012', 145 | a: 100, 146 | b: 90 147 | }], 148 | xkey: 'y', 149 | ykeys: ['a', 'b'], 150 | labels: ['Series A', 'Series B'], 151 | hideHover: 'auto', 152 | resize: true 153 | }); 154 | 155 | }); 156 | -------------------------------------------------------------------------------- /static/panel/js/plugins/dataTables/dataTables.bootstrap.js: -------------------------------------------------------------------------------- 1 | /* Set the defaults for DataTables initialisation */ 2 | $.extend(true, $.fn.dataTable.defaults, { 3 | "sDom": "<'row'<'col-sm-6'l><'col-sm-6'f>r>" + "t" + "<'row'<'col-sm-6'i><'col-sm-6'p>>", 4 | "oLanguage": { 5 | "sLengthMenu": "_MENU_ records per page" 6 | } 7 | }); 8 | 9 | 10 | /* Default class modification */ 11 | $.extend($.fn.dataTableExt.oStdClasses, { 12 | "sWrapper": "dataTables_wrapper form-inline", 13 | "sFilterInput": "form-control input-sm", 14 | "sLengthSelect": "form-control input-sm" 15 | }); 16 | 17 | // In 1.10 we use the pagination renderers to draw the Bootstrap paging, 18 | // rather than custom plug-in 19 | if ($.fn.dataTable.Api) { 20 | $.fn.dataTable.defaults.renderer = 'bootstrap'; 21 | $.fn.dataTable.ext.renderer.pageButton.bootstrap = function(settings, host, idx, buttons, page, pages) { 22 | var api = new $.fn.dataTable.Api(settings); 23 | var classes = settings.oClasses; 24 | var lang = settings.oLanguage.oPaginate; 25 | var btnDisplay, btnClass; 26 | 27 | var attach = function(container, buttons) { 28 | var i, ien, node, button; 29 | var clickHandler = function(e) { 30 | e.preventDefault(); 31 | if (e.data.action !== 'ellipsis') { 32 | api.page(e.data.action).draw(false); 33 | } 34 | }; 35 | 36 | for (i = 0, ien = buttons.length; i < ien; i++) { 37 | button = buttons[i]; 38 | 39 | if ($.isArray(button)) { 40 | attach(container, button); 41 | } else { 42 | btnDisplay = ''; 43 | btnClass = ''; 44 | 45 | switch (button) { 46 | case 'ellipsis': 47 | btnDisplay = '…'; 48 | btnClass = 'disabled'; 49 | break; 50 | 51 | case 'first': 52 | btnDisplay = lang.sFirst; 53 | btnClass = button + (page > 0 ? 54 | '' : ' disabled'); 55 | break; 56 | 57 | case 'previous': 58 | btnDisplay = lang.sPrevious; 59 | btnClass = button + (page > 0 ? 60 | '' : ' disabled'); 61 | break; 62 | 63 | case 'next': 64 | btnDisplay = lang.sNext; 65 | btnClass = button + (page < pages - 1 ? 66 | '' : ' disabled'); 67 | break; 68 | 69 | case 'last': 70 | btnDisplay = lang.sLast; 71 | btnClass = button + (page < pages - 1 ? 72 | '' : ' disabled'); 73 | break; 74 | 75 | default: 76 | btnDisplay = button + 1; 77 | btnClass = page === button ? 78 | 'active' : ''; 79 | break; 80 | } 81 | 82 | if (btnDisplay) { 83 | node = $('
  • ', { 84 | 'class': classes.sPageButton + ' ' + btnClass, 85 | 'aria-controls': settings.sTableId, 86 | 'tabindex': settings.iTabIndex, 87 | 'id': idx === 0 && typeof button === 'string' ? settings.sTableId + '_' + button : null 88 | }) 89 | .append($('', { 90 | 'href': '#' 91 | }) 92 | .html(btnDisplay) 93 | ) 94 | .appendTo(container); 95 | 96 | settings.oApi._fnBindAction( 97 | node, { 98 | action: button 99 | }, clickHandler 100 | ); 101 | } 102 | } 103 | } 104 | }; 105 | 106 | attach( 107 | $(host).empty().html('
      ').children('ul'), 108 | buttons 109 | ); 110 | } 111 | } else { 112 | // Integration for 1.9- 113 | $.fn.dataTable.defaults.sPaginationType = 'bootstrap'; 114 | 115 | /* API method to get paging information */ 116 | $.fn.dataTableExt.oApi.fnPagingInfo = function(oSettings) { 117 | return { 118 | "iStart": oSettings._iDisplayStart, 119 | "iEnd": oSettings.fnDisplayEnd(), 120 | "iLength": oSettings._iDisplayLength, 121 | "iTotal": oSettings.fnRecordsTotal(), 122 | "iFilteredTotal": oSettings.fnRecordsDisplay(), 123 | "iPage": oSettings._iDisplayLength === -1 ? 0 : Math.ceil(oSettings._iDisplayStart / oSettings._iDisplayLength), 124 | "iTotalPages": oSettings._iDisplayLength === -1 ? 0 : Math.ceil(oSettings.fnRecordsDisplay() / oSettings._iDisplayLength) 125 | }; 126 | }; 127 | 128 | /* Bootstrap style pagination control */ 129 | $.extend($.fn.dataTableExt.oPagination, { 130 | "bootstrap": { 131 | "fnInit": function(oSettings, nPaging, fnDraw) { 132 | var oLang = oSettings.oLanguage.oPaginate; 133 | var fnClickHandler = function(e) { 134 | e.preventDefault(); 135 | if (oSettings.oApi._fnPageChange(oSettings, e.data.action)) { 136 | fnDraw(oSettings); 137 | } 138 | }; 139 | 140 | $(nPaging).append( 141 | '' 145 | ); 146 | var els = $('a', nPaging); 147 | $(els[0]).bind('click.DT', { 148 | action: "previous" 149 | }, fnClickHandler); 150 | $(els[1]).bind('click.DT', { 151 | action: "next" 152 | }, fnClickHandler); 153 | }, 154 | 155 | "fnUpdate": function(oSettings, fnDraw) { 156 | var iListLength = 5; 157 | var oPaging = oSettings.oInstance.fnPagingInfo(); 158 | var an = oSettings.aanFeatures.p; 159 | var i, ien, j, sClass, iStart, iEnd, iHalf = Math.floor(iListLength / 2); 160 | 161 | if (oPaging.iTotalPages < iListLength) { 162 | iStart = 1; 163 | iEnd = oPaging.iTotalPages; 164 | } else if (oPaging.iPage <= iHalf) { 165 | iStart = 1; 166 | iEnd = iListLength; 167 | } else if (oPaging.iPage >= (oPaging.iTotalPages - iHalf)) { 168 | iStart = oPaging.iTotalPages - iListLength + 1; 169 | iEnd = oPaging.iTotalPages; 170 | } else { 171 | iStart = oPaging.iPage - iHalf + 1; 172 | iEnd = iStart + iListLength - 1; 173 | } 174 | 175 | for (i = 0, ien = an.length; i < ien; i++) { 176 | // Remove the middle elements 177 | $('li:gt(0)', an[i]).filter(':not(:last)').remove(); 178 | 179 | // Add the new list items and their event handlers 180 | for (j = iStart; j <= iEnd; j++) { 181 | sClass = (j == oPaging.iPage + 1) ? 'class="active"' : ''; 182 | $('
    • ' + j + '
    • ') 183 | .insertBefore($('li:last', an[i])[0]) 184 | .bind('click', function(e) { 185 | e.preventDefault(); 186 | oSettings._iDisplayStart = (parseInt($('a', this).text(), 10) - 1) * oPaging.iLength; 187 | fnDraw(oSettings); 188 | }); 189 | } 190 | 191 | // Add / remove disabled classes from the static elements 192 | if (oPaging.iPage === 0) { 193 | $('li:first', an[i]).addClass('disabled'); 194 | } else { 195 | $('li:first', an[i]).removeClass('disabled'); 196 | } 197 | 198 | if (oPaging.iPage === oPaging.iTotalPages - 1 || oPaging.iTotalPages === 0) { 199 | $('li:last', an[i]).addClass('disabled'); 200 | } else { 201 | $('li:last', an[i]).removeClass('disabled'); 202 | } 203 | } 204 | } 205 | } 206 | }); 207 | } 208 | 209 | 210 | /* 211 | * TableTools Bootstrap compatibility 212 | * Required TableTools 2.1+ 213 | */ 214 | if ($.fn.DataTable.TableTools) { 215 | // Set the classes that TableTools uses to something suitable for Bootstrap 216 | $.extend(true, $.fn.DataTable.TableTools.classes, { 217 | "container": "DTTT btn-group", 218 | "buttons": { 219 | "normal": "btn btn-default", 220 | "disabled": "disabled" 221 | }, 222 | "collection": { 223 | "container": "DTTT_dropdown dropdown-menu", 224 | "buttons": { 225 | "normal": "", 226 | "disabled": "disabled" 227 | } 228 | }, 229 | "print": { 230 | "info": "DTTT_print_info modal" 231 | }, 232 | "select": { 233 | "row": "active" 234 | } 235 | }); 236 | 237 | // Have the collection use a bootstrap compatible dropdown 238 | $.extend(true, $.fn.DataTable.TableTools.DEFAULTS.oTags, { 239 | "collection": { 240 | "container": "ul", 241 | "button": "li", 242 | "liner": "a" 243 | } 244 | }); 245 | } 246 | -------------------------------------------------------------------------------- /static/panel/js/plugins/flot/jquery.flot.resize.js: -------------------------------------------------------------------------------- 1 | /* Flot plugin for automatically redrawing plots as the placeholder resizes. 2 | 3 | Copyright (c) 2007-2013 IOLA and Ole Laursen. 4 | Licensed under the MIT license. 5 | 6 | It works by listening for changes on the placeholder div (through the jQuery 7 | resize event plugin) - if the size changes, it will redraw the plot. 8 | 9 | There are no options. If you need to disable the plugin for some plots, you 10 | can just fix the size of their placeholders. 11 | 12 | */ 13 | 14 | /* Inline dependency: 15 | * jQuery resize event - v1.1 - 3/14/2010 16 | * http://benalman.com/projects/jquery-resize-plugin/ 17 | * 18 | * Copyright (c) 2010 "Cowboy" Ben Alman 19 | * Dual licensed under the MIT and GPL licenses. 20 | * http://benalman.com/about/license/ 21 | */ 22 | 23 | (function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this); 24 | 25 | (function ($) { 26 | var options = { }; // no options 27 | 28 | function init(plot) { 29 | function onResize() { 30 | var placeholder = plot.getPlaceholder(); 31 | 32 | // somebody might have hidden us and we can't plot 33 | // when we don't have the dimensions 34 | if (placeholder.width() == 0 || placeholder.height() == 0) 35 | return; 36 | 37 | plot.resize(); 38 | plot.setupGrid(); 39 | plot.draw(); 40 | } 41 | 42 | function bindEvents(plot, eventHolder) { 43 | plot.getPlaceholder().resize(onResize); 44 | } 45 | 46 | function shutdown(plot, eventHolder) { 47 | plot.getPlaceholder().unbind("resize", onResize); 48 | } 49 | 50 | plot.hooks.bindEvents.push(bindEvents); 51 | plot.hooks.shutdown.push(shutdown); 52 | } 53 | 54 | $.plot.plugins.push({ 55 | init: init, 56 | options: options, 57 | name: 'resize', 58 | version: '1.0' 59 | }); 60 | })(jQuery); -------------------------------------------------------------------------------- /static/panel/js/plugins/flot/jquery.flot.tooltip.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.flot.tooltip 3 | * 4 | * description: easy-to-use tooltips for Flot charts 5 | * version: 0.6.2 6 | * author: Krzysztof Urbas @krzysu [myviews.pl] 7 | * website: https://github.com/krzysu/flot.tooltip 8 | * 9 | * build on 2013-09-30 10 | * released under MIT License, 2012 11 | */ 12 | (function(t){var o={tooltip:!1,tooltipOpts:{content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,shifts:{x:10,y:20},defaultTheme:!0,onHover:function(){}}},i=function(t){this.tipPosition={x:0,y:0},this.init(t)};i.prototype.init=function(o){function i(t){var o={};o.x=t.pageX,o.y=t.pageY,s.updateTooltipPosition(o)}function e(t,o,i){var e=s.getDomElement();if(i){var n;n=s.stringFormat(s.tooltipOptions.content,i),e.html(n),s.updateTooltipPosition({x:o.pageX,y:o.pageY}),e.css({left:s.tipPosition.x+s.tooltipOptions.shifts.x,top:s.tipPosition.y+s.tooltipOptions.shifts.y}).show(),"function"==typeof s.tooltipOptions.onHover&&s.tooltipOptions.onHover(i,e)}else e.hide().html("")}var s=this;o.hooks.bindEvents.push(function(o,n){s.plotOptions=o.getOptions(),s.plotOptions.tooltip!==!1&&void 0!==s.plotOptions.tooltip&&(s.tooltipOptions=s.plotOptions.tooltipOpts,s.getDomElement(),t(o.getPlaceholder()).bind("plothover",e),t(n).bind("mousemove",i))}),o.hooks.shutdown.push(function(o,s){t(o.getPlaceholder()).unbind("plothover",e),t(s).unbind("mousemove",i)})},i.prototype.getDomElement=function(){var o;return t("#flotTip").length>0?o=t("#flotTip"):(o=t("
      ").attr("id","flotTip"),o.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&o.css({background:"#fff","z-index":"100",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),o},i.prototype.updateTooltipPosition=function(o){var i=t("#flotTip").outerWidth()+this.tooltipOptions.shifts.x,e=t("#flotTip").outerHeight()+this.tooltipOptions.shifts.y;o.x-t(window).scrollLeft()>t(window).innerWidth()-i&&(o.x-=i),o.y-t(window).scrollTop()>t(window).innerHeight()-e&&(o.y-=e),this.tipPosition.x=o.x,this.tipPosition.y=o.y},i.prototype.stringFormat=function(t,o){var i=/%p\.{0,1}(\d{0,})/,e=/%s/,s=/%x\.{0,1}(?:\d{0,})/,n=/%y\.{0,1}(?:\d{0,})/;return"function"==typeof t&&(t=t(o.series.label,o.series.data[o.dataIndex][0],o.series.data[o.dataIndex][1],o)),o.series.percent!==void 0&&(t=this.adjustValPrecision(i,t,o.series.percent)),o.series.label!==void 0&&(t=t.replace(e,o.series.label)),this.isTimeMode("xaxis",o)&&this.isXDateFormat(o)&&(t=t.replace(s,this.timestampToDate(o.series.data[o.dataIndex][0],this.tooltipOptions.xDateFormat))),this.isTimeMode("yaxis",o)&&this.isYDateFormat(o)&&(t=t.replace(n,this.timestampToDate(o.series.data[o.dataIndex][1],this.tooltipOptions.yDateFormat))),"number"==typeof o.series.data[o.dataIndex][0]&&(t=this.adjustValPrecision(s,t,o.series.data[o.dataIndex][0])),"number"==typeof o.series.data[o.dataIndex][1]&&(t=this.adjustValPrecision(n,t,o.series.data[o.dataIndex][1])),o.series.xaxis.tickFormatter!==void 0&&(t=t.replace(s,o.series.xaxis.tickFormatter(o.series.data[o.dataIndex][0],o.series.xaxis))),o.series.yaxis.tickFormatter!==void 0&&(t=t.replace(n,o.series.yaxis.tickFormatter(o.series.data[o.dataIndex][1],o.series.yaxis))),t},i.prototype.isTimeMode=function(t,o){return o.series[t].options.mode!==void 0&&"time"===o.series[t].options.mode},i.prototype.isXDateFormat=function(){return this.tooltipOptions.xDateFormat!==void 0&&null!==this.tooltipOptions.xDateFormat},i.prototype.isYDateFormat=function(){return this.tooltipOptions.yDateFormat!==void 0&&null!==this.tooltipOptions.yDateFormat},i.prototype.timestampToDate=function(o,i){var e=new Date(o);return t.plot.formatDate(e,i)},i.prototype.adjustValPrecision=function(t,o,i){var e,s=o.match(t);return null!==s&&""!==RegExp.$1&&(e=RegExp.$1,i=i.toFixed(e),o=o.replace(t,i)),o};var e=function(t){new i(t)};t.plot.plugins.push({init:e,options:o,name:"tooltip",version:"0.6.1"})})(jQuery); -------------------------------------------------------------------------------- /static/panel/js/plugins/metisMenu/jquery.metisMenu.js: -------------------------------------------------------------------------------- 1 | ;(function ($, window, document, undefined) { 2 | 3 | var pluginName = "metisMenu", 4 | defaults = { 5 | toggle: true 6 | }; 7 | 8 | function Plugin(element, options) { 9 | this.element = element; 10 | this.settings = $.extend({}, defaults, options); 11 | this._defaults = defaults; 12 | this._name = pluginName; 13 | this.init(); 14 | } 15 | 16 | Plugin.prototype = { 17 | init: function () { 18 | 19 | var $this = $(this.element), 20 | $toggle = this.settings.toggle; 21 | 22 | $this.find('li.active').has('ul').children('ul').addClass('collapse in'); 23 | $this.find('li').not('.active').has('ul').children('ul').addClass('collapse'); 24 | 25 | $this.find('li').has('ul').children('a').on('click', function (e) { 26 | e.preventDefault(); 27 | 28 | $(this).parent('li').toggleClass('active').children('ul').collapse('toggle'); 29 | 30 | if ($toggle) { 31 | $(this).parent('li').siblings().removeClass('active').children('ul.in').collapse('hide'); 32 | } 33 | }); 34 | } 35 | }; 36 | 37 | $.fn[ pluginName ] = function (options) { 38 | return this.each(function () { 39 | if (!$.data(this, "plugin_" + pluginName)) { 40 | $.data(this, "plugin_" + pluginName, new Plugin(this, options)); 41 | } 42 | }); 43 | }; 44 | 45 | })(jQuery, window, document); 46 | -------------------------------------------------------------------------------- /static/panel/js/sb-admin.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | $('#side-menu').metisMenu(); 4 | 5 | }); 6 | 7 | //Loads the correct sidebar on window load, 8 | //collapses the sidebar on window resize. 9 | $(function() { 10 | $(window).bind("load resize", function() { 11 | width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width; 12 | if (width < 768) { 13 | $('div.sidebar-collapse').addClass('collapse') 14 | } else { 15 | $('div.sidebar-collapse').removeClass('collapse') 16 | } 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /template/common/403.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 3 | 错误信息 4 | {% endblock %} 5 | {% block inside_row %} 6 |
      7 |
      8 |

      无权访问

      9 |
      10 | Go back 11 |
      12 | {% endblock %} -------------------------------------------------------------------------------- /template/common/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 3 | 404 Not Found-页面未找到 4 | {% endblock %} 5 | {% block inside_row %} 6 |
      7 |
      8 |

      页面未找到

      9 |
      10 | Go back 11 |
      12 | {% endblock %} -------------------------------------------------------------------------------- /template/common/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %} 3 | 500 Internal Error 4 | {% endblock %} 5 | {% block inside_row %} 6 |
      7 |
      8 |

      Internal Error Detected

      9 |
      10 | Go back 11 |
      12 | {% endblock %} -------------------------------------------------------------------------------- /template/common/base-with-sidebar.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block inside_row %} 3 |
      4 | {% block left %}{% endblock %} 5 |
      6 | 9 | {% endblock %} -------------------------------------------------------------------------------- /template/common/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | {% load settingsvalue %} 3 | {% load i18n %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | {% block header_ext%} 11 | {% endblock %} 12 | 13 | {% spaceless %} 14 | {% block title %} 15 | {{title}}- 16 | {% if pager %} 17 | {% blocktrans %}page {{ pager }}{% endblocktrans %} 18 | - 19 | {% endif %} 20 | {% conf_value "sitename" %} 21 | {% endblock %} 22 | {% endspaceless %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 64 | 65 |
      66 |
      67 |
      68 | {% block inside_row %} 69 | {% endblock %} 70 |
      71 |
      72 |
      73 | 74 |
      75 |
      76 |
      77 |

      78 | A Django project

      79 |
      80 |

      81 | © 82 | 85 | Powered by FairyBBS 0.0.4. 86 |

      87 |
      88 |
      89 | 90 | 91 | 92 | 93 | {% block footer_ext %}{% endblock %} 94 | 95 | 96 | -------------------------------------------------------------------------------- /template/common/error.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block inside_row %} 3 |
      4 |
      5 |

      {{msg}}

      6 |
      7 | Go back 14 |
      15 | {% endblock %} -------------------------------------------------------------------------------- /template/pagination/pagination.html: -------------------------------------------------------------------------------- 1 | {% if is_paginated %} 2 |
        3 | {% if page_obj.has_previous %} 4 |
      • 5 | « 6 |
      • 7 | {% else %} 8 |
      • 9 | « 10 |
      • 11 | {% endif %} 12 | {% for page in pages %} 13 | {% if page %} 14 | {% ifequal page page_obj.number %} 15 |
      • 16 | {{ page }} 17 |
      • 18 | {% else %} 19 |
      • 20 | {{ page }} 21 |
      • 22 | {% endifequal %} 23 | {% else %} 24 |
      • 25 | ... 26 |
      • 27 | {% endif %} 28 | {% endfor %} 29 | {% if page_obj.has_next %} 30 |
      • 31 | » 32 |
      • 33 | {% else %} 34 |
      • 35 | » 36 |
      • 37 | {% endif %} 38 |
      39 | {% endif %} -------------------------------------------------------------------------------- /template/widget/links.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
      3 |
      4 |

      5 | {% trans 'links' %} 6 |

      7 |
      8 |
        9 | 12 | {% for key,val in conf.links.items %} 13 |
      • 14 | {{key}} 15 |
      • 16 | {% endfor %} 17 |
      18 |
      19 | -------------------------------------------------------------------------------- /template/widget/node-info.html: -------------------------------------------------------------------------------- 1 |
      2 |
      3 |

      4 | {{node.title}} 5 |

      6 |
      7 |
      8 | {{node.description}} 9 |
      10 | 11 |
      12 | -------------------------------------------------------------------------------- /template/widget/node.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
      3 |
      4 |

      5 | {% trans 'nodes' %} 6 |

      7 |
      8 |
      9 | {% for node in conf.nodes %} 10 | {{node.title}} 11 | {% endfor %} 12 |
      13 |
      14 | -------------------------------------------------------------------------------- /template/widget/stat.html: -------------------------------------------------------------------------------- 1 | {% load i18n%} 2 |
      3 |
      4 |

      5 | {% trans 'site stat' %} 6 |

      7 |
      8 |
        9 |
      • 10 | {% trans 'number of users'%}:{{conf.user_count}} 11 |
      • 12 |
      • 13 | {% trans 'number of topics'%}:{{conf.topic_count}} 14 |
      • 15 |
      • 16 | {% trans 'number of replies'%}:{{conf.post_count}} 17 |
      • 18 |
      19 |
      20 | -------------------------------------------------------------------------------- /template/widget/topic-list.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load humanize %} 3 | {% load pagination_tags %} 4 |
      5 |
      6 | 7 | 8 | {% if post_list_title %} 9 | {{post_list_title}} 10 | {% else %} 11 | {% trans 'home' %} {{node.title}} 12 | {% endif %} 13 | 14 | {% if node_view %} 15 | {% trans 'new topic' %} 16 | {% endif %} 17 |
      18 | {% if not topics %} 19 |
      20 |
      21 | {% trans "sorry, but we can't find the topics you need" %} 22 |
      23 |
      24 | {% endif %} 25 |
        26 | {% autopaginate topics 30 %} 27 | {% for t in topics %} 28 |
        29 |
        30 | 31 |
        32 |

        {{t.title}}

        33 |

        34 | {{t.node.title}} 35 | {% with clicks=t.click %} 36 | {% blocktrans %}{{clicks}} clicks{% endblocktrans %} 37 | {% endwith %} 38 | {{t.time_created | naturaltime}} 39 | {{t.user.profile.username}} 40 | {{reply_count}} 41 |

        42 |
        43 |
        44 | 45 |
        46 |
        47 |
        48 | {% endfor %} 49 |
      50 | 53 |
      54 | 55 | -------------------------------------------------------------------------------- /template/widget/user-panel.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
      3 |
      4 |

      {% if request.user.is_authenticated %} 5 | {{request.user.profile.username}} 6 | {% else %} 7 | 什么是FairyBBS 8 | {% endif %}

      9 |
      10 |
      11 | {% if request.user.is_authenticated %} 12 |
      13 | 14 |
      15 | 18 | 21 | 24 |
      25 | 26 |
      27 | {% else %} 28 |

      29 | FairyBBS是使用Django框架建立的小型论坛系统 30 |

      31 |
      32 | 35 |
      36 | {% trans 'reg' %} 37 |
      38 |
      39 | {% comment %} 40 |
      41 |
      42 |

      其他登陆方式: 43 | 44 | 45 | 46 |

      47 |
      48 | {% endcomment %} 49 | {% endif %} 50 |
      51 | {% if request.user.profile.unread_mention %} 52 | 55 | {% endif %} 56 |
      57 | --------------------------------------------------------------------------------