├── fairy ├── __init__.py ├── middleware.py ├── wsgi.py ├── conf.py ├── urls.py └── settings.py ├── forum ├── __init__.py ├── templatetags │ ├── __init__.py │ └── settingsvalue.py ├── tests.py ├── templates │ └── forum │ │ ├── index.html │ │ ├── node-view.html │ │ ├── node-all.html │ │ ├── append.html │ │ ├── edit-topic.html │ │ ├── create-topic.html │ │ └── topic.html ├── admin.py ├── urls.py ├── api.py ├── locale │ └── zh_CN │ │ └── LC_MESSAGES │ │ └── django.po ├── models.py └── views.py ├── panel ├── __init__.py ├── templates │ └── panel │ │ ├── index.html │ │ ├── node-edit.html │ │ ├── node-create.html │ │ ├── login.html │ │ ├── node-manage.html │ │ ├── user-manage.html │ │ ├── topic-manage.html │ │ ├── topic-edit.html │ │ ├── base.html │ │ └── user-edit.html ├── models.py ├── tests.py ├── admin.py ├── urls.py └── locale │ └── zh_CN │ └── LC_MESSAGES │ └── django.po ├── account ├── __init__.py ├── templates │ └── account │ │ ├── reset-password-subject.txt │ │ ├── reset-password-email.html │ │ ├── user-info.html │ │ ├── login.html │ │ ├── user-avatar.html │ │ ├── user-setting.html │ │ ├── reset-password.html │ │ ├── reg.html │ │ ├── reset-password-confirm.html │ │ ├── change-password.html │ │ └── user-mention.html ├── tests.py ├── admin.py ├── urls.py ├── models.py └── locale │ └── zh_CN │ └── LC_MESSAGES │ └── django.po ├── static ├── image │ ├── bg.png │ ├── pixels.png │ └── Connect_logo_7.png ├── fonts │ ├── .directory │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── panel │ ├── font-awesome │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ ├── less │ │ │ ├── fixed-width.less │ │ │ ├── core.less │ │ │ ├── bordered-pulled.less │ │ │ ├── rotated-flipped.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── font-awesome.less │ │ │ ├── stacked.less │ │ │ ├── path.less │ │ │ ├── mixins.less │ │ │ └── spinning.less │ │ └── scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _core.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _larger.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _list.scss │ │ │ ├── font-awesome.scss │ │ │ ├── _stacked.scss │ │ │ ├── _path.scss │ │ │ ├── _mixins.scss │ │ │ └── _spinning.scss │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── css │ │ ├── plugins │ │ │ ├── morris │ │ │ │ └── morris-0.4.3.min.css │ │ │ ├── timeline │ │ │ │ └── timeline.css │ │ │ └── dataTables │ │ │ │ └── dataTables.bootstrap.css │ │ └── sb-admin.css │ └── js │ │ ├── sb-admin.js │ │ ├── plugins │ │ ├── metisMenu │ │ │ └── jquery.metisMenu.js │ │ ├── flot │ │ │ ├── jquery.flot.resize.js │ │ │ └── jquery.flot.tooltip.min.js │ │ └── dataTables │ │ │ └── dataTables.bootstrap.js │ │ ├── demo │ │ ├── dashboard-demo.js │ │ └── morris-demo.js │ │ └── autocomplete.js ├── js │ ├── mention.js │ ├── search.js │ ├── previewer.js │ ├── html5shiv.js │ └── respond.min.js └── css │ ├── codehilite.css │ └── base.css ├── requirements.txt ├── README.md ├── manage.py ├── template ├── common │ ├── base-with-sidebar.html │ ├── 403.html │ ├── 404.html │ ├── 500.html │ ├── error.html │ └── base.html ├── widget │ ├── node-info.html │ ├── node.html │ ├── links.html │ ├── stat.html │ ├── topic-list.html │ └── user-panel.html └── pagination │ └── pagination.html ├── .gitignore └── locale └── zh_CN └── LC_MESSAGES └── django.po /fairy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /forum/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /panel/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /account/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /forum/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /account/templates/account/reset-password-subject.txt: -------------------------------------------------------------------------------- 1 | Password Reset 2 | -------------------------------------------------------------------------------- /panel/templates/panel/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'panel/base.html' %} 2 | 3 | -------------------------------------------------------------------------------- /account/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /forum/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /panel/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /panel/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /panel/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /static/image/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/image/bg.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Markdown==2.4 2 | Pygments==1.6 3 | django-pagination==1.0.7 4 | pillow==2.4.0 -------------------------------------------------------------------------------- /static/image/pixels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/image/pixels.png -------------------------------------------------------------------------------- /static/fonts/.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | PreviewsShown=true 3 | Timestamp=2014,2,19,23,27,30 4 | Version=3 5 | -------------------------------------------------------------------------------- /static/image/Connect_logo_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/image/Connect_logo_7.png -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/panel/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/panel/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/panel/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/panel/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/panel/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/panel/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/panel/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/panel/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/panel/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/panel/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/panel/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/panel/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/panel/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericls/FairyBBS/HEAD/static/panel/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /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/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /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/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/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 %} -------------------------------------------------------------------------------- /static/js/mention.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | $('.reply-btn').click( 3 | function(){ 4 | var user=$(this).data('user'); 5 | console.log(user); 6 | $('#content').append('@' + user + ' '); 7 | } 8 | ); 9 | }); 10 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /template/common/base-with-sidebar.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block inside_row %} 3 |
4 | {% block left %}{% endblock %} 5 |
6 | 9 | {% endblock %} -------------------------------------------------------------------------------- /template/widget/node-info.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{node.title}} 5 |

6 |
7 |
8 | {{node.description}} 9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /static/js/search.js: -------------------------------------------------------------------------------- 1 | function search() { 2 | var keyword = $('#search').val(); 3 | var dst = "/search/" + keyword + "/"; 4 | if (keyword != '') { 5 | window.location = dst; 6 | } else { 7 | return false; 8 | }; 9 | } 10 | 11 | $(document).ready(function(){ 12 | $('#search-btn').click( 13 | function(){ 14 | search(); 15 | } 16 | ); 17 | }); -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /static/panel/css/plugins/morris/morris-0.4.3.min.css: -------------------------------------------------------------------------------- 1 | .morris-hover{position:absolute;z-index:1000;}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255, 255, 255, 0.8);border:solid 2px rgba(230, 230, 230, 0.8);font-family:sans-serif;font-size:12px;text-align:center;}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0;} 2 | .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0;} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/_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 | -------------------------------------------------------------------------------- /template/common/error.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block inside_row %} 3 |
4 |
5 |

{{msg}}

6 |
7 | Go back 14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /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/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/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/widget/links.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
3 |
4 |

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

7 |
8 | 18 |
19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /template/widget/stat.html: -------------------------------------------------------------------------------- 1 | {% load i18n%} 2 |
3 |
4 |

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

7 |
8 | 19 |
20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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/templates/forum/append.html: -------------------------------------------------------------------------------- 1 | {% extends 'base-with-sidebar.html' %} 2 | {% load i18n %} 3 | {% block left %} 4 |
5 |
6 |

7 | {% blocktrans %} 8 | create appendix to topic:{{topic}} 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 %} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /static/js/previewer.js: -------------------------------------------------------------------------------- 1 | function getCookie(name) { 2 | var cookieValue = null; 3 | if (document.cookie && document.cookie != '') { 4 | var cookies = document.cookie.split(';'); 5 | for (var i = 0; i < cookies.length; i++) { 6 | var cookie = jQuery.trim(cookies[i]); 7 | // Does this cookie string begin with the name we want? 8 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 9 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 10 | break; 11 | } 12 | } 13 | } 14 | return cookieValue; 15 | } 16 | var csrftoken = getCookie('csrftoken'); 17 | 18 | function csrfSafeMethod(method) { 19 | // these HTTP methods do not require CSRF protection 20 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 21 | } 22 | 23 | $.ajaxSetup({ 24 | crossDomain: false, // obviates need for sameOrigin test 25 | beforeSend: function(xhr, settings) { 26 | if (!csrfSafeMethod(settings.type)) { 27 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 28 | } 29 | } 30 | }); 31 | 32 | 33 | function pre() { 34 | $.post("/previewer/", 35 | { 'content': $('#content').val()}, 36 | function(data) { 37 | console.log(data); 38 | a = $.parseJSON(data); 39 | $(".previewer").html(a.marked); 40 | }); 41 | } 42 | 43 | 44 | 45 | $(document).ready(function(){ 46 | $(document).keypress(function(e){ 47 | if(e.ctrlKey && e.which==13 || e.which==10) 48 | { 49 | $('#submit').click() 50 | } 51 | }); 52 | $('#pre-btn').click( 53 | function(){ 54 | pre(); 55 | }); 56 | }); -------------------------------------------------------------------------------- /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/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/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 %} -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 %} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 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 %} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/panel/css/plugins/timeline/timeline.css: -------------------------------------------------------------------------------- 1 | .timeline { 2 | position: relative; 3 | padding: 20px 0 20px; 4 | list-style: none; 5 | } 6 | 7 | .timeline:before { 8 | content: " "; 9 | position: absolute; 10 | top: 0; 11 | bottom: 0; 12 | left: 50%; 13 | width: 3px; 14 | margin-left: -1.5px; 15 | background-color: #eeeeee; 16 | } 17 | 18 | .timeline > 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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /static/js/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',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, 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 的附言 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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /static/panel/css/plugins/dataTables/dataTables.bootstrap.css: -------------------------------------------------------------------------------- 1 | div.dataTables_length label { 2 | float: left; 3 | text-align: left; 4 | font-weight: normal; 5 | } 6 | 7 | div.dataTables_length select { 8 | width: 75px; 9 | } 10 | 11 | div.dataTables_filter label { 12 | float: right; 13 | font-weight: normal; 14 | } 15 | 16 | div.dataTables_filter input { 17 | width: 16em; 18 | } 19 | 20 | div.dataTables_info { 21 | padding-top: 8px; 22 | } 23 | 24 | div.dataTables_paginate { 25 | float: right; 26 | margin: 0; 27 | } 28 | 29 | div.dataTables_paginate ul.pagination { 30 | margin: 2px 0; 31 | white-space: nowrap; 32 | } 33 | 34 | table.dataTable, 35 | table.dataTable td, 36 | table.dataTable th { 37 | -webkit-box-sizing: content-box; 38 | -moz-box-sizing: content-box; 39 | box-sizing: content-box; 40 | } 41 | 42 | table.dataTable { 43 | clear: both; 44 | margin-top: 6px !important; 45 | margin-bottom: 6px !important; 46 | max-width: none !important; 47 | } 48 | 49 | table.dataTable thead .sorting, 50 | table.dataTable thead .sorting_asc, 51 | table.dataTable thead .sorting_desc, 52 | table.dataTable thead .sorting_asc_disabled, 53 | table.dataTable thead .sorting_desc_disabled { 54 | cursor: pointer; 55 | } 56 | 57 | table.dataTable thead .sorting { 58 | background: url('../images/sort_both.png') no-repeat center right; 59 | } 60 | 61 | table.dataTable thead .sorting_asc { 62 | background: url('../images/sort_asc.png') no-repeat center right; 63 | } 64 | 65 | table.dataTable thead .sorting_desc { 66 | background: url('../images/sort_desc.png') no-repeat center right; 67 | } 68 | 69 | table.dataTable thead .sorting_asc_disabled { 70 | background: url('../images/sort_asc_disabled.png') no-repeat center right; 71 | } 72 | 73 | table.dataTable thead .sorting_desc_disabled { 74 | background: url('../images/sort_desc_disabled.png') no-repeat center right; 75 | } 76 | 77 | table.dataTable th:active { 78 | outline: none; 79 | } 80 | 81 | /* Scrolling */ 82 | 83 | div.dataTables_scrollHead table { 84 | margin-bottom: 0 !important; 85 | border-bottom-left-radius: 0; 86 | border-bottom-right-radius: 0; 87 | } 88 | 89 | div.dataTables_scrollHead table thead tr:last-child th:first-child, 90 | div.dataTables_scrollHead table thead tr:last-child td:first-child { 91 | border-bottom-left-radius: 0 !important; 92 | border-bottom-right-radius: 0 !important; 93 | } 94 | 95 | div.dataTables_scrollBody table { 96 | margin-top: 0 !important; 97 | margin-bottom: 0 !important; 98 | border-top: none; 99 | } 100 | 101 | div.dataTables_scrollBody tbody tr:first-child th, 102 | div.dataTables_scrollBody tbody tr:first-child td { 103 | border-top: none; 104 | } 105 | 106 | div.dataTables_scrollFoot table { 107 | margin-top: 0 !important; 108 | border-top: none; 109 | } 110 | 111 | /* 112 | * TableTools styles 113 | */ 114 | 115 | .table tbody tr.active td, 116 | .table tbody tr.active th { 117 | color: white; 118 | background-color: #08C; 119 | } 120 | 121 | .table tbody tr.active:hover td, 122 | .table tbody tr.active:hover th { 123 | background-color: #0075b0 !important; 124 | } 125 | 126 | .table tbody tr.active a { 127 | color: white; 128 | } 129 | 130 | .table-striped tbody tr.active:nth-child(odd) td, 131 | .table-striped tbody tr.active:nth-child(odd) th { 132 | background-color: #017ebc; 133 | } 134 | 135 | table.DTTT_selectable tbody tr { 136 | cursor: pointer; 137 | } 138 | 139 | div.DTTT .btn { 140 | font-size: 12px; 141 | color: #333 !important; 142 | } 143 | 144 | div.DTTT .btn:hover { 145 | text-decoration: none !important; 146 | } 147 | 148 | ul.DTTT_dropdown.dropdown-menu { 149 | z-index: 2003; 150 | } 151 | 152 | ul.DTTT_dropdown.dropdown-menu a { 153 | color: #333 !important; /* needed only when demo_page.css is included */ 154 | } 155 | 156 | ul.DTTT_dropdown.dropdown-menu li { 157 | position: relative; 158 | } 159 | 160 | ul.DTTT_dropdown.dropdown-menu li:hover a { 161 | color: white !important; 162 | background-color: #0088cc; 163 | } 164 | 165 | div.DTTT_collection_background { 166 | z-index: 2002; 167 | } 168 | 169 | /* TableTools information display */ 170 | 171 | div.DTTT_print_info.modal { 172 | height: 150px; 173 | margin-top: -75px; 174 | text-align: center; 175 | } 176 | 177 | div.DTTT_print_info h6 { 178 | margin: 1em; 179 | font-size: 28px; 180 | font-weight: normal; 181 | line-height: 28px; 182 | } 183 | 184 | div.DTTT_print_info p { 185 | font-size: 14px; 186 | line-height: 20px; 187 | } 188 | 189 | /* 190 | * FixedColumns styles 191 | */ 192 | 193 | div.DTFC_LeftHeadWrapper table, 194 | div.DTFC_LeftFootWrapper table, 195 | div.DTFC_RightHeadWrapper table, 196 | div.DTFC_RightFootWrapper table, 197 | table.DTFC_Cloned tr.even { 198 | background-color: white; 199 | } 200 | 201 | div.DTFC_RightHeadWrapper table, 202 | div.DTFC_LeftHeadWrapper table { 203 | margin-bottom: 0 !important; 204 | border-top-right-radius: 0 !important; 205 | border-bottom-left-radius: 0 !important; 206 | border-bottom-right-radius: 0 !important; 207 | } 208 | 209 | div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child, 210 | div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child, 211 | div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child, 212 | div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child { 213 | border-bottom-left-radius: 0 !important; 214 | border-bottom-right-radius: 0 !important; 215 | } 216 | 217 | div.DTFC_RightBodyWrapper table, 218 | div.DTFC_LeftBodyWrapper table { 219 | margin-bottom: 0 !important; 220 | border-top: none; 221 | } 222 | 223 | div.DTFC_RightBodyWrapper tbody tr:first-child th, 224 | div.DTFC_RightBodyWrapper tbody tr:first-child td, 225 | div.DTFC_LeftBodyWrapper tbody tr:first-child th, 226 | div.DTFC_LeftBodyWrapper tbody tr:first-child td { 227 | border-top: none; 228 | } 229 | 230 | div.DTFC_RightFootWrapper table, 231 | div.DTFC_LeftFootWrapper table { 232 | border-top: none; 233 | } -------------------------------------------------------------------------------- /forum/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | import markdown 4 | import re 5 | from django.core.urlresolvers import reverse 6 | # Create your models here. 7 | 8 | 9 | class topic(models.Model): 10 | user = models.ForeignKey(User, related_name='topics') 11 | title = models.CharField(max_length=160) 12 | content = models.TextField(blank=True, null=True) 13 | content_rendered = models.TextField(blank=True, null=True) 14 | click = models.IntegerField(default=0) 15 | reply_count = models.IntegerField(default=0) 16 | node = models.ForeignKey('node', related_name='topics') 17 | time_created = models.DateTimeField(auto_now_add=True) 18 | last_replied = models.DateTimeField(blank=True, null=True) 19 | deleted = models.BooleanField(default=False) 20 | order = models.IntegerField(default=10) 21 | 22 | class Meta(): 23 | ordering = ['order' ,'-time_created'] 24 | 25 | def __unicode__(self): 26 | return self.title 27 | 28 | def save(self, *args, **kwargs): 29 | if not self.id: 30 | new = True 31 | else: 32 | new = False 33 | if not self.content: 34 | self.content = '' 35 | self.content_rendered = markdown.markdown(self.content, ['codehilite'], 36 | safe_mode='escape') 37 | self.reply_count = self.post_set.filter(deleted=False).count() 38 | if self.reply_count: 39 | self.last_replied = self.post_set.filter(deleted=False).latest('time_created').time_created 40 | to = [] 41 | for u in re.findall(r'@(.*?)\s', self.content_rendered): 42 | try: 43 | user = User.objects.get(username=u) 44 | except: 45 | pass 46 | else: 47 | to.append(user) 48 | self.content_rendered = re.sub('@%s' % (u), 49 | '@%s' 50 | % (reverse('user_info', 51 | kwargs={'user_id': user.id}), 52 | u), 53 | self.content_rendered) 54 | super(topic, self).save(*args, **kwargs) 55 | if to and new: 56 | for t in to: 57 | m = mention() 58 | m.sender = self.user 59 | m.receiver = t 60 | m.topic = self 61 | m.save() 62 | 63 | if new: 64 | self.last_replied = self.time_created 65 | self.save() 66 | 67 | 68 | class node(models.Model): 69 | title = models.CharField(max_length=12) 70 | description = models.TextField(blank=True) 71 | 72 | def __unicode__(self): 73 | return self.title 74 | 75 | 76 | class post(models.Model): 77 | user = models.ForeignKey(User, related_name='posts') 78 | topic = models.ForeignKey('topic') 79 | content = models.TextField() 80 | content_rendered = models.TextField() 81 | time_created = models.DateTimeField(auto_now_add=True) 82 | deleted = models.BooleanField(default=False) 83 | 84 | class Meta(): 85 | ordering = ['time_created'] 86 | 87 | def __unicode__(self): 88 | return str(self.id) + self.topic.title 89 | 90 | def save(self, *args, **kwargs): 91 | if not self.id: 92 | new = True 93 | else: 94 | new = False 95 | if not self.content: 96 | self.content = '' 97 | self.content_rendered = markdown.markdown(self.content, ['codehilite'], 98 | safe_mode='escape') 99 | to = [] 100 | for u in re.findall(r'@(.*?)\s', self.content_rendered): 101 | try: 102 | user = User.objects.get(username=u) 103 | except: 104 | pass 105 | else: 106 | to.append(user) 107 | self.content_rendered = re.sub('@%s' % (u), 108 | '@%s' 109 | % (reverse('user_info', 110 | kwargs={'user_id': user.id}), 111 | u), 112 | self.content_rendered) 113 | super(post, self).save(*args, **kwargs) 114 | if to and new: 115 | for t in to: 116 | m = mention() 117 | m.sender = self.user 118 | m.receiver = t 119 | m.post = self 120 | m.topic = self.topic 121 | m.save() 122 | self.topic.save() 123 | 124 | 125 | class notification(models.Model): 126 | sender = models.ForeignKey(User, related_name='sent_notifications') 127 | receiver = models.ForeignKey(User, related_name='received_nofitications') 128 | topic = models.ForeignKey(topic, blank=True, null=True) 129 | content = models.TextField(blank=True, null=True) 130 | read = models.BooleanField(default=True) 131 | time_created = models.DateTimeField(auto_now_add=True) 132 | 133 | 134 | class mention(models.Model): 135 | sender = models.ForeignKey(User, related_name='sent_mentions') 136 | receiver = models.ForeignKey(User, related_name='received_mentions') 137 | post = models.ForeignKey(post, blank=True, null=True) 138 | topic = models.ForeignKey(topic, blank=True, null=True) 139 | content = models.TextField(blank=True, null=True) 140 | read = models.BooleanField(default=False) 141 | time_created = models.DateTimeField(auto_now_add=True) 142 | 143 | 144 | class appendix(models.Model): 145 | topic = models.ForeignKey(topic) 146 | time_created = models.DateTimeField(auto_now_add=True) 147 | content = models.TextField() 148 | content_rendered = models.TextField() 149 | 150 | def __unicode__(self): 151 | return self.topic.title + '-Appendix' 152 | 153 | def save(self, *args, **kwargs): 154 | if not self.content: 155 | self.content = '' 156 | self.content_rendered = markdown.markdown(self.content, ['codehilite'], 157 | safe_mode='escape') 158 | super(appendix, self).save(*args, **kwargs) 159 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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('