├── .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 |
6 | {{ flash }} 7 |
8 | {##} 9 |
10 |

11 | 12 | 13 | {{__('category.title_explain')}} 14 |

15 |

16 | 17 | 18 | {{__('category.slug_explain')}} 19 |

20 |

21 | 22 | 24 | {{__('category.description_explain')}} 25 |

26 |
27 | 30 |
31 |
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 |
7 | {{ flash }} 8 | 9 |
10 |
11 |

12 | 13 | 14 | {{__('category.title_explain')}} 15 |

16 |

17 | 18 | 19 | {{__('category.slug_explain')}} 20 |

21 |

22 | 23 | 24 | {{__('category.description_explain')}} 25 |

26 |
27 | 31 |
32 |
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 |
6 |
7 | {{flash}} 8 |
9 |

10 | 11 | 12 | {{__('comment.name_explain')}} 13 |

14 |

15 | 16 | 17 | {{__('comment.email_explain')}} 18 |

19 |

20 | 21 | 22 | {{__('comment.content_explain')}} 23 |

24 |

25 | 26 | 35 | {{__('comment.status_explain')}} 36 |

37 |
38 | 42 |
43 |
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 |

27 | 28 | {{__('comment.nocomments_desc')}} 29 |

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 |
6 | {{ flash }} 7 |
8 | {# #} 9 |
10 |

11 | 12 | 17 | {{__('extend.type_explain')}} 18 |

19 |

20 | 21 | 26 | {{__('extend.field_explain')}} 27 |

28 |

29 | 30 | 31 | {{__('extend.key_explain')}} 32 |

33 |

34 | 35 | 36 | {{__('extend.label_explain')}} 37 |

38 |

39 | 40 | 41 | {{__('extend.attribute_type_explain')}} 42 |

43 |

44 | 45 | 47 | {{__('extend.attributes_size_width_explain')}} 48 |

49 |

50 | 51 | 53 | {{__('extend.attributes_size_height_explain')}} 54 |

55 |
56 | 59 |
60 |
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 |
7 | {{ flash }} 8 |
9 |
10 |

11 | 12 | 18 | {{__('extend.type_explain')}} 19 |

20 |

21 | 22 | 28 | {{__('extend.field_explain')}} 29 |

30 |

31 | 32 | 33 | {{__('extend.key_explain')}} 34 |

35 |

36 | 37 | 38 | {{__('extend.label_explain')}} 39 |

40 |

41 | 42 | {% set value = field.attributes.get('type', '') %} 43 | 44 | {{__('extend.attribute_type_explain')}} 45 |

46 |

47 | 48 | {% set value = field.attributes['size'].get('width') if 'size' in field.attributes and field.attributes['size'].get('width') else '' %} 49 | 51 | {{__('extend.attributes_size_width_explain')}} 52 |

53 |

54 | 55 | {% set value = field.attributes['size'].get('height') if 'size' in field.attributes and field.attributes['size'].get('height') else '' %} 56 | 58 | {{__('extend.attributes_size_height_explain')}} 59 |

60 |
61 | 67 |
68 |
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 |
6 | 26 |
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 |
8 |
9 |

10 | 11 | 12 | {{__('metadata.sitename_explain')}} 13 |

14 |

15 | 16 | 17 | {{__('metadata.sitedescription_explain')}} 18 |

19 |

20 | 21 | 35 | {{__('metadata.homepage_explain')}} 36 |

37 |

38 | 39 | 40 | {{__('metadata.posts_per_page_explain')}} 41 |

42 |
43 |
44 | {{__('metadata.comment_settings')}} 45 |

46 | 47 | {% set checked = request.form.get('auto_published_comments', auto_published_comments) and ' checked' or '' %} 48 | 49 | {{__('metadata.auto_publish_comments_explain')}} 50 |

51 |

52 | 53 | 55 | {{__('metadata.comment_moderation_keys_explain')}} 56 |

57 |
58 | 61 |
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 |
6 |

7 | Soon. 8 |

9 |
10 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/layout/edit.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /white/view/admin/layout/footer.html: -------------------------------------------------------------------------------- 1 |
2 | {{__('global.powered_by_white', args= (0.1,))}} 3 | {{__('global.make_blogging_beautiful')}} 4 |
5 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /white/view/admin/layout/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{__('global.manage')}} - {{site.sitename()}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | {% if not g.user.is_guest() %} 22 | 36 | {{__('global.logout')}} 37 | {% else %} 38 | 41 | {% endif %} 42 |
43 |
-------------------------------------------------------------------------------- /white/view/admin/menu/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('menu.menu', 'Menu')}}

4 |
5 |
6 | {% if pages %} 7 |
    8 | {% for page in pages %} 9 |
  • 10 | {{page.name}} 11 |
  • 12 | {% endfor %} 13 |
14 | {% else %} 15 |

16 | 17 | No menu items yet. 18 |

19 | {% endif %} 20 |
21 | 22 | 38 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/page/add.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 | {{ flash }} 4 |
5 |
6 | 7 | 11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | 21 | {% include "admin/layout/edit.html" %} 22 |
23 |
24 |
25 |
26 |

27 | 28 | 29 | {{__('page.show_in_menu_explain')}} 30 |

31 |

32 | 33 | 34 | {{__('page.name_explain')}} 35 |

36 |

37 | 38 | 39 | {{__('page.slug_explain')}} 40 |

41 |

42 | 43 | 48 | {{__('page.status_explain')}} 49 |

50 |

51 | 52 | 57 | {{__('page.parent_explain')}} 58 |

59 | {% for field in fields %} 60 |

61 | 62 | {{field}} 63 |

64 | {% endfor %} 65 |
66 |
67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 78 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/page/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |
4 | {{ flash }} 5 |
6 | 7 | 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 | {% include "admin/layout/edit.html" %} 23 |
24 |
25 |
26 |
27 |

28 | 29 | 30 | {{__('page.show_in_menu_explain')}} 31 |

32 |

33 | 34 | 35 | {{__('page.name_explain')}} 36 |

37 |

38 | 39 | 40 | {{__('page.slug_explain')}} 41 |

42 |

43 | 44 | 53 | {{__('page.status_explain')}} 54 |

55 |

56 | 57 | 62 | {{__('page.parent_explain')}} 63 |

64 | {% for field in fields %} 65 |

66 | 67 | {{field}} 68 |

69 | {% endfor %} 70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 80 | {%include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/page/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('page.page')}}

4 | {% if pages %} 5 | 8 | {% endif %} 9 |
10 |
11 | 18 | {% if pages %} 19 | 34 | 35 | 36 | {% else %} 37 | 42 | {% endif %} 43 |
44 | {%include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/post/add.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 | {{flash}} 4 |
5 |
6 | 7 | 10 |
11 |
12 |
13 |
14 | 15 | {% include "admin/layout/edit.html" %} 16 |
17 | 18 |
19 |
20 |
21 |

22 | 23 | 24 | {{__('post.slug_explain')}} 25 |

26 |

27 | 28 | 29 | {{__('post.description_explain')}} 30 |

31 |

32 | 33 | 38 | {{__('post.status_explain')}} 39 |

40 |

41 | 42 | 47 | {{__('post.category_explain')}} 48 |

49 |

50 | 51 | 52 | {{__('post.allow_comments_explain')}} 53 |

54 |

55 | 56 | 57 | {{__('post.custom_css_explain')}} 58 |

59 |

60 | 61 | 62 | {{__('post.custom_js_explain')}} 63 |

64 | {% for field in fields %} 65 |

66 | 67 | {{field}} 68 |

69 | {% endfor %} 70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 81 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/post/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |
4 | 5 |
6 | {{ flash }} 7 | 8 | 12 | 13 |
14 |
15 |
16 |
17 | 18 | {%include "admin/layout/edit.html" %} 19 |
20 |
21 |
22 |
23 |

24 | 25 | 26 | {{__('post.slug_explain')}} 27 |

28 |

29 | 30 | 31 | {{__('post.description_explain')}} 32 |

33 |

34 | 35 | 44 | {{__('post.status_explain')}} 45 |

46 |

47 | 48 | 57 | {{__('post.category_explain')}} 58 |

59 |

60 | 61 | 62 | {{__('post.allow_comments_explain')}} 63 |

64 | 65 | 66 | {{__('post.custom_css_explain')}} 67 |

68 |

69 | 70 | 71 | {{__('post.custom_js_explain')}} 72 |

73 | {% for field in fields %} 74 |

75 | 76 | {{field}} 77 |

78 | {% endfor %} 79 |
80 |
81 |
82 | 83 | 84 | 85 | 86 | 89 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/post/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('post.post')}}

4 | {% if posts %} 5 | 8 | {% endif %} 9 |
10 |
11 | {{flash}} 12 | 18 | {% if posts %} 19 | 35 | 36 | {% else %} 37 |

38 | 39 | {{__('post.noposts_desc')}}
40 | {{__('post.create_post')}} 41 |

42 | {% endif %} 43 |
44 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/user/add.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('user.add_user')}}

4 |
5 |
6 |
7 | {{flash}} 8 |
9 |

10 | 11 | 12 | {{__('user.real_name_explain')}} 13 |

14 |

15 | 16 | 17 | {{__('user.bio_explain')}} 18 |

19 |

20 | 21 | 30 | {{__('user.status_explain')}} 31 |

32 |

33 | 34 | 43 | {{__('user.role_explain')}} 44 |

45 |
46 |
47 |

48 | 49 | 50 | {{__('user.role_explain')}} 51 |

52 |

53 | 54 | 55 | {{__('user.password_explain')}} 56 |

57 |

58 | 59 | 60 | {{__('user.email_explain')}} 61 |

62 |
63 | 66 |
67 |
68 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/user/edit.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('users.editing_user', user.username)}}

4 |
5 |
6 |
7 | {{flash}} 8 |
9 |

10 | 11 | 12 | {{__('user.real_name_explain')}} 13 |

14 |

15 | 16 | 17 | echo {{__('user.bio_explain')}} 18 |

19 |

20 | 21 | 30 | {{__('user.status_explain')}} 31 |

32 |

33 | 34 | 46 | {{__('user.role_explain')}} 47 |

48 |
49 |
50 |

51 | 52 | 53 | ('user.role_explain')}} 54 |

55 |

56 | 57 | 58 | {{__('user.password_explain')}} 59 |

60 |

61 | 62 | 63 | New Password 64 |

65 |

66 | 67 | 68 | Password Confirm 69 |

70 |

71 | 72 | 73 | {{__('user.email_explain')}} 74 |

75 |
76 | 82 |
83 |
84 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/user/index.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 |
3 |

{{__('user.user')}}

4 | {% if g.user.is_root() %} 5 | 8 | {% endif %} 9 |
10 |
11 | {{flash}} 12 | 23 | 24 |
25 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/admin/user/login.html: -------------------------------------------------------------------------------- 1 | {% include "admin/layout/header.html" %} 2 | 17 | {% include "admin/layout/footer.html" %} -------------------------------------------------------------------------------- /white/view/theme/default/403.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 |
4 |

5 |

403 Forbidden

6 |

You cant's access this page

7 |

8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /white/view/theme/default/404.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 |
4 |

Page not found

5 |

Unfortunately, the page {{request.path}} could not be found. Your best is either to try the 6 | homepage , try searching , or go and cry in corner (although I don’t recommend the latter).

7 |

8 |
9 | {% endblock %} -------------------------------------------------------------------------------- /white/view/theme/default/article.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% if article.css or article.jss %} 3 | {% block head %} 4 | 5 | 6 | 7 | 8 | {% endblock %} 9 | {% endif %} 10 | {% block body %} 11 |
12 |

{{article.title}}

13 |
14 | {{article.html|markdown|safe}} 15 |
16 |
17 | {% if article.allow_comment %} 18 |
19 |
    20 | {% for i, comment in enumerate(comments) %} 21 |
  • 22 |
    23 |

    {{comment.name }}

    24 | 25 |
    26 | {{comment.content}} 27 |
    28 | {{ i + 1 }} 29 |
    30 |
  • 31 | {% endfor %} 32 |
33 |
34 |

35 | 36 | 37 |

38 | 42 |

43 | 44 | 45 |

46 |

47 | 48 |

49 |
50 |
51 | {% endif %} 52 | {% endblock %} -------------------------------------------------------------------------------- /white/view/theme/default/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 11 | 12 | 24 |
25 |
26 | 27 |
28 | 29 | {% set menus = menus() %} 30 | {% if menus %} 31 | {% macro menu_active(menu) %} 32 | {% if page is defined and page.slug == menu.slug and page.pid == menu.pid %} 33 | class="active" 34 | {% endif %} 35 | {% endmacro %} 36 | 49 | {% endif %} 50 |
-------------------------------------------------------------------------------- /white/view/theme/default/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | © 2015 {{site.sitename()}}. All rights reserved. 4 | 5 | 12 |
13 | 14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /white/view/theme/default/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{page_title}} - {{site.sitename()}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /white/view/theme/default/layout.html: -------------------------------------------------------------------------------- 1 | {% include 'theme/default/header.html' %} 2 | {% block head %}{% endblock %} 3 | 4 | {% include 'theme/default/body.html' %} 5 | {% block body %}{% endblock %} 6 | 7 | {% block footer %}{% endblock %} 8 | {% include 'theme/default/footer.html' %} -------------------------------------------------------------------------------- /white/view/theme/default/page.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 |
4 |

{{page_title}}

5 | {{page_content|markdown|safe}} 6 |
7 | {% endblock %} -------------------------------------------------------------------------------- /white/view/theme/default/posts.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 | {% if posts %} 4 |
5 |
    6 | {% set i = 1 %} 7 | {% for post in posts %} 8 | {% set bg = 'background: hsl(215, 28%%, %d%%)' %( (i /10 * 20) + 20) %} 9 | {% set i = i + 1 %} 10 |
  • 11 |
    12 |

    13 | {{ post.title }} 14 |

    15 |
    16 | Posted by author {{ post.user.username }}. 17 |
    18 |
    19 |
  • 20 | {% endfor %} 21 |
22 | {% if post_total > site.posts_per_page() %} 23 | 29 | {% endif %} 30 |
31 | {% else %} 32 |
33 |

Nothing

34 |

Looks like you have some writing to do!

35 |
36 | {% endif %} 37 | {% endblock %} -------------------------------------------------------------------------------- /white/view/theme/default/search.html: -------------------------------------------------------------------------------- 1 | {% extends "theme/default/layout.html" %} 2 | {% block body %} 3 |

You searched for “{{search_term}}”.

4 | {% if articles %} 5 |
    6 | {% for article in articles %} 7 |
  • 8 | 13 |
  • 14 | {% endfor %} 15 |
16 | {% else %} 17 |

Unfortunately, there's no results for “{{key}}”. Did you spell everything correctly?

18 | {% endif %} 19 | {% if articles.total > site.posts_per_page() %} 20 | 26 | {% endif %} 27 | {% endblock %} -------------------------------------------------------------------------------- /whited: -------------------------------------------------------------------------------- 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.server import WhiteServer 18 | 19 | server = WhiteServer() 20 | server.bootstrap() 21 | server.serve_forever() --------------------------------------------------------------------------------