├── .gitattributes
├── .gitignore
├── LICENSE
├── README.rst
├── conf
└── config
├── requirements.txt
├── schema
└── schema.sql
├── script
└── secretgen.py
├── setup.py
├── snap
├── admin.png
├── article.png
├── home.png
└── login.png
├── t
└── panigationort.py
├── white
├── __init__.py
├── asset
│ ├── css
│ │ ├── admin.css
│ │ ├── forms.css
│ │ ├── login.css
│ │ ├── notifications.css
│ │ ├── reset.css
│ │ └── small.css
│ ├── img
│ │ ├── cloud.png
│ │ ├── cross.gif
│ │ ├── favicon.ico
│ │ ├── icons.png
│ │ ├── logo.png
│ │ ├── piggy.gif
│ │ ├── statuses.png
│ │ ├── tick.gif
│ │ └── tick.png
│ ├── js
│ │ ├── custom-fields.js
│ │ ├── dragdrop.js
│ │ ├── editor.js
│ │ ├── focus-mode.js
│ │ ├── page-name.js
│ │ ├── redirect.js
│ │ ├── slug.js
│ │ ├── sortable.js
│ │ ├── text-resize.js
│ │ ├── upload-fields.js
│ │ └── zepto.js
│ └── theme
│ │ └── default
│ │ ├── css
│ │ ├── reset.css
│ │ ├── small.css
│ │ └── style.css
│ │ ├── img
│ │ ├── categories.png
│ │ ├── favicon.png
│ │ ├── og_image.gif
│ │ └── search.png
│ │ └── js
│ │ └── main.js
├── config
│ ├── __init__.py
│ ├── _config.py
│ ├── errors.py
│ └── hocon.py
├── controller
│ ├── __init__.py
│ ├── admin
│ │ ├── __init__.py
│ │ ├── category.py
│ │ ├── comment.py
│ │ ├── extend.py
│ │ ├── field.py
│ │ ├── menu.py
│ │ ├── metadata.py
│ │ ├── page.py
│ │ ├── post.py
│ │ └── user.py
│ └── front.py
├── domain
│ ├── __init__.py
│ ├── category.py
│ ├── comment.py
│ ├── extend.py
│ ├── menu.py
│ ├── page.py
│ ├── post.py
│ ├── storage.py
│ └── user.py
├── ext.py
├── flash.py
├── helper.py
├── lang
│ ├── __init__.py
│ ├── en_GB
│ │ ├── __init__.py
│ │ ├── category.py
│ │ ├── comment.py
│ │ ├── extend.py
│ │ ├── global.py
│ │ ├── menu.py
│ │ ├── metadata.py
│ │ ├── page.py
│ │ ├── post.py
│ │ └── user.py
│ ├── zh_CN
│ │ ├── __init__.py
│ │ ├── category.py
│ │ ├── comment.py
│ │ ├── extend.py
│ │ ├── global.py
│ │ ├── menu.py
│ │ ├── metadata.py
│ │ ├── page.py
│ │ ├── post.py
│ │ └── user.py
│ └── zh_TW
│ │ ├── __init__.py
│ │ ├── category.py
│ │ ├── comment.py
│ │ ├── extend.py
│ │ ├── global.py
│ │ ├── menu.py
│ │ ├── metadata.py
│ │ ├── page.py
│ │ ├── post.py
│ │ └── user.py
├── lib
│ ├── __init__.py
│ ├── image.py
│ ├── memoize.py
│ ├── paginator.py
│ └── validator.py
├── model.py
├── orm
│ ├── __init__.py
│ ├── base.py
│ ├── category.py
│ ├── comment.py
│ ├── extend.py
│ ├── meta.py
│ ├── page.py
│ ├── pair.py
│ ├── post.py
│ └── user.py
├── patch.py
├── security.py
├── server.py
├── setting.py
├── util.py
└── view
│ ├── admin
│ ├── 403.html
│ ├── category
│ │ ├── add.html
│ │ ├── edit.html
│ │ └── index.html
│ ├── comment
│ │ ├── edit.html
│ │ └── index.html
│ ├── extend
│ │ ├── field
│ │ │ ├── add.html
│ │ │ ├── edit.html
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── metadata
│ │ │ └── edit.html
│ │ └── plugin
│ │ │ └── index.html
│ ├── layout
│ │ ├── edit.html
│ │ ├── footer.html
│ │ └── header.html
│ ├── menu
│ │ └── index.html
│ ├── page
│ │ ├── add.html
│ │ ├── edit.html
│ │ └── index.html
│ ├── post
│ │ ├── add.html
│ │ ├── edit.html
│ │ └── index.html
│ └── user
│ │ ├── add.html
│ │ ├── edit.html
│ │ ├── index.html
│ │ └── login.html
│ └── theme
│ └── default
│ ├── 403.html
│ ├── 404.html
│ ├── article.html
│ ├── body.html
│ ├── footer.html
│ ├── header.html
│ ├── layout.html
│ ├── page.html
│ ├── posts.html
│ └── search.html
└── whited
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | tmp/
9 | *.tmp
10 | *.bak
11 | *.swp
12 | *~.nib
13 | local.properties
14 | .classpath
15 | .settings/
16 | .loadpath
17 |
18 | # External tool builders
19 | .externalToolBuilders/
20 |
21 | # Locally stored "Eclipse launch configurations"
22 | *.launch
23 |
24 | # CDT-specific
25 | .cproject
26 |
27 | # PDT-specific
28 | .buildpath
29 |
30 |
31 | #################
32 | ## Visual Studio
33 | #################
34 |
35 | ## Ignore Visual Studio temporary files, build results, and
36 | ## files generated by popular Visual Studio add-ons.
37 |
38 | # User-specific files
39 | *.suo
40 | *.user
41 | *.sln.docstates
42 |
43 | # Build results
44 |
45 | [Dd]ebug/
46 | [Rr]elease/
47 | x64/
48 | build/
49 | [Oo]bj/
50 |
51 | # MSTest test Results
52 | [Tt]est[Rr]esult*/
53 | [Bb]uild[Ll]og.*
54 |
55 | *_i.c
56 | *_p.c
57 | *.ilk
58 | *.meta
59 | *.obj
60 | *.pch
61 | *.pdb
62 | *.pgc
63 | *.pgd
64 | *.rsp
65 | *.sbr
66 | *.tlb
67 | *.tli
68 | *.tlh
69 | *.tmp
70 | *.tmp_proj
71 | *.log
72 | *.vspscc
73 | *.vssscc
74 | .builds
75 | *.pidb
76 | *.log
77 | *.scc
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opensdf
84 | *.sdf
85 | *.cachefile
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 |
99 | # TeamCity is a build add-in
100 | _TeamCity*
101 |
102 | # DotCover is a Code Coverage Tool
103 | *.dotCover
104 |
105 | # NCrunch
106 | *.ncrunch*
107 | .*crunch*.local.xml
108 |
109 | # Installshield output folder
110 | [Ee]xpress/
111 |
112 | # DocProject is a documentation generator add-in
113 | DocProject/buildhelp/
114 | DocProject/Help/*.HxT
115 | DocProject/Help/*.HxC
116 | DocProject/Help/*.hhc
117 | DocProject/Help/*.hhk
118 | DocProject/Help/*.hhp
119 | DocProject/Help/Html2
120 | DocProject/Help/html
121 |
122 | # Click-Once directory
123 | publish/
124 |
125 | # Publish Web Output
126 | *.Publish.xml
127 | *.pubxml
128 |
129 | # NuGet Packages Directory
130 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
131 | #packages/
132 |
133 | # Windows Azure Build Output
134 | csx
135 | *.build.csdef
136 |
137 | # Windows Store app package directory
138 | AppPackages/
139 |
140 | # Others
141 | sql/
142 | *.Cache
143 | ClientBin/
144 | [Ss]tyle[Cc]op.*
145 | ~$*
146 | *~
147 | *.dbmdl
148 | *.[Pp]ublish.xml
149 | *.pfx
150 | *.publishsettings
151 |
152 | # RIA/Silverlight projects
153 | Generated_Code/
154 |
155 | # Backup & report files from converting an old project file to a newer
156 | # Visual Studio version. Backup files are not needed, because we have git ;-)
157 | _UpgradeReport_Files/
158 | Backup*/
159 | UpgradeLog*.XML
160 | UpgradeLog*.htm
161 |
162 | # SQL Server files
163 | App_Data/*.mdf
164 | App_Data/*.ldf
165 |
166 | #############
167 | ## Windows detritus
168 | #############
169 |
170 | # Windows image file caches
171 | Thumbs.db
172 | ehthumbs.db
173 |
174 | # Folder config file
175 | Desktop.ini
176 |
177 | # Recycle Bin used on file shares
178 | $RECYCLE.BIN/
179 |
180 | # Mac crap
181 | .DS_Store
182 |
183 |
184 | #############
185 | ## Python
186 | #############
187 |
188 | *.py[co]
189 |
190 | # Packages
191 | *.egg
192 | *.egg-info
193 | dist/
194 | build/
195 | eggs/
196 | parts/
197 | var/
198 | sdist/
199 | develop-eggs/
200 | .installed.cfg
201 |
202 | # Installer logs
203 | pip-log.txt
204 |
205 | # Unit test / coverage reports
206 | .coverage
207 | .tox
208 |
209 | #Translations
210 | *.mo
211 |
212 | #Mr Developer
213 | .mr.developer.cfg
214 |
--------------------------------------------------------------------------------
/conf/config:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 | # Human-Optimized Config Object Notation
17 |
18 | HOST = "localhost" # server host
19 | PORT = 5000 # server port
20 |
21 | DEBUG = on # off # open debug mode
22 |
23 | ## Flask Session module
24 | # session
25 | SECRET_KEY = "7oGwHH8NQDKn9hL12Gak9G/MEjZZYk4PsAxqKU4cJoY="
26 |
27 | SESSION_TYPE = "filesystem" # "redis"
28 | ## REDIS_HOST = 127.0.0.1
29 | #PERMANENT_SESSION_LIFETIME = 60
30 |
31 | SESSION_FILE_DIR = "cookie"
32 | SESSION_FILE_THRESHOLD = 100
33 | SESSION_FILE_MODE = 0600
34 |
35 | ## DB Config
36 | DB_CONFIG {
37 | db = white
38 | user = white
39 | passwd = white
40 | host = localhost
41 |
42 | max_idle = 10 # the mysql timeout setting
43 | }
44 |
45 | # DB Adapter
46 | DB_ADAPTER = mysql # or pymysql
47 |
48 | # DB POOL Size
49 | DB_MAXCONN = 10
50 | DB_MINCONN = 5
51 |
52 | # STATIC_FOLDER="pathTo/assets" # static folder if your wanna set custom you media assets
53 |
54 | CONTENT_PATH = "/var/www/$yoursite.com/content"
55 | LANGUAGE = "en_GB"
56 | THEME = "default"
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | dbpy==0.1.2
2 | Flask==1.0
3 | Flask-Session==0.1.1
4 | itsdangerous==0.24
5 | Jinja2==2.7.3
6 | Markdown==2.6.1
7 | MarkupSafe==0.23
8 | MySQL-python==1.2.5
9 | Pillow==2.7.0
10 | Werkzeug==0.15.3
11 |
--------------------------------------------------------------------------------
/script/secretgen.py:
--------------------------------------------------------------------------------
1 | import base64
2 | import uuid
3 |
4 | print base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | import sys
3 | from white import __version__
4 | import os
5 |
6 | def include_path(datas, path):
7 | for root, dirs, files in os.walk(path):
8 | base, ex = path.split(os.path.sep, 1)
9 | for f in files:
10 | name, ext = f.split('.')
11 | if ext in ('css', 'js', 'html', 'ico', 'png', 'gif'):
12 | datas.append(os.path.join(ex, f))
13 | for d in dirs:
14 | include_path(datas, os.path.join(path, d))
15 |
16 |
17 | def _package_data():
18 | datas = []
19 | path = os.path.join(os.path.dirname(__file__), 'white')
20 | include_path(datas, os.path.join(path, 'asset'))
21 | include_path(datas ,os.path.join(path, 'view'))
22 | return datas
23 |
24 | setup(
25 | name = 'white',
26 | version = __version__,
27 | author = "Thomas Huang",
28 | author_email='lyanghwy@gmail.com',
29 | description = "A Blog Cms Website backed by MySQL in Flask&Python",
30 | license = "GPL",
31 | keywords = "A Blog Cms Website backed by MySQL in Flask&Python",
32 | url='https://github.com/thomashuang/white',
33 | long_description=open('README.rst').read(),
34 | packages=find_packages(exclude=['t', 't.*']),
35 | zip_safe=False,
36 | include_package_data=True,
37 | package_data = {
38 | # Non-.py files to distribute as part of each package
39 | 'white': _package_data()
40 | },
41 | install_requires = ['setuptools', 'flask', 'markdown', 'flask_session', 'dbpy', 'pillow'],
42 | test_suite='unittests',
43 | classifiers=(
44 | "Development Status :: Production/Alpha",
45 | "License :: GPL",
46 | "Natural Language :: English",
47 | "Programming Language :: Python",
48 | "Programming Language :: Python :: 2.7",
49 | "Topic :: Blog Cms"
50 | )
51 | )
52 |
--------------------------------------------------------------------------------
/snap/admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/snap/admin.png
--------------------------------------------------------------------------------
/snap/article.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/snap/article.png
--------------------------------------------------------------------------------
/snap/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/snap/home.png
--------------------------------------------------------------------------------
/snap/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/snap/login.png
--------------------------------------------------------------------------------
/t/panigationort.py:
--------------------------------------------------------------------------------
1 | from white.lib.paginator import Paginator
2 | import unittest
3 |
4 |
5 | class PaginatorTest(unittest.TestCase):
6 |
7 | def setUp(self):
8 | result = [_ for _ in range(1, 10)]
9 | self.p = Paginator(result, 100, 10, 5, '/test')
10 |
11 | def test_next_link(self):
12 | self.assertEqual(str(self.p.next_link('next')), 'next')
13 | self.p.page = 22
14 | self.assertEqual(str(self.p.next_link('next')), '')
15 |
16 | def test_pre_link(self):
17 | self.assertEqual(str(self.p.pre_link('pre')), 'pre')
18 | self.p.page = 1
19 | self.assertEqual(str(self.p.pre_link('pre')), '')
20 |
21 | def test_iter(self):
22 | for _ in range(1, 9):
23 | next(self.p)
24 | self.assertEqual(next(self.p), 9)
25 | self.assertEqual(self.p._index, 9)
26 | self.assertRaises(StopIteration, lambda: next(self.p))
27 |
28 | def test_len(self):
29 | self.assertEqual(9, len(self.p))
30 |
31 | def test_links(self):
32 |
33 | self.assertTrue(self.p.links().startswith(
34 | 'FirstPrevious7'))
35 | self.p.page = 0
36 | self.assertEqual(
37 | self.p.links(), '1234Next Last')
38 | self.p.page = 22
39 | self.assertEqual(
40 | self.p.links(), 'FirstPrevious1920')
41 |
42 |
43 | if __name__ == '__main__':
44 | unittest.main(verbosity=2)
--------------------------------------------------------------------------------
/white/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | __version__ = '0.1.0-alpha1-4'
--------------------------------------------------------------------------------
/white/asset/css/login.css:
--------------------------------------------------------------------------------
1 | /*
2 | Login
3 | */
4 | body.login {
5 | position: absolute;
6 | top: 25%;
7 | left: 50%;
8 | margin-left: -150px;
9 | background: #444f5f;
10 | }
11 |
12 | .login .wrap, .login .content {
13 | width: 300px;
14 | }
15 | .login h1 {
16 | padding-bottom: 25px;
17 |
18 | font-size: 25px;
19 | line-height: 60px;
20 | font-weight: lighter;
21 | color: #fff;
22 | }
23 |
24 | .login label {
25 | display: none;
26 | }
27 |
28 | .login fieldset {
29 | border: none;
30 | }
31 |
32 | .login input {
33 | width: 300px;
34 | margin-bottom: 20px;
35 |
36 | padding: 14px 16px;
37 | }
38 |
39 | .login .buttons a {
40 | float: right;
41 | font-size: 13px;
42 | line-height: 38px;
43 | }
44 |
45 | .login .buttons button {
46 | float: left;
47 | }
48 |
49 | .login a {
50 | color: #8491a5;
51 | }
52 |
53 | .login a:hover {
54 | color: #fff;
55 | }
56 |
57 | .login button {
58 | background: #2f3744;
59 | color: #96a4bb;
60 | font-size: 13px;
61 | font-weight: 500;
62 | }
63 |
64 | .login .notification {
65 | width: 300px;
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/white/asset/css/notifications.css:
--------------------------------------------------------------------------------
1 |
2 | .notifications {
3 | margin-bottom: 10px;
4 | }
5 |
6 | .notifications .notice, .notifications .error, .notifications .success {
7 | padding: 10px 18px;
8 | margin-bottom: 20px;
9 |
10 | font-size: 13px;
11 | line-height: 21px;
12 | font-weight: 500;
13 |
14 | border-radius: 5px;
15 | }
16 |
17 | .notifications .notice {
18 | color: #fff;
19 | background: #578cd9;
20 | }
21 |
22 | .notifications .error {
23 | color: #fff;
24 | background: #d34937;
25 | }
26 |
27 | .notifications .success {
28 | color: #fff;
29 | background: #64a524;
30 | }
31 |
32 |
33 | .header .notifications {
34 | position: absolute;
35 | left: 55%;
36 | top: 82px;
37 | z-index: 1200;
38 | width: 320px;
39 | }
40 | .header .page .notifications {
41 | left: 48%;
42 | }
43 | .header .notifications div:after {
44 | content: '';
45 | position: absolute;
46 | display: block;
47 | top: -6px;
48 | right: 50px;
49 |
50 | border-bottom: 6px solid #64a524;
51 | border-left: 6px solid transparent;
52 | border-right: 6px solid transparent;
53 | }
54 |
55 | .header .notifications .error:after {
56 | border-bottom-color: #d34937;
57 | }
--------------------------------------------------------------------------------
/white/asset/css/reset.css:
--------------------------------------------------------------------------------
1 |
2 | * {
3 | margin: 0;
4 | padding: 0;
5 |
6 | -webkit-font-smoothing: antialiased;
7 |
8 | -webkit-box-sizing: border-box;
9 | -moz-box-sizing: border-box;
10 | box-sizing: border-box;
11 | }
12 |
13 | ::selection {
14 | background: #606d80;
15 | color: #fff;
16 | }
17 |
18 | ::-webkit-input-placeholder {
19 | color: #bec8d4;
20 | }
21 |
22 | ::-moz-placeholder {
23 | color: #bec8d4;
24 | }
25 |
26 | ::placeholder {
27 | color: #bec8d4;
28 | }
29 |
--------------------------------------------------------------------------------
/white/asset/css/small.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | width: 100%;
3 | }
4 |
5 | .wrap {
6 | width: 90%;
7 | }
8 |
9 | .top nav {
10 | float: none;
11 | overflow: hidden;
12 | }
13 |
14 | .top .btn {
15 | display: none;
16 | }
17 |
18 | .list, .list li {
19 | width: 100%;
20 | }
21 |
22 | .list li {
23 | float: none;
24 | margin-right: 0;
25 | }
26 |
27 | .list li p {
28 | display: none;
29 | }
30 |
31 | hgroup h1 {
32 | line-height: 80px;
33 | margin: 0;
34 | float: none;
35 | }
36 |
37 | hgroup nav {
38 | float: none;
39 | line-height: 80px;
40 | }
--------------------------------------------------------------------------------
/white/asset/img/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/cloud.png
--------------------------------------------------------------------------------
/white/asset/img/cross.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/cross.gif
--------------------------------------------------------------------------------
/white/asset/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/favicon.ico
--------------------------------------------------------------------------------
/white/asset/img/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/icons.png
--------------------------------------------------------------------------------
/white/asset/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/logo.png
--------------------------------------------------------------------------------
/white/asset/img/piggy.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/piggy.gif
--------------------------------------------------------------------------------
/white/asset/img/statuses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/statuses.png
--------------------------------------------------------------------------------
/white/asset/img/tick.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/tick.gif
--------------------------------------------------------------------------------
/white/asset/img/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/img/tick.png
--------------------------------------------------------------------------------
/white/asset/js/custom-fields.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Extend attribute selection
3 | *
4 | * Show/hide fields depending on type
5 | */
6 | $(function() {
7 | var select = $('#field'), attrs = $('.hide');
8 |
9 | var update = function() {
10 | var value = select.val();
11 |
12 | attrs.hide();
13 |
14 | if(value == 'image') {
15 | attrs.show();
16 | }
17 | else if(value == 'file') {
18 | $('.attributes_type').show();
19 | }
20 | };
21 |
22 | select.bind('change', update);
23 |
24 | update();
25 | });
--------------------------------------------------------------------------------
/white/asset/js/dragdrop.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Drag and Drop upload
3 | *
4 | * Allows the drag and drop of single files into posts
5 | */
6 | $(function() {
7 | var zone = $(document), body = $('body');
8 | var allowed = ['text/css', 'text/javascript', 'application/javascript', 'text/x-markdown'];
9 |
10 | var cancel = function(event) {
11 | event.preventDefault();
12 | return false;
13 | };
14 |
15 | var open = function(event) {
16 | event.preventDefault();
17 | body.addClass('draggy');
18 | return false;
19 | };
20 |
21 | var close = function(event) {
22 | event.preventDefault();
23 | body.removeClass('draggy');
24 | return false;
25 | };
26 |
27 | var drop = function(event) {
28 | event.preventDefault();
29 |
30 | var files = event.target.files || event.dataTransfer.files;
31 |
32 | for(var i = 0; i < files.length; i++) {
33 | var file = files.item(i);
34 |
35 | if(allowed.indexOf(file.type) !== -1) {
36 | transfer(file);
37 | }
38 | }
39 |
40 | body.removeClass('draggy');
41 |
42 | return false;
43 | };
44 |
45 | var transfer = function(file) {
46 | var reader = new FileReader();
47 | reader.file = file;
48 | reader.callback = complete;
49 | reader.onload = reader.callback;
50 | reader.readAsText(file);
51 | };
52 |
53 | var complete = function() {
54 | if(['text/css'].indexOf(this.file.type) !== -1) {
55 | $('textarea[name=css]').val(this.result).parent().show();
56 | }
57 |
58 | if(['text/javascript', 'application/javascript'].indexOf(this.file.type) !== -1) {
59 | $('textarea[name=js]').val(this.result).parent().show();
60 | }
61 |
62 | if(['text/x-markdown'].indexOf(this.file.type) !== -1) {
63 | var textarea = $('textarea[name=html]'), value = textarea.val();
64 |
65 | textarea.val(this.result).trigger('keydown');
66 | }
67 | };
68 |
69 | if(window.FileReader && window.FileList && window.File) {
70 | zone.on('dragover', open);
71 | zone.on('dragenter', cancel);
72 | zone.on('drop', drop);
73 | zone.on('dragleave', cancel);
74 | zone.on('dragexit', close);
75 |
76 | body.append('
Upload your file
');
77 |
78 | // hide drag/drop inputs until populated
79 | $('textarea[name=css],textarea[name=js]').each(function(index, item) {
80 | var element = $(item);
81 |
82 | if(element.val() == '') {
83 | element.parent().hide();
84 | }
85 | });
86 | }
87 | });
--------------------------------------------------------------------------------
/white/asset/js/editor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Zepto plugin to create textareas into markdown editors
3 | */
4 | ;(function($) {
5 | $.fn.editor = function() {
6 |
7 | var options = arguments[1] || {};
8 | var defaults = {};
9 |
10 | var settings = $.extend({}, defaults, options);
11 | var textarea = $(this), container = textarea.parent();
12 |
13 | var insert = function(str) {
14 | var element = textarea[0];
15 | var start = element.selectionStart;
16 | var value = element.value;
17 |
18 | element.value = value.substring(0, start) + str + value.substring(start);
19 |
20 | element.selectionStart = element.selectionEnd = start + str.length;
21 | };
22 |
23 | var wrap = function(left, right) {
24 | var element = textarea[0];
25 | var start = element.selectionStart, end = element.selectionEnd;
26 | var value = element.value;
27 |
28 | element.value = value.substring(0, start) + left + value.substring(start, end) + right + value.substring(end);
29 |
30 | element.selectionStart = end + left.length + right.length;
31 | };
32 |
33 | var tab = function(event) {
34 | var element = textarea[0];
35 | var start = element.selectionStart, end = element.selectionEnd;
36 | var value = element.value;
37 |
38 | var selections = value.substring(start, end).split("\n");
39 |
40 | for(var i = 0; i < selections.length; i++) {
41 | selections[i] = "\t" + selections[i];
42 | }
43 |
44 | element.value = value.substring(0, start) + selections.join("\n") + value.substring(end);
45 |
46 | if(end > start) {
47 | element.selectionStart = start;
48 | element.selectionEnd = end + selections.length;
49 | }
50 | else element.selectionStart = element.selectionEnd = start + 1;
51 | };
52 |
53 | var untab = function(event) {
54 | var element = textarea[0];
55 |
56 | var start = element.selectionStart, end = element.selectionEnd;
57 | var value = element.value;
58 | var pattern = new RegExp(/^[\t]{1}/);
59 | var edits = 0;
60 |
61 | // single line
62 | if(start == end) {
63 | // move to the start of the line
64 | while(start > 0) {
65 | if(value.charAt(start) == "\n") {
66 | start++;
67 | break;
68 | }
69 |
70 | start--;
71 | }
72 |
73 | var portion = value.substring(start, end);
74 | var matches = portion.match(pattern);
75 |
76 | if(matches) {
77 | element.value = value.substring(0, start) + portion.replace(pattern, '') + value.substring(end);
78 | end--;
79 | }
80 |
81 | element.selectionStart = element.selectionEnd = end;
82 | }
83 | // multiline
84 | else {
85 | var selections = value.substring(start, end).split("\n");
86 |
87 | for(var i = 0; i < selections.length; i++) {
88 | if(selections[i].match(pattern)) {
89 | edits++;
90 | selections[i] = selections[i].replace(pattern, '');
91 | }
92 | }
93 |
94 | element.value = value.substring(0, start) + selections.join("\n") + value.substring(end);
95 |
96 | element.selectionStart = start;
97 | element.selectionEnd = end - edits;
98 | }
99 | };
100 |
101 | var controls = {
102 | bold: function() {
103 | wrap('**', '**');
104 | },
105 | italic: function() {
106 | wrap('*', '*');
107 | },
108 | code: function() {
109 | wrap('`', '`');
110 | },
111 | link: function() {
112 | var element = textarea[0];
113 | var start = element.selectionStart, end = element.selectionEnd;
114 | var value = element.value;
115 |
116 | var selection = value.substring(start, end);
117 | var link = '[' + selection + '](' + selection + ')';
118 |
119 | element.value = value.substring(0, start) + link + value.substring(end);
120 | element.selectionStart = element.selectionEnd = end + link.length;
121 | },
122 | list: function() {
123 | var element = textarea[0];
124 | var start = element.selectionStart, end = element.selectionEnd;
125 | var value = element.value;
126 |
127 | var selections = value.substring(start, end).split("\n");
128 |
129 | for(var i = 0; i < selections.length; i++) {
130 | selections[i] = '* ' + selections[i];
131 | }
132 |
133 | element.value = value.substring(0, start) + "\n" + selections.join("\n") + "\n" + value.substring(end);
134 | },
135 | quote: function() {
136 | var element = textarea[0];
137 | var start = element.selectionStart, end = element.selectionEnd;
138 | var value = element.value;
139 |
140 | var selections = value.substring(start, end).split("\n");
141 |
142 | for(var i = 0; i < selections.length; i++) {
143 | selections[i] = '> ' + selections[i];
144 | }
145 |
146 | element.value = value.substring(0, start) + selections.join("\n") + value.substring(end);
147 | }
148 | };
149 |
150 | textarea.on('keydown', function(event) {
151 | if(event.keyCode === 9) {
152 | event.preventDefault();
153 | event.stopPropagation();
154 |
155 | if(event.shiftKey && event.keyCode === 9) {
156 | untab(event);
157 | }
158 | else {
159 | tab(event);
160 | }
161 | }
162 | });
163 |
164 | container.on('click', 'nav a', function(event) {
165 | var a = $(event.target), method = a.attr('href').split('#').pop();
166 |
167 | if(controls[method]) controls[method]();
168 |
169 | return false;
170 | });
171 | };
172 | }(Zepto));
--------------------------------------------------------------------------------
/white/asset/js/focus-mode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Focus mode for post and page main textarea
3 | */
4 | $(function() {
5 | var doc = $(document), html = $('html'), body = html.children('body');
6 |
7 | var Focus = {
8 | // Our element to focus
9 | target: $('textarea[name=html], textarea[name=content]'),
10 | exitSpan: '#exit-focus',
11 |
12 | enter: function() {
13 | html.addClass('focus');
14 |
15 | if( ! body.children(Focus.exitSpan).length) {
16 | body.append('Exit focus mode (ESC)');
17 | }
18 |
19 | body.children(Focus.exitSpan).css('opacity', 0).animate({opacity: 1}, 250);
20 |
21 | // Set titles and placeholders
22 | Focus.target.placeholder = (Focus.target.placeholder || '').split('.')[0] + '.';
23 | },
24 |
25 | exit: function() {
26 | body.children(Focus.exitSpan).animate({opacity: 0}, 250);
27 | html.removeClass('focus');
28 | }
29 | };
30 |
31 | // Bind textarea events
32 | Focus.target.focus(Focus.enter).blur(Focus.exit);
33 |
34 | // Bind key events
35 | doc.on('keyup', function(event) {
36 | // Pressing the "f" key
37 | if(event.keyCode == 70) {
38 | Focus.enter();
39 | }
40 |
41 | // Pressing the Escape key
42 | if(event.keyCode == 27) {
43 | Focus.exit();
44 | }
45 | });
46 | });
--------------------------------------------------------------------------------
/white/asset/js/page-name.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Mirrors the page title into the page name field which is use in the menus
3 | */
4 | $(function(input, output) {
5 | var input = $('input[name=title]'), output = $('input[name=name]');
6 | var changed = false;
7 |
8 | output.bind('keyup', function() {
9 | changed = true;
10 | });
11 |
12 | input.bind('keyup', function() {
13 | if( ! changed) output.val(input.val());
14 | });
15 | });
--------------------------------------------------------------------------------
/white/asset/js/redirect.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Toggles the redirect field in pages
3 | */
4 | $(function() {
5 | var fieldset = $('fieldset.redirect'),
6 | input = $('input[name=redirect]'),
7 | btn = $('button.secondary');
8 |
9 | var toggle = function() {
10 | fieldset.toggleClass('show');
11 | return false;
12 | };
13 |
14 | btn.bind('click', toggle);
15 |
16 | // Hide the input if you get rid of the content within.
17 | input.change(function(){
18 | if(input.val() === '') fieldset.removeClass('show');
19 | });
20 |
21 | // Show the redirect field if it isn't empty.
22 | if(input.val() !== '') fieldset.addClass('show');
23 | });
--------------------------------------------------------------------------------
/white/asset/js/slug.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Format title into a slug value after each keypress
3 | * Disabled if the slug is manually changed
4 | */
5 | $(function() {
6 | var input = $('input[name=title]'), output = $('input[name=slug]');
7 | var changed = false;
8 |
9 | var slugify = function(str) {
10 | str = str.replace(/^\s+|\s+$/g, '').toLowerCase();
11 |
12 | // remove accents
13 | var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;", to = "aaaaeeeeiiiioooouuuunc------";
14 |
15 | for(var i = 0, l = from.length; i < l; i++) {
16 | str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
17 | }
18 |
19 | return str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
20 | .replace(/\s+/g, '-') // collapse whitespace and replace by -
21 | .replace(/-+/g, '-'); // collapse dashes
22 | }
23 |
24 | output.bind('keyup', function() {
25 | changed = true;
26 | });
27 |
28 | input.bind('keyup', function() {
29 | if( ! changed) output.val(slugify(input.val()));
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/white/asset/js/sortable.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Zepto sortable plugin using html5 drag and drop api.
3 | */
4 | ;(function($) {
5 | $.fn.sortable = function(options) {
6 |
7 | var defaults = {
8 | element: 'li',
9 | dropped: function() {}
10 | };
11 |
12 | var settings = $.extend({}, defaults, options);
13 | var sortables = $(this).find(settings.element);
14 | var dragsrc;
15 |
16 | var dragstart = function(event) {
17 | $(this).addClass('moving');
18 |
19 | dragsrc = this;
20 |
21 | event.dataTransfer.effectAllowed = 'move';
22 | event.dataTransfer.setData('text/html', this.innerHTML);
23 | };
24 |
25 | var dragenter = function() {
26 | $(this).addClass('over');
27 | }
28 |
29 | var dragleave = function() {
30 | $(this).removeClass('over');
31 | };
32 |
33 | var dragover = function(event) {
34 | event.preventDefault();
35 | event.stopPropagation();
36 |
37 | event.dataTransfer.dropEffect = 'move';
38 | };
39 |
40 | var drop = function(event) {
41 | event.preventDefault();
42 | event.stopPropagation();
43 |
44 | if (dragsrc != this) {
45 | dragsrc.innerHTML = this.innerHTML;
46 |
47 | this.innerHTML = event.dataTransfer.getData('text/html');
48 | }
49 |
50 | settings.dropped();
51 | };
52 |
53 | var dragend = function() {
54 | $(this).removeClass('moving');
55 | sortables.removeClass('over');
56 | };
57 |
58 | sortables.on('dragstart', dragstart);
59 | sortables.on('dragenter', dragenter);
60 | sortables.on('dragover', dragover);
61 | sortables.on('dragleave', dragleave);
62 | sortables.on('drop', drop);
63 | sortables.on('dragend', dragend);
64 | };
65 | }(Zepto));
--------------------------------------------------------------------------------
/white/asset/js/text-resize.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Textarea auto resize
3 | */
4 | $(function() {
5 | var $text = $('textarea').first();
6 |
7 | function resize(e) {
8 | var bodyScrollPos = $('body').prop('scrollTop');
9 | $text.height('auto');
10 | $text.height($text.prop('scrollHeight') + 'px');
11 | $('body').prop('scrollTop', bodyScrollPos);
12 | }
13 |
14 | /* 0-timeout to get the already changed text */
15 | function delayedResize (e) {
16 | window.setTimeout(function(){
17 | resize(e);
18 | }, 0);
19 | }
20 |
21 | $text.on('change', resize);
22 | $text.on('cut paste drop keydown', delayedResize);
23 |
24 | $text.focus();
25 | $text.select();
26 | resize();
27 | });
--------------------------------------------------------------------------------
/white/asset/js/upload-fields.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Populate placeholder when user selects a file to upload
3 | */
4 | $(function() {
5 | var basename = function(path) {
6 | return path.replace(/\\/g,'/').replace(/.*\//, '');
7 | };
8 |
9 | $('input[type=file]').bind('change', function() {
10 | var input = $(this), placeholder = input.parent().parent().find('.current-file');
11 |
12 | placeholder.html(basename(input.val()));
13 | });
14 | });
--------------------------------------------------------------------------------
/white/asset/theme/default/css/reset.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Anchor, default reset
3 | */
4 | * {
5 | margin: 0;
6 | padding: 0;
7 |
8 | -webkit-font-smoothing: antialiased;
9 |
10 | /* Don't count padding and borders towards widths */
11 | -webkit-box-sizing: border-box;
12 | -moz-box-sizing: border-box;
13 | box-sizing: border-box;
14 | }
15 |
16 | /**
17 | * Typographic reset
18 | */
19 | body {
20 | /* Use a serif font for nice readability, but not Times */
21 | font: 17px/26px Skolar, Tisa, "Chaparral Pro", Merriweather, Georgia, serif;
22 | }
23 |
24 | h1, h2, h3, h4, h5, #logo, #top, .slidey b, .slidey label, .counter, input, textarea, button, .pagination {
25 | font-family: "Helvetica Neue", sans-serif;
26 | font-weight: 300;
27 | }
28 |
29 | pre, code, .mono {
30 | font: 12px/19px "Anonymous Pro", Consolas, monospace;
31 | padding: 0 2px;
32 | }
33 |
34 | p {
35 | padding-bottom: 15px;
36 | }
37 |
38 | pre {
39 | padding: 15px 20px;
40 | margin-bottom: 20px;
41 |
42 | border-radius: 5px;
43 | white-space: pre-wrap;
44 | }
45 |
46 | img {
47 | max-width: 100%;
48 | height: auto;
49 | }
50 |
51 | a {
52 | text-decoration: none;
53 | }
54 | a img {
55 | border: none;
56 | }
57 |
58 | /**
59 | * Layout reset
60 | */
61 | .wrap {
62 | min-width: 280px;
63 | max-width: 750px;
64 | width: 60%;
65 |
66 | margin: 0 auto;
67 | }
68 |
69 | /**
70 | * Default colours
71 | */
72 | body, .items > li:first-child {
73 | color: #6e7886;
74 | }
75 | a, .items h1 a:hover {
76 | color: #4075c3;
77 | }
78 | a:hover {
79 | color: #1e4d92;
80 | }
81 |
82 | pre, .hilite, mark {
83 | background: #f9f6ea;
84 | color: #8b7c65;
85 | text-shadow: 0 1px 0 rgba(255,255,255,.2);
86 | }
87 |
88 | input, textarea {
89 | color: #697281;
90 | }
91 | ::-webkit-input-placeholder {
92 | color: #b2b9c5;
93 | }
94 | :-moz-placeholder, :placeholder {
95 | color: #b2b9c5;
96 | }
97 |
98 | .error, .success {
99 | padding: 20px 30px;
100 | margin-bottom: 30px;
101 |
102 | background: #e25d47;
103 | color: #fff;
104 |
105 | border-radius: 5px;
106 | }
107 | .success {
108 | background: #88be33;
109 | }
110 | .error p, .success p {
111 | float: none !important;
112 | width: 100%;
113 | padding: 0;
114 | margin: 0 !important;
115 | }
116 |
117 | /**
118 | * Transitions and animations
119 | */
120 | a, a img {
121 | -webkit-transition: opacity .2s, color .2s;
122 | -moz-transition: opacity .2s, color .2s;
123 | transition: opacity .2s, color .2s;
124 | }
--------------------------------------------------------------------------------
/white/asset/theme/default/css/small.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Small device CSS, mostly to fix alignment
3 | */
4 |
5 | #top {
6 | position: relative;
7 | }
8 | #top .tray {
9 | position: absolute;
10 | right: 10px;
11 | top: 10px;
12 | }
13 | #top #logo, #top ul {
14 | float: none;
15 | }
16 | #top ul li {
17 | padding: 10px 15px 0 0;
18 | }
19 |
20 | .slidey form, .slidey aside {
21 | float: none;
22 | width: 100%;
23 | }
24 | .slidey form {
25 | margin-bottom: 25px;
26 | }
27 |
28 | #comment p, .footnote {
29 | float: none;
30 | width: 100%;
31 |
32 | white-space: normal;
33 | }
34 |
35 | #bottom {
36 | padding: 20px 0;
37 | }
38 | #bottom small {
39 | display: block;
40 | }
41 | #bottom ul {
42 | overflow: hidden;
43 | float: none;
44 | margin-bottom: 15px;
45 | }
46 | #bottom li {
47 | padding: 15px 15px 0 0;
48 | }
--------------------------------------------------------------------------------
/white/asset/theme/default/img/categories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/theme/default/img/categories.png
--------------------------------------------------------------------------------
/white/asset/theme/default/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/theme/default/img/favicon.png
--------------------------------------------------------------------------------
/white/asset/theme/default/img/og_image.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/theme/default/img/og_image.gif
--------------------------------------------------------------------------------
/white/asset/theme/default/img/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/asset/theme/default/img/search.png
--------------------------------------------------------------------------------
/white/asset/theme/default/js/main.js:
--------------------------------------------------------------------------------
1 | var White = {
2 | init: function() {
3 | White.slidey = $('.slidey');
4 | White.keys = [];
5 |
6 | // Uh, bind to the resizing of the window?
7 | $(window).resize(White.bindResize).trigger('resize');
8 |
9 | // Re-/Set keys
10 | $(window).on('keyup', White.keyup);
11 | $(window).on('keydown', White.keydown);
12 |
13 | // Set up the toggle link
14 | White.linky = $('.linky').on('click', White.toggleSlidey);
15 |
16 | // Hide the thingymabob
17 | setTimeout(function() {
18 | // Set up the slidey panel
19 | White.hideSlidey();
20 |
21 | $('body').addClass('js-enabled');
22 | }, 10);
23 |
24 | // Listen for search link
25 | $('a[href="#search"]').click(function() {
26 | if(!White.linky.hasClass('active')) {
27 | return White.toggleSlidey.call(White.linky);
28 | }
29 | });
30 | },
31 |
32 | keyup: function(event) {
33 | White.keys[event.keyCode] = false;
34 | },
35 |
36 | keydown: function(event) {
37 | White.keys[event.keyCode] = true;
38 |
39 | // ctrl + shift + f => show Slidey and/or focus search bar
40 | if(White.keys[17] && White.keys[16] && White.keys[70]) {
41 | event.preventDefault();
42 |
43 | White.showSlidey.call(White.linky);
44 | $('input[type="search"]').focus();
45 | }
46 |
47 | // esc => hide Slidey
48 | if(White.keys[27]) {
49 | event.preventDefault();
50 |
51 | White.hideSlidey();
52 | $('input[type="search"]').blur();
53 | }
54 | },
55 |
56 | hideSlidey: function() {
57 | White.slidey.css('margin-top', this._slideyHeight);
58 | White.linky && White.linky.removeClass('active');
59 |
60 | return this;
61 | },
62 |
63 | showSlidey: function() {
64 | White.slidey.css('margin-top', 0);
65 | White.linky && White.linky.addClass('active');
66 |
67 | return this;
68 | },
69 |
70 | toggleSlidey: function() {
71 | var self = White;
72 | var me = $(this);
73 |
74 | me.toggleClass('active');
75 | self.slidey.css('margin-top', me.hasClass('active') ? 0 : self._slideyHeight);
76 |
77 | return false;
78 | },
79 |
80 | bindResize: function() {
81 | White._slideyHeight = -(White.slidey.height() + 1);
82 | White.hideSlidey();
83 | }
84 | };
85 |
86 | // And bind loading
87 | $(White.init);
88 |
--------------------------------------------------------------------------------
/white/config/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 Self-Released
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | # Human-Optimized Config Object Notation
18 |
19 | from ._config import ConfigFactory
20 |
21 |
22 | __all__ = ('ConfigFactory', )
23 |
--------------------------------------------------------------------------------
/white/config/errors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Copyright 2015 Self-Released
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License. You may obtain
7 | # a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 | # License for the specific language governing permissions and limitations
15 | # under the License.
16 |
17 | # Human-Optimized Config Object Notation
18 |
19 |
20 | class ConfigError(Exception):
21 | pass
22 |
23 |
24 | class HoconParserException(ConfigError):
25 | pass
26 |
27 |
28 | class HoconTokenizerException(ConfigError):
29 | pass
30 |
--------------------------------------------------------------------------------
/white/controller/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import Blueprint
18 |
19 | admin_bp = Blueprint('admin', 'admin')
20 | site_bp = Blueprint('site', 'site')
21 |
22 |
23 | ADMIN, EDITOR, ROOT = 'administrator', 'editor', 'root'
24 |
25 |
26 | from . import (
27 | admin,
28 | front,
29 | )
30 |
--------------------------------------------------------------------------------
/white/controller/admin/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from . import (
18 | user,
19 | page,
20 | category,
21 | comment,
22 | extend,
23 | field,
24 | menu,
25 | post,
26 | metadata
27 | )
--------------------------------------------------------------------------------
/white/controller/admin/category.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import render_template, redirect, url_for
18 |
19 | from flask import g, request, current_app
20 | from flask import jsonify
21 | from flask import session
22 |
23 | from white.controller import admin_bp as bp, ADMIN, EDITOR
24 | from white.security import security
25 | from white.orm import Backend
26 | from white.model import Category
27 |
28 | from white.domain.category import CategoryService
29 | from white.lang import text
30 | from white.flash import flash
31 | from white.lib.validator import Validator
32 |
33 | category_service = CategoryService()
34 |
35 |
36 | @bp.route('/category')
37 | @bp.route('/category/')
38 | @security(ADMIN)
39 | def category_page(page=1):
40 | pagination = category_service.page(page)
41 | return render_template('admin/category/index.html',
42 | categories=pagination)
43 |
44 |
45 | @bp.route('/category/add', methods=['GET', 'POST'])
46 | @security(ADMIN)
47 | def category_add():
48 | if request.method == 'GET':
49 | return render_template('admin/category/add.html')
50 |
51 | reqp = request.form
52 | title = reqp.get('title')
53 | slug = reqp.get('slug')
54 | description = reqp.get('description')
55 |
56 | validator = Validator()
57 | validator.check(title, 'min', text('category.title_missing'), 1)
58 | if validator.errors:
59 | flash(validator.errors, 'error')
60 | return render_template('admin/category/add.html')
61 |
62 | category_service.add_category(title, slug, description)
63 | return redirect(url_for('admin.category_page'))
64 |
65 |
66 | @bp.route('/category//edit', methods=['GET', 'POST'])
67 | @security(ADMIN)
68 | def category_edit(category_id):
69 | if request.method == 'GET':
70 | category = category_service.get_by_cid(category_id)
71 | return render_template('admin/category/edit.html', category=category)
72 |
73 | p = request.form.get
74 | title = p('title')
75 | slug = p('slug')
76 | description = p('description')
77 |
78 | validator = Validator()
79 | validator.check(title, 'min', text('category.title_missing'), 1)
80 | if validator.errors:
81 | flash(validator.errors, 'error')
82 | return redirect(url_for('admin.category_edit', category_id=category_id))
83 |
84 | category = category_service.update_category(
85 | category_id, title, slug, description)
86 | flash(text('category.updated'), 'success')
87 | return redirect(url_for('admin.category_edit', category_id=category.cid))
88 |
89 |
90 | @bp.route('/category//delete', methods=['GET', 'POST'])
91 | @security(ADMIN)
92 | def category_delete(category_id):
93 | if category_id == 1:
94 | flash('The Uncategory cann\'t delete', 'error')
95 | return redirect(url_for('admin.category_page'))
96 |
97 | category_service.delete(category_id)
98 | flash(text('category.deleted'), 'success')
99 | return redirect(url_for('admin.category_page'))
100 |
--------------------------------------------------------------------------------
/white/controller/admin/comment.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import render_template, redirect, url_for
18 |
19 | from flask import g, request, current_app
20 | from flask import jsonify
21 | from flask import session
22 | from white.lang import text
23 | from white.flash import flash
24 |
25 |
26 | from white.controller import admin_bp as bp, ADMIN, EDITOR
27 | from white.security import security
28 |
29 | from white.domain.comment import CommentService
30 | from white.lang import text
31 | from white.flash import flash
32 | from white.lib.validator import Validator
33 | from white.helper import site
34 |
35 |
36 | comment_service = CommentService()
37 |
38 | COMMENT_STATUSES = [
39 | {'url': 'all', 'lang': text('global.all'), 'class': 'all'},
40 | {'url': 'pending', 'lang': text('global.pending'), 'class': 'pending'},
41 | {'url': 'approved', 'lang': text('global.approved'), 'class': 'approved'},
42 | {'url': 'spam', 'lang': text('global.spam'), 'class': 'spam'}
43 | ]
44 |
45 |
46 | @bp.route('/comment')
47 | @bp.route('/comment/')
48 | @bp.route('/comment//')
49 | @security(EDITOR)
50 | def comment_page(page=1, status='all'):
51 | pagination = comment_service.page(status, page, site.posts_per_page())
52 | return render_template('admin//comment/index.html',
53 | statuses=COMMENT_STATUSES,
54 | status=status,
55 | comments=pagination)
56 |
57 |
58 | @bp.route('/comment//edit', methods=['GET', 'POST'])
59 | @security(EDITOR)
60 | def comment_edit(comment_id):
61 | if request.method == 'GET':
62 | statuses = {
63 | 'approved': text('global.approved'),
64 | 'pending': text('global.pending'),
65 | 'spam': text('global.spam')
66 | }
67 | comment = comment_service.get(comment_id)
68 | return render_template('admin/comment/edit.html',
69 | comment=comment,
70 | statuses=statuses)
71 |
72 | p = request.form.get
73 | name = p('name')
74 | email = p('email')
75 | content = p('content')
76 | status = p('status')
77 |
78 | name, content = name.strip(), content.strip()
79 |
80 | validator = Validator()
81 | (validator.check(name, 'min', text('comment.name_missing'), 1)
82 | .check(content, 'min', text('comment.content_missing'), 1)
83 | )
84 | if validator.errors:
85 | flash(validator.errors, 'error')
86 | return redirect(url_for('admin.comment_edit', comment_id=comment_id))
87 |
88 | comment = comment_service.update_comment(
89 | comment_id, name, email, content, status)
90 | flash(text('comment.updated'), 'success')
91 | return redirect(url_for('admin.comment_edit', comment_id=comment.cid))
92 |
93 |
94 | @bp.route('/comment//delete')
95 | @security(EDITOR)
96 | def comment_delete(comment_id):
97 | comment_service.delete(comment_id)
98 | flash(text('comment.deleted'), 'success')
99 | return redirect(url_for('admin.comment_page'))
100 |
--------------------------------------------------------------------------------
/white/controller/admin/extend.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import render_template
18 |
19 | from white.controller import admin_bp as bp, ADMIN, EDITOR
20 | from white.security import security
21 |
22 |
23 | @bp.route('/extend')
24 | @security(ADMIN)
25 | def extend_index():
26 | return render_template('admin/extend/index.html')
27 |
28 | @bp.route('/extend/variable')
29 | @security(ADMIN)
30 | def variable_index():
31 | return render_template('admin/extend/variable/index.html')
32 |
33 | @bp.route('/extend/variable/add')
34 | @security(ADMIN)
35 | def variable_add_page():
36 | return render_template('admin/extend/variable/add.html')
37 |
38 | @bp.route('/extend/plugin')
39 | @security(ADMIN)
40 | def extend_plugin():
41 | return render_template('admin/extend/plugin/index.html')
--------------------------------------------------------------------------------
/white/controller/admin/field.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import render_template, redirect, url_for
18 |
19 | from flask import g, request, current_app
20 | from flask.json import dumps
21 | from flask import session
22 |
23 | from white.controller import admin_bp as bp, ADMIN, EDITOR
24 | from white.security import security
25 |
26 | from white.orm import Backend
27 |
28 | from white.domain.extend import ExtendService
29 | from white.lang import text
30 | from white.flash import flash
31 | from white.lib.validator import Validator
32 |
33 | extend_service = ExtendService()
34 |
35 |
36 | @bp.route('/extend/field')
37 | @bp.route('/extend/field/')
38 | @security(ADMIN)
39 | def field_page(page=1):
40 | extends = extend_service.field_page(page)
41 | return render_template('admin//extend/field/index.html', fields=extends)
42 |
43 |
44 | @bp.route('/extend/field/add', methods=['GET', 'POST'])
45 | @security(ADMIN)
46 | def field_add():
47 | if request.method == 'GET':
48 | return render_template('admin//extend/field/add.html')
49 |
50 | reqp = request.form
51 | _type = reqp.get('type')
52 | field = reqp.get('field')
53 | key = reqp.get('key')
54 | label = reqp.get('label')
55 | key = key or label
56 |
57 | validator = Validator()
58 | validator.add(
59 | 'valid_key', lambda key: extend_service.count(key, _type) == 0)
60 | (validator
61 | .check(key, 'min', text('extend.key_missing'), 1)
62 | .check(key, 'valid_key', text('extend.key_exists'))
63 | .check(label, 'min', text('extend.label_missing'), 1)
64 | )
65 |
66 | if validator.errors:
67 | flash(validator.errors, 'error')
68 | return render_template('admin/extend/field/add.html')
69 |
70 | if field == 'image':
71 | attributes = {
72 | 'type': reqp.get('attributes[type]'),
73 | 'size': {
74 | 'height': reqp.get('attributes[size][height]', type=int),
75 | 'width': reqp.get('attributes[size][width]', type=int),
76 | }
77 | }
78 | elif field == 'file':
79 | attributes = {
80 | 'type': reqp.get('attributes[type]'),
81 | }
82 | else:
83 | attributes = {}
84 |
85 | extend_service.create_extend(_type, key, label, field, attributes)
86 | return redirect(url_for('admin.field_page'))
87 |
88 |
89 | @bp.route('/extend/field//edit', methods=['GET', 'POST'])
90 | @security(ADMIN)
91 | def field_edit(extend_id):
92 | if request.method == 'GET':
93 | extend = extend_service.get_by_eid(extend_id)
94 | return render_template('admin//extend/field/edit.html', field=extend)
95 |
96 | reqp = request.form
97 | _type = reqp.get('type')
98 | field = reqp.get('field')
99 | key = reqp.get('key')
100 | label = reqp.get('label')
101 | key = key or label
102 |
103 | validator = Validator()
104 | (validator
105 | .check(key, 'min', text('extend.key_missing'), 1)
106 | .check(label, 'min', text('extend.label_missing'), 1)
107 | )
108 |
109 | if validator.errors:
110 | flash(validator.errors, 'error')
111 | return redirect(url_for('admin.field_edit', extend_id=extend_id))
112 |
113 | if field == 'image':
114 | attributes = {
115 | 'type': reqp.get('attributes[type]'),
116 | 'size': {
117 | 'height': reqp.get('attributes[size][height]', type=int),
118 | 'width': reqp.get('attributes[size][width]', type=int),
119 | }
120 | }
121 | elif field == 'file':
122 | attributes = {
123 | 'type': reqp.get('attributes[type]'),
124 | }
125 | else:
126 | attributes = {}
127 |
128 | extend_service.update_extend(
129 | _type, key, label, field, attributes, extend_id)
130 | return redirect(url_for('admin.field_edit', extend_id=extend_id))
131 |
132 |
133 | @bp.route('/extend/field//delete')
134 | @security(ADMIN)
135 | def field_delete(extend_id):
136 | extend_service.delete_extend(extend_id)
137 | return redirect(url_for('admin.field_page'))
138 |
--------------------------------------------------------------------------------
/white/controller/admin/menu.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import render_template
18 | from flask import jsonify, request
19 |
20 | from white.controller import admin_bp as bp, ADMIN, EDITOR
21 | from white.security import security
22 |
23 | from white.domain.menu import MenuService
24 |
25 | menuservice = MenuService()
26 |
27 |
28 | @bp.route('/menu')
29 | @security(ADMIN)
30 | def menu_page():
31 | pages = menuservice.menu(True)
32 |
33 | return render_template('admin/menu/index.html', messages='',
34 | pages=pages)
35 |
36 |
37 | @bp.route('/menu/update', methods=['GET', 'POST'])
38 | @security(ADMIN)
39 | def menu_update():
40 | sort = request.form.getlist('sort')
41 |
42 | menuservice.update(sort)
43 |
44 | return jsonify({'return': True})
45 |
--------------------------------------------------------------------------------
/white/controller/admin/metadata.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | import re
18 | from sys import exc_info
19 |
20 | from flask import render_template, jsonify, request, redirect, url_for, current_app
21 | from flask.json import loads, dumps
22 |
23 | from white.orm import Backend
24 | from white.lang import text
25 | from white.flash import flash
26 | from white.domain.storage import StorageService
27 | from white.domain.page import PageService
28 | from white.lib.validator import Validator
29 | from white.helper import site
30 | from white.util import hide_pass_for_config
31 | from white.ext import db
32 |
33 | from white.controller import admin_bp as bp, ADMIN, EDITOR
34 | from white.security import security
35 |
36 |
37 | page_service = PageService()
38 | storage_service = StorageService()
39 |
40 |
41 | META_KEYS = ('sitename', 'description', 'site_page',
42 | 'posts_per_page', 'auto_published_comments', 'comment_moderation_keys')
43 |
44 |
45 | @bp.route('/meta/db_status.json')
46 | @security(ADMIN)
47 | def db_status():
48 | try:
49 | db.query('SELECT 1')
50 | status = {'status': 'ok', 'message': 'Fine'}
51 | except:
52 | cls, e, tb = exc_info()
53 | message = 'DB Error: %s' % (e)
54 | status = {'status': 'error', 'message': message}
55 | return jsonify(status)
56 |
57 |
58 | @bp.route('/meta/meta.json')
59 | @security(ADMIN)
60 | def meta_json():
61 | pair = storage_service.site_meta()
62 | data = pair.json_value()
63 | config = {key: data[key] for key in META_KEYS}
64 | return jsonify(config)
65 |
66 |
67 | @bp.route('/meta/config.json')
68 | @security(ADMIN)
69 | def site_config():
70 | config = current_app.config.copy()
71 | config['PERMANENT_SESSION_LIFETIME'] = str(
72 | config['PERMANENT_SESSION_LIFETIME'])
73 | hide_pass_for_config(config)
74 | return jsonify(config)
75 |
76 |
77 | @bp.route('/extend/metadata', methods=['GET', 'POST'])
78 | @security(ADMIN)
79 | def metadata_page():
80 | if request.method == 'GET':
81 | pair = storage_service.site_meta()
82 | data = pair.json_value()
83 | data['comment_moderation_keys'] = ','.join(
84 | data['comment_moderation_keys'])
85 | pages = page_service.dropdown(False)
86 | configs = {key: data[key] for key in META_KEYS}
87 | return render_template('admin/extend/metadata/edit.html',
88 | pages=pages,
89 | **configs)
90 |
91 | p = request.form.get
92 | sitename = p('sitename')
93 | description = p('description')
94 | site_page = p('site_page', type=int, default=0)
95 | posts_per_page = p('posts_per_page', type=int, default=0)
96 | auto_published_comments = p('auto_published_comments', type=bool)
97 | comment_moderation_keys = p('comment_moderation_keys')
98 |
99 | storage_service.update_site_meta(sitename, description, site_page,
100 | posts_per_page, auto_published_comments, comment_moderation_keys)
101 | site.clear_cache()
102 | flash(text('metadata.updated'), 'success')
103 | return redirect(url_for('admin.metadata_page'))
104 |
--------------------------------------------------------------------------------
/white/controller/admin/user.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import g, request, flash, current_app
18 | from flask import render_template, redirect, url_for, jsonify
19 | from flask import session
20 |
21 | from white.controller import admin_bp as bp, ADMIN, EDITOR, ROOT
22 | from white.security import security
23 |
24 | from white.orm import Backend
25 | from white.model import User
26 | from white.domain.user import UserService
27 | from white.flash import flash
28 | from white.lang import text
29 | from white.helper import site as config
30 |
31 | user_service = UserService()
32 |
33 |
34 | @bp.route('/login', methods=['GET', 'POST'])
35 | def login():
36 | if request.method == 'GET':
37 | if g.user.is_guest():
38 | return render_template('admin/user/login.html')
39 | return redirect(url_for('admin.post_page'))
40 |
41 | reqp = request.form
42 | username = reqp.get('username')
43 | password = reqp.get('password')
44 |
45 | result = user_service.auth(username, password)
46 | if result['status'] == 200:
47 | user_service.login(result['user'])
48 | return redirect(url_for('admin.post_page'))
49 |
50 | flash(text('user.login_error'), 'error')
51 | return redirect(url_for('admin.login'))
52 |
53 |
54 | @bp.route('/logout', methods=['GET'])
55 | def logout():
56 | user_service.logout()
57 | return redirect(url_for('admin.login'))
58 |
59 |
60 | @bp.route('/user.json')
61 | def user_json():
62 | return jsonify(g.user)
63 |
64 |
65 | @bp.route('/user')
66 | @bp.route('/user/')
67 | @security()
68 | def user_page(page=1):
69 | me = g.user
70 | if me.is_root():
71 | page = user_service.page(page, config.posts_per_page())
72 | else:
73 | page = user_service.get_user_page(me)
74 |
75 | return render_template('admin/user/index.html', users=page)
76 |
77 |
78 | @bp.route('/user/add', methods=['GET', 'POST'])
79 | @security(ROOT)
80 | def user_add():
81 | if request.method == 'GET':
82 | return render_template('admin/user/add.html', statuses=User.STATUSES, roles=User.ROLES)
83 |
84 | p = request.form.get
85 | username = p('username')
86 | email = p('email')
87 | real_name = p('real_name')
88 | password = p('password')
89 | bio = p('bio')
90 | status = p('status', default='inactive')
91 | role = p('role', default='user')
92 |
93 | result = user_service.add_user(
94 | username, email, real_name, password, bio, status, role)
95 | if result['status'] == 'ok':
96 | return redirect(url_for('admin.user_edit', uid=result['user'].uid))
97 | else:
98 | flash(result['errors'], 'error')
99 | return render_template('admin/user/add.html', statuses=User.STATUSES, roles=User.ROLES)
100 |
101 |
102 | @bp.route('/user//edit', methods=['GET', 'POST'])
103 | @security()
104 | def user_edit(uid):
105 | if (not (g.user.is_root() or g.user.is_admin())) and g.user.uid != uid:
106 | return render_template('admin/403.html', message='You can only edit your self')
107 | if request.method == 'GET':
108 | user = user_service.get(uid)
109 | return render_template('admin/user/edit.html', statuses=User.STATUSES, roles=User.ROLES, user=user)
110 |
111 | p = request.form.get
112 | email = p('email')
113 | real_name = p('real_name')
114 | password = p('password', default='')
115 | newpass1 = p('newpass1')
116 | newpass2 = p('newpass2')
117 | bio = p('bio')
118 | status = p('status', default='inactive')
119 | role = p('role', default='user')
120 |
121 | result = user_service.update_user(
122 | uid, email, real_name, password, newpass1, newpass2, bio, status, role)
123 | if result['status'] == 'ok':
124 | return redirect(url_for('admin.user_edit', uid=result['user'].uid))
125 | else:
126 | flash(result['errors'], 'error')
127 | return redirect(url_for('admin.user_edit', uid=uid))
128 |
129 |
130 | @bp.route('/user//delete')
131 | @security(ROOT)
132 | def user_delete(user_id):
133 | user_service.delete(user_id)
134 | flash(text('user.deleted'), 'success')
135 | return redirect(url_for('admin.user_page'))
--------------------------------------------------------------------------------
/white/domain/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
--------------------------------------------------------------------------------
/white/domain/category.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from white.orm import Backend
18 | from white.model import Category
19 | from white.lib.paginator import Paginator
20 | from white.lang import text
21 |
22 | class CategoryService(object):
23 |
24 | def __init__(self):
25 | self.category_repo = Backend('category')
26 | self.post_repo = Backend('post')
27 |
28 | def get_by_cid(self, category_id):
29 | return self.category_repo.find(category_id)
30 |
31 | def dropdown(self):
32 | return self.category_repo.dropdown()
33 |
34 | def page(self, page=1, perpage=10):
35 | total = self.category_repo.count()
36 | pages = self.category_repo.paginate(page, perpage)
37 | pagination = Paginator(pages, total, page, perpage, '/admin/category')
38 | return pagination
39 |
40 | def add_category(self, title, slug, description):
41 | category = Category(title, slug, description)
42 | cid = self.category_repo.create(category)
43 | category.cid = cid
44 | return category
45 |
46 | def update_category(self, category_id, title, slug, description):
47 | slug = slug or title
48 | category = Category(title, slug, description, category_id)
49 | self.category_repo.save(category)
50 | return category
51 |
52 | def delete(self, category_id):
53 | if category_id == 1:
54 | return
55 | category = self.category_repo.find(category_id)
56 | if category and self.category_repo.delete(category_id):
57 | self.post_repo.reset_post_category(category_id)
58 |
--------------------------------------------------------------------------------
/white/domain/comment.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | import re
18 |
19 | from white.orm import Backend
20 | from white.model import Comment
21 | from white.lib.paginator import Paginator
22 | from white.lang import text
23 | from white.helper import site
24 |
25 |
26 | class CommentService(object):
27 |
28 | comment_repo = Backend('comment')
29 |
30 | get = lambda self, cid: self.comment_repo.find(cid)
31 |
32 | def get_by_post_id(self, post_id):
33 | return self.comment_repo.find_by_post_id(post_id)
34 |
35 | def page(self, status, page=1, perpage=10):
36 | total = self.comment_repo.count()
37 | pages = self.comment_repo.paginate(page, perpage, status)
38 | pagination = Paginator(pages, total, page, perpage, '/admin/comment')
39 | return pagination
40 |
41 | def add_comment(self, name, email, content, status, post):
42 | comment = Comment(post.pid, name, email, content, status)
43 | if self.is_spam(comment):
44 | comment.status = 'spam'
45 | cid = self.comment_repo.create(comment)
46 | comment.cid = cid
47 | return comment
48 |
49 | @classmethod
50 | def is_spam(self, comment):
51 | for word in site.comment_moderation_keys():
52 | if word.strip() and re.match(word, comment.content, re.I):
53 | return True
54 |
55 | domain = comment.email.split('@')[1]
56 | if self.comment_repo.spam_count(domain):
57 | return True
58 | return False
59 |
60 | def update_comment(self, comment_id, name, email, content, status):
61 | comment = self.get(comment_id)
62 | if not comment:
63 | return None
64 | comment.status = status
65 | comment.name = name
66 | comment.content = content
67 | comment.email = email
68 | self.comment_repo.save(comment)
69 | return comment
70 |
71 | def delete(self, comment_id):
72 | comment = self.comment_repo.find(comment_id)
73 | if not comment:
74 | return None
75 | return self.comment_repo.delete(comment.cid)
76 |
--------------------------------------------------------------------------------
/white/domain/menu.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from white.orm import Backend
18 | from white.lib.paginator import Paginator
19 |
20 |
21 | class MenuService(object):
22 |
23 | def __init__(self):
24 | self.page_repo = Backend('page')
25 |
26 | def menu(self, page=1):
27 | pages = self.page_repo.menu(True)
28 | return pages
29 |
30 | def update(self, sort):
31 | for menu_order, pid in enumerate(sort):
32 | self.page_repo.update_menu_order(pid, menu_order)
33 |
--------------------------------------------------------------------------------
/white/domain/page.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from white.orm import Backend
18 | from white.model import Page
19 | from white.lib.paginator import Paginator
20 |
21 |
22 | class PageService(object):
23 |
24 | def __init__(self):
25 | self.page_repo = Backend('page')
26 |
27 | get = lambda self, pid: self.page_repo.find(pid)
28 |
29 | def get_by_redirect(self, redirect):
30 | return self.page_repo.find_by_redirect(redirect)
31 |
32 | def get_by_slug(self, slug):
33 | return self.page_repo.find_by_slug(slug)
34 |
35 | def dropdown(self, show_in_menu=True):
36 | return self.page_repo.dropdown(show_in_menu)
37 |
38 | def page(self, status, page=1, perpage=10):
39 | total = self.page_repo.count(status)
40 | pages = self.page_repo.paginate(page, perpage, status)
41 | if status:
42 | url = '/admin/page/status/' + status
43 | else:
44 | url = '/admin/page'
45 | pagination = Paginator(pages, total, page, perpage, url)
46 | return pagination
47 |
48 | def delete(self, page_id):
49 | page = self.page_repo.find(page_id)
50 | if not page:
51 | return None
52 | return self.page_repo.delete(page.pid)
53 |
54 | def add_page(self, parent, name, title, slug, content, status, redirect, show_in_menu):
55 | redirect = redirect.strip()
56 | show_in_menu = 1 if show_in_menu else 0
57 | page = Page(parent, name, title, slug, content, status, redirect, show_in_menu)
58 | pid = self.page_repo.create(page)
59 | page.pid = pid
60 | return page
61 |
62 | def is_exist_slug(self, slug):
63 | return self.page_repo.count_slug(slug) == 1
64 |
65 | def update_page(self, parent, name, title, slug, content, status, redirect, show_in_menu, pid):
66 | show_in_menu = 1 if show_in_menu else 0
67 | redirect = redirect.strip()
68 | page = Page(parent, name, title, slug, content, status, redirect, show_in_menu, pid)
69 | self.page_repo.save(page)
70 | return page
71 |
--------------------------------------------------------------------------------
/white/domain/post.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from white.orm import Backend
18 | from white.model import Post
19 | from white.lib.paginator import Paginator
20 | from white.lang import text
21 | from datetime import datetime
22 |
23 |
24 | class PostService(object):
25 |
26 | def __init__(self, post_repo=None, category_repo=None):
27 | self.post_repo = post_repo or Backend('post')
28 | self.category_repo = category_repo or Backend('category')
29 |
30 | def get_by_pid(self, post_id):
31 | return self.post_repo.find(post_id)
32 |
33 | def get_published_posts(self, page=1, perpage=10, category=None):
34 | return self.post_repo.get_published_posts(page, perpage, category)
35 |
36 | def get_published_posts_page(self, page=1, perpage=10, category=None):
37 | cid = None
38 | if category:
39 | real_category = self.category_repo.find_by_slug(category)
40 | if not real_category:
41 | return Paginator([], 0, page, perpage, '/category/' + category)
42 | cid = real_category.cid
43 |
44 | total = self.post_repo.category_count(cid)
45 | pages = self.post_repo.get_published_posts(page, perpage, cid)
46 | url = 'category/' + category if category else '/posts'
47 | pagination = Paginator(pages, total, page, perpage, url)
48 | return total, pagination
49 |
50 | def search(self, key, page, perpage=10):
51 | pages = self.post_repo.search(key, page, perpage)
52 | total = self.post_repo.serach_count(key)
53 | pagination = Paginator(pages, total, page, perpage, '/admin/post')
54 | return pagination
55 |
56 | def get_by_slug(self, slug):
57 | return self.post_repo.find_by_slug(slug)
58 |
59 | def lists(self, page=1, perpage=10):
60 | total = self.post_repo.count()
61 | pages = self.post_repo.paginate(page, perpage)
62 | pagination = Paginator(pages, total, page, perpage, '/posts')
63 |
64 | return total, pagination
65 |
66 | def page(self, page=1, perpage=10, category=None):
67 | total = self.post_repo.count(category)
68 | pages = self.post_repo.paginate(page, perpage, category)
69 | pagination = Paginator(pages, total, page, perpage, '/admin/post')
70 | return pagination
71 |
72 | def post_count(self, category_id=None):
73 | if category_idi is not None:
74 | return self.post_repo.count()
75 | return self.post_repo.category_count(category_id)
76 |
77 | def add_post(self, title, slug, description, html, css, js, category, status, comments, author):
78 | css, js, description = css.strip(), js.strip(), description.strip()
79 | comments = 1 if comments else 0
80 | if not html.strip():
81 | status = 'draft'
82 |
83 | post = Post(title, slug, description, html, css, js, category, status, comments, author.uid)
84 | post.created = datetime.now()
85 | pid = self.post_repo.create(post)
86 | post.pid = pid
87 | return post
88 |
89 | def update_post(self, title, slug, description, html, css, js, category, status, comments, post_id):
90 | css, js, description = css.strip(), js.strip(), description.strip()
91 | post = self.get_by_pid(post_id)
92 | if not html.strip():
93 | status = 'draft'
94 |
95 | post.title = title
96 | # post.slug = slug
97 | post.description = description
98 | post.html = html
99 | post.css = css
100 | post.js = js
101 |
102 | post.updated = datetime.now()
103 |
104 | post.category = category
105 | post.status = status
106 | post.allow_comment = 1 if comments else 0
107 | self.post_repo.save(post)
108 |
109 | return post
110 |
111 | def delete(self, post_id):
112 | post = self.post_repo.find(post_id)
113 | if not post:
114 | return None
115 | return self.post_repo.delete(post.pid)
116 |
--------------------------------------------------------------------------------
/white/domain/storage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from white.orm import Backend
18 | from white.model import Pair
19 | from flask.json import dumps
20 | import re
21 |
22 |
23 | class StorageService(object):
24 |
25 | def __init__(self):
26 | self.pair_repo = Backend('storage')
27 |
28 | def site_meta(self):
29 | return self.pair_repo.find('system')
30 |
31 | def update_site_meta(self, sitename, description, site_page,
32 | posts_per_page, auto_published_comments, comment_moderation_keys):
33 |
34 | meta = self.site_meta()
35 | config = meta.json_value()
36 |
37 | try:
38 | sitename = sitename or sitename.strip()
39 | if sitename:
40 | config['sitename'] = sitename
41 |
42 | description = description or description.strip()
43 | if description:
44 | config['description'] = description
45 |
46 | site_page = int(site_page)
47 | if site_page >= 0:
48 | config['site_page'] = site_page
49 |
50 | posts_per_page = int(posts_per_page)
51 | if posts_per_page:
52 | config['posts_per_page'] = posts_per_page
53 |
54 | auto_published_comments = bool(auto_published_comments)
55 | config['auto_published_comments'] = auto_published_comments
56 | if comment_moderation_keys is not None:
57 | keys = [key.strip() for key in re.split(' +', comment_moderation_keys) if key.strip()]
58 | config['comment_moderation_keys'] = keys
59 | meta.value = dumps(config)
60 | self.pair_repo.update(meta)
61 | return True
62 | except:
63 | return False
64 |
--------------------------------------------------------------------------------
/white/ext.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask_session import Session
18 | import db
19 | from markdown import Markdown
20 |
21 | markdown = Markdown()
22 | session = Session()
--------------------------------------------------------------------------------
/white/flash.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import flash as _flash, get_flashed_messages
18 |
19 |
20 | class Flash(object):
21 |
22 | def __call__(self, errors, category):
23 | if not isinstance(errors, list):
24 | errors = [errors]
25 | for msg in errors:
26 | _flash(msg, category)
27 |
28 | def render(self):
29 | messages = get_flashed_messages(with_categories=True)
30 | if messages:
31 | html = '\n'
32 | for category, message in messages:
33 | html += '
%s
\n' % (category, message)
34 | html += '
'
35 | return html
36 | return ''
37 |
38 | __html__ = render
39 |
40 |
41 | flash = Flash()
42 |
--------------------------------------------------------------------------------
/white/helper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from white.lib.memoize import memoize
18 | from white.orm import Backend
19 |
20 |
21 | @memoize()
22 | def categories():
23 | return Backend('category').categories()
24 |
25 |
26 | @memoize()
27 | def menus():
28 | return Backend('page').menu(True)
29 |
30 |
31 | @memoize()
32 | def cached_user(uid):
33 | return Backend('user').find(uid)
34 |
35 |
36 | class SiteConfig(object):
37 |
38 | def sitename(self):
39 | return self.config.get('sitename', 'White')
40 |
41 | def description(self):
42 | return self.config.get('site_description', '')
43 |
44 | def posts_per_page(self, perpage=10):
45 | return self.config.get('posts_per_page', perpage)
46 |
47 | def comment_moderation_keys(self):
48 | return self.config.get('comment_moderation_keys', [])
49 |
50 | def get(self, key, default=None):
51 | return self.config.get(key, default)
52 |
53 | @property
54 | def config(self):
55 | return self._config()
56 |
57 | @memoize()
58 | def _config(self):
59 | return Backend('storage').find('system').json_value()
60 |
61 | def clear_cache(self):
62 | self._config.cache.flush()
63 |
64 |
65 | site = SiteConfig()
66 |
--------------------------------------------------------------------------------
/white/lang/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | import codecs
18 | import os
19 | import os.path
20 |
21 |
22 | def setup(language=None):
23 | global __lines
24 | language = language or 'en_GB'
25 | langpath = os.path.join(os.path.dirname(__file__), language)
26 | __lines = {}
27 | for root, dirs, files in os.walk(langpath):
28 | for file in files:
29 | name, ext = file.split('.')
30 | if ext == 'py' and name != '__init__':
31 | ns = {}
32 | with codecs.open(os.path.join(langpath, file), "r", "utf-8") as f:
33 | code = f.read()
34 | exec code in ns
35 | __lines[name] = ns['t']
36 |
37 |
38 | def text(key, default=None, args=None):
39 | parts = key.split('.')
40 | if len(parts) == 2:
41 | name = parts[0]
42 | key = parts[1]
43 | if len(parts) == 1:
44 | name = 'global'
45 | key = parts.pop(0)
46 |
47 | t = __lines.get(name)
48 | if t:
49 | text = t.get(key, default)
50 | if text and args:
51 | text = text % args
52 | else:
53 | text = default
54 | return text
55 |
--------------------------------------------------------------------------------
/white/lang/en_GB/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/lang/en_GB/__init__.py
--------------------------------------------------------------------------------
/white/lang/en_GB/category.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'category': 'Category',
4 | 'categories': 'Categories',
5 |
6 | 'create_category': 'Create a new category',
7 | 'edit_category': 'Editing “%s”',
8 |
9 | # form fields
10 | 'title': 'Title',
11 | 'title_explain': 'Your category title.',
12 | 'title_missing': 'Please enter a title',
13 |
14 | 'slug': 'Slug',
15 | 'slug_explain': 'The slug for your category.',
16 |
17 | 'description': 'Description',
18 | 'description_explain': 'What your category is about.',
19 |
20 | # messages
21 | 'created': 'Your new category has been added.',
22 | 'updated': 'Your category has been updated.',
23 | 'deleted': 'Your category has been deleted.',
24 | 'delete_error': 'You must have at least one category.',
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/white/lang/en_GB/comment.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'comment': 'Comments',
4 | 'nocomments_desc': 'No comments yet.',
5 | 'editing_comment': 'Editing comment',
6 | 'view_comment': 'View comment',
7 |
8 | # form fields
9 | 'name': 'Name',
10 | 'name_explain': 'Author name',
11 | 'name_missing': 'Please enter a name',
12 |
13 | 'email': 'Email address',
14 | 'email_explain': 'Author email',
15 | 'email_missing': 'Please enter a valid email address', # frontend message (appears on your site!)
16 |
17 | 'content': 'Comment',
18 | 'content_explain': '',
19 | 'content_missing': 'Please enter comment text', # frontend message (appears on your site!)
20 |
21 | 'status': 'Status',
22 | 'status_explain': '',
23 |
24 | # messages
25 | 'created': 'Your comment has been added', # frontend message (appears on your site!)
26 | 'updated': 'Your comment has been updated',
27 | 'deleted': 'Your comment has been deleted',
28 |
29 | # email notification
30 | 'notify_subject': 'New comment has been added',
31 | 'nofity_heading': 'A new comment has been submitted to your site.'
32 | }
33 |
--------------------------------------------------------------------------------
/white/lang/en_GB/extend.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'extend': 'Extend',
4 |
5 | 'fields': 'Custom Fields',
6 | 'fields_desc': 'Create additional fields',
7 |
8 | 'variables': 'Site Variables',
9 | 'variables_desc': 'Create additional metadata',
10 |
11 | 'create_field': 'Create a new field',
12 | 'editing_custom_field': 'Editing field “%s”',
13 | 'nofields_desc': 'No fields yet',
14 |
15 | 'create_variable': 'Create a new variable',
16 | 'editing_variable': 'Editing variable “%s”',
17 | 'novars_desc': 'No variables yet',
18 |
19 | # form fields
20 | 'type': 'Type',
21 | 'type_explain': 'The type of content you want to add this field to.',
22 |
23 | 'field': 'Field',
24 | 'field_explain': 'Html input type',
25 |
26 | 'key': 'Unique Key',
27 | 'key_explain': 'The unique key for your field',
28 | 'key_missing': 'Please enter a unique key',
29 | 'key_exists': 'Key is already in use',
30 |
31 | 'label': 'Label',
32 | 'label_explain': 'Human readable name for your field',
33 | 'label_missing': 'Please enter a label',
34 |
35 | 'attribute_type': 'File types',
36 | 'attribute_type_explain': 'Comma separated list of accepted file types, empty to accept all.',
37 |
38 | # images
39 | 'attributes_size_width': 'Image max width',
40 | 'attributes_size_width_explain': 'Images will be resized if they are bigger than the max size',
41 |
42 | 'attributes_size_height': 'Image max height',
43 | 'attributes_size_height_explain': 'Images will be resized if they are bigger than the max size',
44 |
45 | # custom vars
46 | 'name': 'Name',
47 | 'name_explain': 'A unique name',
48 | 'name_missing': 'Please enter a unique name',
49 | 'name_exists': 'Name is already in use',
50 |
51 | 'value': 'Value',
52 | 'value_explain': 'The data you want to store (up to 64kb)',
53 | 'value_code_snipet': 'Snippet to insert into your template:
site_meta(\'%s\')
',
54 |
55 | # messages
56 | 'variable_created': 'Your variable was created',
57 | 'variable_updated': 'Your variable was updated',
58 | 'variable_deleted': 'Your variable was deleted',
59 |
60 | 'field_created': 'Your field was created',
61 | 'field_updated': 'Your field was updated',
62 | 'field_deleted': 'Your field was deleted'
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/white/lang/en_GB/global.py:
--------------------------------------------------------------------------------
1 | t = {
2 | # words
3 | 'save': 'Save',
4 | 'delete': 'Delete',
5 | 'update': 'Update',
6 | 'edit': 'Edit',
7 | 'editing': 'Editing',
8 | 'create': 'Create',
9 | 'created': 'Created',
10 | 'submit': 'Submit',
11 | 'close': 'Close',
12 | 'status': 'Status',
13 | 'manage': 'Manage',
14 | 'reset': 'Reset',
15 | 'all': 'All',
16 |
17 | # pagination
18 | 'next': 'Next',
19 | 'previous': 'Previous',
20 | 'first': 'First',
21 | 'last': 'Last',
22 |
23 | # statuses
24 | 'draft': 'Draft',
25 | 'archived': 'Archived',
26 | 'published': 'Published',
27 | 'pending': 'Pending',
28 | 'approved': 'Approved',
29 | 'spam': 'Spam',
30 |
31 | 'inactive': 'Inactive',
32 | 'active': 'Active',
33 |
34 | # roles
35 | 'administrator': 'Admin',
36 | 'editor': 'Editor',
37 | 'user': 'User',
38 |
39 | 'log_in': 'Log in',
40 | 'login': 'Login',
41 | 'log_out': 'Log out',
42 | 'logout': 'Logout',
43 |
44 | # pharses
45 | 'visit_your_site': 'Visit your site',
46 | 'powered_by_white': 'Powered by White, version %s',
47 | 'make_blogging_beautiful': 'Make blogging beautiful.',
48 |
49 | # intro
50 | 'welcome_to_white': 'Welcome to White',
51 | 'welcome_to_white_lets_go': 'Welcome to White. Let\'s go.',
52 | 'run_the_installer': 'Run the installer',
53 |
54 | # upgrade
55 | 'upgrade': 'Upgrade',
56 | 'good_news': 'Great News!',
57 | 'new_version_available': 'There\'s a new version of white available.',
58 | 'download_now': 'Download Now',
59 | 'upgrade_later': 'Upgrade later',
60 |
61 | # debug profiler
62 | 'profile': 'Profile',
63 | 'profile_memory_usage': 'Total memory usage',
64 |
65 | # messages
66 | 'confirm_delete': 'Are you sure you want to delete? This can\'t be undone!'
67 | }
68 |
--------------------------------------------------------------------------------
/white/lang/en_GB/menu.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'menu': 'Menu',
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/white/lang/en_GB/metadata.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'metadata': 'Site Metadata',
4 | 'metadata_desc': 'Manage your site data',
5 |
6 | 'comment_settings': 'Comments',
7 | 'theme_settings': 'Apperance',
8 |
9 | # form fields
10 | 'sitename': 'Site name',
11 | 'sitename_explain': '',
12 | 'sitename_missing': 'Your site needs a name!',
13 |
14 | 'sitedescription': 'Site description',
15 | 'sitedescription_explain': '',
16 | 'sitedescription_missing': 'Your site needs a description!',
17 |
18 | 'homepage': 'Home Page',
19 | 'homepage_explain': '',
20 |
21 | 'postspage': 'Posts Page',
22 | 'postspage_explain': '',
23 |
24 | 'posts_per_page': 'Posts per page',
25 | 'posts_per_page_explain': '',
26 |
27 | 'auto_publish_comments': 'Auto-allow comments',
28 | 'auto_publish_comments_explain': '',
29 |
30 | 'comment_notifications': 'Email notification for new comments',
31 | 'comment_notifications_explain': '',
32 |
33 | 'comment_moderation_keys': 'Spam keywords',
34 | 'comment_moderation_keys_explain' : 'Comma separated list of keywords to blacklist against. \
35 | Comments will automatically be set as spam.',
36 |
37 | 'current_theme': 'Current theme',
38 | 'current_theme_explain': '',
39 |
40 | # messages
41 | 'updated': 'Metadata updated',
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/white/lang/en_GB/page.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'page': 'Pages',
4 |
5 | 'create_page': 'Create a new page',
6 | 'nopages_desc': 'You don\'t have any pages.',
7 | 'redirect': 'Redirect',
8 |
9 | # form fields
10 | 'redirect_url': 'Redirect Url',
11 | 'redirect_missing': 'Please enter a valid url',
12 |
13 | 'title': 'Page title',
14 | 'title_explain': '',
15 | 'title_missing': 'Please enter a page title',
16 |
17 | 'content': 'Content',
18 | 'content_explain': 'Your page\'s content. Uses Markdown.',
19 |
20 | 'show_in_menu': 'Show In Menu',
21 | 'show_in_menu_explain': '',
22 |
23 | 'name': 'Name',
24 | 'name_explain': '',
25 |
26 | 'slug': 'Slug',
27 | 'slug_explain': 'Slug uri to identify your page, should only contain ascii characters',
28 | 'slug_missing': 'Please enter a slug uri, slugs can only contain ascii characters',
29 | 'slug_duplicate': 'Slug already exists',
30 | 'slug_invalid': 'Slug must contain letters',
31 |
32 | 'status': 'Status',
33 | 'status_explain': '',
34 |
35 | 'parent': 'Parent',
36 | 'parent_explain': '',
37 |
38 | # messages
39 | 'updated': 'Your page was updated.',
40 | 'created': 'Your page was created.',
41 | 'deleted': 'Your page was deleted.'
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/white/lang/en_GB/post.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'post': 'Posts',
4 |
5 | 'create_post': 'Create a new post',
6 | 'noposts_desc': 'You don\'t have any posts!',
7 |
8 | # form fields
9 | 'title': 'Post title',
10 | 'title_explain': '',
11 | 'title_missing': 'Please enter a title',
12 |
13 | 'content': 'Post Content',
14 | 'content_explain': 'Just write.',
15 |
16 | 'slug': 'Slug',
17 | 'slug_explain': 'Slug uri to identify your post, should only contain ascii characters',
18 | 'slug_missing': 'Please enter a slug uri, slugs can only contain ascii characters',
19 | 'slug_duplicate': 'Slug already exists',
20 | 'slug_invalid': 'Slug must contain letters',
21 |
22 | 'description': 'Description',
23 | 'description_explain': '',
24 |
25 | 'status': 'Status',
26 | 'status_explain': '',
27 |
28 | 'category': 'Category',
29 | 'category_explain': '',
30 |
31 | 'allow_comments': 'Allow Comments',
32 | 'allow_comments_explain': '',
33 |
34 | 'custom_css': 'Custom CSS',
35 | 'custom_css_explain': '',
36 |
37 | 'custom_js': 'Custom JS',
38 | 'custom_js_explain': '',
39 |
40 | # messages
41 | 'updated': 'Your article has been updated',
42 | 'created': 'Your new article was created',
43 | 'deleted': 'Your article have been deleted'
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/white/lang/en_GB/user.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'user': 'Users',
4 |
5 | 'create_user': 'Create a new user',
6 | 'add_user': 'Add a new user',
7 | 'editing_user': 'Editing %s’s Profile',
8 | 'remembered': 'I know my password',
9 | 'forgotten_password': 'Forgotten your password?',
10 |
11 | # roles
12 | 'administrator': 'Admin',
13 | 'administrator_explain': '',
14 |
15 | 'editor': 'Editor',
16 | 'editor_explain': '',
17 |
18 | 'user': 'User',
19 | 'user_explain': '',
20 |
21 | # form fields
22 | 'real_name': 'Real Name',
23 | 'real_name_explain': '',
24 |
25 | 'bio': 'Biography',
26 | 'bio_explain': '',
27 |
28 | 'status': 'Status',
29 | 'status_explain': '',
30 |
31 | 'role': 'Role',
32 | 'role_explain': '',
33 |
34 | 'username': 'Username',
35 | 'username_explain': '',
36 | 'username_missing': 'Please enter a username, must be 4-16 length can contains letter,digit, underscore',
37 |
38 | 'password': 'Password',
39 | 'password_explain': '',
40 | 'password_invalid': 'Password must be must be 4-16 length can contains letter,digit, underscore',
41 |
42 | 'new_password': 'New Password',
43 |
44 | 'email': 'Email',
45 | 'email_explain': '',
46 | 'email_missing': 'Please enter a valid email address',
47 | 'email_not_found': 'Profile not found.',
48 |
49 | # messages
50 | 'updated': 'User profile updated.',
51 | 'created': 'User profile created.',
52 | 'deleted': 'User profile deleted.',
53 | 'delete_error': 'You cannot delete your own profile',
54 | 'login_error': 'Username or password is wrong.',
55 | 'logout_notice': 'You are now logged out.',
56 | 'recovery_sent': 'We have sent you an email to confirm your password change.',
57 | 'recovery_expired': 'Password recovery token has expired, please try again.',
58 | 'password_reset': 'Your new password has been set. Go and login now!',
59 |
60 | # password recovery email
61 | 'recovery_subject': 'Password Reset',
62 | 'recovery_message': 'You have requested to reset your password.' +
63 | 'To continue follow the link below.' + '%s',
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/white/lang/zh_CN/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/lang/zh_CN/__init__.py
--------------------------------------------------------------------------------
/white/lang/zh_CN/category.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'category' : u'分类',
4 | 'categories' : u'分类',
5 |
6 | 'create_category' : u'建立新分类',
7 | 'edit_category' : u'编辑 “%s”',
8 |
9 | # form fields
10 | 'title' : u'Title',
11 | 'title_explain' : u'分类名称.',
12 | 'title_missing' : u'请输入分类名称',
13 |
14 | 'slug' : u'Slug',
15 | 'slug_explain' : u'分类缩写.',
16 |
17 | 'description' : u'描述',
18 | 'description_explain' : u'关于您的分类的描述.',
19 |
20 | # messages
21 | 'created' : u'您的新分类已添加.',
22 | 'updated' : u'您的新分类已更新.',
23 | 'deleted' : u'您的分类已删除.',
24 | 'delete_error' : u'您必须有一个分区.',
25 |
26 | }
--------------------------------------------------------------------------------
/white/lang/zh_CN/comment.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'comment' : u'回复',
4 | 'nocomments_desc' : u'还没有恢复.',
5 | 'editing_comment' : u'编辑回复',
6 | 'view_comment' : u'查看回复',
7 |
8 | # form fields
9 | 'name' : u'Name',
10 | 'name_explain' : u'作者',
11 | 'name_missing' : u'请输入作者',
12 |
13 | 'email' : u'邮件地址',
14 | 'email_explain' : u'作者的电子邮件地址',
15 | 'email_missing' : u'请输入一个有效的电子邮件地址', # frontend message (appears on your site!)
16 |
17 | 'content' : u'回复',
18 | 'content_explain' : u'',
19 | 'content_missing' : u'请输入回复内容', # frontend message (appears on your site!)
20 |
21 | 'status' : u'统计',
22 | 'status_explain' : u'',
23 |
24 | # messages
25 | 'created' : u'您的回复已添加', # frontend message (appears on your site!)
26 | 'updated' : u'您的回复已更新',
27 | 'deleted' : u'您的回复已删除',
28 |
29 | # email notification
30 | 'notify_subject' : u'新回复已添加',
31 | 'nofity_heading' : u'有新回复来了.'
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/white/lang/zh_CN/extend.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'extend' : u'扩展',
4 |
5 | 'fields' : u'自定义字段',
6 | 'fields_desc' : u'创建自定义字段',
7 |
8 | 'variables' : u'网站变量',
9 | 'variables_desc' : u'创建网站变量',
10 |
11 | 'create_field' : u'创建一个新的自定义字段',
12 | 'editing_custom_field' : u'编辑自定义字段 “%s”',
13 | 'nofields_desc' : u'还没有自定义字段',
14 |
15 | 'create_variable' : u'创建一个新的变量',
16 | 'editing_variable' : u'编辑变量 “%s”',
17 | 'novars_desc' : u'还没有变量',
18 |
19 | # form fields
20 | 'type' : u'类型',
21 | 'type_explain' : u'要添加的内容类型.',
22 |
23 | 'field' : u'形式',
24 | 'field_explain' : u'Html 输入类型',
25 |
26 | 'key' : u'调用名',
27 | 'key_explain' : u'自定义字段的调用名',
28 | 'key_missing' : u'请输入一个调用名',
29 | 'key_exists' : u'这个名字被用了',
30 |
31 | 'label' : u'标签',
32 | 'label_explain' : u'自定义字段的备忘名',
33 | 'label_missing' : u'请输入一个标签',
34 |
35 | 'attribute_type' : u'文件类型',
36 | 'attribute_type_explain' : u'接受的文件类型,用逗号分隔,留空接受所有.',
37 |
38 | # images
39 | 'attributes_size_width' : u'图片最大宽度',
40 | 'attributes_size_width_explain' : u'如果图片宽度超过最大值,将会被压缩',
41 |
42 | 'attributes_size_height' : u'图片最大高度',
43 | 'attributes_size_height_explain' : u'如果图片高度超过最大值,将会被压缩',
44 |
45 | # custom vars
46 | 'name' : u'名称',
47 | 'name_explain' : u'唯一名称',
48 | 'name_missing' : u'请输入一个唯一名称',
49 | 'name_exists' : u'名称已被使用',
50 |
51 | 'value' : u'值',
52 | 'value_explain' : u'你要存储的数据(多达64KB)',
53 | 'value_code_snipet' : u'将被插入到模版的片段:
site_meta(\'%s\'
',
54 |
55 | # messages
56 | 'variable_created' : u'您的变量已创建',
57 | 'variable_updated' : u'您的变量已更新',
58 | 'variable_deleted' : u'您的变量已删除',
59 |
60 | 'field_created' : u'您的自定义字段已创建',
61 | 'field_updated' : u'您的自定义字段已更新',
62 | 'field_deleted' : u'您的自定义字段已删除'
63 |
64 | }
--------------------------------------------------------------------------------
/white/lang/zh_CN/global.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | # words
4 | 'save' : u'保存',
5 | 'delete' : u'删除',
6 | 'update' : u'更新',
7 | 'edit' : u'编辑',
8 | 'editing' : u'编辑中',
9 | 'create' : u'创建',
10 | 'created' : u'已创建',
11 | 'submit' : u'提交',
12 | 'close' : u'关闭',
13 | 'status' : u'统计',
14 | 'manage' : u'管理',
15 | 'reset' : u'重置',
16 | 'all' : u'所有',
17 |
18 | # pagination
19 | 'next' : u'下一个',
20 | 'previous' : u'上一个',
21 | 'first' : u'第一个',
22 | 'last' : u'最后一个',
23 |
24 | # statuses
25 | 'draft' : u'草稿',
26 | 'archived' : u'存档',
27 | 'published' : u'发布',
28 | 'pending' : u'待定',
29 | 'approved' : u'批准',
30 | 'spam' : u'Spam',
31 |
32 | 'inactive' : u'待启用',
33 | 'active' : u'启用',
34 |
35 | # roles
36 | 'administrator' : u'管理员',
37 | 'editor' : u'编辑',
38 | 'user' : u'用户',
39 |
40 | 'log_in' : u'登录',
41 | 'login' : u'登录',
42 | 'log_out' : u'登出',
43 | 'logout' : u'登出',
44 |
45 | # pharses
46 | 'visit_your_site' : u'访问首页',
47 | 'powered_by_white' : u'Powered by White, version %s',
48 | 'make_blogging_beautiful' : u'编写精彩的博客。',
49 |
50 | # messages
51 | 'confirm_delete' : u'你确定要删除么?不能反悔哦!'
52 |
53 | }
--------------------------------------------------------------------------------
/white/lang/zh_CN/menu.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'menu' : u'导航',
4 |
5 | }
--------------------------------------------------------------------------------
/white/lang/zh_CN/metadata.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'metadata' : u'站点设置',
4 | 'metadata_desc' : u'管理您的站点设置',
5 |
6 | 'comment_settings' : u'回复',
7 | 'theme_settings' : u'模版',
8 |
9 | # form fields
10 | 'sitename' : u'站点名称',
11 | 'sitename_explain' : u'',
12 | 'sitename_missing' : u'您的网站需要一个名字!',
13 |
14 | 'sitedescription' : u'站点描述',
15 | 'sitedescription_explain' : u'',
16 | 'sitedescription_missing' : u'您的网站需要一个描述!',
17 |
18 | 'homepage' : u'首页',
19 | 'homepage_explain' : u'',
20 |
21 | 'postspage' : u'文章页',
22 | 'postspage_explain' : u'',
23 |
24 | 'posts_per_page' : u'每页文章数',
25 | 'posts_per_page_explain' : u'',
26 |
27 | 'auto_publish_comments' : u'自动通过回复',
28 | 'auto_publish_comments_explain' : u'',
29 |
30 | 'comment_notifications' : u'新回复邮件通知',
31 | 'comment_notifications_explain' : u'',
32 |
33 | 'comment_moderation_keys' : u'垃圾信息关键词',
34 | 'comment_moderation_keys_explain' : u'用逗号分隔创建关键字列表到黑名单,评论将被自动设置为垃圾评论.',
35 |
36 | 'current_theme' : u'网站模版',
37 | 'current_theme_explain' : u'',
38 |
39 | # messages
40 | 'updated' : u'站点信息已更新',
41 |
42 | }
--------------------------------------------------------------------------------
/white/lang/zh_CN/page.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'page' : u'页面',
4 |
5 | 'create_page' : u'创建新页面',
6 | 'nopages_desc' : u'您没有页面.',
7 | 'redirect' : u'重定向',
8 |
9 | # form fields
10 | 'redirect_url' : u'重定向链接',
11 | 'redirect_missing' : u'请输入一个有效的链接',
12 |
13 | 'title' : u'页面标题',
14 | 'title_explain' : u'',
15 | 'title_missing' : u'请输入一个页面标题',
16 |
17 | 'content' : u'内容',
18 | 'content_explain' : u'页面内容.使用 Markdown.',
19 |
20 | 'show_in_menu' : u'在导航中显示',
21 | 'show_in_menu_explain' : u'',
22 |
23 | 'name' : u'名称',
24 | 'name_explain' : u'',
25 |
26 | 'slug' : u'缩写',
27 | 'slug_explain' : u'您的页面的缩写网址,只能包含 ASCII 字符',
28 | 'slug_missing' : u'请输入缩写网址,只能包含 ASCII 字符',
29 | 'slug_duplicate' : u'缩写已存在',
30 | 'slug_invalid' : u'缩写必须包含字母',
31 |
32 | 'status' : u'统计',
33 | 'status_explain' : u'',
34 |
35 | 'parent' : u'Parent',
36 | 'parent_explain' : u'',
37 |
38 | # messages
39 | 'updated' : u'您的页面已更新.',
40 | 'created' : u'您的页面已创建.',
41 | 'deleted' : u'您的页面已删除.'
42 |
43 | }
--------------------------------------------------------------------------------
/white/lang/zh_CN/post.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'post' : u'文章',
4 |
5 | 'create_post' : u'发表文章',
6 | 'noposts_desc' : u'您没有文章!',
7 |
8 | # form fields
9 | 'title' : u'标题',
10 | 'title_explain' : u'',
11 | 'title_missing' : u'请输入标题',
12 |
13 | 'content' : u'文章正文',
14 | 'content_explain' : u'来写吧.',
15 |
16 | 'slug' : u'缩写',
17 | 'slug_explain' : u'用缩写来识别您的文章,只能包含 ASCII 字符',
18 | 'slug_missing' : u'请输入一个缩写网址,只能包含 ASCII 字符',
19 | 'slug_duplicate' : u'缩写已存在',
20 | 'slug_invalid' : u'缩写必须包含字母',
21 |
22 | 'description' : u'描述',
23 | 'description_explain' : u'',
24 |
25 | 'status' : u'统计',
26 | 'status_explain' : u'',
27 |
28 | 'category' : u'分类',
29 | 'category_explain' : u'',
30 |
31 | 'allow_comments' : u'允许评论',
32 | 'allow_comments_explain' : u'',
33 |
34 | 'custom_css' : u'自定义 CSS',
35 | 'custom_css_explain' : u'',
36 |
37 | 'custom_js' : u'自定义 JS',
38 | 'custom_js_explain' : u'',
39 |
40 | # messages
41 | 'updated' : u'您的文章已更新',
42 | 'created' : u'您的文章已创建',
43 | 'deleted' : u'您的文章已删除'
44 |
45 | }
--------------------------------------------------------------------------------
/white/lang/zh_CN/user.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'user' : u'用户',
4 |
5 | 'create_user' : u'建立新用户',
6 | 'add_user' : u'添加一个新用户',
7 | 'editing_user' : u'编辑 %s’s Profile',
8 | 'remembered' : u'我知道我的密码',
9 | 'forgotten_password' : u'忘记密码了?',
10 |
11 | # roles
12 | 'administrator' : u'管理员',
13 | 'administrator_explain' : u'',
14 |
15 | 'editor' : u'编辑',
16 | 'editor_explain' : u'',
17 |
18 | 'user' : u'用户',
19 | 'user_explain' : u'',
20 |
21 | # form fields
22 | 'real_name' : u'真实姓名',
23 | 'real_name_explain' : u'',
24 |
25 | 'bio' : u'简介',
26 | 'bio_explain' : u'',
27 |
28 | 'status' : u'状态',
29 | 'status_explain' : u'',
30 |
31 | 'role' : u'角色',
32 | 'role_explain' : u'',
33 |
34 | 'username' : u'用户名',
35 | 'username_explain' : u'',
36 | 'username_missing' : u'请输入一个4-16长度且仅含字母数字下划线',
37 |
38 | 'password' : u'密码',
39 | 'password_explain' : u'',
40 | 'password_invalid' : u'请输入一个4-16长度且仅含字母数字下划线的密码',
41 |
42 | 'new_password' : u'新密码',
43 |
44 | 'email' : u'邮件地址',
45 | 'email_explain' : u'',
46 | 'email_missing' : u'请输入一个有效的电子信箱',
47 | 'email_not_found' : u'找不到个人资料.',
48 |
49 | # messages
50 | 'updated' : u'用户资料已更新.',
51 | 'created' : u'用户资料已创建.',
52 | 'deleted' : u'用户资料已删除.',
53 | 'delete_error' : u'您不能删除自己的个人资料',
54 | 'login_error' : u'用户名或密码错误.',
55 | 'logout_notice' : u'您已登出.',
56 | 'recovery_sent' : u'我们已经发送了一封邮件来确认您的密码更改.',
57 | 'recovery_expired' : u'密码恢复令牌已过期,请再试一次.',
58 | 'password_reset' : u'您的新密码已设置,去登录吧!',
59 |
60 | # password recovery email
61 | 'recovery_subject' : u'密码重置',
62 | 'recovery_message' : u'请重置您的密码. 要按照下面的链接继续. %s',
63 |
64 | }
--------------------------------------------------------------------------------
/white/lang/zh_TW/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/whiteclover/white/633c5f0c7e9e9518f495b0afddf06786d4e229c4/white/lang/zh_TW/__init__.py
--------------------------------------------------------------------------------
/white/lang/zh_TW/category.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'category': u'分類',
4 | 'categories': u'分類',
5 |
6 | 'create_category': u'建立新分類',
7 | 'edit_category': u'編輯 “%s”',
8 |
9 | # form fields
10 | 'title': u'標題',
11 | 'title_explain': u'分類標題。',
12 | 'title_missing': u'請輸入標題',
13 |
14 | 'slug': u'縮寫',
15 | 'slug_explain': u'分類的縮寫。',
16 |
17 | 'description': u'描述',
18 | 'description_explain': u'分類關於什麼。',
19 |
20 | # messages
21 | 'created': u'分類已新增。',
22 | 'updated': u'分類已更新。',
23 | 'deleted': u'分類已刪除。',
24 | 'delete_error': u'至少要有一個分類。',
25 |
26 | }
--------------------------------------------------------------------------------
/white/lang/zh_TW/comment.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'comment' : u'評論',
4 | 'nocomments_desc' : u'暫無評論。',
5 | 'editing_comment' : u'編輯評論',
6 | 'view_comment' : u'查看評論',
7 |
8 | # form fields
9 | 'name' : u'名稱',
10 | 'name_explain' : u'作者名稱',
11 | 'name_missing' : u'請輸入名稱',
12 |
13 | 'email' : u'Email 地址',
14 | 'email_explain' : u'作者 Email',
15 | 'email_missing' : u'請輸入有效的 Email 地址', # frontend message (appears on your site!)
16 |
17 | 'content' : u'評論',
18 | 'content_explain' : u'',
19 | 'content_missing' : u'請輸入評論內容', # frontend message (appears on your site!)
20 |
21 | 'status' : u'狀態',
22 | 'status_explain' : u'',
23 |
24 | # messages
25 | 'created' : u'你的評論已新增', # frontend message (appears on your site!)
26 | 'updated' : u'你的評論已更新',
27 | 'deleted' : u'你的評論已刪除',
28 |
29 | # email notification
30 | 'notify_subject' : u'新評論已加入',
31 | 'nofity_heading' : u'新評論已被送交到你的網站。'
32 |
33 | }
--------------------------------------------------------------------------------
/white/lang/zh_TW/extend.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'extend' : u'擴充',
4 |
5 | 'fields' : u'自訂欄位',
6 | 'fields_desc' : u'建立額外欄位',
7 |
8 | 'variables' : u'網站變數',
9 | 'variables_desc' : u'建立額外變數',
10 |
11 | 'create_field' : u'建立新欄位',
12 | 'editing_custom_field' : u'編輯欄位 “%s”',
13 | 'nofields_desc' : u'還沒有欄位',
14 |
15 | 'create_variable' : u'建立新變數',
16 | 'editing_variable' : u'編輯變數 “%s”',
17 | 'novars_desc' : u'還沒有變數',
18 |
19 | # form fields
20 | 'type' : u'類型',
21 | 'type_explain' : u'你想要添加到此欄位內容的類型',
22 |
23 | 'field' : u'欄位',
24 | 'field_explain' : u'Html input 類型',
25 |
26 | 'key' : u'唯一鍵',
27 | 'key_explain' : u'欄位的唯一鍵',
28 | 'key_missing' : u'請輸入唯一鍵',
29 | 'key_exists' : u'鍵已使用',
30 |
31 | 'label' : u'標籤',
32 | 'label_explain' : u'欄位的人類可讀名稱',
33 | 'label_missing' : u'請輸入標籤',
34 |
35 | 'attribute_type' : u'檔案類型',
36 | 'attribute_type_explain' : u'以逗號分隔接受的檔案類型,留空接受所有。',
37 |
38 | # images
39 | 'attributes_size_width' : u'圖片最大寬度',
40 | 'attributes_size_width_explain' : u'如果大於最大值,圖片會被調整尺寸',
41 |
42 | 'attributes_size_height' : u'圖片最大高度',
43 | 'attributes_size_height_explain' : u'如果大於最大值,圖片會被調整尺寸',
44 |
45 | # custom vars
46 | 'name' : u'名稱',
47 | 'name_explain' : u'唯一名稱',
48 | 'name_missing' : u'請輸入唯一名稱',
49 | 'name_exists' : u'名稱已使用',
50 |
51 | 'value' : u'值',
52 | 'value_explain' : u'你想儲存的資料(最多 64Kb)',
53 | 'value_code_snipet' : u'要插入到樣板的片段:
site_meta(\'%s\')
',
54 |
55 | # messages
56 | 'variable_created' : u'變數已建立',
57 | 'variable_updated' : u'變數已更新',
58 | 'variable_deleted' : u'變數已刪除',
59 |
60 | 'field_created' : u'欄位已建立',
61 | 'field_updated' : u'欄位已更新',
62 | 'field_deleted' : u'欄位已刪除'
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/white/lang/zh_TW/global.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | # words
4 | 'save' : u'儲存',
5 | 'delete' : u'刪除',
6 | 'update' : u'更新',
7 | 'edit' : u'編輯',
8 | 'editing' : u'編輯',
9 | 'create' : u'建立',
10 | 'created' : u'建立',
11 | 'submit' : u'送交',
12 | 'close' : u'關閉',
13 | 'status' : u'狀態',
14 | 'manage' : u'管理',
15 | 'reset' : u'重設',
16 | 'all' : u'全部',
17 |
18 | # pagination
19 | 'next' : u'下一頁',
20 | 'previous' : u'上一頁',
21 | 'first' : u'第一頁',
22 | 'last' : u'最後一頁',
23 |
24 | # statuses
25 | 'draft' : u'草稿',
26 | 'archived' : u'封存',
27 | 'published' : u'公佈',
28 | 'pending' : u'等待',
29 | 'approved' : u'通過',
30 | 'spam' : u'垃圾',
31 |
32 | 'inactive' : u'未啟用',
33 | 'active' : u'啟用',
34 |
35 | # roles
36 | 'administrator' : u'管理者',
37 | 'editor' : u'編輯者',
38 | 'user' : u'使用者',
39 |
40 | 'log_in' : u'登入',
41 | 'login' : u'登入',
42 | 'log_out' : u'登出',
43 | 'logout' : u'登出',
44 |
45 | # pharses
46 | 'visit_your_site' : u'造訪你的網站',
47 | 'powered_by_white' : u'由 White 驅動,版本 %s',
48 | 'make_blogging_beautiful' : u'讓部落格更美好。',
49 |
50 | # intro
51 | 'welcome_to_anchor' : u'歡迎來到 Anchor',
52 | 'welcome_to_anchor_lets_go' : u'歡迎來到 Anchor。我們走吧。',
53 | 'run_the_installer' : u'執行安裝程序',
54 |
55 | # upgrade
56 | 'upgrade' : u'升級',
57 | 'good_news' : u'好消息!',
58 | 'new_version_available' : u'有可用的 anchor 新版本。',
59 | 'download_now' : u'立即下載',
60 | 'upgrade_later' : u'稍後升級',
61 |
62 | # debug profiler
63 | 'profile' : u'分析',
64 | 'profile_memory_usage' : u'記憶體總使用量',
65 |
66 | # messages
67 | 'confirm_delete' : u'確定刪除?此操作不能還原!'
68 |
69 | }
--------------------------------------------------------------------------------
/white/lang/zh_TW/menu.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'menu' : u'選單',
4 |
5 | }
--------------------------------------------------------------------------------
/white/lang/zh_TW/metadata.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'metadata' : u'網站資料',
4 | 'metadata_desc' : u'管理網站資料',
5 |
6 | 'comment_settings' : u'評論',
7 | 'theme_settings' : u'外觀',
8 |
9 | # form fields
10 | 'sitename' : u'網站名稱',
11 | 'sitename_explain' : u'',
12 | 'sitename_missing' : u'網站需要名稱!',
13 |
14 | 'sitedescription' : u'網站描述',
15 | 'sitedescription_explain' : u'',
16 | 'sitedescription_missing' : u'網站需要描述!',
17 |
18 | 'homepage' : u'首頁',
19 | 'homepage_explain' : u'',
20 |
21 | 'postspage' : u'文章頁',
22 | 'postspage_explain' : u'',
23 |
24 | 'posts_per_page' : u'每頁文章',
25 | 'posts_per_page_explain' : u'',
26 |
27 | 'auto_publish_comments' : u'自動允許評論',
28 | 'auto_publish_comments_explain' : u'',
29 |
30 | 'comment_notifications' : u'Email 通知新評論',
31 | 'comment_notifications_explain' : u'',
32 |
33 | 'comment_moderation_keys' : u'垃圾評論關鍵字',
34 | 'comment_moderation_keys_explain' : u'用逗號分隔關鍵字黑名單列表。評論會自動被設為垃圾。',
35 |
36 | 'current_theme' : u'目前主題',
37 | 'current_theme_explain' : u'',
38 |
39 | # messages
40 | 'updated' : u'資料已更新',
41 |
42 | }
--------------------------------------------------------------------------------
/white/lang/zh_TW/page.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'page' : u'頁面',
4 |
5 | 'create_page' : u'建立新頁面',
6 | 'nopages_desc' : u'你沒有任何頁面',
7 | 'redirect' : u'重導',
8 |
9 | # form fields
10 | 'redirect_url' : u'重導 URL',
11 | 'redirect_missing' : u'請輸入有效的 URL',
12 |
13 | 'title' : u'頁面標題',
14 | 'title_explain' : u'',
15 | 'title_missing' : u'請輸入頁面標題',
16 |
17 | 'content' : u'內容',
18 | 'content_explain' : u'頁面內容。使用 Markdown。',
19 |
20 | 'show_in_menu' : u'顯示在選單',
21 | 'show_in_menu_explain' : u'',
22 |
23 | 'name' : u'名稱',
24 | 'name_explain' : u'',
25 |
26 | 'slug' : u'縮寫',
27 | 'slug_explain' : u'定義頁面的縮寫,只應該包含 ascii 字元',
28 | 'slug_missing' : u'請輸入縮寫 URL,縮寫只能包含 ascii 字元',
29 | 'slug_duplicate' : u'縮寫已存在',
30 | 'slug_invalid' : u'縮寫必須包含字母',
31 |
32 | 'status' : u'狀態',
33 | 'status_explain' : u'',
34 |
35 | 'parent' : u'上層',
36 | 'parent_explain' : u'',
37 |
38 | # messages
39 | 'updated' : u'頁面已更新。',
40 | 'created' : u'頁面已建立。',
41 | 'deleted' : u'頁面已刪除。'
42 |
43 | }
--------------------------------------------------------------------------------
/white/lang/zh_TW/post.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'post' : u'文章',
4 |
5 | 'create_post' : u'建立新文章',
6 | 'noposts_desc' : u'你沒有任何文章!',
7 |
8 | # form fields
9 | 'title' : u'文章標題',
10 | 'title_explain' : u'',
11 | 'title_missing' : u'請輸入標題',
12 |
13 | 'content' : u'文章內容',
14 | 'content_explain' : u'隨便寫。',
15 |
16 | 'slug' : u'縮寫',
17 | 'slug_explain' : u'定義頁面的縮寫,只應該包含 ascii 字元',
18 | 'slug_missing' : u'請輸入縮寫 URL,縮寫只能包含 ascii 字元',
19 | 'slug_duplicate' : u'縮寫已存在',
20 | 'slug_invalid' : u'縮寫必須包含字母',
21 |
22 | 'description' : u'描述',
23 | 'description_explain' : u'',
24 |
25 | 'status' : u'狀態',
26 | 'status_explain' : u'',
27 |
28 | 'category' : u'分類',
29 | 'category_explain' : u'',
30 |
31 | 'allow_comments' : u'允許評論',
32 | 'allow_comments_explain' : u'',
33 |
34 | 'custom_css' : u'自訂 CSS',
35 | 'custom_css_explain' : u'',
36 |
37 | 'custom_js' : u'自訂 JS',
38 | 'custom_js_explain' : u'',
39 |
40 | # messages
41 | 'updated' : u'文章已更新',
42 | 'created' : u'文章已建立',
43 | 'deleted' : u'文章已刪除'
44 |
45 | }
--------------------------------------------------------------------------------
/white/lang/zh_TW/user.py:
--------------------------------------------------------------------------------
1 | t = {
2 |
3 | 'user' : u'使用者',
4 |
5 | 'create_user' : u'建立新使用者',
6 | 'add_user' : u'新增使用者',
7 | 'editing_user' : u'編輯 %s 的個人資料',
8 | 'remembered' : u'我知道我的密碼',
9 | 'forgotten_password' : u'忘記密碼?',
10 |
11 | # roles
12 | 'administrator' : u'管理者',
13 | 'administrator_explain' : u'',
14 |
15 | 'editor' : u'編輯者',
16 | 'editor_explain' : u'',
17 |
18 | 'user' : u'使用者',
19 | 'user_explain' : u'',
20 |
21 | # form fields
22 | 'real_name' : u'名稱',
23 | 'real_name_explain' : u'',
24 |
25 | 'bio' : u'個人簡歷',
26 | 'bio_explain' : u'',
27 |
28 | 'status' : u'狀態',
29 | 'status_explain' : u'',
30 |
31 | 'role' : u'角色',
32 | 'role_explain' : u'',
33 |
34 | 'username' : u'使用者名稱',
35 | 'username_explain' : u'',
36 | 'username_missing' : u'請輸入使用者名稱長度在4-16個字元,必須是字母數字下劃線組合',
37 |
38 | 'password' : u'密碼',
39 | 'password_explain' : u'',
40 | 'password_invalid' : u'密碼長度在4-16個字元,必須是字母數字下劃線組合',
41 |
42 | 'new_password' : u'新密碼',
43 |
44 | 'email' : u'Email',
45 | 'email_explain' : u'',
46 | 'email_missing' : u'請輸入有效的 Email 地址',
47 | 'email_not_found' : u'找不到個人資料。',
48 |
49 | # messages
50 | 'updated' : u'使用者個人資料已更新。',
51 | 'created' : u'使用者個人資料已建立。',
52 | 'deleted' : u'使用者個人資料已刪除。',
53 | 'delete_error' : u'不能刪除自己的個人資料',
54 | 'login_error' : u'使用者名稱或密碼錯誤。',
55 | 'logout_notice' : u'現在你已經登出。',
56 | 'recovery_sent' : u'已發送確認密碼變更的信件。',
57 | 'recovery_expired' : u'密碼復原符記已過期,請重試。',
58 | 'password_reset' : u'新密碼已經設定。立即登入!',
59 |
60 | # password recovery email
61 | 'recovery_subject' : u'重設密碼',
62 | 'recovery_message' : u'你請求重設密碼。跟隨以下連結繼續。%s',
63 |
64 | }
--------------------------------------------------------------------------------
/white/lib/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
--------------------------------------------------------------------------------
/white/lib/image.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | import os
18 | import sys
19 | from PIL import Image
20 |
21 |
22 | def img_resize(path, size):
23 | im = Image.open(path)
24 | # im.resize(size)
25 | im.thumbnail(size, Image.ANTIALIAS)
26 | im.save(path)
27 |
--------------------------------------------------------------------------------
/white/lib/paginator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from flask import Markup
18 |
19 |
20 | class Paginator(object):
21 |
22 | def __init__(self, results, total, page, perpage, url, glue='/'):
23 | self.results = results
24 | self.total = total
25 | self.page = page
26 | self.first = 'First'
27 | self.last = 'Last'
28 | self._next = 'Next'
29 | self._prev = 'Previous'
30 | self.perpage = perpage
31 | self.url = url
32 | self._index = 0
33 | self.glue = glue
34 |
35 | def next_link(self, text='← Previous', default=''):
36 | text = text or self._next
37 | pages = (self.total / self.perpage) + 1
38 | if self.page < pages:
39 | page = self.page + 1
40 | return '' + text + ''
41 |
42 | return default
43 |
44 | def pre_link(self, text='← Previous', default=''):
45 | text = text or self._prev
46 | if self.page > 1:
47 | page = self.page - 1
48 | return Markup('' + text + '')
49 |
50 | return Markup(default)
51 |
52 | def links(self):
53 | html = ''
54 | pages = (self.total / self.perpage)
55 | if self.total % self.perpage != 0:
56 | pages += 1
57 | ranged = 4
58 | if pages > 1:
59 | if self.page > 1:
60 | page = self.page - 1
61 | html += '' + self.first + '' + \
62 | '' + self._prev + ''
63 | for i in range(self.page - ranged, self.page + ranged):
64 | if i < 0:
65 | continue
66 | page = i + 1
67 | if page > pages:
68 | break
69 |
70 | if page == self.page:
71 | html += '' + str(page) + ''
72 | else:
73 | html += '' + str(page) + ''
74 |
75 | if self.page < pages:
76 | page = self.page + 1
77 |
78 | html += '' + self._next + ' ' + self.last + ''
80 |
81 | return html
82 |
83 | __html__ = links
84 |
85 | def __len__(self):
86 | return len(self.results)
87 |
88 | def __iter__(self):
89 | return self
90 |
91 | def next(self):
92 | try:
93 | result = self.results[self._index]
94 | except IndexError:
95 | raise StopIteration
96 | self._index += 1
97 | return result
98 |
99 | __next__ = next # py3 compat
100 |
--------------------------------------------------------------------------------
/white/orm/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | __all__ = ['Backend']
18 | __backends = {}
19 |
20 |
21 | def Backend(name):
22 | return __backends.get(name)
23 |
24 |
25 | def setup():
26 | from .user import UserMapper
27 | from .category import CategoryMapper
28 | from .page import PageMapper
29 | from .post import PostMapper
30 | from .extend import ExtendMapper
31 | from .meta import MetaMapper
32 | from .pair import PairMapper
33 | from .comment import CommentMapper
34 | __backends['user'] = UserMapper()
35 | __backends['category'] = CategoryMapper()
36 | __backends['page'] = PageMapper()
37 | __backends['post'] = PostMapper()
38 | __backends['extend'] = ExtendMapper()
39 | __backends['meta'] = MetaMapper()
40 | __backends['storage'] = PairMapper()
41 | __backends['comment'] = CommentMapper()
42 |
--------------------------------------------------------------------------------
/white/orm/base.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from white.ext import db
18 |
19 |
20 | class BaseMapper(object):
21 |
22 | def load(self, data, o):
23 | return o(*data)
24 |
25 |
26 | class PrimaryTrait(object):
27 |
28 | primary_id = 'id'
29 |
30 | def find(self, id):
31 | q = db.select(self.table).condition(self.primary_id, id)
32 | data = q.query()
33 | if data:
34 | return self.load(data[0], self.model)
35 |
--------------------------------------------------------------------------------
/white/orm/category.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from .base import BaseMapper
18 | from white.ext import db
19 | from white.lib.paginator import Paginator
20 | from white.model import Category
21 |
22 |
23 | class CategoryMapper(BaseMapper):
24 |
25 | table = 'categories'
26 | model = Category
27 |
28 | def find(self, cid):
29 | """Find category by category id, return the category model instance if category id exists in database"""
30 | data = db.select(self.table).fields('title', 'slug', 'description', 'cid').condition('cid', cid).execute()
31 | if data:
32 | return self.load(data[0], self.model)
33 |
34 | def dropdown(self):
35 | """Returns the all category id"""
36 | return db.select(self.table).fields('cid', 'title').execute(as_dict=True)
37 |
38 | def order_by_title(self):
39 | results = db.select(self.table).fields('title', 'slug', 'description', 'cid').order_by('title').execute()
40 | return [self.load(data, self.model) for data in results]
41 |
42 | categories = order_by_title
43 |
44 | def find_by_slug(self, slug):
45 | """Find all categories by slug sql like rule"""
46 | data = db.select(self.table).fields('title', 'slug', 'description', 'cid').condition('slug', slug).execute()
47 | if data:
48 | return self.load(data[0], self.model)
49 |
50 | def count(self):
51 | return db.select(self.table).fields(db.expr('COUNT(*)')).execute()[0][0]
52 |
53 | def paginate(self, page=1, perpage=10):
54 | """Paginate the categories"""
55 | results = (db.select(self.table).fields('title', 'slug', 'description', 'cid')
56 | .limit(perpage).offset((page - 1) * perpage)
57 | .order_by('title').execute())
58 | return [self.load(data, self.model) for data in results]
59 |
60 | def create(self, category):
61 | """Create a new category"""
62 | return db.execute("INSERT INTO categories(title, slug, description) VALUES(%s, %s, %s)",
63 | (category.title, category.slug, category.description))
64 |
65 | def save(self, category):
66 | """Save and update the category"""
67 | return (db.update(self.table).
68 | mset(dict(title=category.title,
69 | description=category.description,
70 | slug=category.slug))
71 | .condition('cid', category.cid).execute())
72 |
73 | def delete(self, category_id):
74 | """Delete category by category id"""
75 | return db.delete(self.table).condition('cid', category_id).execute()
76 |
--------------------------------------------------------------------------------
/white/orm/comment.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from .base import BaseMapper
18 | from white.ext import db
19 | from white.model import Comment
20 |
21 |
22 | class CommentMapper(BaseMapper):
23 |
24 | table = 'comments'
25 | model = Comment
26 |
27 | def find(self, cid):
28 | data = db.select(self.table).fields('post_id', 'name',
29 | 'email', 'content', 'status', 'created', 'cid').condition('cid', cid).execute()
30 | if data:
31 | return self.load(data[0], self.model)
32 |
33 | def find_by_post_id(self, post_id, status='approved'):
34 | q = db.select(self.table).fields('post_id', 'name',
35 | 'email', 'content', 'status', 'created', 'cid').condition('post_id', post_id)
36 | if status:
37 | q.condition('status', status)
38 | data = q.execute()
39 | return [self.load(_, self.model) for _ in data]
40 |
41 | def paginate(self, page=1, perpage=10, status='all'):
42 | q = db.select(self.table).fields('post_id', 'name', 'email', 'content', 'status', 'created', 'cid')
43 | if status != 'all':
44 | q.condition('status', status)
45 | results = q.limit(perpage).offset((page - 1) * perpage).order_by('created').execute()
46 | pages = [self.load(page, self.model) for page in results]
47 | return pages
48 |
49 | def count(self):
50 | return db.select(self.table).fields(db.expr('COUNT(*)')).execute()[0][0]
51 |
52 | def spam_count(self, domain):
53 | return db.select(self.table).fields(db.expr('COUNT(*)')).condition('email', domain, 'LIKE').execute()[0][0]
54 |
55 | def create(self, comment):
56 | """Create a new comment"""
57 | return db.execute("INSERT INTO comments(post_id, name, email, content, status, created) VALUES(%s, %s, %s, %s, %s, %s)",
58 | (comment.post_id, comment.name, comment.email, comment.content, comment.status, comment.created))
59 |
60 | def save(self, comment):
61 | """Save Comment"""
62 | q = db.update(self.table)
63 | data = dict( (_, getattr(comment, _)) for _ in ('post_id', 'name',
64 | 'email', 'content', 'status', 'created', 'cid'))
65 | q.mset(data)
66 | return q.condition('cid', comment.cid).execute()
67 |
68 | def delete(self, comment_id):
69 | """Delete category by commment id"""
70 | return db.delete(self.table).condition('cid', comment_id).execute()
--------------------------------------------------------------------------------
/white/orm/extend.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from .base import BaseMapper
18 | from white.ext import db
19 | from white.model import Extend, Field
20 |
21 | from flask.json import loads, dumps
22 |
23 |
24 | class ExtendMapper(BaseMapper):
25 |
26 | model = Extend
27 | table = 'extend'
28 |
29 | def find(self, eid):
30 | """Find and load the extend from database by eid(extend id)"""
31 | data = (db.select(self.table).fields('type', 'key', 'label', 'field', 'attributes', 'eid').
32 | condition('eid', eid).execute())
33 | if data:
34 | return self.load(data[0])
35 |
36 | def find_by_type(self, type):
37 | """Find and load the extend from database by eid(extend id)"""
38 | data = (db.select(self.table).fields('type', 'key', 'label', 'field', 'attributes', 'eid').
39 | condition('type', type).execute())
40 | return [self.load(_) for _ in data]
41 |
42 | def paginate(self, page=1, perpage=10):
43 | data = db.select(self.table).fields('type', 'key', 'label', 'field', 'attributes', 'eid').limit(perpage).offset((page - 1) * perpage).execute()
44 | return [self.load(_) for _ in data]
45 |
46 | def load(self, data):
47 | data = list(data)
48 | try:
49 | data[4] = loads(data[4])
50 | except:
51 | data[4] = dict()
52 | return BaseMapper.load(self, data, self.model)
53 |
54 | def field(self, type, key, eid=-1):
55 | field = db.select(self.table).fields('type', 'key', 'label', 'field', 'attributes', 'eid').condition('type', type).condition('key', key).execute()
56 | if field:
57 | return self.load(field[0])
58 |
59 | def count(self, **kw):
60 | q = db.select(self.table).fields(db.expr('COUNT(*)'))
61 | if kw:
62 | for k, v in kw.iteritems():
63 | q.condition(k, v)
64 | return q.execute()[0][0]
65 |
66 | def create(self, extend):
67 | """Create a new extend"""
68 | attributes = dumps(extend.attributes)
69 | return db.execute('INSERT INTO extend (`type`, `label`, `field`, `key`, `attributes`) VALUES (%s, %s, %s, %s, %s)',
70 | (extend.type, extend.label, extend.field, extend.key, attributes))
71 |
72 | def save(self, extend):
73 | """Save and update the extend"""
74 | attributes = dumps(extend.attributes)
75 | return (db.update(self.table).
76 | mset(dict(type=extend.type,
77 | label=extend.label,
78 | key=extend.key,
79 | attributes=attributes,
80 | field=extend.field))
81 | .condition('eid', extend.eid).execute())
82 |
83 | def delete(self, extend):
84 | """Delete category by extend"""
85 | return db.delete(self.table).condition('eid', extend.eid).execute()
86 |
--------------------------------------------------------------------------------
/white/orm/meta.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from .base import BaseMapper
18 | from white.ext import db
19 | from white.model import Meta
20 | from flask.json import loads, dumps
21 |
22 |
23 | class MetaMapper(BaseMapper):
24 |
25 | model = Meta
26 | table = 'meta'
27 |
28 | def find(self, type, node_id, extend_id):
29 | data = (db.select(self.table).fields('node_id', 'type', 'extend', 'data', 'mid')
30 | .condition('type', type)
31 | .condition('node_id', node_id)
32 | .condition('extend', extend_id)
33 | .execute())
34 | if data:
35 | return self.load(data[0])
36 |
37 | def load(self, data):
38 | data = list(data)
39 | try:
40 | data[3] = loads(data[3])
41 | except:
42 | data[3] = dict()
43 | return BaseMapper.load(self, data, self.model)
44 |
45 | def create(self, meta):
46 | data = dumps(meta.data)
47 | return (db.insert(self.table).fields('node_id', 'type', 'extend', 'data')
48 | .values((meta.node_id, meta.type, meta.extend, data)).execute())
49 |
50 | def save(self, meta):
51 | data = dumps(meta.data)
52 | return (db.update(self.table).mset(
53 | dict(node_id=meta.node_id,
54 | type=meta.type,
55 | extend=meta.extend,
56 | data=data)).condition('mid', meta.mid).execute())
57 |
58 | def delete(self, meta):
59 | return db.delete(self.table).condition('mid', meta.mid)
60 |
--------------------------------------------------------------------------------
/white/orm/page.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from .base import BaseMapper
18 | from white.ext import db
19 | from white.model import Page
20 |
21 |
22 | class PageMapper(BaseMapper):
23 |
24 | table = 'pages'
25 | model = Page
26 |
27 | def find(self, pid):
28 | data = db.select(self.table).fields('parent', 'name', 'title', 'slug',
29 | 'content', 'status', 'redirect', 'show_in_menu', 'pid').condition('pid', pid).execute()
30 | if data:
31 | return self.load(data[0], self.model)
32 |
33 |
34 | def find_by_redirect(self, redirect):
35 | data = db.select(self.table).fields('parent', 'name', 'title', 'slug',
36 | 'content', 'status', 'redirect', 'show_in_menu', 'pid').condition('redirect', redirect).execute()
37 | if data:
38 | return self.load(data[0], self.model)
39 |
40 | def find_by_slug(self, slug):
41 | data = db.select(self.table).fields('parent', 'name', 'title', 'slug',
42 | 'content', 'status', 'redirect', 'show_in_menu', 'pid').condition('slug', slug).execute()
43 | if data:
44 | return self.load(data[0], self.model)
45 |
46 | def menu(self, is_menu=False):
47 | q = db.select(self.table).fields('parent', 'name', 'title', 'slug',
48 | 'content', 'status', 'redirect', 'show_in_menu', 'pid').condition('show_in_menu', 1)
49 | if not is_menu:
50 | res = q.execute()
51 | else:
52 | res = q.condition('status', 'published').order_by('menu_order').execute()
53 | return [self.load(data,self.model) for data in res]
54 |
55 |
56 | def dropdown(self, show_empty_option=True, exclude=[]):
57 | items = []
58 | if show_empty_option:
59 | items.append((0, '--'))
60 |
61 | pages = db.select(self.table).fields('pid', 'name').execute()
62 | for page in pages:
63 | if page[0] in exclude:
64 | continue
65 | items.append((page[0], page[1]))
66 |
67 | return items
68 |
69 | def count(self, status=None):
70 | q= db.select(self.table).fields(db.expr('COUNT(*)'))
71 | if status != 'all':
72 | q.condition('status', status)
73 | return q.execute()[0][0]
74 |
75 |
76 | def count_slug(self, slug):
77 | return db.select(self.table).fields(db.expr('COUNT(*)')).condition('slug', slug).execute()[0][0]
78 |
79 | def paginate(self, page=1, perpage=10, status='all'):
80 | q = db.select(self.table).fields('parent', 'name', 'title', 'slug',
81 | 'content', 'status', 'redirect', 'show_in_menu', 'pid')
82 | if status != 'all':
83 | q.condition('status', status)
84 | results = q.limit(perpage).offset((page - 1) * perpage).order_by('title', 'desc').execute()
85 | pages = [self.load(page, self.model) for page in results]
86 | return pages
87 |
88 | def create(self, page):
89 | row = []
90 | for _ in ('parent', 'name', 'title', 'slug', 'content', 'status', 'redirect', 'show_in_menu'):
91 | row.append(getattr(page, _))
92 | return db.insert(self.table).fields('parent', 'name', 'title', 'slug',
93 | 'content', 'status', 'redirect', 'show_in_menu').values(row).execute()
94 |
95 | def save(self, page):
96 | q = db.update(self.table)
97 | data = dict( (_, getattr(page, _)) for _ in ('parent', 'name',
98 | 'title', 'slug', 'content', 'status', 'redirect', 'show_in_menu'))
99 | q.mset(data)
100 | return q.condition('pid', page.pid).execute()
101 |
102 | def update_menu_order(self, pid, menu_order):
103 | return db.update(self.table).condition('pid', pid).set('menu_order', menu_order).execute()
104 |
105 | def delete(self, page_id):
106 | return db.delete(self.table).condition('pid', page_id).execute()
107 |
--------------------------------------------------------------------------------
/white/orm/pair.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from .base import BaseMapper
18 | from white.ext import db
19 | from white.model import Pair
20 |
21 |
22 | class PairMapper(BaseMapper):
23 |
24 | model = Pair
25 | table = 'storage'
26 |
27 | def lists(self, exclude=None, sorted=False):
28 | q = db.select(self.table)
29 | if sorted:
30 | q.sort_by('key')
31 | if exclude:
32 | db.condition('key', exculde, '<>')
33 | res = q.execute()
34 | return [self.load(row, self.model) for row in res]
35 |
36 | def find(self, key):
37 | data = db.select(self.table).condition('key', key).execute()
38 | if data:
39 | return self.load(data[0], self.model)
40 |
41 | def save(self, pair):
42 | return db.insert(self.table).values((pair.key, pair.value, pair.type)).execute()
43 |
44 | def update(self, pair):
45 | return db.update(self.table).set('value', pair.value).condition('key', pair.key).execute()
46 |
47 | def delete(self, pair):
48 | return db.delete(self.table).condition('key', pair.key).condition('type', pair.type).execute()
49 |
--------------------------------------------------------------------------------
/white/orm/user.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from white.model import User
18 | from white.ext import db
19 | from .base import BaseMapper
20 |
21 |
22 | class UserMapper(BaseMapper):
23 |
24 | model = User
25 | table = 'users'
26 |
27 | def find(self, uid):
28 | """Find and load the user from database by uid(user id)"""
29 | data = (db.select(self.table).select('username', 'email', 'real_name',
30 | 'password', 'bio', 'status', 'role', 'uid').
31 | condition('uid', uid).execute()
32 | )
33 | if data:
34 | return self.load(data[0], self.model)
35 |
36 | def find_by_username(self, username):
37 | """Return user by username if find in database otherwise None"""
38 | data = (db.select(self.table).select('username', 'email', 'real_name',
39 | 'password', 'bio', 'status', 'role', 'uid').
40 | condition('username', username).execute()
41 | )
42 | if data:
43 | return self.load(data[0], self.model)
44 |
45 | def find_by_email(self, email):
46 | """Return user by email if find in database otherwise None"""
47 | data = (db.select(self.table).select('username', 'email', 'real_name',
48 | 'password', 'bio', 'status', 'role', 'uid').
49 | condition('email', email).execute()
50 | )
51 | if data:
52 | return self.load(data[0], self.model)
53 |
54 |
55 | def create(self, user):
56 | return db.execute("INSERT INTO users(username, email, real_name, password, bio, status, role) \
57 | VALUES(%s, %s, %s, %s, %s, %s, %s)",
58 | (user.username, user.email, user.real_name, user.password, user.bio, user.status, user.role))
59 |
60 | def search(self, **kw):
61 | """Find the users match the condition in kw"""
62 | q = db.select(self.table).condition('status', 'active')
63 | for k, v in kw:
64 | q.condition(k, v)
65 | data = q.execute()
66 | users = []
67 | for user in data:
68 | users.append(self.load(user, self.model))
69 | return users
70 |
71 | def count(self):
72 | return db.query('SELECT COUNT(*) FROM ' + self.table)[0][0]
73 |
74 | def take(self, page=1, perpage=10):
75 | count = self.count()
76 | q = db.select(self.table).select('username', 'email', 'real_name',
77 | 'password', 'bio', 'status', 'role', 'uid')
78 | results = q.limit(perpage).offset((page - 1) * perpage).order_by('real_name', 'desc').execute()
79 | users = [self.load(user, self.model) for user in results]
80 | return users
81 |
82 | def save(self, user):
83 | q = db.update(self.table)
84 | data = dict( (_, getattr(user, _)) for _ in ('username', 'email', 'real_name', 'password',
85 | 'bio', 'status', 'role'))
86 | q.mset(data)
87 | return q.condition('uid', user.uid).execute()
88 |
89 | def delete(self, user):
90 | return db.delete(self.table).condition('uid', user.uid).execute()
91 |
--------------------------------------------------------------------------------
/white/patch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 | from datetime import date, datetime, time
17 | import decimal
18 | import flask
19 | from flask._compat import text_type
20 | from flask.json import JSONEncoder as _JSONEncoder, dumps
21 | from flask import Flask, json, current_app, request
22 |
23 |
24 | class JSONEncoder(_JSONEncoder):
25 |
26 | def default(self, o):
27 | if hasattr(o, '__json__') and callable(o.__json__):
28 | return o.__json__()
29 | if isinstance(o, (date,
30 | datetime,
31 | time)):
32 | return o.isoformat()[:19].replace('T', ' ')
33 | elif isinstance(o, (int, long)):
34 | return int(o)
35 | elif isinstance(o, decimal.Decimal):
36 | return str(o)
37 | elif hasattr(o, '__html__'):
38 | return text_type(o.__html__())
39 | return _JSONEncoder.default(self, o)
40 |
41 |
42 | def jsonify(value):
43 | """Creates a :class:`~flask.Response` with the JSON representation of
44 | the given arguments with an `application/json` mimetype. The arguments
45 | to this function are the same as to the :class:`dict` constructor.
46 |
47 | Example usage::
48 |
49 | from flask import jsonify
50 |
51 | class User(object):
52 | def __json__(self):
53 | return dict(username=g.user.username,
54 | email=g.user.email,
55 | id=g.user.id)
56 |
57 | @app.route('/_get_current_user')
58 | def get_current_user():
59 | return jsonify(user)
60 |
61 | This will send a JSON response like this to the browser::
62 |
63 | {
64 | "username": "admin",
65 | "email": "admin@localhost",
66 | "id": 42
67 | }
68 |
69 | For security reasons only objects are supported toplevel. For more
70 | information about this, have a look at :ref:`json-security`.
71 |
72 | This function's response will be pretty printed if it was not requested
73 | with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless
74 | the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false.
75 |
76 | """
77 | indent = None
78 | if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] \
79 | and not request.is_xhr:
80 | indent = 2
81 | return current_app.response_class(dumps(value,
82 | indent=indent),
83 | mimetype='application/json')
84 |
85 |
86 | def patch_flask():
87 | flask.json.jsonify = jsonify
88 | flask.jsonify = flask.json.jsonify
89 | Flask.json_encoder = JSONEncoder
90 |
--------------------------------------------------------------------------------
/white/security.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 | import hmac
17 | from hashlib import sha1
18 | import base64
19 | import uuid
20 |
21 | from functools import wraps
22 |
23 | from white.orm import Backend
24 | from white.model import User
25 | from flask import g, session, request, abort, redirect, url_for, current_app, render_template
26 |
27 |
28 | _guest = User(
29 | 'guest', 'email', 'Guest', 'password', 'bio', 'active', 'guest', uid=0)
30 |
31 |
32 | def security(role=None):
33 | def decorator(f):
34 | @wraps(f)
35 | def _decorator(*args, **kw):
36 | me = g.user
37 | if me.is_guest() and request.path != 'admin/login':
38 | return redirect(url_for('admin.login'))
39 | access = False
40 | if me.is_root():
41 | access = True
42 | elif me.inactive():
43 | access = False
44 | elif me.role == role:
45 | access = True
46 | elif me.is_admin and role in (User.EDITOR, None):
47 | access = True
48 |
49 | if access:
50 | return f(*args, **kw)
51 | else:
52 | return render_template('admin/403.html')
53 | return _decorator
54 | return decorator
55 |
56 |
57 | def init_user():
58 | """Load user if the auth session validates."""
59 | try:
60 | user = _guest
61 | if 'auth' in session:
62 | uid = session['auth']
63 | user = Backend('user').find(uid)
64 | if user is None:
65 | session.pop('auth', None)
66 | user = _guest
67 | except:
68 | user = _guest
69 | g.user = user
70 |
71 |
72 | @wraps
73 | def csrf_protect(f):
74 | if request.method == "POST":
75 | token = session.pop('_csrf_token', None)
76 | if not token or token != request.form.get('_csrf_token'):
77 | abort(403)
78 |
79 | return f
80 |
81 |
82 | def generate_csrf_token():
83 | if '_csrf_token' not in session:
84 | session['_csrf_token'] = _secert_signature(
85 | current_app.config['CSRF_SECRET'], str(uuid.uuid4()))
86 | return session['_csrf_token']
87 |
88 |
89 | def _secert_signature(secret, *parts):
90 | hash = hmac.new(secret, digestmod=sha1)
91 | for part in parts:
92 | hash.update(part)
93 | return hash.hexdigest()
94 |
--------------------------------------------------------------------------------
/white/setting.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | class Config:
18 |
19 | HOST = 'localhost' # server host
20 | PORT = 5000 # server port
21 |
22 | DEBUG = True # open debug mode
23 |
24 | # csrf protect
25 | # CSRF_SECRET = 'fRLoWItHQQajq//4ebYUDewXVF2B9UEznoVD7kC7D9o='
26 |
27 | # Flask Session module
28 | # session
29 | # SECRET_KEY = '7oGwHH8NQDKn9hL12Gak9G/MEjZZYk4PsAxqKU4cJoY='
30 | # SESSION_TYPE = 'filesystem'
31 | # SESSION_FILE_DIR = '/var/www/$yoursite.com/cookies'
32 | # SESSION_FILE_THRESHOLD = 100
33 | # SESSION_FILE_MODE = 0600
34 |
35 | ######
36 | # Wanna use redis session, please comment filesystem session settings
37 | # SESSION_TYPE = 'redis'
38 | #
39 | # REDIS_HOST = localhost
40 | # PERMANENT_SESSION_LIFETIME = 60
41 |
42 | # DB Config
43 | DB_CONFIG = {
44 | 'db': 'white',
45 | 'user': 'white',
46 | 'passwd': 'white',
47 | 'host': 'localhost',
48 |
49 | 'max_idle': 10 # the mysql timeout setting
50 | }
51 | DB_MAXCONN = 10
52 | DB_MINCONN = 5
53 |
54 | # the custom fields asset path
55 | CONTENT_PATH = '/var/www/$yoursite.com/content'
56 |
57 | LANGUAGE = 'en_GB' # in ('zh_CN', 'zh_TW', 'en_GB')
58 |
59 | THEME = 'default' # the froent theme name
60 |
--------------------------------------------------------------------------------
/white/util.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # 2015 Copyright (C) White
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, version 2 of the License.
7 | #
8 | # This program is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU General Public License
14 | # along with this program. If not, see .
15 |
16 |
17 | from hashlib import sha256
18 |
19 |
20 | def hide_pass_for_config(config):
21 | for key, value in config.iteritems():
22 | if ('pass' in key.lower() or 'secret' in key.lower()) and isinstance(value, (str, unicode)):
23 | config[key] = 'hide: %s' % (sha256(value).hexdigest())
24 | elif isinstance(value, dict):
25 | hide_pass_for_config(value)
26 |
--------------------------------------------------------------------------------
/white/view/admin/403.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 |
4 |
403 Forbidden
5 | You cant's access this module {{message}}
6 |
7 |
8 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/category/add.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {{__('category.create_category')}}
4 |
5 |
32 |
33 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/category/edit.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {% set title = __('category.edit_category', args = (category.title,)) %}
4 | {{title|safe}}
5 |
6 |
33 |
34 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/category/index.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {{__('category.category')}}
4 |
7 |
8 |
9 | {{ flash }}
10 |
20 |
21 |
22 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/comment/edit.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {{__('comment.editing_comment')}}
4 |
5 |
44 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/comment/index.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {{__('comment.comment')}}
4 |
5 |
6 | {{flash}}
7 |
12 | {% if comments %}
13 |
24 |
25 | {% else %}
26 |
30 | {% endif %}
31 |
32 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/extend/field/add.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {{__('extend.create_field')}}
4 |
5 |
61 |
62 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/extend/field/edit.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {% set title = __('extend.editing_custom_field', args=(field.label,)) %}
4 | {{title|safe}}
5 |
6 |
69 |
70 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/extend/field/index.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {{__('extend.fields')}}
4 |
7 |
8 |
9 | {% if fields %}
10 |
20 |
21 | {% else %}
22 |
23 | {{__('extend.nofields_desc')}}
24 |
25 | {% endif %}
26 |
27 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/extend/index.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {{__('extend.variables')}}
4 |
5 |
27 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/extend/metadata/edit.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | {{__('metadata.metadata')}}
4 |
5 |
6 | {{ flash }}
7 |
62 |
63 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/extend/plugin/index.html:
--------------------------------------------------------------------------------
1 | {% include "admin/layout/header.html" %}
2 |
3 | Plugins
4 |
5 |
10 | {% include "admin/layout/footer.html" %}
--------------------------------------------------------------------------------
/white/view/admin/layout/edit.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/white/view/admin/layout/footer.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
11 |
12 |