├── .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 |
42 |
66 |
67 | 登录
68 |
69 |
70 |
71 |
72 |
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 |
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 |
84 |
85 |
将文件拖动到这里来上传 或
86 |
87 | 选择文件
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | 作为图片插入
96 | 作为链接插入
97 | 取消
98 |
99 |
100 |
101 |
102 |
103 |
104 |
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 |
63 |
64 |
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>2]|=(k>>>16-8*h&255)<<24-b%4*8,b+=1}return{value:c,binLen:8*b}}function O(a,c){var b="",e=4*a.length,g,h;
19 | for(g=0;g>>2]>>>8*(3-g%4),b+="0123456789abcdef".charAt(h>>>4&15)+"0123456789abcdef".charAt(h&15);return c.outputUpper?b.toUpperCase():b}function P(a,c){var b="",e=4*a.length,g,h,k;for(g=0;g>>2]>>>8*(3-g%4)&255)<<16|(a[g+1>>>2]>>>8*(3-(g+1)%4)&255)<<8|a[g+2>>>2]>>>8*(3-(g+2)%4)&255,h=0;4>h;h+=1)b=8*g+6*h<=32*a.length?b+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(k>>>6*(3-h)&63):b+c.b64Pad;return b}function Q(a){var c="",b=4*a.length,e,
20 | g;for(e=0;e>>2]>>>8*(3-e%4)&255,c+=String.fromCharCode(g);return c}function R(a){var c={outputUpper:!1,b64Pad:"="};try{a.hasOwnProperty("outputUpper")&&(c.outputUpper=a.outputUpper),a.hasOwnProperty("b64Pad")&&(c.b64Pad=a.b64Pad)}catch(b){}if("boolean"!==typeof c.outputUpper)throw"Invalid outputUpper formatting option";if("string"!==typeof c.b64Pad)throw"Invalid b64Pad formatting option";return c}function x(a,c){return a<>>32-c}function p(a,c){return a>>>c|a<<32-c}function s(a,
21 | c){var b=null,b=new u(a.a,a.b);return b=32>=c?new u(b.a>>>c|b.b<<32-c&4294967295,b.b>>>c|b.a<<32-c&4294967295):new u(b.b>>>c-32|b.a<<64-c&4294967295,b.a>>>c-32|b.b<<64-c&4294967295)}function S(a,c){var b=null;return b=32>=c?new u(a.a>>>c,a.b>>>c|a.a<<32-c&4294967295):new u(0,a.a>>>c-32)}function V(a,c,b){return a&c^~a&b}function W(a,c,b){return new u(a.a&c.a^~a.a&b.a,a.b&c.b^~a.b&b.b)}function T(a,c,b){return a&c^a&b^c&b}function X(a,c,b){return new u(a.a&c.a^a.a&b.a^c.a&b.a,a.b&c.b^a.b&b.b^c.b&b.b)}
22 | function Y(a){return p(a,2)^p(a,13)^p(a,22)}function Z(a){var c=s(a,28),b=s(a,34);a=s(a,39);return new u(c.a^b.a^a.a,c.b^b.b^a.b)}function $(a){return p(a,6)^p(a,11)^p(a,25)}function aa(a){var c=s(a,14),b=s(a,18);a=s(a,41);return new u(c.a^b.a^a.a,c.b^b.b^a.b)}function ba(a){return p(a,7)^p(a,18)^a>>>3}function ca(a){var c=s(a,1),b=s(a,8);a=S(a,7);return new u(c.a^b.a^a.a,c.b^b.b^a.b)}function da(a){return p(a,17)^p(a,19)^a>>>10}function ea(a){var c=s(a,19),b=s(a,61);a=S(a,6);return new u(c.a^b.a^
23 | a.a,c.b^b.b^a.b)}function A(a,c){var b=(a&65535)+(c&65535);return((a>>>16)+(c>>>16)+(b>>>16)&65535)<<16|b&65535}function fa(a,c,b,e){var g=(a&65535)+(c&65535)+(b&65535)+(e&65535);return((a>>>16)+(c>>>16)+(b>>>16)+(e>>>16)+(g>>>16)&65535)<<16|g&65535}function D(a,c,b,e,g){var h=(a&65535)+(c&65535)+(b&65535)+(e&65535)+(g&65535);return((a>>>16)+(c>>>16)+(b>>>16)+(e>>>16)+(g>>>16)+(h>>>16)&65535)<<16|h&65535}function ga(a,c){var b,e,g;b=(a.b&65535)+(c.b&65535);e=(a.b>>>16)+(c.b>>>16)+(b>>>16);g=(e&65535)<<
24 | 16|b&65535;b=(a.a&65535)+(c.a&65535)+(e>>>16);e=(a.a>>>16)+(c.a>>>16)+(b>>>16);return new u((e&65535)<<16|b&65535,g)}function ha(a,c,b,e){var g,h,k;g=(a.b&65535)+(c.b&65535)+(b.b&65535)+(e.b&65535);h=(a.b>>>16)+(c.b>>>16)+(b.b>>>16)+(e.b>>>16)+(g>>>16);k=(h&65535)<<16|g&65535;g=(a.a&65535)+(c.a&65535)+(b.a&65535)+(e.a&65535)+(h>>>16);h=(a.a>>>16)+(c.a>>>16)+(b.a>>>16)+(e.a>>>16)+(g>>>16);return new u((h&65535)<<16|g&65535,k)}function ia(a,c,b,e,g){var h,k,t;h=(a.b&65535)+(c.b&65535)+(b.b&65535)+(e.b&
25 | 65535)+(g.b&65535);k=(a.b>>>16)+(c.b>>>16)+(b.b>>>16)+(e.b>>>16)+(g.b>>>16)+(h>>>16);t=(k&65535)<<16|h&65535;h=(a.a&65535)+(c.a&65535)+(b.a&65535)+(e.a&65535)+(g.a&65535)+(k>>>16);k=(a.a>>>16)+(c.a>>>16)+(b.a>>>16)+(e.a>>>16)+(g.a>>>16)+(h>>>16);return new u((k&65535)<<16|h&65535,t)}function z(a,c){var b=[],e,g,h,k,t,u,p,q,s,d=[1732584193,4023233417,2562383102,271733878,3285377520];a[c>>>5]|=128<<24-c%32;a[(c+65>>>9<<4)+15]=c;s=a.length;for(p=0;pq;q+=1)b[q]=16>q?a[q+p]:x(b[q-3]^b[q-8]^b[q-14]^b[q-16],1),u=20>q?D(x(e,5),g&h^~g&k,t,1518500249,b[q]):40>q?D(x(e,5),g^h^k,t,1859775393,b[q]):60>q?D(x(e,5),T(g,h,k),t,2400959708,b[q]):D(x(e,5),g^h^k,t,3395469782,b[q]),t=k,k=h,h=x(g,30),g=e,e=u;d[0]=A(e,d[0]);d[1]=A(g,d[1]);d[2]=A(h,d[2]);d[3]=A(k,d[3]);d[4]=A(t,d[4])}return d}function w(a,c,b){var e,g,h,k,t,p,s,q,w,d,n,m,r,x,y,v,z,E,F,G,H,I,J,K,f,B=[],C,l=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,
27 | 3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,
28 | 1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];d=[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428];g=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225];if("SHA-224"===b||"SHA-256"===b)n=64,e=(c+65>>>9<<4)+15,x=16,y=1,f=Number,v=A,z=fa,E=D,F=ba,G=da,H=Y,I=$,K=T,J=V,d="SHA-224"===b?d:g;else if("SHA-384"===b||"SHA-512"===b)n=80,e=(c+128>>>10<<5)+31,x=32,y=2,f=u,v=ga,z=ha,E=
29 | ia,F=ca,G=ea,H=Z,I=aa,K=X,J=W,l=[new f(l[0],3609767458),new f(l[1],602891725),new f(l[2],3964484399),new f(l[3],2173295548),new f(l[4],4081628472),new f(l[5],3053834265),new f(l[6],2937671579),new f(l[7],3664609560),new f(l[8],2734883394),new f(l[9],1164996542),new f(l[10],1323610764),new f(l[11],3590304994),new f(l[12],4068182383),new f(l[13],991336113),new f(l[14],633803317),new f(l[15],3479774868),new f(l[16],2666613458),new f(l[17],944711139),new f(l[18],2341262773),new f(l[19],2007800933),new f(l[20],
30 | 1495990901),new f(l[21],1856431235),new f(l[22],3175218132),new f(l[23],2198950837),new f(l[24],3999719339),new f(l[25],766784016),new f(l[26],2566594879),new f(l[27],3203337956),new f(l[28],1034457026),new f(l[29],2466948901),new f(l[30],3758326383),new f(l[31],168717936),new f(l[32],1188179964),new f(l[33],1546045734),new f(l[34],1522805485),new f(l[35],2643833823),new f(l[36],2343527390),new f(l[37],1014477480),new f(l[38],1206759142),new f(l[39],344077627),new f(l[40],1290863460),new f(l[41],
31 | 3158454273),new f(l[42],3505952657),new f(l[43],106217008),new f(l[44],3606008344),new f(l[45],1432725776),new f(l[46],1467031594),new f(l[47],851169720),new f(l[48],3100823752),new f(l[49],1363258195),new f(l[50],3750685593),new f(l[51],3785050280),new f(l[52],3318307427),new f(l[53],3812723403),new f(l[54],2003034995),new f(l[55],3602036899),new f(l[56],1575990012),new f(l[57],1125592928),new f(l[58],2716904306),new f(l[59],442776044),new f(l[60],593698344),new f(l[61],3733110249),new f(l[62],2999351573),
32 | new f(l[63],3815920427),new f(3391569614,3928383900),new f(3515267271,566280711),new f(3940187606,3454069534),new f(4118630271,4000239992),new f(116418474,1914138554),new f(174292421,2731055270),new f(289380356,3203993006),new f(460393269,320620315),new f(685471733,587496836),new f(852142971,1086792851),new f(1017036298,365543100),new f(1126000580,2618297676),new f(1288033470,3409855158),new f(1501505948,4234509866),new f(1607167915,987167468),new f(1816402316,1246189591)],d="SHA-384"===b?[new f(3418070365,
33 | d[0]),new f(1654270250,d[1]),new f(2438529370,d[2]),new f(355462360,d[3]),new f(1731405415,d[4]),new f(41048885895,d[5]),new f(3675008525,d[6]),new f(1203062813,d[7])]:[new f(g[0],4089235720),new f(g[1],2227873595),new f(g[2],4271175723),new f(g[3],1595750129),new f(g[4],2917565137),new f(g[5],725511199),new f(g[6],4215389547),new f(g[7],327033209)];else throw"Unexpected error in SHA-2 implementation";a[c>>>5]|=128<<24-c%32;a[e]=c;C=a.length;for(m=0;mr?new f(a[r*y+m],a[r*y+m+1]):z(G(B[r-2]),B[r-7],F(B[r-15]),B[r-16]),q=E(s,I(k),J(k,t,p),l[r],B[r]),w=v(H(c),K(c,e,g)),s=p,p=t,t=k,k=v(h,q),h=g,g=e,e=c,c=v(q,w);d[0]=v(c,d[0]);d[1]=v(e,d[1]);d[2]=v(g,d[2]);d[3]=v(h,d[3]);d[4]=v(k,d[4]);d[5]=v(t,d[5]);d[6]=v(p,d[6]);d[7]=v(s,d[7])}if("SHA-224"===b)a=[d[0],d[1],d[2],d[3],d[4],d[5],d[6]];else if("SHA-256"===b)a=d;else if("SHA-384"===b)a=[d[0].a,d[0].b,d[1].a,d[1].b,d[2].a,d[2].b,d[3].a,d[3].b,d[4].a,d[4].b,
35 | d[5].a,d[5].b];else if("SHA-512"===b)a=[d[0].a,d[0].b,d[1].a,d[1].b,d[2].a,d[2].b,d[3].a,d[3].b,d[4].a,d[4].b,d[5].a,d[5].b,d[6].a,d[6].b,d[7].a,d[7].b];else throw"Unexpected error in SHA-2 implementation";return a}"function"===typeof define&&define.amd?define(function(){return y}):"undefined"!==typeof exports?"undefined"!==typeof module&&module.exports?module.exports=exports=y:exports=y:U.jsSHA=y})(this);
36 |
--------------------------------------------------------------------------------
/spirit/nutrition/summernight/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/futursolo/furtherland/33ead7d4e651ed3154c8047e3bdc4bb2871e4468/spirit/nutrition/summernight/avatar.jpg
--------------------------------------------------------------------------------
/spirit/nutrition/summernight/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/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 |
--------------------------------------------------------------------------------