├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── blessing.py ├── creater.py ├── factory ├── management │ ├── checkin.htm │ ├── error.htm │ └── office.htm └── nutrition │ └── summernight │ └── model.htm ├── foundation ├── __init__.py ├── memory.py ├── office.py └── place.py ├── library_sample.json ├── melody_example.py └── spirit ├── favicon.ico ├── favicon.jpg ├── management ├── avatar.jpg ├── checkin.css ├── checkin.js ├── fetch.js ├── highlight.min.css ├── highlight.min.js ├── marked.js ├── material.min.css ├── material.min.css.map ├── material.min.js ├── material.min.js.map ├── office.css ├── office.js └── sha.js ├── nutrition └── summernight │ ├── avatar.jpg │ ├── fetch.js │ ├── highlight.min.css │ ├── highlight.min.js │ ├── marked.js │ ├── material.min.css │ ├── material.min.css.map │ ├── material.min.js │ ├── material.min.js.map │ ├── model.css │ ├── model.js │ └── natsu-background.jpg └── robots.txt /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | # Development Temporary Directory 5 | rubbish/ 6 | 7 | # FurtherLand Public Area 8 | /spirit/public/* 9 | 10 | # FurtherLand Biosphere 11 | biosphere/ 12 | 13 | # FurtherLand Melody 14 | melody.py 15 | 16 | # Byte-compiled / optimized / DLL files 17 | __pycache__/ 18 | */__pycache__/ 19 | *.py[cod] 20 | *.pyc 21 | 22 | # Node.js Extensions 23 | node_modules/ 24 | 25 | # C extensions 26 | *.so 27 | 28 | # Distribution / packaging 29 | .Python 30 | env/ 31 | build/ 32 | develop-eggs/ 33 | dist/ 34 | downloads/ 35 | eggs/ 36 | .eggs/ 37 | lib/ 38 | lib64/ 39 | parts/ 40 | sdist/ 41 | var/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | 46 | # PyInstaller 47 | # Usually these files are written by a python script from a template 48 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 49 | *.manifest 50 | *.spec 51 | 52 | # Installer logs 53 | pip-log.txt 54 | pip-delete-this-directory.txt 55 | 56 | # Unit test / coverage reports 57 | htmlcov/ 58 | .tox/ 59 | .coverage 60 | .coverage.* 61 | .cache 62 | nosetests.xml 63 | coverage.xml 64 | *,cover 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Django stuff: 71 | *.log 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | v20150912 dev 4 | ------------- 5 | - Bugs Fix 6 | - Remove bundled pyotp library, use pyotp from PyPi(version>=2.0.0). 7 | - Two Factor Authentication Login Experience Optimization 8 | - Management Office Draft and Not Permitted Reply Statistics 9 | 10 | v20150731 dev 11 | ------------- 12 | - Switch Markdown Rendering Library from Misaka to markdown with py-gfm extension 13 | 14 | v20150725 dev 15 | ------------- 16 | - Bugs Fix 17 | - New FurtherLand Used Time Header 18 | 19 | v20150724 dev 20 | ------------- 21 | - Bugs Fix 22 | 23 | v20150723 dev 24 | ------------- 25 | - Bugs Fix 26 | 27 | v20150722 dev 28 | ------------- 29 | - Removed useless console logs. 30 | 31 | v20150721 dev 32 | ------------- 33 | - Bugs Fix 34 | - Polymer Framework Removed 35 | - New Management Office without Polymer 36 | - Remove GitHub OAuth Configuration from Library Sample 37 | - Summer Night Firefox and Safari browser Compatibility 38 | 39 | v20150719 dev 40 | ------------- 41 | - New Theme Summer Night Released. 42 | 43 | v20150716 dev 44 | ------------- 45 | - All Internal Areas has moved to New Terminal Service(A new, united API handler). 46 | 47 | v20150715 dev 48 | ------------- 49 | - Bugs Fix 50 | - Management Office Security Enhancement 51 | - Mako Template has been removed since it is not more flexible than default tornado template but causes more problems. 52 | 53 | v20150713 dev 54 | ------------- 55 | - Bugs Fix 56 | - Reply Area GitHub OAuth System removed, because it is not a graceful enough way to stop spammers. 57 | 58 | v20150712 dev 59 | ------------- 60 | - Bugs Fix 61 | - New Checkin Office User Interface 62 | - Changed Password Encryption Algorithm to Blowfish Encrypt Algorithm 63 | 64 | v20150711 dev 65 | ------------- 66 | - Bugs Fix 67 | - New Main Office with full pjax technology. 68 | - Checkin Progress Optimization 69 | - Rising Progress Optimization 70 | - Database Action Counts Removed 71 | 72 | v20150707 dev 73 | ------------- 74 | - Removed Config Preload and FurtherLand SelfKill function because these are not stable enough. 75 | 76 | v20150704 dev 77 | ------------- 78 | - Bugs Fix 79 | - Virtualenv has been removed from Requirements List, please use venv Python Standard Library as instead. 80 | - Formally Start to use Polymer Framework 81 | 82 | v20150629 dev 83 | ------------- 84 | - More Graceful FurtherLand Rising Method 85 | - Polymer Framework Added 86 | 87 | v20150531 dev 88 | ------------- 89 | - Stop Using Static File Content Delivery Network to Prevent Connection Problems 90 | 91 | v20150529 dev 92 | ------------- 93 | - Bugs Fix 94 | - Stop Using Asyncio Event Loop Because it is slower than Tornado IO Loop 95 | - Reply Area GitHub OAuth System Works 96 | - Atom RSS Feed System Works 97 | 98 | v20150502 dev 99 | ------------- 100 | - Bugs Fix 101 | - Stop Support for pypy3(Because it is slower than normal Python3 on Tornado) and update minimal Python version to 3.3 102 | - Master Information Preload Function 103 | - Public URL function Removed 104 | - Writings and Pages Erase Function 105 | - Fixed a serious Spelling Mistake 106 | 107 | v20150501 dev 108 | ------------- 109 | - Bugs Fix 110 | - Performance Optimization 111 | - Config Preload Function 112 | - Template Preload Function 113 | - Footer Used Time and DB Actions Function 114 | 115 | v20150430 dev 116 | ------------- 117 | - Bugs Fix 118 | - Reply Edit Function Works 119 | 120 | v20150429 dev 121 | ------------- 122 | - Reply Management Pages has more details about the Replies 123 | - Disabled Mako Module Folder to Prevent 500 Errors in C10K Test 124 | 125 | v20150424 dev 126 | ------------- 127 | - Anti Base64 and Javascript Protocol XSS Function 128 | - Use Asyncio Event Loop if Possible 129 | 130 | v20150422 dev 131 | ------------- 132 | - Safe Land Option(HSTS, Secure Attributed Cookies, etc.) 133 | - Starts to Use Httponly Cookies 134 | 135 | v20150420 dev 136 | ------------- 137 | - Bugs Fix 138 | - Moved Template Files to Factory Folder 139 | - NotFoundHandler Renamed to LostAndFoundPlace 140 | - SpiritPlace Removed 141 | - robots.txt Added 142 | 143 | v20150419 dev 144 | ------------- 145 | - Bugs Fix 146 | - Management Office Working Page Preview Supports Code Highlights 147 | - Replies Supports Code Highlights 148 | - Management Office Replies Supports Code Highlights 149 | - Site Favicon Supports 150 | - Switch Content Delivery Network from Microsoft to CloudFlare 151 | - 404 Page Message Updates 152 | 153 | v20150418 dev 154 | ------------- 155 | - Bugs Fix 156 | - Supports for Analytical Code(Such as Google Analytics) 157 | - Management Office Reply Details 158 | - Custom Error Pages 159 | 160 | v20150417 dev 161 | ------------- 162 | - Bugs Fix 163 | - Supports for Public Library(Database Without Authentication) 164 | - Melody Options Updated 165 | 166 | v20150414 dev 167 | ------------- 168 | - Bugs Fix 169 | 170 | v20150413 dev 171 | ------------- 172 | - Bugs Fix 173 | - Working Management Function Works 174 | - Reply Management Function Works 175 | - Pages Funciton Works 176 | - Configuration Modify Function Works 177 | - Code Highlights Works 178 | 179 | v20150412 dev 180 | ------------- 181 | - Bugs Fix 182 | - Public Area (File Upload) Works 183 | - Working Submit Validation Works 184 | 185 | v20150411 dev 186 | ------------- 187 | - Bugs Fix 188 | - Working Preview Function Works 189 | 190 | v20150410 dev 191 | ------------- 192 | - Bugs Fix 193 | - New Management Office Reaction Header 194 | - New Management Lobby Office Page UI 195 | 196 | v20150409 dev 197 | ------------- 198 | - Bugs Fix 199 | - New Reply Style 200 | - Reply GitHub Flavored Markdown Support 201 | - New Checkin Page UI 202 | 203 | v20150408 dev 204 | ------------- 205 | - Bugs Fix 206 | - Gravatar Cache Function Works 207 | - Reply Function Works 208 | 209 | v20150407 dev 210 | ------------- 211 | - Bugs Fix 212 | - New UI 213 | 214 | v20150406 dev 215 | ------------- 216 | - Bugs Fix 217 | - Misaka GitHub Flavored Markdown works 218 | 219 | v20150404 dev 220 | ------------- 221 | - Bugs Fix 222 | - Removed jQuery file and Starts to Use Microsoft Ajax Content Delivery Network 223 | - Index Page Framework Finished 224 | 225 | v20150403 dev 226 | ------------- 227 | - Bugs Fix 228 | - Publish Function Works 229 | - CRDA Page Added 230 | 231 | v20150402 dev 232 | ------------- 233 | - Slug Feature Added 234 | - Working Page JavaScript Post Function Completed 235 | 236 | v20150401 dev 237 | ------------- 238 | - Management Enhance 239 | - Changed Template System to Mako 240 | - Completed Apache License Comments 241 | - Bugs Fix 242 | - Font Family Update 243 | - Footer Added 244 | 245 | v20150331 dev 246 | ------------- 247 | - Bugs Fix 248 | - Checkout function works 249 | 250 | v20150330 dev 251 | ------------- 252 | - Project initialized. 253 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FurtherLand 2 | A Blog Platform, written in Python, using Tornado and MongoDB, with fully asynchronous support. 3 | 4 | Important 5 | --------- 6 | FurtherLand is in development, DO NOT USE it in production environment unless you know what you are doing. 7 | 8 | Requirements 9 | ------------ 10 | - Python>=3.3(FurtherLand will not work under Python2) 11 | - Tornado>=4.0 12 | - Motor>=0.4 13 | - Feedgen>=0.3.1 14 | - Pykismet3>=0.1.1 15 | - bcrypt>=2.0.0 16 | - markdown>=2.6.2 17 | - py-gfm>=0.1.1 18 | - pyotp>=2.0.0 19 | 20 | All listed Requirements(Except Python3.3) are able to be installed by pip. 21 | 22 | License 23 | ------- 24 | Copyright 2015 Futur Solo 25 | 26 | Licensed under the Apache License, Version 2.0 (the "License"); 27 | you may not use this file except in compliance with the License. 28 | You may obtain a copy of the License at 29 | 30 | http://www.apache.org/licenses/LICENSE-2.0 31 | 32 | Unless required by applicable law or agreed to in writing, software 33 | distributed under the License is distributed on an "AS IS" BASIS, 34 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35 | See the License for the specific language governing permissions and 36 | limitations under the License. 37 | -------------------------------------------------------------------------------- /blessing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Futur Solo 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | import melody 20 | import foundation 21 | 22 | 23 | # Build FurtherLand 24 | furtherland = foundation.FurtherLand(melody) 25 | 26 | # Rise FurtherLand 27 | furtherland.rise() 28 | -------------------------------------------------------------------------------- /creater.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Futur Solo 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | try: 19 | import sys 20 | if sys.version_info.major < 3: 21 | raise 22 | if sys.version_info.minor < 3: 23 | raise 24 | except: 25 | print("FurtherLand needs as least Python3.3 or higher.") 26 | print("Please upgrade your Python version.") 27 | exit(1) 28 | 29 | try: 30 | import tornado 31 | except: 32 | print("Please install tornado Firstly") 33 | exit(1) 34 | 35 | try: 36 | import motor 37 | except: 38 | print("Please install motor Firstly") 39 | exit(1) 40 | 41 | try: 42 | import misaka 43 | except: 44 | print("Please install misaka Firstly") 45 | exit(1) 46 | 47 | try: 48 | import mako 49 | except: 50 | print("Please install mako Firstly") 51 | exit(1) 52 | 53 | try: 54 | import pycurl 55 | except: 56 | print("Please install pycurl Firstly") 57 | exit(1) 58 | 59 | try: 60 | import feedgen 61 | except: 62 | print("Please install feedgen Firstly") 63 | exit(1) 64 | 65 | try: 66 | from . import melody 67 | secret = melody.secret 68 | base = melody.base 69 | safeland = melody.safeland 70 | dev = melody.dev 71 | listen_ip = melody.listen_ip 72 | listen_port = melody.listen_port 73 | library = melody.library 74 | import pymongo 75 | 76 | except: 77 | print("You should Configure melody.py correctly firstly.") 78 | exit(1) 79 | -------------------------------------------------------------------------------- /factory/management/checkin.htm: -------------------------------------------------------------------------------- 1 | {% comment Copyright 2015 Futur Solo 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | %} 15 | 16 | 17 | 18 | 19 | 登录 - {{ config["office_name"] }} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 |
37 |
欢迎回来,为什么不快乐地登录呢?
38 |
39 |
40 |
41 |
42 |
{% if checkin_status != "ok" %} 43 |
44 | error 45 | {% if checkin_status == "password" %}登录失败,请检查登录信息后重试。{% elif checkin_status == "two" %}登录失败,两步验证代码无效。{% else %}现在遇到了未知错误,请稍候再试。{% end %} 46 |
{% end %} 47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 |
55 |
56 | 57 | 58 |
59 |
60 | 64 |
65 |
66 | 69 |
70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | {% module xsrf_form_html() %} 78 |
79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /factory/management/error.htm: -------------------------------------------------------------------------------- 1 | {% comment Copyright 2015 Futur Solo 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | %} 15 | 16 | 17 | 18 | Server Error Occurred! 19 | 38 | 39 | 40 |
41 |
It Seems that A server Error has occurred!
42 |
{{ status_code }} : {{ error_message }}
43 |
Go to GitHub and create a new issue =>
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /factory/management/office.htm: -------------------------------------------------------------------------------- 1 | {% comment Copyright 2015 Futur Solo 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | %} 15 | 16 | 17 | 18 | 19 | {{ config["office_name"] }} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 45 | 46 | 47 |
48 |
49 |
50 |
51 | 抱歉,发生了未知错误。重新加载 52 |
53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 |
继续
61 |
取消
62 |
63 |
64 |
65 | 66 |
67 |
68 |
公共区域
69 |
70 |
现在上传
71 |
从已存在的文件中选择
72 |
73 | 74 |
75 |
76 |
77 |
78 |
正在上传...
79 |
80 |
81 |
82 |
83 |
84 |
85 |
将文件拖动到这里来上传 或
86 |
87 | 88 |
89 | 90 |
91 |
92 |
93 |
94 |
95 | 96 | 97 | 98 |
99 |
100 |
101 | 102 | 103 | 104 |
105 |
106 |
107 | {{ config["office_name"] }} 108 |
109 | 113 |
114 |
115 |
116 | {{ config["office_name"] }} 117 | 125 |
126 |
127 |
128 |
129 |
130 |
文章
131 |
0
132 | 133 |
134 |
其中有0篇草稿
135 |
136 | 137 |
138 | 管理文章keyboard_arrow_right 139 |
140 |
141 | 142 |
143 |
页面
144 |
0
145 | 146 |
147 |
其中有0篇草稿
148 |
149 | 150 |
151 | 管理页面keyboard_arrow_right 152 |
153 |
154 | 155 |
156 |
评论
157 |
0
158 | 159 |
160 |
其中有0条在等待审核
161 |
162 | 163 |
164 | 管理评论keyboard_arrow_right 165 |
166 |
167 |
168 |
169 | 172 |
173 |
174 | 175 |
176 |
177 |
178 | 179 | 180 |
181 |
182 | 183 |
184 |
创作
185 | 186 |
187 | 188 |
189 |
预览
190 |
191 |
192 | 193 |
194 |
195 |
196 | clear 197 |
198 | 作品信息 199 |
200 | 201 |
202 |
作品类型:
203 | 207 | 211 |
212 | 213 |
214 |
215 | 216 | 217 | 链接形式:[\-a-zA-Z0-9]* ! 218 |
219 |
220 |
221 | 222 |
223 |
224 | 225 | 226 |
227 | 231 |
232 | 233 |
234 |
235 | 立即发表 236 |
237 |
238 | 存为草稿 239 |
240 |
241 | 242 |
243 |
创作提示
244 |
245 |
FurtherLand支持GitHub Flavored Markdown以及绝大部分HTML5代码,请尽情使用。
246 |
247 |
248 |
249 | 250 | 251 | 252 |
253 |
254 | 255 |
256 |
257 | attachment 258 |
259 |
260 | publish 261 |
262 |
263 | 264 |
265 |
266 | 作品已经存为草稿。 267 |
268 |
269 | 作品已经发布。立即查看 270 |
271 |
272 | 请将信息填写完整后再试。 273 |
274 |
275 | 抱歉,发生了未知错误。再次尝试 276 |
277 |
278 |
279 | 280 |
281 |
282 |
283 |
文章
284 |
页面
285 |
评论
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
编辑评论
296 |
297 |
298 |
299 | 300 | 301 |
302 |
303 |
304 | 308 |
309 |
310 |
311 |
312 | 313 | 314 |
315 |
316 |
317 | 318 |
319 |
320 |
取消
321 |
保存
322 |
323 |
324 |
325 |
326 |
327 |
328 | 操作已经成功执行。 329 |
330 |
331 | 抱歉,发生了未知错误。请稍后再试。 332 |
333 |
334 |
335 | 336 |
337 |
338 |
339 |
340 |
341 | 342 | 343 |
344 |
345 |
346 |
347 | 348 | 349 |
350 |
描述一般会显示在名称的下方,一个好的描述能够帮助人们更好的理解名称。
351 |
352 |
353 |
354 | 355 | 356 |
357 |
标签将会帮助搜索引擎进行归类,以便带来正确的访客。
358 |
如果你需要键入多个标签,请使用英文逗号分割它们。
359 |
360 |
361 |
362 | 363 | 364 |
365 |
这将成为「FurtherLand」对统一资源定位器进行格式化的时候使用的开头。
366 |
367 |
368 |
369 | 370 | 371 |
372 |
请填写一个已经被上传的主题的目录名称。
373 |
374 |
375 | 376 |
追踪代码将会用于追踪并了解你的访客群体,以帮助你提高内容的质量。
377 |
378 |
379 |
复原
380 |
保存
381 |
382 |
383 |
384 |
385 |
386 | 设置保存成功,但是你可能需要刷新页面才能看到改变。立即刷新 387 |
388 |
389 | 抱歉,发生了未知错误。请稍后再试。 390 |
391 |
392 |
393 |
394 |
395 | 396 | 397 | 402 | 403 | 404 | -------------------------------------------------------------------------------- /factory/nutrition/summernight/model.htm: -------------------------------------------------------------------------------- 1 | {% comment Copyright 2015 Futur Solo 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | %} 15 | 16 | 17 | 18 | 19 | {{ page_title }} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 54 | 55 | 56 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | 75 |
76 |
主页
77 |
关于
78 |
79 |
80 |
81 |
{% if slug == "index" %}{% for value in contents.values() %} 82 |
83 | 84 |
85 |
86 |
{{ value["author"]["username"] }}
87 |
{{ value["time"] }}
88 |
89 |
{{ value["content"] }}
90 | 91 |
92 | {% end %}{% end %} 93 |
94 |
95 |
96 |
{% if slug == "writing" %}{{ content["title"] }}{% end %}
97 |
98 |
99 |
{% if slug == "writing" %}{{ content["author"]["username"] }}{% end %}
100 |
{% if slug == "writing" %}{{ content["time"] }}{% end %}
101 |
102 |
{% if slug == "writing" %}{{ content["content"] }}{% end %}
103 |
104 |
105 |
106 |
107 |
108 |
{% if slug == "page" %}{{ content["title"] }}{% end %}
109 |
110 |
111 |
{% if slug == "page" %}{{ content["author"]["username"] }}{% end %}
112 |
{% if slug == "page" %}{{ content["time"] }}{% end %}
113 |
114 |
{% if slug == "page" %}{{ content["content"] }}{% end %}
115 |
116 |
117 |
118 |
119 |
这个页面好像已经消失在时间的夹缝里了Poi! (*>д<)o″))
120 |
このページやファイルはもう飛んじゃったぽい! ╮(╯-╰)╭
121 |
The page you requested was not found on this server!(~ ̄▽ ̄)ノ
122 |
123 |
124 |
125 | 135 |
136 | 137 | 154 | {{ config["trace_code"] }} 155 | 156 | 157 | -------------------------------------------------------------------------------- /foundation/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Futur Solo 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | from tornado.web import * 20 | import tornado.ioloop 21 | import tornado.process 22 | import tornado.netutil 23 | import tornado.httpserver 24 | import os 25 | 26 | from . import place 27 | from . import office 28 | from . import memory as historial 29 | 30 | 31 | navigation = [ 32 | (r"/", place.CentralSquare), 33 | # (r"/classes/(.*).htm", ClassesPlace), This will be avaliable in future 34 | # (r"/timeline", HistoryLibrary), 35 | (r"/feed.xml", place.NewsAnnouncement), 36 | (r"/api", place.TerminalService), 37 | (r"/avatar/(.*)", place.IllustratePlace), 38 | (r"/writings/(.*).htm", place.ConferenceHall), 39 | (r"/pages/(.*).htm", place.MemorialWall), 40 | 41 | # Office Redirects 42 | (r"/management/checkin/", RedirectHandler, {"url": "/management/checkin"}), 43 | (r"/management/checkout/", RedirectHandler, 44 | {"url": "/management/checkout"}), 45 | (r"/management", RedirectHandler, {"url": "/management/lobby"}), 46 | (r"/management/", RedirectHandler, {"url": "/management/lobby"}), 47 | (r"/management/lobby/", RedirectHandler, {"url": "/management/lobby"}), 48 | (r"/management/working", RedirectHandler, 49 | {"url": "/management/working/new"}), 50 | (r"/management/working/", RedirectHandler, 51 | {"url": "/management/working/new"}), 52 | (r"/management/crda", RedirectHandler, 53 | {"url": "/management/crda/writings"}), 54 | (r"/management/crda/", RedirectHandler, 55 | {"url": "/management/crda/writings"}), 56 | (r"/management/configuration/", RedirectHandler, 57 | {"url": "/management/configuration"}), 58 | 59 | (r"/management/checkin", office.CheckinOffice), 60 | (r"/management/checkout", office.CheckoutOffice), 61 | (r"/management/api", office.ActionOffice), 62 | (r"/management/(.*)/(.*)", office.MainOffice), 63 | (r"/management/(.*)", office.MainOffice), 64 | 65 | (r"(.*)", place.LostAndFoundPlace) 66 | ] 67 | 68 | 69 | class FurtherLand: 70 | def __init__(self, melody): 71 | import os 72 | self.identity = os.getpid() 73 | self.melody = melody 74 | # Build A Port 75 | self.port = tornado.netutil.bind_sockets( 76 | melody.listen_port, address=melody.listen_ip) 77 | self.stage = Application( 78 | handlers=navigation, 79 | 80 | cookie_secret=melody.secret, 81 | xsrf_cookies=True, 82 | root_path=os.path.split(os.path.realpath(melody.base))[0], 83 | static_path=os.path.join( 84 | os.path.split(os.path.realpath(melody.base))[0], "spirit"), 85 | template_path=os.path.join( 86 | os.path.split(os.path.realpath(melody.base))[0], "factory"), 87 | 88 | login_url="/management/checkin", 89 | 90 | historial_records=historial.Records(melody.library), 91 | 92 | autoescape=None, 93 | debug=melody.dev, 94 | static_url_prefix="/spirit/", 95 | further_land=self, 96 | safe_land=melody.safeland 97 | ) 98 | try: 99 | # Build Multi Land Enterance 100 | tornado.process.fork_processes( 101 | tornado.process.cpu_count() * 2, max_restarts=100) 102 | except: 103 | pass 104 | 105 | def rise(self): 106 | try: 107 | print("FurtherLand has been risen on %s:%d." % ( 108 | self.melody.listen_ip, self.melody.listen_port)) 109 | import tornado.ioloop 110 | self.land = tornado.httpserver.HTTPServer(self.stage) 111 | self.land.add_sockets(self.port) 112 | tornado.ioloop.IOLoop.current().start() 113 | except: 114 | self.set() 115 | 116 | def set(self): 117 | tornado.ioloop.IOLoop.current().stop() 118 | print("FurtherLand set.") 119 | 120 | def version(self): 121 | return "FurtherLand Sakihokori Edition" 122 | -------------------------------------------------------------------------------- /foundation/memory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Futur Solo 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | from tornado.gen import * 20 | from collections import OrderedDict 21 | import motor 22 | import tornado.web 23 | 24 | 25 | class Element: 26 | def __init__(self, collection, dict_key): 27 | self._current_collection = collection 28 | self._dict_key = dict_key 29 | self.reset() 30 | 31 | def find(self, condition={}, ignore=None): 32 | self.reset() 33 | find = [] 34 | find.append(condition) 35 | if ignore: 36 | ignore_condition = {} 37 | for item in ignore: 38 | ignore_condition[item] = 0 39 | find.append(ignore_condition) 40 | self._action = self._current_collection.find(*find) 41 | self._action_ready = True 42 | self._use_cursor = True 43 | self._allow_filter = True 44 | self._length = 1 45 | return self 46 | 47 | def erase(self, condition): 48 | self.reset() 49 | self._action = self._current_collection.remove(condition) 50 | self._action_ready = True 51 | return self 52 | 53 | def add(self, content): 54 | self.reset() 55 | self._action = self._current_collection.insert(content) 56 | self._action_ready = True 57 | return self 58 | 59 | def set(self, condition, content): 60 | self.reset() 61 | self._action = self._current_collection.update(condition, 62 | {"$set": content}) 63 | self._action_ready = True 64 | return self 65 | 66 | def find_modify(self, condition, update, method="inc", new=True): 67 | self.reset() 68 | if method == "inc": 69 | update_condition = {} 70 | for item in update: 71 | update_condition[item] = 1 72 | update = {"$inc": update_condition} 73 | elif method == "disinc": 74 | update_condition = {} 75 | for item in update: 76 | update_condition[item] = -1 77 | update = {"$inc": update_condition} 78 | elif method == "set": 79 | update_condition = update 80 | update = {"$set": update_condition} 81 | self._action = self._current_collection.find_and_modify( 82 | query=condition, update=update, new=new) 83 | self._action_ready = True 84 | return self 85 | 86 | def count(self, do_find=False, condition={}, ignore=None): 87 | self.reset() 88 | if do_find: 89 | self.find(condition=condition, ignore=ignore) 90 | self._action = self._action.count() 91 | self._use_cursor = False 92 | else: 93 | self._action = self._current_collection.count() 94 | self._action_ready = True 95 | return self 96 | 97 | def skip(self, skip=0): 98 | if (not (hasattr(self, "_skiped") and 99 | self._skiped)) and self._allow_filter: 100 | self._action.skip(skip) 101 | self._skiped = True 102 | else: 103 | raise Exception 104 | return self 105 | 106 | def length(self, length=1, force_dict=False): 107 | if (not (hasattr(self, "_lengthed") and 108 | self._lengthed)) and self._allow_filter: 109 | self._length = length 110 | self._force_dict = force_dict 111 | self._lengthed = True 112 | else: 113 | raise Exception 114 | return self 115 | 116 | def sort(self, sort): 117 | if (not (hasattr(self, "_sorted") and 118 | self._sorted)) and self._allow_filter: 119 | sort_condition = [] 120 | for item in sort: 121 | if item[1]: 122 | sort_condition.append((item[0], 1)) 123 | else: 124 | sort_condition.append((item[0], -1)) 125 | self._action.sort(sort_condition) 126 | self._sorted = True 127 | else: 128 | raise Exception 129 | return self 130 | 131 | @coroutine 132 | def do(self): 133 | if (not self._result_ready) and self._action_ready: 134 | if self._use_cursor: 135 | if self._length != 0: 136 | self._action.limit(self._length) 137 | self._result = OrderedDict() 138 | while (yield self._action.fetch_next): 139 | item = self._action.next_object() 140 | self._result[item[self._dict_key]] = item 141 | if (len(self._result) != 0 and 142 | self._length == 1 and (not self._force_dict)): 143 | self._result = self._result[list(self._result.keys())[0]] 144 | else: 145 | self._result = yield self._action 146 | self._result_ready = True 147 | self._action_ready = False 148 | self._allow_filter = False 149 | else: 150 | raise Exception 151 | return self 152 | 153 | def result(self): 154 | if self._result_ready: 155 | self._result_ready = False 156 | return self._result 157 | else: 158 | raise Exception 159 | 160 | def reset(self): 161 | self._length = 1 162 | self._use_cursor = False 163 | self._allow_filter = False 164 | self._skiped = False 165 | self._lengthed = False 166 | self._sorted = False 167 | self._result_ready = False 168 | self._action_ready = False 169 | self._force_dict = False 170 | return self 171 | 172 | 173 | class Records: 174 | def __init__(self, library): 175 | self.library = library 176 | self._initialized = False 177 | 178 | def initialize(self): 179 | if self._initialized: 180 | return 181 | credentials = "" 182 | if self.library["auth"]: 183 | credentials = ( 184 | self.library["user"] + ":" + self.library["passwd"] + "@") 185 | self.connection = motor.MotorClient( 186 | "mongodb://" + credentials + self.library["host"] + ":" + 187 | str(self.library["port"]) + 188 | "/" + self.library["database"]) 189 | self.database = self.connection[ 190 | self.library["database"]] 191 | self._initialized = True 192 | 193 | def select(self, collection): 194 | if not self._initialized: 195 | raise tornado.web.HTTPError(500) 196 | _current_collection = self.database[ 197 | self.library["prefix"] + collection] 198 | return Element(collection=_current_collection, dict_key="_id") 199 | 200 | def __del__(self): 201 | if self._initialized: 202 | self.connection.disconnect() 203 | del self.connection 204 | -------------------------------------------------------------------------------- /foundation/office.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Futur Solo 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | from tornado.web import * 20 | from tornado.gen import * 21 | from foundation.place import PlacesOfInterest, slug_validation, visitor_only 22 | from collections import OrderedDict 23 | import pyotp 24 | import json 25 | import bcrypt 26 | import datetime 27 | import time 28 | 29 | 30 | class ManagementOffice(PlacesOfInterest): 31 | def management_url(self, path, include_host=None, **kwargs): 32 | path = "management/" + path 33 | return RequestHandler.static_url( 34 | self, path, include_host=include_host, **kwargs) 35 | 36 | def management_render(self, page): 37 | self.render_list["management_url"] = self.management_url 38 | page = "management/" + page 39 | self.render(page, nutrition=False) 40 | 41 | 42 | class CheckinOffice(ManagementOffice): 43 | @coroutine 44 | @visitor_only 45 | def get(self): 46 | self.render_list["checkin_status"] = self.get_scookie( 47 | "checkin_status", arg_type="hash", default="ok") 48 | self.clear_cookie("checkin_status") 49 | self.management_render("checkin.htm") 50 | 51 | @coroutine 52 | @visitor_only 53 | def post(self): 54 | username = self.get_arg("username", arg_type="username") 55 | password = self.get_arg("password", arg_type="hash") 56 | two = self.get_arg("two", arg_type="number") 57 | remember = self.get_arg("remember", arg_type="boolean") 58 | 59 | if not (username and password and two): 60 | self.set_scookie("checkin_status", "password", expires_days=None) 61 | self.redirect("/management/checkin") 62 | return 63 | 64 | user = yield self.get_user(username=username) 65 | 66 | """ 67 | generate new password by listed commands: 68 | password = "YOUR NEW PASSWORD" 69 | bcrypt.hashpw( 70 | hashlib.sha256(password.encode() 71 | ).hexdigest().encode(), bcrypt.gensalt() 72 | ) 73 | """ 74 | 75 | if not user: 76 | self.set_scookie("checkin_status", "password", expires_days=None) 77 | self.redirect("/management/checkin") 78 | return 79 | 80 | if bcrypt.hashpw( 81 | password.encode("utf-8"), 82 | user["password"].encode()) != user["password"].encode(): 83 | self.set_scookie("checkin_status", "password", expires_days=None) 84 | self.redirect("/management/checkin") 85 | return 86 | 87 | def verify_otp(key, two): 88 | totp = pyotp.TOTP(key) 89 | current_datetime = datetime.datetime.now() 90 | if totp.verify(two, for_time=current_datetime): 91 | return True 92 | early_datetime = current_datetime - datetime.timedelta(seconds=30) 93 | if totp.verify(two, for_time=early_datetime): 94 | return True 95 | later_datetime = current_datetime + datetime.timedelta(seconds=30) 96 | if totp.verify(two, for_time=later_datetime): 97 | return True 98 | return False 99 | 100 | if not verify_otp(user["otp_key"], two): 101 | self.set_scookie("checkin_status", "two", expires_days=None) 102 | self.redirect("/management/checkin") 103 | return 104 | 105 | else: 106 | expires_days = None 107 | if remember: 108 | expires_days = 180 109 | 110 | device_id = self.get_random(32) 111 | 112 | agent_auth = self.hash((device_id + user["password"]), "sha256") 113 | 114 | self.set_scookie("user_id", user["_id"], expires_days=expires_days, 115 | httponly=True) 116 | 117 | self.set_scookie("device_id", device_id, expires_days=expires_days, 118 | httponly=True) 119 | 120 | self.set_scookie("agent_auth", agent_auth, 121 | expires_days=expires_days, httponly=True) 122 | 123 | self.redirect(self.next_url) 124 | 125 | 126 | class CheckoutOffice(ManagementOffice): 127 | @authenticated 128 | def get(self): 129 | self.clear_cookie("user_id") 130 | self.clear_cookie("device_id") 131 | self.clear_cookie("agent_auth") 132 | self.redirect(self.next_url) 133 | 134 | 135 | class MainOffice(ManagementOffice): 136 | @coroutine 137 | @authenticated 138 | def get(self, slug, sub_slug=""): 139 | if not self.value_validation("hash", slug): 140 | raise HTTPError(404) 141 | if sub_slug and not self.value_validation("hash", sub_slug): 142 | raise HTTPError(404) 143 | self.render_list["slug"] = slug 144 | self.render_list["sub_slug"] = sub_slug 145 | self.management_render("office.htm") 146 | 147 | 148 | class ActionOffice(ManagementOffice): 149 | @coroutine 150 | @authenticated 151 | def post(self): 152 | action = self.get_arg("action", default=None, arg_type="link") 153 | if hasattr(self, action): 154 | yield getattr(self, action)() 155 | else: 156 | raise HTTPError(500) 157 | 158 | @coroutine 159 | def load_public(self): 160 | book = self.memories.select("Publics") 161 | book.find({"type": "file"}).sort([["time", False]]) 162 | book.length(0, force_dict=True) 163 | yield book.do() 164 | result = book.result() 165 | self.finish(json.dumps(list(result.values()))) 166 | 167 | @coroutine 168 | def save_public(self): 169 | public_path = os.path.join( 170 | os.path.join( 171 | self.settings["static_path"], "public"), "files") 172 | url_base = "/spirit/public/files" 173 | 174 | if self.request.files: 175 | for f in self.request.files["files[]"]: 176 | book = self.memories.select("Publics") 177 | current_time = int(time.time()) 178 | current_path = os.path.join(public_path, str( 179 | current_time)) 180 | current_url = os.path.join(url_base, str( 181 | current_time)) 182 | if not os.path.exists(current_path): 183 | os.makedirs(current_path) 184 | 185 | filename = f["filename"] 186 | current_file_path = os.path.join( 187 | current_path, filename) 188 | current_file_url = os.path.join( 189 | current_url, filename) 190 | 191 | with open(current_file_path, "wb") as file: 192 | file.write(f["body"]) 193 | 194 | file_info = OrderedDict() 195 | file_info["time"] = current_time 196 | file_info["type"] = "file" 197 | file_info["content_type"] = None 198 | file_info["filename"] = filename 199 | file_info["filepath"] = current_file_path 200 | file_info["fileurl"] = current_file_url 201 | file_info["email_md5"] = None 202 | file_info["_id"] = yield self.issue_id("Publics") 203 | book.add(file_info) 204 | yield book.do() 205 | else: 206 | raise HTTPError(500) 207 | self.finish(json.dumps({"status": True})) 208 | 209 | @coroutine 210 | def count(self): 211 | info = yield self.get_count() 212 | self.finish(json.dumps(info)) 213 | 214 | @coroutine 215 | def save_working(self): 216 | working_type = self.get_arg("working_type", arg_type="hash") 217 | if working_type == "writing": 218 | book = self.memories.select("Writings") 219 | elif working_type == "page": 220 | book = self.memories.select("Pages") 221 | else: 222 | raise HTTPError(500) 223 | 224 | working_method = self.get_arg("working_method", arg_type="hash") 225 | 226 | working_id = self.get_arg("working_id", arg_type="number") 227 | 228 | def make_working(): 229 | working = {} 230 | working["title"] = self.get_arg("working_title", arg_type="origin") 231 | working["content"] = self.get_arg("working_content", 232 | arg_type="origin") 233 | working["time"] = self.get_arg("working_time", arg_type="number") 234 | working["publish"] = self.get_arg("working_publish", 235 | arg_type="boolean") 236 | working["slug"] = self.get_arg("working_slug", arg_type="slug") 237 | working["author"] = self.current_user["_id"] 238 | if not working["slug"]: 239 | raise HTTPError(500) 240 | return working 241 | 242 | def check_slug(slug): 243 | book.find({"slug": slug}) 244 | yield book.do() 245 | slug_result = book.result() 246 | if slug_result and ( 247 | slug_result is not False and slug_result["_id"] != working_id): 248 | self.finish(json.dumps({"succeed": False, "reason": "slug"})) 249 | return False 250 | return True 251 | 252 | if working_method == "new": 253 | working = make_working() 254 | if not check_slug(working["slug"]): 255 | return 256 | if working_type == "writing": 257 | working_id = yield self.issue_id("Writings") 258 | elif working_type == "page": 259 | working_id = yield self.issue_id("Pages") 260 | else: 261 | raise HTTPError(500) 262 | working["_id"] = working_id 263 | book.add(working) 264 | elif working_method == "edit": 265 | working = make_working() 266 | if not check_slug(working["slug"]): 267 | return 268 | book.set({"_id": working_id}, working) 269 | elif working_method == "erase": 270 | @coroutine 271 | def erase_reply(working_id): 272 | book = self.memories.select("Replies") 273 | book.erase({"writing_id": working_id}) 274 | yield book.do() 275 | if working_type == "writing": 276 | yield erase_reply(working_id) 277 | book.erase({"_id": working_id}) 278 | else: 279 | raise HTTPError(500) 280 | yield book.do() 281 | self.finish(json.dumps({ 282 | "succeed": True, 283 | "id": working_id, 284 | })) 285 | 286 | @coroutine 287 | def load_working(self): 288 | working_type = self.get_arg("type", arg_type="hash") 289 | working_id = self.get_arg("id", arg_type="number") 290 | if working_type == "writing": 291 | book = self.memories.select("Writings") 292 | elif working_type == "page": 293 | book = self.memories.select("Pages") 294 | else: 295 | raise HTTPError(500) 296 | book.find({"_id": working_id}) 297 | yield book.do() 298 | working = book.result() 299 | self.finish(json.dumps(working)) 300 | 301 | @coroutine 302 | def load_crda(self): 303 | type = self.get_arg("type", arg_type="hash") 304 | 305 | if type == "writings": 306 | book = self.memories.select("Writings") 307 | book.find({}, ["content"]) 308 | elif type == "pages": 309 | book = self.memories.select("Pages") 310 | book.find({}, ["content"]) 311 | elif type == "replies": 312 | book = self.memories.select("Replies") 313 | book.find({}) 314 | writing_list = [] 315 | else: 316 | raise HTTPError(500) 317 | 318 | book.sort([["time", False]]) 319 | book.length(0, True) 320 | yield book.do() 321 | content_list = book.result() 322 | 323 | if type == "replies": 324 | for key in content_list: 325 | content_list[key]["_id"] = int( 326 | content_list[key]["_id"]) 327 | if content_list[key]["writing_id"] not in writing_list: 328 | writing_list.append(content_list[key]["writing_id"]) 329 | 330 | writing_list = yield self.get_writing(writing_list=writing_list) 331 | for key in content_list: 332 | if content_list[key]["writing_id"] not in writing_list.keys(): 333 | del content_list[key] 334 | continue 335 | content_list[key]["writing"] = writing_list[ 336 | content_list[key]["writing_id"]] 337 | 338 | self.finish(json.dumps(list(content_list.values()))) 339 | 340 | @coroutine 341 | def save_reply(self): 342 | reply_id = self.get_arg("reply", arg_type="number") 343 | reply_method = self.get_arg("method", arg_type="hash") 344 | book = self.memories.select("Replies") 345 | 346 | if reply_method == "permit": 347 | permit = self.get_arg("permit", arg_type="boolean") 348 | if permit is None: 349 | raise HTTPError(500) 350 | 351 | book.find({"_id": reply_id}) 352 | yield book.do() 353 | reply = book.result() 354 | if not reply: 355 | raise HTTPError(500) 356 | 357 | book.set({"_id": reply_id}, {"permit": permit}) 358 | yield book.do() 359 | self.finish(json.dumps({"status": True})) 360 | 361 | elif reply_method == "erase": 362 | book.erase({"_id": reply_id}) 363 | yield book.do() 364 | self.finish(json.dumps({"status": True})) 365 | 366 | elif reply_method == "edit": 367 | reply_name = self.get_arg("name", arg_type="origin") 368 | reply_homepage = self.get_arg("homepage", arg_type="origin") 369 | reply_email = self.get_arg("email", arg_type="mail_address") 370 | reply_content = self.get_arg("content", arg_type="origin") 371 | if not (reply_id and reply_name and reply_homepage and 372 | reply_email and reply_content): 373 | raise HTTPError(500) 374 | reply = {} 375 | reply["name"] = reply_name 376 | reply["homepage"] = reply_homepage 377 | reply["email"] = reply_email 378 | reply["content"] = reply_content 379 | book.set({"_id": reply_id}, reply) 380 | yield book.do() 381 | self.finish(json.dumps({"status": True})) 382 | 383 | @coroutine 384 | def load_configuration(self): 385 | book = self.memories.select("Configs") 386 | book.find({}) 387 | book.length(0, True) 388 | yield book.do() 389 | configs = book.result() 390 | self.finish(json.dumps(configs)) 391 | 392 | @coroutine 393 | def save_configuration(self): 394 | post_config = OrderedDict() 395 | post_config["site_name"] = self.get_arg("site_name", arg_type="origin") 396 | post_config["site_description"] = self.get_arg( 397 | "site_description", arg_type="origin") 398 | post_config["site_keywords"] = self.get_arg( 399 | "site_keywords", arg_type="origin") 400 | post_config["site_url"] = self.get_arg("site_url", arg_type="link") 401 | post_config["nutrition_type"] = self.get_arg( 402 | "nutrition_type", arg_type="hash") 403 | post_config["trace_code"] = self.get_arg( 404 | "trace_code", arg_type="origin") 405 | for key in post_config: 406 | if not post_config[key]: 407 | raise HTTPError(500) 408 | book = self.memories.select("Configs") 409 | book.find({}).length(0, force_dict=True) 410 | yield book.do() 411 | origin_config = book.result() 412 | for key in post_config: 413 | if origin_config[key] != post_config[key]: 414 | book.set({"_id": key}, {"value": post_config[key]}) 415 | yield book.do() 416 | self.finish(json.dumps({"status": True})) 417 | -------------------------------------------------------------------------------- /foundation/place.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Futur Solo 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | from tornado.web import * 20 | from tornado.gen import * 21 | from tornado.escape import * 22 | import tornado.httpclient 23 | from collections import OrderedDict 24 | import json 25 | import os 26 | import re 27 | import markdown 28 | import hashlib 29 | import random 30 | import string 31 | import functools 32 | import time 33 | import datetime 34 | import feedgen.feed 35 | 36 | 37 | def decorator_with_args(decorator_to_enhance): 38 | def decorator_maker(*args, **kwargs): 39 | def decorator_wrapper(func): 40 | return decorator_to_enhance(func, *args, **kwargs) 41 | return decorator_wrapper 42 | return decorator_maker 43 | 44 | 45 | @decorator_with_args 46 | def slug_validation(func, *args, **kwargs): 47 | @functools.wraps(func) 48 | def wrapper(self, *func_args, **func_kwargs): 49 | valid_list = args[0] 50 | new_slug = [] 51 | for number in range(0, len(valid_list)): 52 | value = self.value_validation( 53 | valid_list[number], func_args[number]) 54 | if value is not False: 55 | new_slug.append(value) 56 | else: 57 | raise HTTPError(404) 58 | return func(self, *new_slug, **func_kwargs) 59 | return wrapper 60 | 61 | 62 | def visitor_only(func): 63 | @functools.wraps(func) 64 | def wrapper(self, *args, **kwargs): 65 | if self.current_user: 66 | self.redirect(self.next_url) 67 | return 68 | return func(self, *args, **kwargs) 69 | return wrapper 70 | 71 | 72 | class PlacesOfInterest(RequestHandler): 73 | current_user = None 74 | 75 | @coroutine 76 | def prepare(self): 77 | self.start_time = time.time() 78 | self.furtherland = self.settings["further_land"] 79 | self.render_list = {} 80 | self.memories = self.settings["historial_records"] 81 | self.memories.initialize() 82 | self.current_user = yield self.get_current_user() 83 | self.config = yield self.get_config() 84 | 85 | self.next_url = self.get_arg("next", arg_type="link", default="/") 86 | self.remote_ip = self.request.headers.get( 87 | "X-Forwarded-For", self.request.headers.get( 88 | "X-Real-Ip", self.request.remote_ip)) 89 | self.using_ssl = (self.request.headers.get( 90 | "X-Scheme", "http") == "https") 91 | self.safe_land = self.settings["safe_land"] 92 | if self.safe_land: 93 | self.set_header("strict-transport-security", 94 | "max-age=39420000") 95 | 96 | @coroutine 97 | def get_config(self): 98 | if not hasattr(self, "_config"): 99 | book = self.memories.select("Configs") 100 | book.find().length(0) 101 | yield book.do() 102 | result = book.result() 103 | self._config = {} 104 | for value in result.values(): 105 | self._config[value["_id"]] = value["value"] 106 | return self._config 107 | 108 | @coroutine 109 | def get_current_user(self): 110 | if not hasattr(self, "_current_user"): 111 | user_id = self.get_scookie("user_id", arg_type="number") 112 | device_id = self.get_scookie("device_id", arg_type="hash") 113 | agent_auth = self.get_scookie("agent_auth", arg_type="hash") 114 | if not (user_id and device_id and agent_auth): 115 | self._current_user = None 116 | else: 117 | user = yield self.get_user(_id=user_id) 118 | if self.hash((device_id + user["password"]), 119 | "sha256") != agent_auth: 120 | self._current_user = None 121 | else: 122 | self._current_user = user 123 | return (self._current_user) 124 | 125 | def get_arg(self, arg, default=None, arg_type="origin"): 126 | result = RequestHandler.get_argument(self, arg, None) 127 | if isinstance(result, bytes): 128 | result = str(result.decode()) 129 | else: 130 | result = str(result) 131 | if (not result) or (result == "None"): 132 | return default 133 | return self.value_validation(arg_type, result) 134 | 135 | def get_scookie(self, arg, default=None, arg_type="origin"): 136 | result = RequestHandler.get_secure_cookie( 137 | self, arg, None, max_age_days=181) 138 | if isinstance(result, bytes): 139 | result = str(result.decode()) 140 | else: 141 | result = str(result) 142 | if (not result) or (result == "None"): 143 | return default 144 | return self.value_validation(arg_type, result) 145 | 146 | def set_scookie(self, arg, value="", expires_days=30, httponly=False): 147 | if not isinstance(value, str): 148 | value = str(value) 149 | if self.safe_land: 150 | secure = True 151 | else: 152 | secure = False 153 | RequestHandler.set_secure_cookie( 154 | self, arg, value, expires_days, 155 | httponly=httponly, secure=secure) 156 | 157 | def value_validation(self, arg_type, value): 158 | if arg_type == "origin": 159 | return value 160 | elif arg_type == "mail_address": 161 | mail_address = str(value) 162 | if re.match( 163 | r"^([\._+\-a-zA-Z0-9]+)@{1}([a-zA-Z0-9\-]+)\.([a-zA-Z0-9\-]+)$", 164 | mail_address) == None: 165 | return False 166 | else: 167 | return mail_address 168 | elif arg_type == "hash": 169 | hash_value = str(value) 170 | if re.match(r"^([a-zA-Z0-9]+)$", hash_value) == None: 171 | return False 172 | else: 173 | return hash_value 174 | elif arg_type == "slug": 175 | hash_value = str(value) 176 | if re.match(r"^([\-a-zA-Z0-9]+)$", hash_value) == None: 177 | return False 178 | else: 179 | return hash_value 180 | elif arg_type == "number": 181 | number = str(value) 182 | if re.match(r"^([\-\+0-9]+)$", number) == None: 183 | return False 184 | else: 185 | return int(number) 186 | elif arg_type == "boolean": 187 | boo = str(value).lower() 188 | if boo == "1" or boo == "true" or boo == "on": 189 | return True 190 | else: 191 | return False 192 | elif arg_type == "username": 193 | string = str(value) 194 | if re.match(r"^([ a-zA-Z]+)$", string) == None: 195 | return False 196 | else: 197 | return string 198 | elif arg_type == "link": 199 | link = str(value) 200 | if re.match(r"^(.*)$", link) == None: 201 | return False 202 | else: 203 | return link 204 | 205 | def hash(self, target, method): 206 | if not isinstance(target, bytes): 207 | target = target.encode(encoding="utf-8") 208 | 209 | if method == "sha1": 210 | return hashlib.sha1(target).hexdigest() 211 | elif method == "sha256": 212 | return hashlib.sha256(target).hexdigest() 213 | elif method == "md5": 214 | return hashlib.md5(target).hexdigest() 215 | 216 | @coroutine 217 | def get_user(self, with_privacy=True, **kwargs): 218 | condition = list(kwargs.keys())[0] 219 | value = kwargs[condition] 220 | if condition != "user_list": 221 | if not hasattr(self, "_master_list"): 222 | self._master_list = {} 223 | 224 | if condition not in self._master_list.keys(): 225 | self._master_list[condition] = {} 226 | 227 | if value not in self._master_list[condition].keys(): 228 | book = self.memories.select("Masters") 229 | book.find({condition: value}).length(1) 230 | yield book.do() 231 | self._master_list[condition][value] = book.result() 232 | 233 | user = {} 234 | user.update(self._master_list[condition][value]) 235 | 236 | if not with_privacy: 237 | del user["password"] 238 | del user["otp_key"] 239 | del user["email"] 240 | 241 | return user 242 | 243 | def get_random(self, length): 244 | return "".join(random.sample(string.ascii_letters + string.digits, 245 | length)) 246 | 247 | @coroutine 248 | def get_class(self): 249 | pass 250 | 251 | @coroutine 252 | def get_writing(self, only_published=True, **kwargs): 253 | book = self.memories.select("Writings") 254 | find_condition = {} 255 | if only_published is True: 256 | find_condition["publish"] = True 257 | if "class_id" in kwargs.keys(): 258 | if kwargs["class_id"] != 0: 259 | find_condition["class_id"] = kwargs["class_id"] 260 | book.find(find_condition) 261 | book.sort([["time", False]]) 262 | book.length(0, force_dict=True) 263 | elif "writing_list" in kwargs.keys(): 264 | find_condition["_id"] = {"$in": kwargs["writing_list"]} 265 | book.find(find_condition, ["content"]) 266 | book.sort([["time", False]]) 267 | book.length(0, force_dict=True) 268 | elif "slug" in kwargs.keys(): 269 | find_condition["slug"] = kwargs["slug"] 270 | book.find(find_condition) 271 | elif "id" in kwargs.keys(): 272 | find_condition["_id"] = kwargs["id"] 273 | book.find(find_condition) 274 | yield book.do() 275 | return book.result() 276 | 277 | @coroutine 278 | def get_page(self, only_published=True, **kwargs): 279 | book = self.memories.select("Pages") 280 | find_condition = {} 281 | if only_published is True: 282 | find_condition["publish"] = True 283 | if "class_id" in kwargs.keys(): 284 | if kwargs["class_id"] != 0: 285 | find_condition["class_id"] = kwargs["class_id"] 286 | book.find(find_condition) 287 | book.sort([["time", False]]) 288 | book.length(0, force_dict=True) 289 | elif "slug" in kwargs.keys(): 290 | find_condition["slug"] = kwargs["slug"] 291 | book.find(find_condition) 292 | elif "id" in kwargs.keys(): 293 | find_condition["_id"] = kwargs["id"] 294 | book.find(find_condition) 295 | yield book.do() 296 | return book.result() 297 | 298 | @coroutine 299 | def get_reply(self, only_permitted=True, with_privacy=False, **kwargs): 300 | book = self.memories.select("Replies") 301 | ignore = None 302 | if not with_privacy: 303 | ignore = ["email", "ip"] 304 | find_condition = {} 305 | if only_permitted is True: 306 | find_condition["permit"] = True 307 | if "writing_id" in kwargs.keys(): 308 | if kwargs["writing_id"] != 0: 309 | find_condition["writing_id"] = kwargs["writing_id"] 310 | book.find(find_condition, ignore) 311 | book.sort([["time", True]]) 312 | book.length(0, force_dict=True) 313 | elif "id" in kwargs.keys(): 314 | find_condition["_id"] = kwargs["id"] 315 | book.find(find_condition, ignore) 316 | yield book.do() 317 | return book.result() 318 | 319 | @coroutine 320 | def issue_id(self, working_type): 321 | book = self.memories.select("Counts") 322 | book.find_modify({"_id": working_type}, ["number"]) 323 | yield book.do() 324 | return int(book.result()["number"]) 325 | 326 | def make_md(self, content, more=True): 327 | if not more: 328 | content = content.split("")[0] 329 | return markdown.markdown(content, extensions=["gfm"]) 330 | 331 | def static_url(self, path, include_host=None, nutrition=True, **kwargs): 332 | if nutrition: 333 | path = "nutrition/" + self.config["nutrition_type"] + "/" + path 334 | return RequestHandler.static_url( 335 | self, path, include_host=include_host, **kwargs) 336 | 337 | def render(self, page, nutrition=True): 338 | if ("page_title" not in self.render_list.keys() and 339 | "origin_title" in self.render_list.keys()): 340 | self.render_list["page_title"] = ( 341 | self.render_list["origin_title"] + 342 | " - " + self.config["site_name"]) 343 | 344 | if not self.render_list.pop("__without_database", False): 345 | self.render_list["config"] = self.config 346 | self.render_list["FurtherLand"] = self.furtherland 347 | self.set_header("Furtherland-Used-Time", 348 | int((time.time() - self.start_time) * 1000)) 349 | 350 | self.xsrf_form_html() 351 | 352 | if nutrition: 353 | page = "nutrition/" + self.config["nutrition_type"] + "/" + page 354 | RequestHandler.render(self, page, **self.render_list) 355 | 356 | @coroutine 357 | def get_count(self): 358 | result = {} 359 | book = self.memories.select("Writings").count() 360 | yield book.do() 361 | result["writings"] = book.result() 362 | book.count(do_find=True, condition={"publish": False}) 363 | yield book.do() 364 | result["writings_draft"] = book.result() 365 | 366 | book = self.memories.select("Pages").count() 367 | yield book.do() 368 | result["pages"] = book.result() 369 | book.count(do_find=True, condition={"publish": False}) 370 | yield book.do() 371 | result["pages_draft"] = book.result() 372 | 373 | book = self.memories.select("Replies").count() 374 | yield book.do() 375 | result["replies"] = book.result() 376 | book.count(do_find=True, condition={"permit": False}) 377 | yield book.do() 378 | result["replies_waiting_permit"] = book.result() 379 | 380 | return result 381 | 382 | def escape(self, item, item_type="html"): 383 | if item_type == "html": 384 | return xhtml_escape(item) 385 | elif item_type == "url": 386 | return url_escape(item) 387 | else: 388 | raise HTTPError(500) 389 | 390 | def write_error(self, status_code, **kwargs): 391 | if status_code == 404: 392 | self.render_list["origin_title"] = "出错了!" 393 | self.render_list["slug"] = "not-found" 394 | self.render_list["sub_slug"] = "" 395 | self.render_list["current_content_id"] = 0 396 | self.render("model.htm") 397 | return 398 | if self.settings.get("serve_traceback") and "exc_info" in kwargs: 399 | self.set_header("Content-Type", "text/plain") 400 | for line in traceback.format_exception(*kwargs["exc_info"]): 401 | self.write(line) 402 | self.finish() 403 | else: 404 | self.render_list["status_code"] = status_code 405 | self.render_list["error_message"] = self._reason 406 | self.finish( 407 | self.render_string( 408 | "management/error.htm", 409 | __without_database=True, 410 | **self.render_list)) 411 | 412 | 413 | class CentralSquare(PlacesOfInterest): 414 | @coroutine 415 | def get(self): 416 | contents = yield self.get_writing(class_id=0) 417 | for key in contents: 418 | contents[key]["author"] = yield self.get_user( 419 | _id=contents[key]["author"], with_privacy=False) 420 | contents[key]["content"] = self.make_md(contents[key]["content"], 421 | more=False) 422 | self.render_list["contents"] = contents 423 | self.render_list["origin_title"] = "首页" 424 | self.render_list["slug"] = "index" 425 | self.render_list["sub_slug"] = "" 426 | self.render_list["current_content_id"] = 0 427 | self.render("model.htm") 428 | 429 | 430 | class ConferenceHall(PlacesOfInterest): 431 | @coroutine 432 | @slug_validation(["slug"]) 433 | def get(self, writing_slug): 434 | writing = yield self.get_writing(slug=writing_slug) 435 | if not writing: 436 | raise HTTPError(404) 437 | writing["author"] = yield self.get_user(_id=writing["author"], 438 | with_privacy=False) 439 | writing["content"] = self.make_md(writing["content"]) 440 | self.render_list["content"] = writing 441 | self.render_list["origin_title"] = writing["title"] 442 | self.render_list["slug"] = "writing" 443 | self.render_list["sub_slug"] = writing["slug"] 444 | self.render_list["current_content_id"] = writing["_id"] 445 | self.render("model.htm") 446 | 447 | 448 | class MemorialWall(PlacesOfInterest): 449 | @coroutine 450 | @slug_validation(["slug"]) 451 | def get(self, page_slug): 452 | page = yield self.get_page(slug=page_slug) 453 | if not page: 454 | raise HTTPError(404) 455 | page["author"] = yield self.get_user(_id=page["author"], 456 | with_privacy=False) 457 | page["content"] = self.make_md(page["content"]) 458 | self.render_list["content"] = page 459 | self.render_list["origin_title"] = page["title"] 460 | self.render_list["slug"] = "page" 461 | self.render_list["sub_slug"] = page["slug"] 462 | self.render_list["current_content_id"] = page["_id"] 463 | self.render("model.htm") 464 | 465 | 466 | class NewsAnnouncement(PlacesOfInterest): 467 | @coroutine 468 | def get(self): 469 | 470 | self.set_header("Content-Type", "application/xml; charset=\"utf-8\"") 471 | 472 | content = yield self.get_writing(class_id=0) 473 | 474 | fg = feedgen.feed.FeedGenerator() 475 | update_time = 0 476 | author = yield self.get_user(_id=1) 477 | fg.id(self.config["site_url"]) 478 | fg.title(self.config["site_name"]) 479 | fg.author({"name": author["username"], "email": author["email"]}) 480 | fg.link(href=self.config["site_url"], rel="alternate") 481 | fg.link(href=self.config["site_url"] + "/feed.xml", rel="self") 482 | fg.language("zh-CN") 483 | fg.logo(self.config["site_url"] + "/spirit/favicon.jpg") 484 | 485 | for key in content.keys(): 486 | current = fg.add_entry() 487 | current.id((self.config["site_url"] + "/writings/{0}.htm").format( 488 | content[key]["slug"]) 489 | ) 490 | current.link(href=(self.config[ 491 | "site_url"] + "/writings/{0}.htm").format( 492 | content[key]["slug"])) 493 | current.title(content[key]["title"]) 494 | current.content(self.make_md(content[key]["content"])) 495 | if content[key]["time"] > update_time: 496 | update_time = content[key]["time"] 497 | current.updated( 498 | datetime.datetime.fromtimestamp(content[key]["time"]).replace( 499 | tzinfo=datetime.timezone.utc)) 500 | 501 | fg.updated(datetime.datetime.fromtimestamp( 502 | update_time).replace( 503 | tzinfo=datetime.timezone.utc)) 504 | 505 | atomfeed = fg.atom_str(pretty=True) 506 | self.write(atomfeed) 507 | 508 | 509 | class HistoryLibrary(PlacesOfInterest): 510 | pass 511 | 512 | 513 | class TerminalService(PlacesOfInterest): 514 | @coroutine 515 | def post(self): 516 | action = self.get_arg("action", default=None, arg_type="link") 517 | if hasattr(self, action): 518 | yield getattr(self, action)() 519 | else: 520 | raise HTTPError(500) 521 | 522 | @coroutine 523 | def load_index(self): 524 | contents = yield self.get_writing(class_id=0) 525 | for key in contents: 526 | contents[key]["author"] = yield self.get_user( 527 | _id=contents[key]["author"], with_privacy=False) 528 | contents[key]["content"] = contents[key]["content"].split( 529 | "")[0] 530 | self.finish(json.dumps(list(contents.values()))) 531 | 532 | @coroutine 533 | def load_writing(self): 534 | writing_slug = self.get_arg("slug", arg_type="slug") 535 | writing = yield self.get_writing(slug=writing_slug) 536 | if not writing: 537 | self.finish(json.dumps({ 538 | "success": False, 539 | "reason": "notfound" 540 | })) 541 | return 542 | writing["author"] = yield self.get_user(_id=writing["author"], 543 | with_privacy=False) 544 | writing["success"] = True 545 | self.finish(json.dumps(writing)) 546 | 547 | @coroutine 548 | def load_page(self): 549 | page_slug = self.get_arg("slug", arg_type="slug") 550 | page = yield self.get_page(slug=page_slug) 551 | if not page: 552 | self.finish(json.dumps({ 553 | "success": False, 554 | "reason": "notfound" 555 | })) 556 | return 557 | page["author"] = yield self.get_user(_id=page["author"], 558 | with_privacy=False) 559 | page["success"] = True 560 | self.finish(json.dumps(page)) 561 | 562 | @coroutine 563 | def load_reply(self): 564 | writing_id = self.get_arg("writing", arg_type="number") 565 | reply_id = self.get_arg("reply", arg_type="number") 566 | method = self.get_arg("method", arg_type="hash") 567 | if method == "list" and writing_id: 568 | result = yield self.get_reply(writing_id=writing_id) 569 | elif method == "single" and reply_id: 570 | result = yield self.get_reply(id=reply_id) 571 | else: 572 | raise HTTPError(500) 573 | self.finish(json.dumps(result)) 574 | 575 | @coroutine 576 | def new_reply(self): 577 | writing_id = self.get_arg("writing", arg_type="number") 578 | reply_id = self.get_arg("reply", arg_type="number") 579 | reply = OrderedDict() 580 | reply["writing_id"] = writing_id 581 | if not self.current_user: 582 | reply["master"] = False 583 | reply["name"] = self.get_arg("name", arg_type="origin") 584 | reply["email"] = self.get_arg("email", arg_type="mail_address") 585 | reply["homepage"] = self.get_arg("homepage", arg_type="link") 586 | if not (reply["name"] and reply["email"]): 587 | result = { 588 | "success": False, 589 | "reason": "incomplation" 590 | } 591 | self.finish(json.dumps(result)) 592 | return 593 | reply["name"] = self.escape(reply["name"], item_type="html") 594 | reply["permit"] = False 595 | else: 596 | reply["master"] = True 597 | reply["name"] = self.current_user["username"] 598 | reply["email"] = self.current_user["email"] 599 | reply["homepage"] = self.current_user["homepage"] 600 | reply["permit"] = True 601 | reply["ip"] = self.remote_ip 602 | reply["time"] = int(time.time()) 603 | reply["emailmd5"] = self.hash(reply["email"].lower(), 604 | "md5") 605 | content = self.escape(self.get_arg("content", arg_type="origin"), 606 | item_type="html") 607 | content = re.sub( 608 | re.compile(r"(data:)", re.IGNORECASE), "data:", content) 609 | content = re.sub( 610 | re.compile( 611 | r"(javascript:)", re.IGNORECASE), "javascript:", content) 612 | reply["content"] = content 613 | reply["_id"] = yield self.issue_id("Replies") 614 | book = self.memories.select("Replies") 615 | book.add(reply) 616 | result = {} 617 | try: 618 | yield book.do() 619 | result["success"] = reply["master"] 620 | result["id"] = reply["_id"] 621 | if not reply["master"]: 622 | result["reason"] = "waitforcheck" 623 | if result["success"]: 624 | result.update(reply) 625 | except: 626 | result["success"] = False 627 | result["reason"] = "unkonwn" 628 | self.finish(json.dumps(result)) 629 | 630 | 631 | class IllustratePlace(PlacesOfInterest): 632 | @coroutine 633 | @slug_validation(["hash"]) 634 | def get(self, slug): 635 | size = self.get_arg("s", default=80, arg_type="number") 636 | default = self.get_arg("d", default=404, arg_type="hash") 637 | current_time = int(time.time()) 638 | 639 | path = self.settings["static_path"] + "/public/avatar/" + slug 640 | if not os.path.exists(path): 641 | os.makedirs(path) 642 | 643 | file_path = path + "/" + str(size) 644 | if os.path.exists(file_path): 645 | book = self.memories.select("Publics") 646 | book.find( 647 | {"filename": str(size), "email_md5": slug, "type": "avatar"}) 648 | yield book.do() 649 | avatar_info = book.result() 650 | if not avatar_info: 651 | os.remove(file_path) 652 | book.erase( 653 | { 654 | "filename": str(size), 655 | "email_md5": slug, 656 | "type": "avatar" 657 | } 658 | ) 659 | yield book.do() 660 | elif (current_time - avatar_info["time"]) <= (15 * 24 * 60 * 60): 661 | self.set_header( 662 | "content-type", avatar_info["content_type"]) 663 | with open(file_path, "rb") as f: 664 | self.finish(f.read()) 665 | return 666 | else: 667 | os.remove(file_path) 668 | book.erase( 669 | { 670 | "filename": str(size), 671 | "email_md5": slug, 672 | "type": "avatar" 673 | } 674 | ) 675 | yield book.do() 676 | 677 | client = tornado.httpclient.AsyncHTTPClient() 678 | link = ( 679 | "https://secure.gravatar.com/avatar/" + slug + "?s=" + 680 | str(size) + "&d=" + str(default)) 681 | response = yield client.fetch(link) 682 | if response.error: 683 | raise HTTPError(response.code) 684 | avatar = response.body 685 | content_type = response.headers.get("content-type") 686 | avatar_info = OrderedDict() 687 | avatar_info["time"] = current_time 688 | avatar_info["type"] = "avatar" 689 | avatar_info["content_type"] = content_type 690 | avatar_info["filename"] = str(size) 691 | avatar_info["filepath"] = file_path 692 | avatar_info["fileurl"] = None 693 | avatar_info["email_md5"] = slug 694 | avatar_info["_id"] = yield self.issue_id("Publics") 695 | 696 | with open(file_path, "wb") as f: 697 | f.write(avatar) 698 | 699 | book = self.memories.select("Publics") 700 | book.find( 701 | {"filename": str(size), "email_md5": slug, "type": "avatar"}) 702 | yield book.do() 703 | if book.result(): 704 | book.erase( 705 | { 706 | "filename": str(size), 707 | "email_md5": slug, 708 | "type": "avatar" 709 | } 710 | ) 711 | yield book.do() 712 | book.add(avatar_info) 713 | yield book.do() 714 | 715 | self.set_header("content-type", content_type) 716 | self.finish(avatar) 717 | 718 | 719 | class LostAndFoundPlace(PlacesOfInterest): 720 | def get(self, *args, **kwargs): 721 | raise HTTPError(404) 722 | 723 | post = get 724 | -------------------------------------------------------------------------------- /library_sample.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Configs", 4 | "documents": [ 5 | { 6 | "_id": { 7 | "type": "prepared", 8 | "content": "site_name" 9 | }, 10 | "value": { 11 | "type": "prepared", 12 | "content": "FurtherLand" 13 | } 14 | }, 15 | { 16 | "_id": { 17 | "type": "prepared", 18 | "content": "site_url" 19 | }, 20 | "value": { 21 | "type": "prepared", 22 | "content": "" 23 | } 24 | }, 25 | { 26 | "_id": { 27 | "type": "prepared", 28 | "content": "nutrition_type" 29 | }, 30 | "value": { 31 | "type": "prepared", 32 | "content": "blossom" 33 | } 34 | }, 35 | { 36 | "_id": { 37 | "type": "prepared", 38 | "content": "site_description" 39 | }, 40 | "value": { 41 | "type": "prepared", 42 | "content": "Nobody knows the Future, Why not fight up?" 43 | } 44 | }, 45 | { 46 | "_id": { 47 | "type": "prepared", 48 | "content": "site_keywords" 49 | }, 50 | "value": { 51 | "type": "prepared", 52 | "content": "FurtherLand, Python" 53 | } 54 | }, 55 | { 56 | "_id": { 57 | "type": "prepared", 58 | "content": "trace_code" 59 | }, 60 | "value": { 61 | "type": "prepared", 62 | "content": "" 63 | } 64 | }, 65 | { 66 | "_id": { 67 | "type": "prepared", 68 | "content": "configuration_name" 69 | }, 70 | "value": { 71 | "type": "prepared", 72 | "content": "设置" 73 | } 74 | }, 75 | { 76 | "_id": { 77 | "type": "prepared", 78 | "content": "crda_name" 79 | }, 80 | "value": { 81 | "type": "prepared", 82 | "content": "管理" 83 | } 84 | }, 85 | { 86 | "_id": { 87 | "type": "prepared", 88 | "content": "lobby_name" 89 | }, 90 | "value": { 91 | "type": "prepared", 92 | "content": "首页" 93 | } 94 | }, 95 | { 96 | "_id": { 97 | "type": "prepared", 98 | "content": "public_name" 99 | }, 100 | "value": { 101 | "type": "prepared", 102 | "content": "文件上传" 103 | } 104 | }, 105 | { 106 | "_id": { 107 | "type": "prepared", 108 | "content": "working_name" 109 | }, 110 | "value": { 111 | "type": "prepared", 112 | "content": "创作" 113 | } 114 | }, 115 | { 116 | "_id": { 117 | "type": "prepared", 118 | "content": "office_name" 119 | }, 120 | "value": { 121 | "type": "prepared", 122 | "content": "控制台" 123 | } 124 | }, 125 | ] 126 | }, 127 | { 128 | "name": "Classes", 129 | "documents": [] 130 | }, 131 | { 132 | "name": "Counts", 133 | "documents": [ 134 | { 135 | "_id": { 136 | "type": "prepared", 137 | "content": "Classes" 138 | }, 139 | "value": { 140 | "type": "prepared", 141 | "content": 0 142 | } 143 | }, 144 | { 145 | "_id": { 146 | "type": "prepared", 147 | "content": "Masters" 148 | }, 149 | "value": { 150 | "type": "prepared", 151 | "content": 1 152 | } 153 | }, 154 | { 155 | "_id": { 156 | "type": "prepared", 157 | "content": "Pages" 158 | }, 159 | "value": { 160 | "type": "prepared", 161 | "content": 0 162 | } 163 | }, 164 | { 165 | "_id": { 166 | "type": "prepared", 167 | "content": "Publics" 168 | }, 169 | "value": { 170 | "type": "prepared", 171 | "content": 0 172 | } 173 | }, 174 | { 175 | "_id": { 176 | "type": "prepared", 177 | "content": "Replies" 178 | }, 179 | "value": { 180 | "type": "prepared", 181 | "content": 0 182 | } 183 | }, 184 | { 185 | "_id": { 186 | "type": "prepared", 187 | "content": "Tags" 188 | }, 189 | "value": { 190 | "type": "prepared", 191 | "content": 0 192 | } 193 | }, 194 | { 195 | "_id": { 196 | "type": "prepared", 197 | "content": "Visitors" 198 | }, 199 | "value": { 200 | "type": "prepared", 201 | "content": 0 202 | } 203 | }, 204 | { 205 | "_id": { 206 | "type": "prepared", 207 | "content": "Writings" 208 | }, 209 | "value": { 210 | "type": "prepared", 211 | "content": 0 212 | } 213 | }, 214 | ] 215 | }, 216 | { 217 | "name": "Masters", 218 | "documents": [ 219 | { 220 | "_id": { 221 | "type": "prepared", 222 | "content": 1 223 | }, 224 | "role": { 225 | "type": "prepared", 226 | "content": "master" 227 | }, 228 | "username": { 229 | "type": "required", 230 | "content": "" 231 | }, 232 | "email": { 233 | "type": "required", 234 | "content": "" 235 | }, 236 | "password": { 237 | "type": "required", 238 | "content": "" 239 | }, 240 | "otp_key": { 241 | "type": "required", 242 | "content": "" 243 | }, 244 | "last_login": { 245 | "type": "prepared", 246 | "content": 0 247 | }, 248 | "created": { 249 | "type": "prepared", 250 | "content": 0 251 | }, 252 | "homepage": { 253 | "type": "required", 254 | "content": "" 255 | } 256 | }, 257 | ] 258 | }, 259 | { 260 | "name": "Pages", 261 | "documents": [] 262 | }, 263 | { 264 | "name": "Publics", 265 | "documents": [] 266 | }, 267 | { 268 | "name": "Replies", 269 | "documents": [] 270 | }, 271 | { 272 | "name": "Tags", 273 | "documents": [] 274 | }, 275 | { 276 | "name": "Visitors", 277 | "documents": [] 278 | }, 279 | { 280 | "name": "Writings", 281 | "documents": [] 282 | } 283 | ] 284 | -------------------------------------------------------------------------------- /melody_example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright 2015 Futur Solo 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | secret = "" 20 | # This will be the top secret of the FurtherLand 21 | 22 | base = __file__ 23 | # There the FurtherLand exactly located 24 | 25 | safeland = False 26 | # Turn it to True if you want your Visitors to Visit FurtherLand in HTTPS only. 27 | # Furthermore, this will also enable some HTTPS only Protection, such as: 28 | # Secure Only Cookies, HSTS Header, etc. 29 | 30 | dev = False 31 | # Turn it to True if you are building FurtherLand 32 | # Make Sure to Turn it to False when FurtherLand is ready to Serve Visitors 33 | 34 | listen_ip = "127.0.0.1" 35 | listen_port = 1741 36 | # The place the FurtherLand will rise 37 | 38 | library = { 39 | "host": "", 40 | "port": "", 41 | "auth": True, 42 | "user": "", 43 | "passwd": "", 44 | "database": "", 45 | "prefix": "" 46 | } 47 | # This is the Library of FurtherLand 48 | # If the Library is open to Public(Connect Without Authentication), 49 | # Turn auth option to False and leave user and passwd arguments blank 50 | # BUT DO NOT REMOVE THEM! 51 | -------------------------------------------------------------------------------- /spirit/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futursolo/furtherland/33ead7d4e651ed3154c8047e3bdc4bb2871e4468/spirit/favicon.ico -------------------------------------------------------------------------------- /spirit/favicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futursolo/furtherland/33ead7d4e651ed3154c8047e3bdc4bb2871e4468/spirit/favicon.jpg -------------------------------------------------------------------------------- /spirit/management/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futursolo/furtherland/33ead7d4e651ed3154c8047e3bdc4bb2871e4468/spirit/management/avatar.jpg -------------------------------------------------------------------------------- /spirit/management/checkin.css: -------------------------------------------------------------------------------- 1 | /*-- 2 | 3 | Copyright 2015 Futur Solo 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain 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, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --*/ 18 | html, body{ 19 | margin: 0; 20 | padding: 0; 21 | height: 100%; 22 | font-family: Oxygen, Verdana, Tahoma, STHeiti, "Noto Sans CJK SC", "Microsoft YaHei", SimHei; 23 | background-repeat: no-repeat; 24 | font-size: 15px; 25 | color: rgb(100, 100, 100); 26 | background-color: rgb(245, 245, 245); 27 | word-break: break-all; 28 | } 29 | 30 | textarea, input{ 31 | font-family: Oxygen, Verdana, Tahoma, STHeiti, "Noto Sans CJK SC", "Microsoft YaHei", SimHei; 32 | } 33 | 34 | .main{ 35 | display: table; 36 | width: 100%; 37 | height: 100%; 38 | } 39 | .main > .container{ 40 | display: table-cell; 41 | vertical-align: middle; 42 | } 43 | .main > .container .panel{ 44 | max-width: 400px; 45 | width: 100%; 46 | min-height: 200px; 47 | margin-left: auto; 48 | margin-right: auto; 49 | box-sizing: border-box; 50 | } 51 | .main > .container .panel .title{ 52 | text-align: center; 53 | font-size: 18px; 54 | width: 100%; 55 | display: block; 56 | } 57 | .main > .container .panel .alert{ 58 | color: rgb(244, 67, 54); 59 | font-size: 15px; 60 | text-align: center; 61 | line-height: 30px; 62 | } 63 | .main > .container .panel .alert i{ 64 | color: rgb(244, 67, 54); 65 | font-size: 15px; 66 | display: inline; 67 | box-sizing: border-box; 68 | word-break: normal; 69 | padding: 0; 70 | } 71 | .main > .container .panel .avatar-back{ 72 | background-color: rgb(255, 255, 255); 73 | padding-top: 20px; 74 | } 75 | .main > .container .panel .avatar{ 76 | margin-left: auto; 77 | margin-right: auto; 78 | height: 100px; 79 | width: 100px; 80 | border-radius: 500px; 81 | background-size: cover; 82 | display: block; 83 | } 84 | 85 | .main > .container .panel .form-back{ 86 | background-color: rgb(255, 255, 255); 87 | padding: 20px; 88 | padding-top: 0; 89 | } 90 | .main > .container .panel .input-back{ 91 | width: 100%; 92 | } 93 | .main > .container .panel .remember-container{ 94 | width: 100%; 95 | position: relative; 96 | } 97 | .main > .container .panel .remember-back{ 98 | width: 150px; 99 | position: absolute; 100 | left: 50%; 101 | margin-left: -80px; 102 | } 103 | .main > .container .panel .login-back{ 104 | padding: 20px; 105 | } 106 | .main > .container .panel .login{ 107 | width: calc(100% - 20px); 108 | } 109 | form{ 110 | display: none; 111 | } 112 | -------------------------------------------------------------------------------- /spirit/management/checkin.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2015 Futur Solo 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | document.getElementById("login").addEventListener("click", function () { 16 | document.getElementById("real-username").value = document.getElementById("username").value; 17 | 18 | var SHAObj = new jsSHA(document.getElementById("password").value, "TEXT"); 19 | document.getElementById("real-password").value = SHAObj.getHash("SHA-256", "HEX"); 20 | 21 | document.getElementById("real-two").value = document.getElementById("two").value; 22 | 23 | if (document.getElementById("remember").checked) { 24 | document.getElementById("real-remember").value = "true"; 25 | } else { 26 | document.getElementById("real-remember").value = "false"; 27 | } 28 | document.getElementsByTagName("form")[0].submit(); 29 | }); 30 | -------------------------------------------------------------------------------- /spirit/management/fetch.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | if (self.fetch) { 5 | return 6 | } 7 | 8 | function normalizeName(name) { 9 | if (typeof name !== 'string') { 10 | name = name.toString(); 11 | } 12 | if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { 13 | throw new TypeError('Invalid character in header field name') 14 | } 15 | return name.toLowerCase() 16 | } 17 | 18 | function normalizeValue(value) { 19 | if (typeof value !== 'string') { 20 | value = value.toString(); 21 | } 22 | return value 23 | } 24 | 25 | function Headers(headers) { 26 | this.map = {} 27 | 28 | if (headers instanceof Headers) { 29 | headers.forEach(function(value, name) { 30 | this.append(name, value) 31 | }, this) 32 | 33 | } else if (headers) { 34 | Object.getOwnPropertyNames(headers).forEach(function(name) { 35 | this.append(name, headers[name]) 36 | }, this) 37 | } 38 | } 39 | 40 | Headers.prototype.append = function(name, value) { 41 | name = normalizeName(name) 42 | value = normalizeValue(value) 43 | var list = this.map[name] 44 | if (!list) { 45 | list = [] 46 | this.map[name] = list 47 | } 48 | list.push(value) 49 | } 50 | 51 | Headers.prototype['delete'] = function(name) { 52 | delete this.map[normalizeName(name)] 53 | } 54 | 55 | Headers.prototype.get = function(name) { 56 | var values = this.map[normalizeName(name)] 57 | return values ? values[0] : null 58 | } 59 | 60 | Headers.prototype.getAll = function(name) { 61 | return this.map[normalizeName(name)] || [] 62 | } 63 | 64 | Headers.prototype.has = function(name) { 65 | return this.map.hasOwnProperty(normalizeName(name)) 66 | } 67 | 68 | Headers.prototype.set = function(name, value) { 69 | this.map[normalizeName(name)] = [normalizeValue(value)] 70 | } 71 | 72 | Headers.prototype.forEach = function(callback, thisArg) { 73 | Object.getOwnPropertyNames(this.map).forEach(function(name) { 74 | this.map[name].forEach(function(value) { 75 | callback.call(thisArg, value, name, this) 76 | }, this) 77 | }, this) 78 | } 79 | 80 | function consumed(body) { 81 | if (body.bodyUsed) { 82 | return Promise.reject(new TypeError('Already read')) 83 | } 84 | body.bodyUsed = true 85 | } 86 | 87 | function fileReaderReady(reader) { 88 | return new Promise(function(resolve, reject) { 89 | reader.onload = function() { 90 | resolve(reader.result) 91 | } 92 | reader.onerror = function() { 93 | reject(reader.error) 94 | } 95 | }) 96 | } 97 | 98 | function readBlobAsArrayBuffer(blob) { 99 | var reader = new FileReader() 100 | reader.readAsArrayBuffer(blob) 101 | return fileReaderReady(reader) 102 | } 103 | 104 | function readBlobAsText(blob) { 105 | var reader = new FileReader() 106 | reader.readAsText(blob) 107 | return fileReaderReady(reader) 108 | } 109 | 110 | var support = { 111 | blob: 'FileReader' in self && 'Blob' in self && (function() { 112 | try { 113 | new Blob(); 114 | return true 115 | } catch(e) { 116 | return false 117 | } 118 | })(), 119 | formData: 'FormData' in self 120 | } 121 | 122 | function Body() { 123 | this.bodyUsed = false 124 | 125 | 126 | this._initBody = function(body) { 127 | this._bodyInit = body 128 | if (typeof body === 'string') { 129 | this._bodyText = body 130 | } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { 131 | this._bodyBlob = body 132 | } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { 133 | this._bodyFormData = body 134 | } else if (!body) { 135 | this._bodyText = '' 136 | } else { 137 | throw new Error('unsupported BodyInit type') 138 | } 139 | } 140 | 141 | if (support.blob) { 142 | this.blob = function() { 143 | var rejected = consumed(this) 144 | if (rejected) { 145 | return rejected 146 | } 147 | 148 | if (this._bodyBlob) { 149 | return Promise.resolve(this._bodyBlob) 150 | } else if (this._bodyFormData) { 151 | throw new Error('could not read FormData body as blob') 152 | } else { 153 | return Promise.resolve(new Blob([this._bodyText])) 154 | } 155 | } 156 | 157 | this.arrayBuffer = function() { 158 | return this.blob().then(readBlobAsArrayBuffer) 159 | } 160 | 161 | this.text = function() { 162 | var rejected = consumed(this) 163 | if (rejected) { 164 | return rejected 165 | } 166 | 167 | if (this._bodyBlob) { 168 | return readBlobAsText(this._bodyBlob) 169 | } else if (this._bodyFormData) { 170 | throw new Error('could not read FormData body as text') 171 | } else { 172 | return Promise.resolve(this._bodyText) 173 | } 174 | } 175 | } else { 176 | this.text = function() { 177 | var rejected = consumed(this) 178 | return rejected ? rejected : Promise.resolve(this._bodyText) 179 | } 180 | } 181 | 182 | if (support.formData) { 183 | this.formData = function() { 184 | return this.text().then(decode) 185 | } 186 | } 187 | 188 | this.json = function() { 189 | return this.text().then(JSON.parse) 190 | } 191 | 192 | return this 193 | } 194 | 195 | // HTTP methods whose capitalization should be normalized 196 | var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] 197 | 198 | function normalizeMethod(method) { 199 | var upcased = method.toUpperCase() 200 | return (methods.indexOf(upcased) > -1) ? upcased : method 201 | } 202 | 203 | function Request(url, options) { 204 | options = options || {} 205 | this.url = url 206 | 207 | this.credentials = options.credentials || 'omit' 208 | this.headers = new Headers(options.headers) 209 | this.method = normalizeMethod(options.method || 'GET') 210 | this.mode = options.mode || null 211 | this.referrer = null 212 | 213 | if ((this.method === 'GET' || this.method === 'HEAD') && options.body) { 214 | throw new TypeError('Body not allowed for GET or HEAD requests') 215 | } 216 | this._initBody(options.body) 217 | } 218 | 219 | function decode(body) { 220 | var form = new FormData() 221 | body.trim().split('&').forEach(function(bytes) { 222 | if (bytes) { 223 | var split = bytes.split('=') 224 | var name = split.shift().replace(/\+/g, ' ') 225 | var value = split.join('=').replace(/\+/g, ' ') 226 | form.append(decodeURIComponent(name), decodeURIComponent(value)) 227 | } 228 | }) 229 | return form 230 | } 231 | 232 | function headers(xhr) { 233 | var head = new Headers() 234 | var pairs = xhr.getAllResponseHeaders().trim().split('\n') 235 | pairs.forEach(function(header) { 236 | var split = header.trim().split(':') 237 | var key = split.shift().trim() 238 | var value = split.join(':').trim() 239 | head.append(key, value) 240 | }) 241 | return head 242 | } 243 | 244 | Body.call(Request.prototype) 245 | 246 | function Response(bodyInit, options) { 247 | if (!options) { 248 | options = {} 249 | } 250 | 251 | this._initBody(bodyInit) 252 | this.type = 'default' 253 | this.url = null 254 | this.status = options.status 255 | this.ok = this.status >= 200 && this.status < 300 256 | this.statusText = options.statusText 257 | this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) 258 | this.url = options.url || '' 259 | } 260 | 261 | Body.call(Response.prototype) 262 | 263 | self.Headers = Headers; 264 | self.Request = Request; 265 | self.Response = Response; 266 | 267 | self.fetch = function(input, init) { 268 | // TODO: Request constructor should accept input, init 269 | var request 270 | if (Request.prototype.isPrototypeOf(input) && !init) { 271 | request = input 272 | } else { 273 | request = new Request(input, init) 274 | } 275 | 276 | return new Promise(function(resolve, reject) { 277 | var xhr = new XMLHttpRequest() 278 | 279 | function responseURL() { 280 | if ('responseURL' in xhr) { 281 | return xhr.responseURL 282 | } 283 | 284 | // Avoid security warnings on getResponseHeader when not allowed by CORS 285 | if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { 286 | return xhr.getResponseHeader('X-Request-URL') 287 | } 288 | 289 | return; 290 | } 291 | 292 | xhr.onload = function() { 293 | var status = (xhr.status === 1223) ? 204 : xhr.status 294 | if (status < 100 || status > 599) { 295 | reject(new TypeError('Network request failed')) 296 | return 297 | } 298 | var options = { 299 | status: status, 300 | statusText: xhr.statusText, 301 | headers: headers(xhr), 302 | url: responseURL() 303 | } 304 | var body = 'response' in xhr ? xhr.response : xhr.responseText; 305 | resolve(new Response(body, options)) 306 | } 307 | 308 | xhr.onerror = function() { 309 | reject(new TypeError('Network request failed')) 310 | } 311 | 312 | xhr.open(request.method, request.url, true) 313 | 314 | if (request.credentials === 'include') { 315 | xhr.withCredentials = true 316 | } 317 | 318 | if ('responseType' in xhr && support.blob) { 319 | xhr.responseType = 'blob' 320 | } 321 | 322 | request.headers.forEach(function(value, name) { 323 | xhr.setRequestHeader(name, value) 324 | }) 325 | 326 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) 327 | }) 328 | } 329 | self.fetch.polyfill = true 330 | })(); 331 | -------------------------------------------------------------------------------- /spirit/management/highlight.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#f0f0f0;-webkit-text-size-adjust:none}.hljs,.hljs-subst,.hljs-tag .hljs-title,.nginx .hljs-title{color:black}.hljs-string,.hljs-title,.hljs-constant,.hljs-parent,.hljs-tag .hljs-value,.hljs-rule .hljs-value,.hljs-preprocessor,.hljs-pragma,.hljs-name,.haml .hljs-symbol,.ruby .hljs-symbol,.ruby .hljs-symbol .hljs-string,.hljs-template_tag,.django .hljs-variable,.smalltalk .hljs-class,.hljs-addition,.hljs-flow,.hljs-stream,.bash .hljs-variable,.pf .hljs-variable,.apache .hljs-tag,.apache .hljs-cbracket,.tex .hljs-command,.tex .hljs-special,.erlang_repl .hljs-function_or_atom,.asciidoc .hljs-header,.markdown .hljs-header,.coffeescript .hljs-attribute{color:#800}.smartquote,.hljs-comment,.hljs-annotation,.diff .hljs-header,.hljs-chunk,.asciidoc .hljs-blockquote,.markdown .hljs-blockquote{color:#888}.hljs-number,.hljs-date,.hljs-regexp,.hljs-literal,.hljs-hexcolor,.smalltalk .hljs-symbol,.smalltalk .hljs-char,.go .hljs-constant,.hljs-change,.lasso .hljs-variable,.makefile .hljs-variable,.asciidoc .hljs-bullet,.markdown .hljs-bullet,.asciidoc .hljs-link_url,.markdown .hljs-link_url{color:#080}.hljs-label,.hljs-javadoc,.ruby .hljs-string,.hljs-decorator,.hljs-filter .hljs-argument,.hljs-localvars,.hljs-array,.hljs-attr_selector,.hljs-important,.hljs-pseudo,.hljs-pi,.haml .hljs-bullet,.hljs-doctype,.hljs-deletion,.hljs-envvar,.hljs-shebang,.apache .hljs-sqbracket,.nginx .hljs-built_in,.tex .hljs-formula,.erlang_repl .hljs-reserved,.hljs-prompt,.asciidoc .hljs-link_label,.markdown .hljs-link_label,.vhdl .hljs-attribute,.clojure .hljs-attribute,.asciidoc .hljs-attribute,.lasso .hljs-attribute,.coffeescript .hljs-property,.hljs-phony{color:#88f}.hljs-keyword,.hljs-id,.hljs-title,.hljs-built_in,.css .hljs-tag,.hljs-javadoctag,.hljs-phpdoc,.hljs-dartdoc,.hljs-yardoctag,.smalltalk .hljs-class,.hljs-winutils,.bash .hljs-variable,.pf .hljs-variable,.apache .hljs-tag,.hljs-type,.hljs-typename,.tex .hljs-command,.asciidoc .hljs-strong,.markdown .hljs-strong,.hljs-request,.hljs-status{font-weight:bold}.asciidoc .hljs-emphasis,.markdown .hljs-emphasis{font-style:italic}.nginx .hljs-built_in{font-weight:normal}.coffeescript .javascript,.javascript .xml,.lasso .markup,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata{opacity:0.5} -------------------------------------------------------------------------------- /spirit/management/office.css: -------------------------------------------------------------------------------- 1 | /*-- 2 | 3 | Copyright 2015 Futur Solo 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain 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, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --*/ 18 | html, body{ 19 | margin: 0; 20 | padding: 0; 21 | height: 100%; 22 | font-family: Oxygen, Verdana, Tahoma, "Noto Sans CJK SC", "Noto Sans CJK JP", "Microsoft YaHei", STHeiti, SimHei !important; 23 | background-repeat: no-repeat; 24 | font-size: 15px; 25 | color: rgb(100, 100, 100); 26 | background-color: rgb(245, 245, 245); 27 | } 28 | 29 | textarea, input{ 30 | font-family: Oxygen, Verdana, Tahoma, "Noto Sans CJK SC", "Noto Sans CJK JP", "Microsoft YaHei", STHeiti, SimHei !important; 31 | } 32 | 33 | .toast{ 34 | position: fixed; 35 | left: 10px; 36 | padding: 15px; 37 | bottom: -100px; 38 | background-color: rgb(50, 50, 50); 39 | border-radius: 2px; 40 | min-width: 200px; 41 | max-width: 100%; 42 | transition: bottom 0.20s ease-in-out; 43 | color: rgb(255, 255, 255); 44 | box-shadow: 0 4px 10px 0px rgb(100, 100, 100); 45 | } 46 | .toast.visible{ 47 | bottom: 10px; 48 | } 49 | 50 | .toast .action{ 51 | color: #eeff41; 52 | margin: 10px; 53 | cursor: pointer; 54 | } 55 | 56 | .toast a{ 57 | text-decoration: none; 58 | } 59 | 60 | .load{ 61 | height: 0; 62 | width: 0; 63 | z-index: 1000; 64 | display: table; 65 | background-color: rgb(245, 245, 245); 66 | position: fixed; 67 | overflow: hidden; 68 | opacity: 0; 69 | transition: opacity 0.30s ease-in-out; 70 | } 71 | .load > .container{ 72 | display: table-cell; 73 | vertical-align: middle; 74 | text-align: center; 75 | } 76 | .load.visible{ 77 | opacity: 1; 78 | } 79 | .load .failed{ 80 | z-index: 1001; 81 | } 82 | 83 | .confirm{ 84 | height: 0; 85 | width: 0; 86 | z-index: 105; 87 | display: table; 88 | background-color: rgba(0, 0, 0, 0.4); 89 | position: fixed; 90 | overflow: hidden; 91 | opacity: 0; 92 | transition: opacity 0.30s ease-in-out; 93 | top: -10000px; 94 | left: -10000px; 95 | } 96 | .confirm.visible{ 97 | opacity: 1; 98 | } 99 | .confirm > .container{ 100 | display: table-cell; 101 | vertical-align: middle; 102 | } 103 | .confirm .dialog{ 104 | max-width: 500px; 105 | box-sizing: border-box; 106 | padding: 20px; 107 | padding-bottom: 55px; 108 | width: calc(100% - 40px); 109 | margin-left: auto; 110 | margin-right: auto; 111 | margin-top: 20px; 112 | border-radius: 2px; 113 | background-color: rgb(255, 255, 255); 114 | position: relative; 115 | } 116 | .confirm .message{ 117 | padding-bottom: 50px; 118 | font-size: 18px; 119 | } 120 | .confirm .mdl-button{ 121 | float: right; 122 | } 123 | .confirm .continue{ 124 | color: rgb(54, 134, 190); 125 | } 126 | 127 | .public{ 128 | height: 0; 129 | width: 0; 130 | z-index: 100; 131 | background-color: rgba(0, 0, 0, 0.4); 132 | position: fixed; 133 | overflow: hidden; 134 | opacity: 0; 135 | transition: opacity 0.30s ease-in-out; 136 | } 137 | .public.visible{ 138 | opacity: 1; 139 | } 140 | .public > .container{ 141 | max-width: 700px; 142 | width: calc(100% - 40px); 143 | height: calc(100% - 40px); 144 | margin-left: auto; 145 | margin-right: auto; 146 | margin-top: 20px; 147 | border-radius: 2px; 148 | background-color: rgb(255, 255, 255); 149 | } 150 | .public .content-selector{ 151 | display: flex; 152 | } 153 | .public .content-selector .item{ 154 | background-color: rgb(255, 255, 255); 155 | transition: background 0.30s ease-in-out; 156 | display: inline-block; 157 | flex-grow: 1; 158 | height: 50px; 159 | line-height: 50px; 160 | text-align: center; 161 | } 162 | .public .content-selector .item.current{ 163 | background-color: rgba(54, 134, 190, 0.5); 164 | } 165 | 166 | .public .title{ 167 | padding: 15px; 168 | box-sizing: border-box; 169 | border-bottom: 1px solid rgb(200, 200, 200); 170 | } 171 | 172 | .public > .container > .content{ 173 | height: calc(100% - 170px); 174 | border-top: 1px solid rgb(200, 200, 200); 175 | position: relative; 176 | } 177 | 178 | .public > .container > .content .content-block{ 179 | overflow: hidden; 180 | height: 100%; 181 | width: 100%; 182 | display: none; 183 | overflow-y: auto; 184 | } 185 | .public > .container > .content .content-block.current{ 186 | display: block; 187 | } 188 | .public > .container > .content .content-block.upload-now.current{ 189 | display: table; 190 | } 191 | 192 | .public > .container > .content .upload-now .table-cell{ 193 | display: table-cell; 194 | vertical-align: middle; 195 | } 196 | 197 | .public > .container > .content .upload-now .uploading{ 198 | height: 0; 199 | width: 0; 200 | opacity: 0; 201 | overflow: hidden; 202 | transition: opacity 0.30s ease-in-out; 203 | display: table; 204 | position: absolute; 205 | top: 0; 206 | left: 0; 207 | background-color: rgb(255, 255, 255); 208 | z-index: 1; 209 | } 210 | 211 | .public > .container > .content .upload-now .uploading.visible{ 212 | height: 100%; 213 | width: 100%; 214 | opacity: 1; 215 | } 216 | .public > .container > .content .upload-now .uploading > .content{ 217 | display: table-cell; 218 | vertical-align: middle; 219 | } 220 | .public > .container > .content .upload-now .uploading > .content > div{ 221 | text-align: center; 222 | } 223 | .public > .container > .content .upload-now .uploading > .content > div .progress-bar{ 224 | width: 300px; 225 | margin-left: auto; 226 | margin-right: auto; 227 | } 228 | .public > .container > .content .upload-now .tips{ 229 | text-align: center; 230 | } 231 | 232 | .public > .container > .content .upload-now .select-file-line{ 233 | text-align: center; 234 | padding-top: 10px; 235 | padding-bottom: 10px; 236 | } 237 | 238 | .public > .container > .content .upload-now .hidden-file-selector{ 239 | display: none; 240 | } 241 | .public .content .uploaded{ 242 | overflow-y: auto; 243 | } 244 | .public .content .uploaded .file-item{ 245 | position: relative; 246 | height: 50px; 247 | width: 100%; 248 | border-bottom: 1px solid rgb(200, 200, 200); 249 | line-height: 50px; 250 | font-size: 20px; 251 | padding-left: 20px; 252 | padding-right: 20px; 253 | box-sizing: border-box; 254 | transition: background-color 0.30s ease-in-out; 255 | } 256 | .public .content .uploaded .file-item.current{ 257 | background-color: rgba(54, 134, 190, 0.5); 258 | } 259 | .public .content .uploaded .file-item .time{ 260 | float: right; 261 | } 262 | 263 | .public .action{ 264 | padding: 15px; 265 | box-sizing: border-box; 266 | border-top: 1px solid rgb(200, 200, 200); 267 | text-align: right; 268 | } 269 | 270 | .public .action .insert-as-photo{ 271 | color: rgb(76, 175, 80); 272 | } 273 | 274 | .public .action .insert-as-link{ 275 | color: rgb(54, 134, 190); 276 | } 277 | 278 | .mdl-layout__header{ 279 | background-color: rgb(54, 134, 190); 280 | } 281 | .mdl-layout__header .mdl-layout__header-row .mdl-navigation__link{ 282 | line-height: 0; 283 | } 284 | 285 | .aside .mdl-navigation__link{ 286 | cursor: pointer; 287 | } 288 | 289 | main{ 290 | position: relative; 291 | } 292 | 293 | main > .container{ 294 | height: 100%; 295 | width: 100%; 296 | box-sizing: border-box; 297 | display: none; 298 | overflow-y: auto; 299 | position: absolute; 300 | } 301 | 302 | main > .current{ 303 | display: block; 304 | } 305 | 306 | main > .lobby.current{ 307 | padding: 20px; 308 | display: table; 309 | } 310 | 311 | main > .lobby > .container{ 312 | display: table-cell; 313 | text-align: center; 314 | } 315 | 316 | main > .lobby .tile{ 317 | display: inline-block; 318 | position: relative; 319 | vertical-align: middle; 320 | text-align: center; 321 | width: 300px; 322 | height: 400px; 323 | margin: 20px; 324 | border-radius: 2px; 325 | background-color: rgb(255, 255, 255); 326 | padding: 10px; 327 | box-shadow: 0 4px 10px 0px rgb(100, 100, 100); 328 | } 329 | main > .lobby .tile > .tips-list{ 330 | height: 150px; 331 | } 332 | 333 | main > .lobby .writing-num.tile > .tips-list{ 334 | color: rgb(33, 150, 243); 335 | } 336 | main > .lobby .page-num.tile > .tips-list{ 337 | color: rgb(76, 175, 80); 338 | } 339 | main > .lobby .reply-num.tile > .tips-list{ 340 | color: rgb(255, 193, 7); 341 | } 342 | main > .lobby .tile > .name{ 343 | font-size: 40px; 344 | line-height: 100px; 345 | } 346 | main > .lobby .tile > .content{ 347 | font-size: 40px; 348 | line-height: 100px; 349 | } 350 | 351 | main > .lobby .manage-button i{ 352 | vertical-align: middle; 353 | } 354 | 355 | main > .lobby > .float-container{ 356 | position: fixed; 357 | bottom: 20px; 358 | right: 20px; 359 | } 360 | 361 | main > .working{ 362 | background-color: rgb(255, 255, 255); 363 | } 364 | 365 | main > .working > .title-container{ 366 | width: 100%; 367 | } 368 | 369 | main > .working > .title-container .title-input-back{ 370 | width: 100%; 371 | } 372 | 373 | main > .working .description{ 374 | height: 40px; 375 | box-sizing: border-box; 376 | padding-left: 10px; 377 | line-height: 40px; 378 | font-size: 15px; 379 | color: rgb(150, 150, 150); 380 | } 381 | 382 | main > .working > .editor-container{ 383 | height: calc(100% - 55px); 384 | width: 50%; 385 | display: inline-block; 386 | vertical-align: top; 387 | position: absolute; 388 | top: 53px; 389 | left: 0; 390 | box-sizing: border-box; 391 | border-right: 1px solid rgb(230, 230, 230); 392 | } 393 | 394 | main > .working > .editor-container .editor-textarea{ 395 | resize: none; 396 | border: none; 397 | height: calc(100% - 45px); 398 | width: 100%; 399 | box-sizing: border-box; 400 | padding: 10px; 401 | padding-top: 0; 402 | font-size: 15px; 403 | line-height: 20px; 404 | color: rgb(100, 100, 100); 405 | outline: 0; 406 | } 407 | 408 | main > .working > .preview-container{ 409 | height: calc(100% - 55px); 410 | width: 50%; 411 | display: inline-block; 412 | vertical-align: top; 413 | position: absolute; 414 | top: 53px; 415 | right: 0; 416 | box-sizing: border-box; 417 | border-left: 1px solid rgb(230, 230, 230); 418 | } 419 | 420 | main > .working > .preview-container .preview-div{ 421 | height: calc(100% - 45px); 422 | width: 100%; 423 | box-sizing: border-box; 424 | overflow-y: auto; 425 | padding: 10px; 426 | padding-top: 0; 427 | font-size: 15px; 428 | line-height: 20px; 429 | color: rgb(100, 100, 100); 430 | position: relative; 431 | word-break: break-all; 432 | } 433 | main > .working > .preview-container .preview-div *{ 434 | max-width: 100%; 435 | } 436 | 437 | main > .working > .info-container{ 438 | height: calc(100% - 64px); 439 | width: 400px; 440 | max-width: 100%; 441 | position: fixed; 442 | top: 64px; 443 | right: 0; 444 | right: -400px; 445 | transition: all 0.30s ease-in-out; 446 | z-index: 3; 447 | background-color: rgb(255, 255, 255); 448 | box-shadow: 0 0 10px 0 rgb(100, 100, 100); 449 | box-sizing: border-box; 450 | padding: 15px; 451 | } 452 | main > .working .info-container.visible{ 453 | right: 0; 454 | } 455 | 456 | main > .working .info-container > .title-container{ 457 | text-align: center; 458 | font-size: 20px; 459 | font-weight: bold; 460 | } 461 | main > .working .info-container > .title-container .close-button{ 462 | position: absolute; 463 | vertical-align: middle; 464 | top: 9px; 465 | left: 9px; 466 | } 467 | main > .working .info-container > .type-container .title{ 468 | padding-top: 20px; 469 | display: inline-block; 470 | } 471 | main > .working .info-container > .type-container .mdl-radio__outer-circle{ 472 | top: 4px; 473 | left: 4px; 474 | } 475 | main > .working .info-container > .type-container .mdl-radio__inner-circle{ 476 | top: 8px; 477 | left: 8px; 478 | } 479 | main > .working .info-container > .slug-container .slug-input-back{ 480 | width: 100%; 481 | } 482 | main > .working .info-container > .slug-container .slug-preview{ 483 | min-height: 40px; 484 | word-break: break-all; 485 | } 486 | main > .working .info-container > .time-container .time-input-back{ 487 | width: 100%; 488 | } 489 | main > .working .info-container > .save-container{ 490 | height: 40px; 491 | } 492 | main > .working .info-container > .tips-container .title{ 493 | font-size: 20px; 494 | line-height: 30px; 495 | margin-top:20px; 496 | margin-bottom:20px; 497 | font-weight: bold; 498 | text-align: center; 499 | } 500 | 501 | main > .working .info-container > .save-container .publish-button{ 502 | float: right; 503 | } 504 | 505 | main > .working .info-container > .save-container .draft-button{ 506 | float: right; 507 | } 508 | 509 | main > .working .float-container{ 510 | position: fixed; 511 | bottom: 20px; 512 | right: 20px; 513 | } 514 | 515 | main > .working .float-container .mdl-button--fab{ 516 | display: block; 517 | margin: 5px; 518 | } 519 | main > .crda .main-container{ 520 | max-width: 1200px; 521 | width: 100%; 522 | margin-left: auto; 523 | margin-right: auto; 524 | margin-top: 20px; 525 | margin-bottom: 20px; 526 | background-color: rgb(255, 255, 255); 527 | border-radius: 2px; 528 | box-shadow: 0 4px 10px 0 rgb(100, 100, 100); 529 | } 530 | 531 | main > .crda .main-container .type-selector{ 532 | display: flex; 533 | width: 100%; 534 | height: 50px; 535 | line-height: 50px; 536 | border-bottom: 1px solid rgb(233, 233, 233); 537 | } 538 | main > .crda .main-container .type-selector .item{ 539 | flex-grow: 1; 540 | text-align: center; 541 | } 542 | main > .crda .main-container .type-selector .item.current{ 543 | background-color: rgba(54, 134, 190, 0.5); 544 | } 545 | main > .crda .main-container .workings-list{ 546 | display: none; 547 | } 548 | main > .crda .main-container .workings-list.current{ 549 | display: block; 550 | } 551 | main > .crda .main-container .workings-list .item{ 552 | box-sizing: border-box; 553 | padding: 15px; 554 | padding-left: 20px; 555 | padding-right: 20px; 556 | position: relative; 557 | border-bottom: 1px solid rgb(233, 233, 233); 558 | } 559 | main > .crda .main-container .workings-list .item:last-child{ 560 | border-bottom: none !important; 561 | } 562 | main > .crda .main-container .workings-list .item .title{ 563 | line-height: 24px; 564 | display: inline-block; 565 | font-size: 20px; 566 | width: calc(100% - 64px); 567 | box-sizing: border-box; 568 | padding-left: 20px; 569 | } 570 | main > .crda .main-container .workings-list .item a:link{ 571 | color: rgb(100, 100, 100); 572 | } 573 | main > .crda .main-container .workings-list .item a:visited{ 574 | color: rgb(100, 100, 100); 575 | } 576 | main > .crda .main-container .workings-list .item .launch-working{ 577 | z-index: 1; 578 | } 579 | main > .crda .main-container .workings-list .item .remove{ 580 | color: rgb(244, 67, 54); 581 | display: inline-block; 582 | z-index: 1; 583 | } 584 | 585 | main > .crda .main-container .workings-list .reply-item{ 586 | box-sizing: border-box; 587 | padding: 15px; 588 | padding-left: 20px; 589 | padding-right: 20px; 590 | position: relative; 591 | border-bottom: 1px solid rgb(233, 233, 233); 592 | } 593 | main > .crda .main-container .workings-list .reply-item:last-child{ 594 | border-bottom: none !important; 595 | } 596 | main > .crda .main-container .workings-list .reply-item .avatar-block{ 597 | width: 70px; 598 | height: 70px; 599 | border-radius: 500px; 600 | background-size: cover; 601 | display: inline-block; 602 | position: relative; 603 | vertical-align: top; 604 | } 605 | main > .crda .main-container .workings-list .reply-item .info-block{ 606 | position: relative; 607 | display: inline-block; 608 | height: 60px; 609 | padding-left: 20px; 610 | padding-right: 20px; 611 | line-height: 20px; 612 | } 613 | main > .crda .main-container .workings-list .reply-item .info-block .mdl-button i{ 614 | vertical-align: middle; 615 | } 616 | main > .crda .main-container .workings-list .reply-item .waiting-for-permit{ 617 | margin-left: 10px; 618 | color: rgb(3, 169, 244); 619 | opacity: 0; 620 | display: inline-block; 621 | transition: all 0.30s ease-in-out; 622 | } 623 | main > .crda .main-container .workings-list .reply-item .waiting-for-permit.visible{ 624 | opacity: 1; 625 | } 626 | main > .crda .main-container .workings-list .reply-item .info-block a:link{ 627 | color: rgb(100, 100, 100); 628 | font-weight: bold; 629 | text-decoration: none; 630 | transition: all 0.20s ease-in-out; 631 | } 632 | main > .crda .main-container .workings-list .reply-item .info-block a:visited{ 633 | color: rgb(100, 100, 100); 634 | font-weight: bold; 635 | text-decoration: none; 636 | transition: all 0.20s ease-in-out; 637 | } 638 | main > .crda .main-container .workings-list .reply-item .info-block a:hover{ 639 | text-decoration: underline; 640 | } 641 | main > .crda .main-container .workings-list .reply-item .info-block{ 642 | display: inline-block; 643 | } 644 | main > .crda .main-container .workings-list .reply-item .info-block .time{ 645 | line-height: 40px; 646 | } 647 | main > .crda .main-container .workings-list .reply-item .info-block .email{ 648 | display: inline-block; 649 | } 650 | main > .crda .main-container .workings-list .reply-item .body-block{ 651 | width: 100%; 652 | padding-top: 10px; 653 | word-break: break-all; 654 | position: relative; 655 | } 656 | main > .crda .main-container .workings-list .reply-item .body-block *{ 657 | max-width: 100%; 658 | } 659 | main > .crda .main-container .workings-list .reply-item .action-block{ 660 | display: inline-block; 661 | } 662 | 663 | main > .crda .main-container .workings-list .reply-item .action-block .toggle-permit{ 664 | color: rgb(76, 175, 80); 665 | display: inline-block; 666 | } 667 | main > .crda .main-container .workings-list .reply-item .action-block .edit{ 668 | color: rgb(255, 152, 0); 669 | display: inline-block; 670 | } 671 | main > .crda .main-container .workings-list .reply-item .action-block .remove{ 672 | color: rgb(244, 67, 54); 673 | display: inline-block; 674 | } 675 | main > .crda .main-container .workings-list .no-content{ 676 | box-sizing: border-box; 677 | padding: 15px; 678 | padding-left: 20px; 679 | padding-right: 20px; 680 | line-height: 40px; 681 | position: relative; 682 | text-align: center; 683 | font-size: 20px; 684 | font-weight: bold; 685 | } 686 | 687 | main > .crda .reply-editor{ 688 | height: 0; 689 | width: 0; 690 | z-index: 50; 691 | display: table; 692 | background-color: rgba(0, 0, 0, 0.4); 693 | position: fixed; 694 | overflow: hidden; 695 | opacity: 0; 696 | top: -10000px; 697 | left: -10000px; 698 | transition: opacity 0.30s ease-in-out; 699 | } 700 | main > .crda .reply-editor.visible{ 701 | opacity: 1; 702 | } 703 | main > .crda .reply-editor > .table-cell{ 704 | display: table-cell; 705 | vertical-align: middle; 706 | } 707 | main > .crda .reply-editor .container{ 708 | max-width: 500px; 709 | width: calc(100% - 40px); 710 | max-height: calc(100% - 40px); 711 | margin-left: auto; 712 | margin-right: auto; 713 | border-radius: 2px; 714 | background-color: rgb(255, 255, 255); 715 | position: relative; 716 | padding: 15px; 717 | box-sizing: border-box; 718 | } 719 | main > .crda .reply-editor .container .input-block{ 720 | width: 100%; 721 | text-align: center; 722 | } 723 | main > .crda .reply-editor .container .input-line{ 724 | display: inline-block; 725 | width: calc(50% - 2px); 726 | padding-left: 10px; 727 | padding-right: 10px; 728 | box-sizing: border-box; 729 | text-align: left; 730 | } 731 | main > .crda .reply-editor .container .homepage-line{ 732 | width: 100%; 733 | text-align: center; 734 | padding-left: 7px; 735 | padding-right: 7px; 736 | box-sizing: border-box; 737 | } 738 | main > .crda .reply-editor .container .homepage-line .homepage-back{ 739 | text-align: left; 740 | width: 100%; 741 | } 742 | main > .crda .reply-editor .container .content-line{ 743 | width: 100%; 744 | position: relative; 745 | text-align: center; 746 | } 747 | main > .crda .reply-editor .container .content-line .content{ 748 | resize: none; 749 | width: calc(100% - 14px); 750 | min-height: 200px; 751 | box-sizing: border-box; 752 | padding: 5px; 753 | text-align: left; 754 | font-size: 15px; 755 | } 756 | main > .crda .reply-editor .container .content-line .content:focus{ 757 | border: 2px solid rgb(54, 134, 190); 758 | outline: 0; 759 | padding: 4px; 760 | } 761 | main > .crda .reply-editor .container .action-line{ 762 | text-align: right; 763 | padding-top: 10px; 764 | } 765 | main > .crda .reply-editor .container .action-line .save{ 766 | color: rgb(54, 134, 190); 767 | } 768 | main > .configuration{ 769 | position: relative; 770 | background-color: rgb(245, 245, 245); 771 | } 772 | main > .configuration > .container{ 773 | max-width: 600px; 774 | margin-left: auto; 775 | margin-right: auto; 776 | background-color: rgb(255, 255, 255); 777 | box-sizing: border-box; 778 | border-radius: 2px; 779 | box-shadow: 0 4px 10px 0 rgb(100, 100, 100); 780 | } 781 | main > .configuration > .container .config-item{ 782 | padding: 15px; 783 | padding-left: 40px; 784 | padding-right: 40px; 785 | border-bottom: 1px solid rgb(233, 233, 233); 786 | } 787 | main > .configuration > .container .config-value-back{ 788 | width: 100%; 789 | } 790 | main > .configuration > .container .textarea{ 791 | width: 100%; 792 | box-sizing: border-box; 793 | height: 200px; 794 | padding: 5px; 795 | resize: none; 796 | font-size: 15px; 797 | } 798 | main > .configuration > .container .textarea:focus{ 799 | padding: 4px; 800 | border: 2px solid rgb(54, 134, 190); 801 | outline: 0; 802 | } 803 | main > .configuration > .container .config-item .description{ 804 | text-align: left; 805 | } 806 | main > .configuration > .container .action-line{ 807 | padding: 10px; 808 | text-align: right; 809 | } 810 | main > .configuration > .container .action-line .reset{ 811 | color: rgb(244, 67, 54); 812 | } 813 | main > .configuration > .container .action-line .save{ 814 | color: rgb(54, 134, 190); 815 | } 816 | -------------------------------------------------------------------------------- /spirit/management/sha.js: -------------------------------------------------------------------------------- 1 | /* 2 | A JavaScript implementation of the SHA family of hashes, as 3 | defined in FIPS PUB 180-2 as well as the corresponding HMAC implementation 4 | as defined in FIPS PUB 198a 5 | 6 | Copyright Brian Turek 2008-2014 7 | Distributed under the BSD License 8 | See http://caligatio.github.com/jsSHA/ for more information 9 | 10 | Several functions taken from Paul Johnston 11 | */ 12 | 'use strict';(function(U){function y(a,c,b){var e=0,g=[0],h="",k=null,h=b||"UTF8";if("UTF8"!==h&&"UTF16"!==h)throw"encoding must be UTF8 or UTF16";if("HEX"===c){if(0!==a.length%2)throw"srcString of HEX type must be in byte increments";k=C(a);e=k.binLen;g=k.value}else if("TEXT"===c)k=L(a,h),e=k.binLen,g=k.value;else if("B64"===c)k=M(a),e=k.binLen,g=k.value;else if("BYTES"===c)k=N(a),e=k.binLen,g=k.value;else throw"inputFormat must be HEX, TEXT, B64, or BYTES";this.getHash=function(a,c,b,h){var k=null, 13 | d=g.slice(),n=e,m;3===arguments.length?"number"!==typeof b&&(h=b,b=1):2===arguments.length&&(b=1);if(b!==parseInt(b,10)||1>b)throw"numRounds must a integer >= 1";switch(c){case "HEX":k=O;break;case "B64":k=P;break;case "BYTES":k=Q;break;default:throw"format must be HEX, B64, or BYTES";}if("SHA-1"===a)for(m=0;mm/8&&(d[b]&=4294967040);for(n=0;n<=b;n+=1)p[n]=d[n]^909522486,s[n]=d[n]^1549556828;c="SHA-1"===c?z(s.concat(z(p.concat(g),a+e)),a+r):w(s.concat(w(p.concat(g),a+e,c)),a+r,c);return k(c,R(u))}} 16 | function u(a,c){this.a=a;this.b=c}function L(a,c){var b=[],e,g=[],h=0,k;if("UTF8"===c)for(k=0;ke?g.push(e):2048>e?(g.push(192|e>>>6),g.push(128|e&63)):55296>e||57344<=e?g.push(224|e>>>12,128|e>>>6&63,128|e&63):(k+=1,e=65536+((e&1023)<<10|a.charCodeAt(k)&1023),g.push(240|e>>>18,128|e>>>12&63,128|e>>>6&63,128|e&63)),e=0;e>>2)+1>b.length&&b.push(0),b[h>>>2]|=g[e]<<24-h%4*8,h+=1;else if("UTF16"===c)for(k=0;k>>2)+1>b.length&& 17 | b.push(0),b[h>>>2]|=a.charCodeAt(k)<<16-h%4*8,h+=2;return{value:b,binLen:8*h}}function C(a){var c=[],b=a.length,e,g;if(0!==b%2)throw"String of HEX type must be in byte increments";for(e=0;e>>3]|=g<<24-e%8*4}return{value:c,binLen:4*b}}function N(a){var c=[],b,e;for(e=0;e>>2)+1>c.length&&c.push(0),c[e>>>2]|=b<<24-e%4*8;return{value:c,binLen:8*a.length}}function M(a){var c= 18 | [],b=0,e,g,h,k,t;if(-1===a.search(/^[a-zA-Z0-9=+\/]+$/))throw"Invalid character in base-64 string";e=a.indexOf("=");a=a.replace(/\=/g,"");if(-1!==e&&e -1) ? upcased : method 201 | } 202 | 203 | function Request(url, options) { 204 | options = options || {} 205 | this.url = url 206 | 207 | this.credentials = options.credentials || 'omit' 208 | this.headers = new Headers(options.headers) 209 | this.method = normalizeMethod(options.method || 'GET') 210 | this.mode = options.mode || null 211 | this.referrer = null 212 | 213 | if ((this.method === 'GET' || this.method === 'HEAD') && options.body) { 214 | throw new TypeError('Body not allowed for GET or HEAD requests') 215 | } 216 | this._initBody(options.body) 217 | } 218 | 219 | function decode(body) { 220 | var form = new FormData() 221 | body.trim().split('&').forEach(function(bytes) { 222 | if (bytes) { 223 | var split = bytes.split('=') 224 | var name = split.shift().replace(/\+/g, ' ') 225 | var value = split.join('=').replace(/\+/g, ' ') 226 | form.append(decodeURIComponent(name), decodeURIComponent(value)) 227 | } 228 | }) 229 | return form 230 | } 231 | 232 | function headers(xhr) { 233 | var head = new Headers() 234 | var pairs = xhr.getAllResponseHeaders().trim().split('\n') 235 | pairs.forEach(function(header) { 236 | var split = header.trim().split(':') 237 | var key = split.shift().trim() 238 | var value = split.join(':').trim() 239 | head.append(key, value) 240 | }) 241 | return head 242 | } 243 | 244 | Body.call(Request.prototype) 245 | 246 | function Response(bodyInit, options) { 247 | if (!options) { 248 | options = {} 249 | } 250 | 251 | this._initBody(bodyInit) 252 | this.type = 'default' 253 | this.url = null 254 | this.status = options.status 255 | this.ok = this.status >= 200 && this.status < 300 256 | this.statusText = options.statusText 257 | this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) 258 | this.url = options.url || '' 259 | } 260 | 261 | Body.call(Response.prototype) 262 | 263 | self.Headers = Headers; 264 | self.Request = Request; 265 | self.Response = Response; 266 | 267 | self.fetch = function(input, init) { 268 | // TODO: Request constructor should accept input, init 269 | var request 270 | if (Request.prototype.isPrototypeOf(input) && !init) { 271 | request = input 272 | } else { 273 | request = new Request(input, init) 274 | } 275 | 276 | return new Promise(function(resolve, reject) { 277 | var xhr = new XMLHttpRequest() 278 | 279 | function responseURL() { 280 | if ('responseURL' in xhr) { 281 | return xhr.responseURL 282 | } 283 | 284 | // Avoid security warnings on getResponseHeader when not allowed by CORS 285 | if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { 286 | return xhr.getResponseHeader('X-Request-URL') 287 | } 288 | 289 | return; 290 | } 291 | 292 | xhr.onload = function() { 293 | var status = (xhr.status === 1223) ? 204 : xhr.status 294 | if (status < 100 || status > 599) { 295 | reject(new TypeError('Network request failed')) 296 | return 297 | } 298 | var options = { 299 | status: status, 300 | statusText: xhr.statusText, 301 | headers: headers(xhr), 302 | url: responseURL() 303 | } 304 | var body = 'response' in xhr ? xhr.response : xhr.responseText; 305 | resolve(new Response(body, options)) 306 | } 307 | 308 | xhr.onerror = function() { 309 | reject(new TypeError('Network request failed')) 310 | } 311 | 312 | xhr.open(request.method, request.url, true) 313 | 314 | if (request.credentials === 'include') { 315 | xhr.withCredentials = true 316 | } 317 | 318 | if ('responseType' in xhr && support.blob) { 319 | xhr.responseType = 'blob' 320 | } 321 | 322 | request.headers.forEach(function(value, name) { 323 | xhr.setRequestHeader(name, value) 324 | }) 325 | 326 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) 327 | }) 328 | } 329 | self.fetch.polyfill = true 330 | })(); 331 | -------------------------------------------------------------------------------- /spirit/nutrition/summernight/highlight.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#f0f0f0;-webkit-text-size-adjust:none}.hljs,.hljs-subst,.hljs-tag .hljs-title,.nginx .hljs-title{color:black}.hljs-string,.hljs-title,.hljs-constant,.hljs-parent,.hljs-tag .hljs-value,.hljs-rule .hljs-value,.hljs-preprocessor,.hljs-pragma,.hljs-name,.haml .hljs-symbol,.ruby .hljs-symbol,.ruby .hljs-symbol .hljs-string,.hljs-template_tag,.django .hljs-variable,.smalltalk .hljs-class,.hljs-addition,.hljs-flow,.hljs-stream,.bash .hljs-variable,.pf .hljs-variable,.apache .hljs-tag,.apache .hljs-cbracket,.tex .hljs-command,.tex .hljs-special,.erlang_repl .hljs-function_or_atom,.asciidoc .hljs-header,.markdown .hljs-header,.coffeescript .hljs-attribute{color:#800}.smartquote,.hljs-comment,.hljs-annotation,.diff .hljs-header,.hljs-chunk,.asciidoc .hljs-blockquote,.markdown .hljs-blockquote{color:#888}.hljs-number,.hljs-date,.hljs-regexp,.hljs-literal,.hljs-hexcolor,.smalltalk .hljs-symbol,.smalltalk .hljs-char,.go .hljs-constant,.hljs-change,.lasso .hljs-variable,.makefile .hljs-variable,.asciidoc .hljs-bullet,.markdown .hljs-bullet,.asciidoc .hljs-link_url,.markdown .hljs-link_url{color:#080}.hljs-label,.hljs-javadoc,.ruby .hljs-string,.hljs-decorator,.hljs-filter .hljs-argument,.hljs-localvars,.hljs-array,.hljs-attr_selector,.hljs-important,.hljs-pseudo,.hljs-pi,.haml .hljs-bullet,.hljs-doctype,.hljs-deletion,.hljs-envvar,.hljs-shebang,.apache .hljs-sqbracket,.nginx .hljs-built_in,.tex .hljs-formula,.erlang_repl .hljs-reserved,.hljs-prompt,.asciidoc .hljs-link_label,.markdown .hljs-link_label,.vhdl .hljs-attribute,.clojure .hljs-attribute,.asciidoc .hljs-attribute,.lasso .hljs-attribute,.coffeescript .hljs-property,.hljs-phony{color:#88f}.hljs-keyword,.hljs-id,.hljs-title,.hljs-built_in,.css .hljs-tag,.hljs-javadoctag,.hljs-phpdoc,.hljs-dartdoc,.hljs-yardoctag,.smalltalk .hljs-class,.hljs-winutils,.bash .hljs-variable,.pf .hljs-variable,.apache .hljs-tag,.hljs-type,.hljs-typename,.tex .hljs-command,.asciidoc .hljs-strong,.markdown .hljs-strong,.hljs-request,.hljs-status{font-weight:bold}.asciidoc .hljs-emphasis,.markdown .hljs-emphasis{font-style:italic}.nginx .hljs-built_in{font-weight:normal}.coffeescript .javascript,.javascript .xml,.lasso .markup,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata{opacity:0.5} -------------------------------------------------------------------------------- /spirit/nutrition/summernight/model.css: -------------------------------------------------------------------------------- 1 | /*-- 2 | 3 | Copyright 2015 Futur Solo 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain 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, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | --*/ 18 | html, body{ 19 | margin: 0; 20 | padding: 0; 21 | height: 100%; 22 | font-family: Oxygen, Verdana, Tahoma, "Noto Sans CJK SC", "Noto Sans CJK JP", "Microsoft YaHei", STHeiti, SimHei !important; 23 | background-repeat: no-repeat; 24 | font-size: 15px; 25 | color: rgb(100, 100, 100); 26 | word-break: break-all; 27 | } 28 | body{ 29 | background-size: cover; 30 | background-position: top right; 31 | background-attachment: fixed; 32 | } 33 | a{ 34 | color: rgb(54, 134, 190); 35 | text-decoration: none; 36 | font-weight: normal; 37 | } 38 | .load-back{ 39 | height: 0; 40 | width: 0; 41 | z-index: 999; 42 | position: fixed; 43 | overflow: hidden; 44 | opacity: 0; 45 | transition: opacity 0.30s ease-in-out; 46 | background-size: cover; 47 | background-position: top right; 48 | background-attachment: fixed; 49 | } 50 | .load-back.visible{ 51 | opacity: 1; 52 | } 53 | .load{ 54 | height: 0; 55 | width: 0; 56 | z-index: 1000; 57 | display: table; 58 | position: fixed; 59 | overflow: hidden; 60 | opacity: 0; 61 | transition: opacity 0.30s ease-in-out; 62 | background-color: rgba(0, 0, 0, 0.4); 63 | } 64 | .load > .container{ 65 | display: table-cell; 66 | vertical-align: middle; 67 | text-align: center; 68 | } 69 | .load.visible{ 70 | opacity: 1; 71 | } 72 | .load .failed{ 73 | z-index: 1001; 74 | } 75 | .layout{ 76 | height: 100%; 77 | width: 100%; 78 | overflow-y: auto; 79 | } 80 | header{ 81 | color: rgb(255, 255, 255); 82 | margin-left: auto; 83 | margin-right: auto; 84 | } 85 | header > nav{ 86 | line-height: 60px; 87 | position: relative; 88 | } 89 | header > nav .title{ 90 | display: inline-block; 91 | font-size: 20px; 92 | padding-left: 20px; 93 | padding-right: 20px; 94 | box-sizing: border-box; 95 | } 96 | header > nav .title .title-link{ 97 | color: rgb(255, 255, 255); 98 | text-decoration: none; 99 | font-weight: normal; 100 | } 101 | header > nav .description{ 102 | display: inline-block; 103 | font-size: 15px; 104 | box-sizing: border-box; 105 | } 106 | body.small header > nav .description{ 107 | display: block; 108 | padding-left: 20px; 109 | padding-right: 20px; 110 | margin-left: auto; 111 | margin-right: auto; 112 | } 113 | header > nav .links{ 114 | position: absolute; 115 | top: 0; 116 | right: 0; 117 | padding-left: 10px; 118 | padding-right: 10px; 119 | } 120 | body.small header > nav .links{ 121 | position: static; 122 | margin-left: auto; 123 | margin-right: auto; 124 | text-align: center; 125 | } 126 | header > nav .links > a > .item{ 127 | min-width: 40px; 128 | color: rgb(255, 255, 255); 129 | font-family: Oxygen, Verdana, Tahoma, "Noto Sans CJK SC", "Noto Sans CJK JP", "Microsoft YaHei", STHeiti, SimHei !important; 130 | } 131 | header > nav .links > a > .item .mdl-ripple{ 132 | background-color: rgb(255, 255, 255); 133 | } 134 | header > nav .links > a > .item:hover{ 135 | background-color: rgba(255, 255, 255, 0.1); 136 | } 137 | header > .tabs{ 138 | height: 60px; 139 | line-height: 60px; 140 | padding-left: 5px; 141 | padding-right: 5px; 142 | } 143 | body.small header > .tabs{ 144 | text-align: center; 145 | } 146 | header > .tabs .item{ 147 | display: inline-block; 148 | color: rgb(255, 255, 255); 149 | font-family: Oxygen, Verdana, Tahoma, "Noto Sans CJK SC", "Noto Sans CJK JP", "Microsoft YaHei", STHeiti, SimHei !important; 150 | } 151 | 152 | header > .tabs .item .mdl-ripple{ 153 | background-color: rgb(255, 255, 255); 154 | } 155 | header > .tabs .item:hover{ 156 | background-color: rgba(255, 255, 255, 0.1); 157 | } 158 | .main{ 159 | min-height: calc(100% - 210px); 160 | } 161 | .main > .content{ 162 | display: none; 163 | } 164 | .main > .content.current{ 165 | display: block; 166 | } 167 | .main > .content .card{ 168 | border-radius: 3px; 169 | width: calc(100% - 20px); 170 | max-width: 1000px; 171 | background-color: rgba(255, 255, 255, 0.8); 172 | margin-left: auto; 173 | margin-right: auto; 174 | box-shadow: 0 4px 8px 0 rgb(100, 100, 100); 175 | box-sizing: border-box; 176 | margin-top: 15px; 177 | margin-bottom: 15px; 178 | } 179 | .main > .content .card .title{ 180 | line-height: 25px; 181 | font-size: 25px; 182 | padding-top: 20px; 183 | padding-left: 20px; 184 | padding-right: 20px; 185 | } 186 | .main > .content .card .title a{ 187 | color: rgb(100, 100, 100); 188 | text-decoration: none; 189 | font-weight: normal; 190 | } 191 | .main > .content .card .card-info{ 192 | line-height: 30px; 193 | font-size: 12px; 194 | color: rgb(150, 150, 150); 195 | padding-left: 20px; 196 | padding-right: 20px; 197 | padding-bottom: 10px; 198 | border-bottom: 1px solid rgb(200, 200, 200); 199 | } 200 | .main > .content .card .card-info .content{ 201 | display: inline-block; 202 | vertical-align: middle; 203 | } 204 | .main > .content .card .card-info .author-avatar{ 205 | width: 26px; 206 | height: 26px; 207 | margin: 2px; 208 | border-radius: 500px; 209 | background-size: cover; 210 | } 211 | .main > .content .card > .content{ 212 | padding: 20px; 213 | position: relative; 214 | } 215 | .main > .content .card > .content *{ 216 | max-width: 100%; 217 | } 218 | .main > .content .card > .more{ 219 | padding-left: 20px; 220 | padding-right: 20px; 221 | padding-bottom: 20px; 222 | } 223 | .main > .writing > .replies{ 224 | max-width: 1020px; 225 | width: 100%; 226 | margin-left: auto; 227 | margin-right: auto; 228 | position: relative; 229 | margin-bottom: 15px; 230 | } 231 | .main > .writing > .replies:after{ 232 | content:"."; 233 | display:block; 234 | height:0; 235 | visibility:hidden; 236 | clear:both; 237 | } 238 | .main > .writing > .replies > .reply-card{ 239 | border-radius: 3px; 240 | width: calc(50% - 20px); 241 | max-width: 500px; 242 | background-color: rgba(255, 255, 255, 0.8); 243 | box-shadow: 0 4px 8px 0 rgb(100, 100, 100); 244 | box-sizing: border-box; 245 | margin: 10px; 246 | } 247 | body.small .main > .writing > .replies > .reply-card{ 248 | border-radius: 3px; 249 | width: calc(100% - 20px); 250 | max-width: 1000px; 251 | position: static; 252 | } 253 | .main > .writing > .replies > .reply-card.left{ 254 | float: left; 255 | } 256 | .main > .writing > .replies > .reply-card.right{ 257 | float: right; 258 | } 259 | .main > .writing > .replies > .reply-card .avatar{ 260 | width: 50px; 261 | height: 50px; 262 | margin-top: 15px; 263 | margin-left: 20px; 264 | border-radius: 500px; 265 | background-size: cover; 266 | display: inline-block; 267 | vertical-align: middle; 268 | } 269 | .main > .writing > .replies > .reply-card .reply-info{ 270 | font-size: 15px; 271 | line-height: 25px; 272 | margin-left: 5px; 273 | margin-right: 20px; 274 | margin-top: 15px; 275 | display: inline-block; 276 | vertical-align: middle; 277 | color: rgb(150, 150, 150); 278 | } 279 | .main > .writing > .replies > .reply-card .reply-info .name a{ 280 | text-decoration: none; 281 | color: rgb(150, 150, 150); 282 | line-height: 25px; 283 | vertical-align: top; 284 | } 285 | .main > .writing > .replies > .reply-card .reply-info .name .master{ 286 | font-size: 18px; 287 | line-height: 25px; 288 | text-align: left; 289 | color: rgb(3, 169, 244); 290 | height: 25px; 291 | vertical-align: top; 292 | cursor: default; 293 | } 294 | .main > .writing > .replies > .reply-card > .content{ 295 | padding-top: 10px; 296 | padding-left: 20px; 297 | padding-right: 20px; 298 | padding-bottom: 15px; 299 | position: relative; 300 | } 301 | .main > .writing > .replies > .reply-card > .content *{ 302 | max-width: 100%; 303 | } 304 | .main > .writing > .replies > .reply-card.new-reply .title{ 305 | padding-top: 15px; 306 | font-size: 20px; 307 | padding-left: 20px; 308 | padding-right: 20px; 309 | } 310 | .main > .writing > .replies > .reply-card.new-reply .error{ 311 | padding-top: 5px; 312 | height: 15px; 313 | font-size: 15px; 314 | text-align: center; 315 | color: rgb(244, 67, 54); 316 | } 317 | .main > .writing > .replies > .reply-card.new-reply .input-back{ 318 | width: calc(100% - 40px); 319 | margin-left: 20px; 320 | margin-right: 20px; 321 | } 322 | .main > .writing > .replies > .reply-card.new-reply .content{ 323 | width: calc(100% - 40px); 324 | box-sizing: border-box; 325 | resize: none; 326 | margin-left: 20px; 327 | margin-right: 20px; 328 | margin-top: 15px; 329 | padding: 5px; 330 | background-color: rgba(255, 255, 255, 0); 331 | height: 200px; 332 | font-size: 15px; 333 | transition: background 0.20s ease-in-out; 334 | border: 1px solid rgb(150, 150, 150); 335 | } 336 | .main > .writing > .replies > .reply-card.new-reply .content:focus{ 337 | outline: 0; 338 | border: 2px solid rgb(33,150,243); 339 | padding: 4px; 340 | background-color: rgba(255, 255, 255, 1); 341 | } 342 | .main > .writing > .replies > .reply-card.new-reply .submit{ 343 | margin-top: 15px; 344 | margin-bottom: 15px; 345 | margin-left: 20px; 346 | margin-right: 20px; 347 | } 348 | .main > .not-found .card{ 349 | padding-top: 20px; 350 | padding-bottom: 20px; 351 | } 352 | .main > .not-found .card .line{ 353 | font-family: Oxygen, Verdana, Tahoma, "Noto Sans CJK SC", "Noto Sans CJK JP", "Microsoft YaHei", STHeiti, SimHei !important; 354 | text-align: center; 355 | font-size: 25px; 356 | line-height: 50px; 357 | font-weight: bold; 358 | } 359 | footer{ 360 | background-color: rgba(255, 255, 255, 0.8); 361 | width: 100%; 362 | display: -webkit-flex; 363 | display: flex; 364 | line-height: 60px; 365 | box-sizing: border-box; 366 | padding-left: 20px; 367 | padding-right: 20px; 368 | } 369 | body.small footer{ 370 | display: block; 371 | } 372 | footer .item{ 373 | height: 60px; 374 | display: inline-block; 375 | padding-left: 10px; 376 | padding-right: 10px; 377 | } 378 | footer .first{ 379 | height: 60px; 380 | flex-grow: 1; 381 | } 382 | footer .second{ 383 | height: 60px; 384 | flex-grow: 1; 385 | text-align: right; 386 | } 387 | body.small footer .first{ 388 | width: 100%; 389 | flex-grow: 0; 390 | text-align: center; 391 | } 392 | body.small footer .second{ 393 | width: 100%; 394 | flex-grow: 0; 395 | text-align: center; 396 | } 397 | -------------------------------------------------------------------------------- /spirit/nutrition/summernight/natsu-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/futursolo/furtherland/33ead7d4e651ed3154c8047e3bdc4bb2871e4468/spirit/nutrition/summernight/natsu-background.jpg -------------------------------------------------------------------------------- /spirit/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /management/ 3 | Disallow: /spirit/ 4 | --------------------------------------------------------------------------------