@ -0,0 +1,3 @@ | |||||
[run] | |||||
source = bluelog | |||||
branch = true |
@ -0,0 +1,4 @@ | |||||
[flake8] | |||||
exclude = .git,*migrations* | |||||
max-line-length = 119 | |||||
max-complexity = 7 |
@ -0,0 +1,2 @@ | |||||
FLASK_APP=bluelog | |||||
FLASK_ENV=development |
@ -0,0 +1,112 @@ | |||||
# Byte-compiled / optimized / DLL files | |||||
__pycache__/ | |||||
*.py[cod] | |||||
*$py.class | |||||
# C extensions | |||||
*.so | |||||
# Distribution / packaging | |||||
.Python | |||||
build/ | |||||
develop-eggs/ | |||||
dist/ | |||||
downloads/ | |||||
eggs/ | |||||
.eggs/ | |||||
lib/ | |||||
lib64/ | |||||
parts/ | |||||
sdist/ | |||||
var/ | |||||
wheels/ | |||||
*.egg-info/ | |||||
.installed.cfg | |||||
*.egg | |||||
MANIFEST | |||||
# PyInstaller | |||||
# Usually these files are written by a python script from a template | |||||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | |||||
*.manifest | |||||
*.spec | |||||
# Installer logs | |||||
pip-log.txt | |||||
pip-delete-this-directory.txt | |||||
# Unit test / coverage reports | |||||
htmlcov/ | |||||
.tox/ | |||||
.coverage | |||||
.coverage.* | |||||
.cache | |||||
nosetests.xml | |||||
coverage.xml | |||||
*.cover | |||||
.hypothesis/ | |||||
.pytest_cache/ | |||||
# Translations | |||||
*.mo | |||||
*.pot | |||||
# Django stuff: | |||||
*.log | |||||
local_settings.py | |||||
db.sqlite3 | |||||
# Flask stuff: | |||||
instance/ | |||||
.webassets-cache | |||||
# Scrapy stuff: | |||||
.scrapy | |||||
# Sphinx documentation | |||||
docs/_build/ | |||||
# PyBuilder | |||||
target/ | |||||
# Jupyter Notebook | |||||
.ipynb_checkpoints | |||||
# pyenv | |||||
.python-version | |||||
# celery beat schedule file | |||||
celerybeat-schedule | |||||
# SageMath parsed files | |||||
*.sage.py | |||||
# Environments | |||||
.env | |||||
.venv | |||||
env/ | |||||
venv/ | |||||
ENV/ | |||||
env.bak/ | |||||
venv.bak/ | |||||
# Spyder project settings | |||||
.spyderproject | |||||
.spyproject | |||||
# Rope project settings | |||||
.ropeproject | |||||
# mkdocs documentation | |||||
/site | |||||
# mypy | |||||
.mypy_cache/ | |||||
# Development | |||||
*.db | |||||
.idea | |||||
logs/* | |||||
!logs/.gitkeep | |||||
uploads/* | |||||
!uploads/.gitkeep |
@ -0,0 +1,21 @@ | |||||
MIT License | |||||
Copyright (c) 2017 Grey Li | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
SOFTWARE. |
@ -0,0 +1,25 @@ | |||||
[[source]] | |||||
url = "https://pypi.org/simple" | |||||
verify_ssl = true | |||||
name = "pypi" | |||||
[dev-packages] | |||||
coverage = "*" | |||||
watchdog = "*" | |||||
"flake8" = "*" | |||||
faker = "*" | |||||
[packages] | |||||
flask = "*" | |||||
flask-ckeditor = "*" | |||||
flask-mail = "*" | |||||
flask-sqlalchemy = "*" | |||||
flask-wtf = "*" | |||||
flask-moment = "*" | |||||
python-dotenv = "*" | |||||
bootstrap-flask = "*" | |||||
flask-login = "*" | |||||
flask-debugtoolbar = "*" | |||||
gunicorn = "*" | |||||
"psycopg2" = "*" | |||||
flask-migrate = "*" |
@ -0,0 +1,369 @@ | |||||
{ | |||||
"_meta": { | |||||
"hash": { | |||||
"sha256": "b211c0ef54a1d431af2250e563c542fa0a957b3d009bd8005c0195ce56fd55e3" | |||||
}, | |||||
"pipfile-spec": 6, | |||||
"requires": {}, | |||||
"sources": [ | |||||
{ | |||||
"name": "pypi", | |||||
"url": "https://pypi.org/simple", | |||||
"verify_ssl": true | |||||
} | |||||
] | |||||
}, | |||||
"default": { | |||||
"alembic": { | |||||
"hashes": [ | |||||
"sha256:035ab00497217628bf5d0be82d664d8713ab13d37b630084da8e1f98facf4dbf" | |||||
], | |||||
"version": "==1.4.2" | |||||
}, | |||||
"blinker": { | |||||
"hashes": [ | |||||
"sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6" | |||||
], | |||||
"version": "==1.4" | |||||
}, | |||||
"bootstrap-flask": { | |||||
"hashes": [ | |||||
"sha256:d03c738f5377dbe7ecce010e28afcc0fb8373c4c9d01f8590af7657b53342c1d", | |||||
"sha256:ed817e82acadac4c8b3fc2d1a310325b6ed395d13b0f6ca9d14e1742f880e685" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==1.2.0" | |||||
}, | |||||
"click": { | |||||
"hashes": [ | |||||
"sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", | |||||
"sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" | |||||
], | |||||
"version": "==7.1.1" | |||||
}, | |||||
"flask": { | |||||
"hashes": [ | |||||
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", | |||||
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==1.1.2" | |||||
}, | |||||
"flask-ckeditor": { | |||||
"hashes": [ | |||||
"sha256:327ad60c3787d96cdc579ce04c284d33343802d3b7422ba998aadbbdee6756f1", | |||||
"sha256:8d91ee747473dc4e6e0dc166fd71b3136b7d388de757731c019fad835d776c8e" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==0.4.3" | |||||
}, | |||||
"flask-debugtoolbar": { | |||||
"hashes": [ | |||||
"sha256:0e9a80d4c599233c68376e81cc99976200b5ac5248cfb24f18935cc5b69ac5b3", | |||||
"sha256:3c4e79d354ede014e6657c545a536d4fb273cc89e3fd6b4835b02e346dd3aab4" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==0.11.0" | |||||
}, | |||||
"flask-login": { | |||||
"hashes": [ | |||||
"sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b", | |||||
"sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==0.5.0" | |||||
}, | |||||
"flask-mail": { | |||||
"hashes": [ | |||||
"sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==0.9.1" | |||||
}, | |||||
"flask-migrate": { | |||||
"hashes": [ | |||||
"sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732", | |||||
"sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==2.5.3" | |||||
}, | |||||
"flask-moment": { | |||||
"hashes": [ | |||||
"sha256:3c509afa25fd77459c9d799f292dcea384b1cd588ed1fd68f97f6fda6131299e", | |||||
"sha256:3e8b88f99af7cf75f2f29ef9d8c158eb92ca6f3c1ba6456ad70f715efa6eb7f7" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==0.9.0" | |||||
}, | |||||
"flask-sqlalchemy": { | |||||
"hashes": [ | |||||
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327", | |||||
"sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==2.4.1" | |||||
}, | |||||
"flask-wtf": { | |||||
"hashes": [ | |||||
"sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2", | |||||
"sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==0.14.3" | |||||
}, | |||||
"gunicorn": { | |||||
"hashes": [ | |||||
"sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", | |||||
"sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==20.0.4" | |||||
}, | |||||
"itsdangerous": { | |||||
"hashes": [ | |||||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", | |||||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" | |||||
], | |||||
"version": "==1.1.0" | |||||
}, | |||||
"jinja2": { | |||||
"hashes": [ | |||||
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", | |||||
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" | |||||
], | |||||
"version": "==2.11.1" | |||||
}, | |||||
"mako": { | |||||
"hashes": [ | |||||
"sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d", | |||||
"sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9" | |||||
], | |||||
"version": "==1.1.2" | |||||
}, | |||||
"markupsafe": { | |||||
"hashes": [ | |||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", | |||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", | |||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", | |||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", | |||||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", | |||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", | |||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", | |||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", | |||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", | |||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", | |||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", | |||||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", | |||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", | |||||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", | |||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", | |||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", | |||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", | |||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", | |||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", | |||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", | |||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", | |||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", | |||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", | |||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", | |||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", | |||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", | |||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", | |||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", | |||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", | |||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", | |||||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", | |||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", | |||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" | |||||
], | |||||
"version": "==1.1.1" | |||||
}, | |||||
"psycopg2": { | |||||
"hashes": [ | |||||
"sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677", | |||||
"sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d", | |||||
"sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b", | |||||
"sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c", | |||||
"sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0", | |||||
"sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f", | |||||
"sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4", | |||||
"sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38", | |||||
"sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6", | |||||
"sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b", | |||||
"sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151", | |||||
"sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a", | |||||
"sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==2.8.4" | |||||
}, | |||||
"python-dateutil": { | |||||
"hashes": [ | |||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", | |||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" | |||||
], | |||||
"version": "==2.8.1" | |||||
}, | |||||
"python-dotenv": { | |||||
"hashes": [ | |||||
"sha256:81822227f771e0cab235a2939f0f265954ac4763cafd806d845801c863bf372f", | |||||
"sha256:92b3123fb2d58a284f76cc92bfe4ee6c502c32ded73e8b051c4f6afc8b6751ed" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==0.12.0" | |||||
}, | |||||
"python-editor": { | |||||
"hashes": [ | |||||
"sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", | |||||
"sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", | |||||
"sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8" | |||||
], | |||||
"version": "==1.0.4" | |||||
}, | |||||
"six": { | |||||
"hashes": [ | |||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", | |||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" | |||||
], | |||||
"version": "==1.14.0" | |||||
}, | |||||
"sqlalchemy": { | |||||
"hashes": [ | |||||
"sha256:c4cca4aed606297afbe90d4306b49ad3a4cd36feb3f87e4bfd655c57fd9ef445" | |||||
], | |||||
"version": "==1.3.15" | |||||
}, | |||||
"werkzeug": { | |||||
"hashes": [ | |||||
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", | |||||
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" | |||||
], | |||||
"version": "==1.0.1" | |||||
}, | |||||
"wtforms": { | |||||
"hashes": [ | |||||
"sha256:0cdbac3e7f6878086c334aa25dc5a33869a3954e9d1e015130d65a69309b3b61", | |||||
"sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1" | |||||
], | |||||
"version": "==2.2.1" | |||||
} | |||||
}, | |||||
"develop": { | |||||
"coverage": { | |||||
"hashes": [ | |||||
"sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0", | |||||
"sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30", | |||||
"sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b", | |||||
"sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0", | |||||
"sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823", | |||||
"sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe", | |||||
"sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037", | |||||
"sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6", | |||||
"sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31", | |||||
"sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd", | |||||
"sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892", | |||||
"sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1", | |||||
"sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78", | |||||
"sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac", | |||||
"sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006", | |||||
"sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014", | |||||
"sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2", | |||||
"sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7", | |||||
"sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8", | |||||
"sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7", | |||||
"sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9", | |||||
"sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1", | |||||
"sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307", | |||||
"sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a", | |||||
"sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435", | |||||
"sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0", | |||||
"sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5", | |||||
"sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441", | |||||
"sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732", | |||||
"sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de", | |||||
"sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==5.0.4" | |||||
}, | |||||
"entrypoints": { | |||||
"hashes": [ | |||||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", | |||||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" | |||||
], | |||||
"version": "==0.3" | |||||
}, | |||||
"faker": { | |||||
"hashes": [ | |||||
"sha256:2d3f866ef25e1a5af80e7b0ceeacc3c92dec5d0fdbad3e2cb6adf6e60b22188f", | |||||
"sha256:b89aa33837498498e15c709eb40c31386408a901a53c7a5e12a425737a767976" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==4.0.2" | |||||
}, | |||||
"flake8": { | |||||
"hashes": [ | |||||
"sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", | |||||
"sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==3.7.9" | |||||
}, | |||||
"mccabe": { | |||||
"hashes": [ | |||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", | |||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" | |||||
], | |||||
"version": "==0.6.1" | |||||
}, | |||||
"pathtools": { | |||||
"hashes": [ | |||||
"sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0" | |||||
], | |||||
"version": "==0.1.2" | |||||
}, | |||||
"pycodestyle": { | |||||
"hashes": [ | |||||
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", | |||||
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" | |||||
], | |||||
"version": "==2.5.0" | |||||
}, | |||||
"pyflakes": { | |||||
"hashes": [ | |||||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", | |||||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" | |||||
], | |||||
"version": "==2.1.1" | |||||
}, | |||||
"python-dateutil": { | |||||
"hashes": [ | |||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", | |||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" | |||||
], | |||||
"version": "==2.8.1" | |||||
}, | |||||
"six": { | |||||
"hashes": [ | |||||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", | |||||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" | |||||
], | |||||
"version": "==1.14.0" | |||||
}, | |||||
"text-unidecode": { | |||||
"hashes": [ | |||||
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", | |||||
"sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" | |||||
], | |||||
"version": "==1.3" | |||||
}, | |||||
"watchdog": { | |||||
"hashes": [ | |||||
"sha256:c560efb643faed5ef28784b2245cf8874f939569717a4a12826a173ac644456b" | |||||
], | |||||
"index": "pypi", | |||||
"version": "==0.10.2" | |||||
} | |||||
} | |||||
} |
@ -0,0 +1 @@ | |||||
web: gunicorn wsgi:app --log-file - |
@ -0,0 +1 @@ | |||||
web: flask run |
@ -1,3 +1,83 @@ | |||||
# bluelog | |||||
# Bluelog 安装 | |||||
博客系统 | |||||
**使用镜像直接看第4步** | |||||
**云数据库以删除,需要新建**(防止花费过高) | |||||
## 1,安装过程 | |||||
clone: | |||||
``` | |||||
$ git clone https://github.com/hsy77/bluelog.git | |||||
$ cd bluelog | |||||
``` | |||||
## 2,使用已有数据库 | |||||
## (使用新建数据库先跳转至3,再做2) | |||||
在云主机中安装依赖: | |||||
``` | |||||
(这两步可不做,即不用创建虚拟环境) | |||||
$ python3 -m venv env | |||||
$ source env/bin/activate | |||||
(需要在云主机上或者虚拟环境中运行,两个环境都可以,为了保证psycopg2的安装不报错) | |||||
$ sudo yum install postgresql-libs -y | |||||
$ sudo yum install postgresql-devel -y | |||||
$ sudo yum install gcc -y | |||||
(安装需要的包) | |||||
$ pip3 install -r requirements.txt | |||||
``` | |||||
生成数据并且运行: | |||||
``` | |||||
$ flask forge #将数据导入数据表中 | |||||
$ flask run -h 0.0.0.0 -p 80 #在0.0.0.0 80端口运行,为了使得使用外网ip可以访问 | |||||
* Running on http://0.0.0.0:80/ | |||||
``` | |||||
接下来就可以在网页上输入云主机外网ip访问 | |||||
Test account: | |||||
* username: `admin` | |||||
* password: `helloflask` | |||||
## 3, 使用新建云数据库 | |||||
- 将`bluelog\bluelog\settings.py` 中的以下四个参数改为自己数据库的设置 | |||||
 | |||||
# 4,从镜像中导入: | |||||
### - 先做步骤3,新建数据库 | |||||
### - 然后 | |||||
生成数据并且运行: | |||||
``` | |||||
$ flask forge #将数据导入数据表中 | |||||
$ flask run -h 0.0.0.0 -p 80 #在0.0.0.0 80端口运行,为了使得使用外网ip可以访问 | |||||
* Running on http://0.0.0.0:80/ | |||||
``` | |||||
接下来就可以在网页上输入云主机外网ip访问 | |||||
Test account: | |||||
* username: `admin` | |||||
* password: `helloflask` | |||||
## 安装成果 | |||||
 |
@ -0,0 +1,223 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
import logging | |||||
import os | |||||
from logging.handlers import SMTPHandler, RotatingFileHandler | |||||
import click | |||||
from flask import Flask, render_template, request | |||||
from flask_login import current_user | |||||
from flask_sqlalchemy import get_debug_queries | |||||
from flask_wtf.csrf import CSRFError | |||||
from bluelog.blueprints.admin import admin_bp | |||||
from bluelog.blueprints.auth import auth_bp | |||||
from bluelog.blueprints.blog import blog_bp | |||||
from bluelog.extensions import bootstrap, db, login_manager, csrf, ckeditor, mail, moment, toolbar, migrate | |||||
from bluelog.models import Admin, Post, Category, Comment, Link | |||||
from bluelog.settings import config | |||||
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) | |||||
def create_app(config_name=None): | |||||
if config_name is None: | |||||
config_name = os.getenv('FLASK_CONFIG', 'development') | |||||
app = Flask('bluelog') | |||||
app.config.from_object(config[config_name]) | |||||
register_logging(app) | |||||
register_extensions(app) | |||||
register_blueprints(app) | |||||
register_commands(app) | |||||
register_errors(app) | |||||
register_shell_context(app) | |||||
register_template_context(app) | |||||
register_request_handlers(app) | |||||
return app | |||||
def register_logging(app): | |||||
class RequestFormatter(logging.Formatter): | |||||
def format(self, record): | |||||
record.url = request.url | |||||
record.remote_addr = request.remote_addr | |||||
return super(RequestFormatter, self).format(record) | |||||
request_formatter = RequestFormatter( | |||||
'[%(asctime)s] %(remote_addr)s requested %(url)s\n' | |||||
'%(levelname)s in %(module)s: %(message)s' | |||||
) | |||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |||||
file_handler = RotatingFileHandler(os.path.join(basedir, 'logs/bluelog.log'), | |||||
maxBytes=10 * 1024 * 1024, backupCount=10) | |||||
file_handler.setFormatter(formatter) | |||||
file_handler.setLevel(logging.INFO) | |||||
mail_handler = SMTPHandler( | |||||
mailhost=app.config['MAIL_SERVER'], | |||||
fromaddr=app.config['MAIL_USERNAME'], | |||||
toaddrs=['ADMIN_EMAIL'], | |||||
subject='Bluelog Application Error', | |||||
credentials=(app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'])) | |||||
mail_handler.setLevel(logging.ERROR) | |||||
mail_handler.setFormatter(request_formatter) | |||||
if not app.debug: | |||||
app.logger.addHandler(mail_handler) | |||||
app.logger.addHandler(file_handler) | |||||
def register_extensions(app): | |||||
bootstrap.init_app(app) | |||||
db.init_app(app) | |||||
login_manager.init_app(app) | |||||
csrf.init_app(app) | |||||
ckeditor.init_app(app) | |||||
mail.init_app(app) | |||||
moment.init_app(app) | |||||
toolbar.init_app(app) | |||||
migrate.init_app(app, db) | |||||
def register_blueprints(app): | |||||
app.register_blueprint(blog_bp) | |||||
app.register_blueprint(admin_bp, url_prefix='/admin') | |||||
app.register_blueprint(auth_bp, url_prefix='/auth') | |||||
def register_shell_context(app): | |||||
@app.shell_context_processor | |||||
def make_shell_context(): | |||||
return dict(db=db, Admin=Admin, Post=Post, Category=Category, Comment=Comment) | |||||
def register_template_context(app): | |||||
@app.context_processor | |||||
def make_template_context(): | |||||
admin = Admin.query.first() | |||||
categories = Category.query.order_by(Category.name).all() | |||||
links = Link.query.order_by(Link.name).all() | |||||
if current_user.is_authenticated: | |||||
unread_comments = Comment.query.filter_by(reviewed=False).count() | |||||
else: | |||||
unread_comments = None | |||||
return dict( | |||||
admin=admin, categories=categories, | |||||
links=links, unread_comments=unread_comments) | |||||
def register_errors(app): | |||||
@app.errorhandler(400) | |||||
def bad_request(e): | |||||
return render_template('errors/400.html'), 400 | |||||
@app.errorhandler(404) | |||||
def page_not_found(e): | |||||
return render_template('errors/404.html'), 404 | |||||
@app.errorhandler(500) | |||||
def internal_server_error(e): | |||||
return render_template('errors/500.html'), 500 | |||||
@app.errorhandler(CSRFError) | |||||
def handle_csrf_error(e): | |||||
return render_template('errors/400.html', description=e.description), 400 | |||||
def register_commands(app): | |||||
@app.cli.command() | |||||
@click.option('--drop', is_flag=True, help='Create after drop.') | |||||
def initdb(drop): | |||||
"""Initialize the database.""" | |||||
if drop: | |||||
click.confirm('This operation will delete the database, do you want to continue?', abort=True) | |||||
db.drop_all() | |||||
click.echo('Drop tables.') | |||||
db.create_all() | |||||
click.echo('Initialized database.') | |||||
@app.cli.command() | |||||
@click.option('--username', prompt=True, help='The username used to login.') | |||||
@click.option('--password', prompt=True, hide_input=True, | |||||
confirmation_prompt=True, help='The password used to login.') | |||||
def init(username, password): | |||||
"""Building Bluelog, just for you.""" | |||||
click.echo('Initializing the database...') | |||||
db.create_all() | |||||
admin = Admin.query.first() | |||||
if admin is not None: | |||||
click.echo('The administrator already exists, updating...') | |||||
admin.username = username | |||||
admin.set_password(password) | |||||
else: | |||||
click.echo('Creating the temporary administrator account...') | |||||
admin = Admin( | |||||
username=username, | |||||
blog_title='Bluelog', | |||||
blog_sub_title="No, I'm the real thing.", | |||||
name='Admin', | |||||
about='Anything about you.' | |||||
) | |||||
admin.set_password(password) | |||||
db.session.add(admin) | |||||
category = Category.query.first() | |||||
if category is None: | |||||
click.echo('Creating the default category...') | |||||
category = Category(name='Default') | |||||
db.session.add(category) | |||||
db.session.commit() | |||||
click.echo('Done.') | |||||
@app.cli.command() | |||||
@click.option('--category', default=10, help='Quantity of categories, default is 10.') | |||||
@click.option('--post', default=15, help='Quantity of posts, default is 50.') | |||||
@click.option('--comment', default=500, help='Quantity of comments, default is 500.') | |||||
def forge(category, post, comment): | |||||
"""Generate fake data.""" | |||||
from bluelog.fakes import fake_admin, fake_categories, fake_posts, fake_comments, fake_links | |||||
db.drop_all() | |||||
db.create_all() | |||||
click.echo('Generating the administrator...') | |||||
fake_admin() | |||||
click.echo('Generating %d categories...' % category) | |||||
fake_categories(category) | |||||
click.echo('Generating %d posts...' % post) | |||||
fake_posts(post) | |||||
click.echo('Generating %d comments...' % comment) | |||||
fake_comments(comment) | |||||
click.echo('Generating links...') | |||||
fake_links() | |||||
click.echo('Done.') | |||||
def register_request_handlers(app): | |||||
@app.after_request | |||||
def query_profiler(response): | |||||
for q in get_debug_queries(): | |||||
if q.duration >= app.config['BLUELOG_SLOW_QUERY_THRESHOLD']: | |||||
app.logger.warning( | |||||
'Slow query: Duration: %fs\n Context: %s\nQuery: %s\n ' | |||||
% (q.duration, q.context, q.statement) | |||||
) | |||||
return response |
@ -0,0 +1,259 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
import os | |||||
from flask import render_template, flash, redirect, url_for, request, current_app, Blueprint, send_from_directory | |||||
from flask_login import login_required, current_user | |||||
from flask_ckeditor import upload_success, upload_fail | |||||
from bluelog.extensions import db | |||||
from bluelog.forms import SettingForm, PostForm, CategoryForm, LinkForm | |||||
from bluelog.models import Post, Category, Comment, Link | |||||
from bluelog.utils import redirect_back, allowed_file | |||||
admin_bp = Blueprint('admin', __name__) | |||||
@admin_bp.route('/settings', methods=['GET', 'POST']) | |||||
@login_required | |||||
def settings(): | |||||
form = SettingForm() | |||||
if form.validate_on_submit(): | |||||
current_user.name = form.name.data | |||||
current_user.blog_title = form.blog_title.data | |||||
current_user.blog_sub_title = form.blog_sub_title.data | |||||
current_user.about = form.about.data | |||||
db.session.commit() | |||||
flash('Setting updated.', 'success') | |||||
return redirect(url_for('blog.index')) | |||||
form.name.data = current_user.name | |||||
form.blog_title.data = current_user.blog_title | |||||
form.blog_sub_title.data = current_user.blog_sub_title | |||||
form.about.data = current_user.about | |||||
return render_template('admin/settings.html', form=form) | |||||
@admin_bp.route('/post/manage') | |||||
@login_required | |||||
def manage_post(): | |||||
page = request.args.get('page', 1, type=int) | |||||
pagination = Post.query.order_by(Post.timestamp.desc()).paginate( | |||||
page, per_page=current_app.config['BLUELOG_MANAGE_POST_PER_PAGE']) | |||||
posts = pagination.items | |||||
return render_template('admin/manage_post.html', page=page, pagination=pagination, posts=posts) | |||||
@admin_bp.route('/post/new', methods=['GET', 'POST']) | |||||
@login_required | |||||
def new_post(): | |||||
form = PostForm() | |||||
if form.validate_on_submit(): | |||||
title = form.title.data | |||||
body = form.body.data | |||||
category = Category.query.get(form.category.data) | |||||
post = Post(title=title, body=body, category=category) | |||||
# same with: | |||||
# category_id = form.category.data | |||||
# post = Post(title=title, body=body, category_id=category_id) | |||||
db.session.add(post) | |||||
db.session.commit() | |||||
flash('Post created.', 'success') | |||||
return redirect(url_for('blog.show_post', post_id=post.id)) | |||||
return render_template('admin/new_post.html', form=form) | |||||
@admin_bp.route('/post/<int:post_id>/edit', methods=['GET', 'POST']) | |||||
@login_required | |||||
def edit_post(post_id): | |||||
form = PostForm() | |||||
post = Post.query.get_or_404(post_id) | |||||
if form.validate_on_submit(): | |||||
post.title = form.title.data | |||||
post.body = form.body.data | |||||
post.category = Category.query.get(form.category.data) | |||||
db.session.commit() | |||||
flash('Post updated.', 'success') | |||||
return redirect(url_for('blog.show_post', post_id=post.id)) | |||||
form.title.data = post.title | |||||
form.body.data = post.body | |||||
form.category.data = post.category_id | |||||
return render_template('admin/edit_post.html', form=form) | |||||
@admin_bp.route('/post/<int:post_id>/delete', methods=['POST']) | |||||
@login_required | |||||
def delete_post(post_id): | |||||
post = Post.query.get_or_404(post_id) | |||||
db.session.delete(post) | |||||
db.session.commit() | |||||
flash('Post deleted.', 'success') | |||||
return redirect_back() | |||||
@admin_bp.route('/post/<int:post_id>/set-comment', methods=['POST']) | |||||
@login_required | |||||
def set_comment(post_id): | |||||
post = Post.query.get_or_404(post_id) | |||||
if post.can_comment: | |||||
post.can_comment = False | |||||
flash('Comment disabled.', 'success') | |||||
else: | |||||
post.can_comment = True | |||||
flash('Comment enabled.', 'success') | |||||
db.session.commit() | |||||
return redirect_back() | |||||
@admin_bp.route('/comment/manage') | |||||
@login_required | |||||
def manage_comment(): | |||||
filter_rule = request.args.get('filter', 'all') # 'all', 'unreviewed', 'admin' | |||||
page = request.args.get('page', 1, type=int) | |||||
per_page = current_app.config['BLUELOG_COMMENT_PER_PAGE'] | |||||
if filter_rule == 'unread': | |||||
filtered_comments = Comment.query.filter_by(reviewed=False) | |||||
elif filter_rule == 'admin': | |||||
filtered_comments = Comment.query.filter_by(from_admin=True) | |||||
else: | |||||
filtered_comments = Comment.query | |||||
pagination = filtered_comments.order_by(Comment.timestamp.desc()).paginate(page, per_page=per_page) | |||||
comments = pagination.items | |||||
return render_template('admin/manage_comment.html', comments=comments, pagination=pagination) | |||||
@admin_bp.route('/comment/<int:comment_id>/approve', methods=['POST']) | |||||
@login_required | |||||
def approve_comment(comment_id): | |||||
comment = Comment.query.get_or_404(comment_id) | |||||
comment.reviewed = True | |||||
db.session.commit() | |||||
flash('Comment published.', 'success') | |||||
return redirect_back() | |||||
@admin_bp.route('/comment/<int:comment_id>/delete', methods=['POST']) | |||||
@login_required | |||||
def delete_comment(comment_id): | |||||
comment = Comment.query.get_or_404(comment_id) | |||||
db.session.delete(comment) | |||||
db.session.commit() | |||||
flash('Comment deleted.', 'success') | |||||
return redirect_back() | |||||
@admin_bp.route('/category/manage') | |||||
@login_required | |||||
def manage_category(): | |||||
return render_template('admin/manage_category.html') | |||||
@admin_bp.route('/category/new', methods=['GET', 'POST']) | |||||
@login_required | |||||
def new_category(): | |||||
form = CategoryForm() | |||||
if form.validate_on_submit(): | |||||
name = form.name.data | |||||
category = Category(name=name) | |||||
db.session.add(category) | |||||
db.session.commit() | |||||
flash('Category created.', 'success') | |||||
return redirect(url_for('.manage_category')) | |||||
return render_template('admin/new_category.html', form=form) | |||||
@admin_bp.route('/category/<int:category_id>/edit', methods=['GET', 'POST']) | |||||
@login_required | |||||
def edit_category(category_id): | |||||
form = CategoryForm() | |||||
category = Category.query.get_or_404(category_id) | |||||
if category.id == 1: | |||||
flash('You can not edit the default category.', 'warning') | |||||
return redirect(url_for('blog.index')) | |||||
if form.validate_on_submit(): | |||||
category.name = form.name.data | |||||
db.session.commit() | |||||
flash('Category updated.', 'success') | |||||
return redirect(url_for('.manage_category')) | |||||
form.name.data = category.name | |||||
return render_template('admin/edit_category.html', form=form) | |||||
@admin_bp.route('/category/<int:category_id>/delete', methods=['POST']) | |||||
@login_required | |||||
def delete_category(category_id): | |||||
category = Category.query.get_or_404(category_id) | |||||
if category.id == 1: | |||||
flash('You can not delete the default category.', 'warning') | |||||
return redirect(url_for('blog.index')) | |||||
category.delete() | |||||
flash('Category deleted.', 'success') | |||||
return redirect(url_for('.manage_category')) | |||||
@admin_bp.route('/link/manage') | |||||
@login_required | |||||
def manage_link(): | |||||
return render_template('admin/manage_link.html') | |||||
@admin_bp.route('/link/new', methods=['GET', 'POST']) | |||||
@login_required | |||||
def new_link(): | |||||
form = LinkForm() | |||||
if form.validate_on_submit(): | |||||
name = form.name.data | |||||
url = form.url.data | |||||
link = Link(name=name, url=url) | |||||
db.session.add(link) | |||||
db.session.commit() | |||||
flash('Link created.', 'success') | |||||
return redirect(url_for('.manage_link')) | |||||
return render_template('admin/new_link.html', form=form) | |||||
@admin_bp.route('/link/<int:link_id>/edit', methods=['GET', 'POST']) | |||||
@login_required | |||||
def edit_link(link_id): | |||||
form = LinkForm() | |||||
link = Link.query.get_or_404(link_id) | |||||
if form.validate_on_submit(): | |||||
link.name = form.name.data | |||||
link.url = form.url.data | |||||
db.session.commit() | |||||
flash('Link updated.', 'success') | |||||
return redirect(url_for('.manage_link')) | |||||
form.name.data = link.name | |||||
form.url.data = link.url | |||||
return render_template('admin/edit_link.html', form=form) | |||||
@admin_bp.route('/link/<int:link_id>/delete', methods=['POST']) | |||||
@login_required | |||||
def delete_link(link_id): | |||||
link = Link.query.get_or_404(link_id) | |||||
db.session.delete(link) | |||||
db.session.commit() | |||||
flash('Link deleted.', 'success') | |||||
return redirect(url_for('.manage_link')) | |||||
@admin_bp.route('/uploads/<path:filename>') | |||||
def get_image(filename): | |||||
return send_from_directory(current_app.config['BLUELOG_UPLOAD_PATH'], filename) | |||||
@admin_bp.route('/upload', methods=['POST']) | |||||
def upload_image(): | |||||
f = request.files.get('upload') | |||||
if not allowed_file(f.filename): | |||||
return upload_fail('Image only!') | |||||
f.save(os.path.join(current_app.config['BLUELOG_UPLOAD_PATH'], f.filename)) | |||||
url = url_for('.get_image', filename=f.filename) | |||||
return upload_success(url, f.filename) |
@ -0,0 +1,86 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
from flask import render_template, flash, redirect, url_for, Blueprint | |||||
from flask_login import login_user, logout_user, login_required, current_user | |||||
from bluelog.forms import LoginForm, RegisterForm | |||||
from bluelog.models import Admin | |||||
from bluelog.utils import redirect_back | |||||
from bluelog.extensions import db | |||||
from sqlalchemy.exc import IntegrityError | |||||
auth_bp = Blueprint('auth', __name__) | |||||
@auth_bp.route('/login', methods=['GET', 'POST']) | |||||
def login(): | |||||
if current_user.is_authenticated: | |||||
return redirect(url_for('blog.index')) | |||||
form = LoginForm() | |||||
if form.validate_on_submit(): | |||||
username = form.username.data | |||||
password = form.password.data | |||||
remember = form.remember.data | |||||
admin = Admin.query.filter_by(username=username).first() | |||||
# admin = Admin.query.first() | |||||
if admin: | |||||
if username == admin.username and admin.validate_password(password): | |||||
login_user(admin, remember) | |||||
flash('Welcome back.', 'info') | |||||
return redirect_back() | |||||
flash('Invalid username or password.', 'warning') | |||||
else: | |||||
flash('No account.', 'warning') | |||||
return render_template('auth/login.html', form=form) | |||||
@auth_bp.route('/logout') | |||||
@login_required | |||||
def logout(): | |||||
logout_user() | |||||
flash('Logout success.', 'info') | |||||
return redirect_back() | |||||
def create_user(username, password): | |||||
obj = Admin( | |||||
username=username, | |||||
blog_title='Bluelog', | |||||
blog_sub_title="No, I'm the real thing.", | |||||
name='Mima Kirigoe', | |||||
about='Um, l, Mima Kirigoe, had a fun time as a member of CHAM...' | |||||
) | |||||
obj.set_password(password) | |||||
db.session.add(obj) | |||||
code = 200 | |||||
try: | |||||
db.session.commit() | |||||
return code | |||||
except: | |||||
code = 500 | |||||
db.session.rollback() | |||||
return code | |||||
@auth_bp.route('/register', methods=['GET', 'POST']) | |||||
def register(): | |||||
form = RegisterForm() | |||||
if form.validate_on_submit(): | |||||
username = form.username.data | |||||
password = form.password.data | |||||
remember = form.remember.data | |||||
code = create_user(username, password) | |||||
if code == 200: | |||||
flash('注册成功', 'info') | |||||
return redirect_back() | |||||
else: | |||||
flash('存在该用户名', 'warning') | |||||
return render_template('auth/register.html', form=form) |
@ -0,0 +1,106 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
from flask import render_template, flash, redirect, url_for, request, current_app, Blueprint, abort, make_response | |||||
from flask_login import current_user | |||||
from bluelog.emails import send_new_comment_email, send_new_reply_email | |||||
from bluelog.extensions import db | |||||
from bluelog.forms import CommentForm, AdminCommentForm | |||||
from bluelog.models import Post, Category, Comment | |||||
from bluelog.utils import redirect_back | |||||
blog_bp = Blueprint('blog', __name__) | |||||
@blog_bp.route('/') | |||||
def index(): | |||||
page = request.args.get('page', 1, type=int) | |||||
per_page = current_app.config['BLUELOG_POST_PER_PAGE'] | |||||
pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page, per_page=per_page) | |||||
posts = pagination.items | |||||
return render_template('blog/index.html', pagination=pagination, posts=posts) | |||||
@blog_bp.route('/about') | |||||
def about(): | |||||
return render_template('blog/about.html') | |||||
@blog_bp.route('/category/<int:category_id>') | |||||
def show_category(category_id): | |||||
category = Category.query.get_or_404(category_id) | |||||
page = request.args.get('page', 1, type=int) | |||||
per_page = current_app.config['BLUELOG_POST_PER_PAGE'] | |||||
pagination = Post.query.with_parent(category).order_by(Post.timestamp.desc()).paginate(page, per_page) | |||||
posts = pagination.items | |||||
return render_template('blog/category.html', category=category, pagination=pagination, posts=posts) | |||||
@blog_bp.route('/post/<int:post_id>', methods=['GET', 'POST']) | |||||
def show_post(post_id): | |||||
post = Post.query.get_or_404(post_id) | |||||
page = request.args.get('page', 1, type=int) | |||||
per_page = current_app.config['BLUELOG_COMMENT_PER_PAGE'] | |||||
pagination = Comment.query.with_parent(post).filter_by(reviewed=True).order_by(Comment.timestamp.asc()).paginate( | |||||
page, per_page) | |||||
comments = pagination.items | |||||
if current_user.is_authenticated: | |||||
form = AdminCommentForm() | |||||
form.author.data = current_user.name | |||||
form.email.data = current_app.config['BLUELOG_EMAIL'] | |||||
form.site.data = url_for('.index') | |||||
from_admin = True | |||||
reviewed = True | |||||
else: | |||||
form = CommentForm() | |||||
from_admin = False | |||||
reviewed = False | |||||
if form.validate_on_submit(): | |||||
author = form.author.data | |||||
email = form.email.data | |||||
site = form.site.data | |||||
body = form.body.data | |||||
comment = Comment( | |||||
author=author, email=email, site=site, body=body, | |||||
from_admin=from_admin, post=post, reviewed=reviewed) | |||||
replied_id = request.args.get('reply') | |||||
if replied_id: | |||||
replied_comment = Comment.query.get_or_404(replied_id) | |||||
comment.replied = replied_comment | |||||
send_new_reply_email(replied_comment) | |||||
db.session.add(comment) | |||||
db.session.commit() | |||||
if current_user.is_authenticated: # send message based on authentication status | |||||
flash('Comment published.', 'success') | |||||
else: | |||||
flash('Thanks, your comment will be published after reviewed.', 'info') | |||||
send_new_comment_email(post) # send notification email to admin | |||||
return redirect(url_for('.show_post', post_id=post_id)) | |||||
return render_template('blog/post.html', post=post, pagination=pagination, form=form, comments=comments) | |||||
@blog_bp.route('/reply/comment/<int:comment_id>') | |||||
def reply_comment(comment_id): | |||||
comment = Comment.query.get_or_404(comment_id) | |||||
if not comment.post.can_comment: | |||||
flash('Comment is disabled.', 'warning') | |||||
return redirect(url_for('.show_post', post_id=comment.post.id)) | |||||
return redirect( | |||||
url_for('.show_post', post_id=comment.post_id, reply=comment_id, author=comment.author) + '#comment-form') | |||||
@blog_bp.route('/change-theme/<theme_name>') | |||||
def change_theme(theme_name): | |||||
if theme_name not in current_app.config['BLUELOG_THEMES'].keys(): | |||||
abort(404) | |||||
response = make_response(redirect_back()) | |||||
response.set_cookie('theme', theme_name, max_age=30 * 24 * 60 * 60) | |||||
return response |
@ -0,0 +1,44 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
from threading import Thread | |||||
from flask import url_for, current_app | |||||
from flask_mail import Message | |||||
from bluelog.extensions import mail | |||||
def _send_async_mail(app, message): | |||||
with app.app_context(): | |||||
mail.send(message) | |||||
def send_mail(subject, to, html): | |||||
app = current_app._get_current_object() | |||||
message = Message(subject, recipients=[to], html=html) | |||||
thr = Thread(target=_send_async_mail, args=[app, message]) | |||||
thr.start() | |||||
return thr | |||||
def send_new_comment_email(post): | |||||
post_url = url_for('blog.show_post', post_id=post.id, _external=True) + '#comments' | |||||
send_mail(subject='New comment', to=current_app.config['BLUELOG_EMAIL'], | |||||
html='<p>New comment in post <i>%s</i>, click the link below to check:</p>' | |||||
'<p><a href="%s">%s</a></P>' | |||||
'<p><small style="color: #868e96">Do not reply this email.</small></p>' | |||||
% (post.title, post_url, post_url)) | |||||
def send_new_reply_email(comment): | |||||
post_url = url_for('blog.show_post', post_id=comment.post_id, _external=True) + '#comments' | |||||
send_mail(subject='New reply', to=comment.email, | |||||
html='<p>New reply for the comment you left in post <i>%s</i>, click the link below to check: </p>' | |||||
'<p><a href="%s">%s</a></p>' | |||||
'<p><small style="color: #868e96">Do not reply this email.</small></p>' | |||||
% (comment.post.title, post_url, post_url)) |
@ -0,0 +1,38 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
from flask_bootstrap import Bootstrap | |||||
from flask_ckeditor import CKEditor | |||||
from flask_login import LoginManager | |||||
from flask_mail import Mail | |||||
from flask_moment import Moment | |||||
from flask_sqlalchemy import SQLAlchemy | |||||
from flask_wtf import CSRFProtect | |||||
from flask_debugtoolbar import DebugToolbarExtension | |||||
from flask_migrate import Migrate | |||||
bootstrap = Bootstrap() | |||||
db = SQLAlchemy() | |||||
login_manager = LoginManager() | |||||
csrf = CSRFProtect() | |||||
ckeditor = CKEditor() | |||||
mail = Mail() | |||||
moment = Moment() | |||||
toolbar = DebugToolbarExtension() | |||||
migrate = Migrate() | |||||
@login_manager.user_loader | |||||
def load_user(user_id): | |||||
from bluelog.models import Admin | |||||
user = Admin.query.get(int(user_id)) | |||||
return user | |||||
login_manager.login_view = 'auth.login' | |||||
# login_manager.login_message = 'Your custom message' | |||||
login_manager.login_message_category = 'warning' |
@ -0,0 +1,216 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
import random | |||||
from faker import Faker | |||||
from sqlalchemy.exc import IntegrityError | |||||
from bluelog.extensions import db | |||||
from bluelog.models import Admin, Category, Post, Comment, Link | |||||
fake = Faker("zh_CN") | |||||
def fake_admin(): | |||||
admin = Admin( | |||||
username='admin', | |||||
blog_title='博客', | |||||
blog_sub_title="写下你的感悟。", | |||||
name='思悟', | |||||
about='思世间之大,悟人生变换。' | |||||
) | |||||
admin.set_password('helloflask') | |||||
db.session.add(admin) | |||||
db.session.commit() | |||||
def fake_categories(count=10): | |||||
category1 = Category(name='新冠疫情') | |||||
db.session.add(category1) | |||||
category2 = Category(name='健康') | |||||
db.session.add(category2) | |||||
category3 = Category(name='体育') | |||||
db.session.add(category3) | |||||
category5 = Category(name='经济') | |||||
db.session.add(category5) | |||||
category6 = Category(name='军事') | |||||
db.session.add(category6) | |||||
category7 = Category(name='艺术') | |||||
db.session.add(category7) | |||||
try: | |||||
db.session.commit() | |||||
except IntegrityError: | |||||
db.session.rollback() | |||||
# path="D:/这学期/云计算/云计算项目/bluelog" | |||||
path="" | |||||
def fake_posts(count=15): | |||||
f1 = open(path+"/root/content/文本/content.txt", "r", encoding="utf-8") | |||||
content1 = f1.readlines() | |||||
f2 = open(path+"/root/content/文本/title.txt", "r", encoding="utf-8") | |||||
head1 = f2.readlines() | |||||
# f3 = open(path+"/root/content/健康/content.txt", "r", encoding="utf-8") | |||||
# content2 = f3.readlines() | |||||
# f4 = open(path+"/root/content/健康/title.txt", "r", encoding="utf-8") | |||||
# head2 = f4.readlines() | |||||
f5 = open(path+"/root/content/体育/content.txt", "r", encoding="utf-8") | |||||
content3 = f5.readlines() | |||||
f6 = open(path+"/root/content/体育/title.txt", "r", encoding="utf-8") | |||||
head3 = f6.readlines() | |||||
# f7 = open("C:/Users/12293/Desktop/程序员/content.txt", "r", encoding="utf-8") | |||||
# content4 = f7.readlines() | |||||
# f8 = open("C:/Users/12293/Desktop/程序员/title.txt", "r", encoding="utf-8") | |||||
# head4 = f8.readlines() | |||||
f9 = open(path+"/root/content/经济/content.txt", "r", encoding="utf-8") | |||||
content5 = f9.readlines() | |||||
f10 = open(path+"/root/content/经济/title.txt", "r", encoding="utf-8") | |||||
head5 = f10.readlines() | |||||
f11 = open(path+"/root/content/军事/content.txt", "r", encoding="utf-8") | |||||
content6 = f11.readlines() | |||||
f12 = open(path+"/root/content/军事/title.txt", "r", encoding="utf-8") | |||||
head6 = f12.readlines() | |||||
f13 = open(path+"/root/content/艺术/content.txt", "r", encoding="utf-8") | |||||
content7 = f13.readlines() | |||||
f14 = open(path+"/root/content/艺术/title.txt", "r", encoding="utf-8") | |||||
head7 = f14.readlines() | |||||
for i in range(count): | |||||
post1 = Post( | |||||
title=head1[i][:-19], | |||||
body=content1[i], | |||||
category=Category.query.get(1), | |||||
timestamp=fake.date_time_this_year() | |||||
) | |||||
# post2 = Post( | |||||
# title=head2[i], | |||||
# body=content2[i], | |||||
# category=Category.query.get(2), | |||||
# timestamp=fake.date_time_this_year() | |||||
# ) | |||||
post3 = Post( | |||||
title=head3[i][:-19], | |||||
body=content3[i], | |||||
category=Category.query.get(3), | |||||
timestamp=fake.date_time_this_year() | |||||
) | |||||
# post4 = Post( | |||||
# title=head4[i], | |||||
# body=content4[i], | |||||
# category=Category.query.get(4), | |||||
# timestamp=fake.date_time_this_year() | |||||
# ) | |||||
post5 = Post( | |||||
title=head5[i][:-19], | |||||
body=content5[i], | |||||
category=Category.query.get(4), | |||||
timestamp=fake.date_time_this_year() | |||||
) | |||||
post6 = Post( | |||||
title=head6[i][:-19], | |||||
body=content6[i], | |||||
category=Category.query.get(5), | |||||
timestamp=fake.date_time_this_year() | |||||
) | |||||
post7 = Post( | |||||
title=head7[i][:-19], | |||||
body=content7[i], | |||||
category=Category.query.get(6), | |||||
timestamp=fake.date_time_this_year() | |||||
) | |||||
db.session.add(post1) | |||||
# db.session.add(post2) | |||||
db.session.add(post3) | |||||
# db.session.add(post4) | |||||
db.session.add(post5) | |||||
db.session.add(post6) | |||||
db.session.add(post7) | |||||
db.session.commit() | |||||
# for i in range(count): | |||||
# post = Post( | |||||
# title=fake.sentence(), | |||||
# body=fake.text(2000), | |||||
# category=Category.query.get(random.randint(1, Category.query.count())), | |||||
# timestamp=fake.date_time_this_year() | |||||
# ) | |||||
# print(type(fake.sentence()),type(fake.text(2000))) | |||||
# print(fake.sentence(),fake.text(2000)) | |||||
# | |||||
# db.session.add(post) | |||||
# db.session.commit() | |||||
def fake_comments(count=500): | |||||
for i in range(count): | |||||
comment = Comment( | |||||
author=fake.name(), | |||||
email=fake.email(), | |||||
site=fake.url(), | |||||
body=fake.sentence(), | |||||
timestamp=fake.date_time_this_year(), | |||||
reviewed=True, | |||||
post=Post.query.get(random.randint(1, Post.query.count())) | |||||
) | |||||
db.session.add(comment) | |||||
salt = int(count * 0.1) | |||||
for i in range(salt): | |||||
# unreviewed comments | |||||
comment = Comment( | |||||
author=fake.name(), | |||||
email=fake.email(), | |||||
site=fake.url(), | |||||
body=fake.sentence(), | |||||
timestamp=fake.date_time_this_year(), | |||||
reviewed=False, | |||||
post=Post.query.get(random.randint(1, Post.query.count())) | |||||
) | |||||
db.session.add(comment) | |||||
# from admin | |||||
comment = Comment( | |||||
author='Mima Kirigoe', | |||||
email='mima@example.com', | |||||
site='example.com', | |||||
body=fake.sentence(), | |||||
timestamp=fake.date_time_this_year(), | |||||
from_admin=True, | |||||
reviewed=True, | |||||
post=Post.query.get(random.randint(1, Post.query.count())) | |||||
) | |||||
db.session.add(comment) | |||||
db.session.commit() | |||||
# replies | |||||
for i in range(salt): | |||||
comment = Comment( | |||||
author=fake.name(), | |||||
email=fake.email(), | |||||
site=fake.url(), | |||||
body=fake.sentence(), | |||||
timestamp=fake.date_time_this_year(), | |||||
reviewed=True, | |||||
replied=Comment.query.get(random.randint(1, Comment.query.count())), | |||||
post=Post.query.get(random.randint(1, Post.query.count())) | |||||
) | |||||
db.session.add(comment) | |||||
db.session.commit() | |||||
def fake_links(): | |||||
twitter = Link(name='Twitter', url='#') | |||||
facebook = Link(name='Facebook', url='#') | |||||
linkedin = Link(name='LinkedIn', url='#') | |||||
google = Link(name='Google+', url='#') | |||||
db.session.add_all([twitter, facebook, linkedin, google]) | |||||
db.session.commit() |
@ -0,0 +1,77 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
from flask_ckeditor import CKEditorField | |||||
from flask_wtf import FlaskForm | |||||
from wtforms import StringField, SubmitField, SelectField, TextAreaField, ValidationError, HiddenField, \ | |||||
BooleanField, PasswordField | |||||
from wtforms.validators import DataRequired, Email, Length, Optional, URL | |||||
from bluelog.models import Category | |||||
class LoginForm(FlaskForm): | |||||
username = StringField('Username', validators=[DataRequired(), Length(1, 20)]) | |||||
password = PasswordField('Password', validators=[DataRequired(), Length(1, 128)]) | |||||
remember = BooleanField('Remember me') | |||||
submit = SubmitField('Log in') | |||||
class RegisterForm(FlaskForm): | |||||
username = StringField('Username', validators=[DataRequired(), Length(1, 20)]) | |||||
password = PasswordField('Password', validators=[DataRequired(), Length(1, 128)]) | |||||
remember = BooleanField('Remember me') | |||||
submit = SubmitField('Register') | |||||
class SettingForm(FlaskForm): | |||||
name = StringField('Name', validators=[DataRequired(), Length(1, 30)]) | |||||
blog_title = StringField('Blog Title', validators=[DataRequired(), Length(1, 60)]) | |||||
blog_sub_title = StringField('Blog Sub Title', validators=[DataRequired(), Length(1, 100)]) | |||||
about = CKEditorField('About Page', validators=[DataRequired()]) | |||||
submit = SubmitField() | |||||
class PostForm(FlaskForm): | |||||
title = StringField('Title', validators=[DataRequired(), Length(1, 60)]) | |||||
category = SelectField('Category', coerce=int, default=1) | |||||
body = CKEditorField('Body', validators=[DataRequired()]) | |||||
submit = SubmitField() | |||||
def __init__(self, *args, **kwargs): | |||||
super(PostForm, self).__init__(*args, **kwargs) | |||||
self.category.choices = [(category.id, category.name) | |||||
for category in Category.query.order_by(Category.name).all()] | |||||
class CategoryForm(FlaskForm): | |||||
name = StringField('Name', validators=[DataRequired(), Length(1, 30)]) | |||||
submit = SubmitField() | |||||
def validate_name(self, field): | |||||
if Category.query.filter_by(name=field.data).first(): | |||||
raise ValidationError('Name already in use.') | |||||
class CommentForm(FlaskForm): | |||||
author = StringField('Name', validators=[DataRequired(), Length(1, 30)]) | |||||
email = StringField('Email', validators=[DataRequired(), Email(), Length(1, 254)]) | |||||
site = StringField('Site', validators=[Optional(), URL(), Length(0, 255)]) | |||||
body = TextAreaField('Comment', validators=[DataRequired()]) | |||||
submit = SubmitField() | |||||
class AdminCommentForm(CommentForm): | |||||
author = HiddenField() | |||||
email = HiddenField() | |||||
site = HiddenField() | |||||
class LinkForm(FlaskForm): | |||||
name = StringField('Name', validators=[DataRequired(), Length(1, 30)]) | |||||
url = StringField('URL', validators=[DataRequired(), URL(), Length(1, 255)]) | |||||
submit = SubmitField() |
@ -0,0 +1,84 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
from datetime import datetime | |||||
from flask_login import UserMixin | |||||
from werkzeug.security import generate_password_hash, check_password_hash | |||||
from bluelog.extensions import db | |||||
class Admin(db.Model, UserMixin): | |||||
id = db.Column(db.Integer, primary_key=True) | |||||
username = db.Column(db.String(20), unique=True) | |||||
password_hash = db.Column(db.String(128)) | |||||
blog_title = db.Column(db.String(60)) | |||||
blog_sub_title = db.Column(db.String(100)) | |||||
name = db.Column(db.String(30)) | |||||
about = db.Column(db.Text) | |||||
def set_password(self, password): | |||||
self.password_hash = generate_password_hash(password) | |||||
def validate_password(self, password): | |||||
return check_password_hash(self.password_hash, password) | |||||
class Category(db.Model): | |||||
id = db.Column(db.Integer, primary_key=True) | |||||
name = db.Column(db.String(30), unique=True) | |||||
posts = db.relationship('Post', back_populates='category') | |||||
def delete(self): | |||||
default_category = Category.query.get(1) | |||||
posts = self.posts[:] | |||||
for post in posts: | |||||
post.category = default_category | |||||
db.session.delete(self) | |||||
db.session.commit() | |||||
class Post(db.Model): | |||||
id = db.Column(db.Integer, primary_key=True) | |||||
title = db.Column(db.String(60)) | |||||
body = db.Column(db.Text) | |||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True) | |||||
can_comment = db.Column(db.Boolean, default=True) | |||||
category_id = db.Column(db.Integer, db.ForeignKey('category.id')) | |||||
category = db.relationship('Category', back_populates='posts') | |||||
comments = db.relationship('Comment', back_populates='post', cascade='all, delete-orphan') | |||||
class Comment(db.Model): | |||||
id = db.Column(db.Integer, primary_key=True) | |||||
author = db.Column(db.String(30)) | |||||
email = db.Column(db.String(254)) | |||||
site = db.Column(db.String(255)) | |||||
body = db.Column(db.Text) | |||||
from_admin = db.Column(db.Boolean, default=False) | |||||
reviewed = db.Column(db.Boolean, default=False) | |||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True) | |||||
replied_id = db.Column(db.Integer, db.ForeignKey('comment.id')) | |||||
post_id = db.Column(db.Integer, db.ForeignKey('post.id')) | |||||
post = db.relationship('Post', back_populates='comments') | |||||
replies = db.relationship('Comment', back_populates='replied', cascade='all, delete-orphan') | |||||
replied = db.relationship('Comment', back_populates='replies', remote_side=[id]) | |||||
# Same with: | |||||
# replies = db.relationship('Comment', backref=db.backref('replied', remote_side=[id]), | |||||
# cascade='all,delete-orphan') | |||||
class Link(db.Model): | |||||
id = db.Column(db.Integer, primary_key=True) | |||||
name = db.Column(db.String(30)) | |||||
url = db.Column(db.String(255)) |
@ -0,0 +1,74 @@ | |||||
# -*- coding: utf-8 -*- | |||||
""" | |||||
:author: Grey Li (李辉) | |||||
:url: http://greyli.com | |||||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||||
:license: MIT, see LICENSE for more details. | |||||
""" | |||||
import os | |||||
import sys | |||||
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) | |||||
#等会记得改回去 | |||||
myhost="localhost" #数据库内网地址 | |||||
myroot="root" | |||||
mypwd="hsy19991231" | |||||
mydb="bluelog" | |||||
# SQLite URI compatible | |||||
WIN = sys.platform.startswith('win') | |||||
if WIN: | |||||
prefix = 'sqlite:///' | |||||
else: | |||||
prefix = 'sqlite:////' | |||||
class BaseConfig(object): | |||||
SECRET_KEY = os.getenv('SECRET_KEY', 'dev key') | |||||
DEBUG_TB_INTERCEPT_REDIRECTS = False | |||||
SQLALCHEMY_TRACK_MODIFICATIONS = False | |||||
SQLALCHEMY_RECORD_QUERIES = True | |||||
CKEDITOR_ENABLE_CSRF = True | |||||
CKEDITOR_FILE_UPLOADER = 'admin.upload_image' | |||||
MAIL_SERVER = os.getenv('MAIL_SERVER') | |||||
MAIL_PORT = 465 | |||||
MAIL_USE_SSL = True | |||||
MAIL_USERNAME = os.getenv('MAIL_USERNAME') | |||||
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD') | |||||
MAIL_DEFAULT_SENDER = ('Bluelog Admin', MAIL_USERNAME) | |||||
BLUELOG_EMAIL = os.getenv('BLUELOG_EMAIL') | |||||
BLUELOG_POST_PER_PAGE = 10 | |||||
BLUELOG_MANAGE_POST_PER_PAGE = 15 | |||||
BLUELOG_COMMENT_PER_PAGE = 15 | |||||
# ('theme name', 'display name') | |||||
BLUELOG_THEMES = {'perfect_blue': 'Perfect Blue', 'black_swan': 'Black Swan'} | |||||
BLUELOG_SLOW_QUERY_THRESHOLD = 1 | |||||
BLUELOG_UPLOAD_PATH = os.path.join(basedir, 'uploads') | |||||
BLUELOG_ALLOWED_IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif'] | |||||
class DevelopmentConfig(BaseConfig): | |||||
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://"+myroot+":"+mypwd+"@"+myhost+"/"+mydb #连接mysql数据库 | |||||
class TestingConfig(BaseConfig): | |||||
TESTING = True | |||||
WTF_CSRF_ENABLED = False | |||||
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://"+myroot+":"+mypwd+"@"+myhost+"/"+mydb # in-memory database | |||||
class ProductionConfig(BaseConfig): | |||||
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://"+myroot+":"+mypwd+"@"+myhost+"/"+mydb | |||||
config = { | |||||
'development': DevelopmentConfig, | |||||
'testing': TestingConfig, | |||||
'production': ProductionConfig | |||||
} |
@ -0,0 +1,39 @@ | |||||
CKEditor 4 | |||||
========== | |||||
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||||
http://ckeditor.com - See LICENSE.md for license information. | |||||
CKEditor is a text editor to be used inside web pages. It's not a replacement | |||||
for desktop text editors like Word or OpenOffice, but a component to be used as | |||||
part of web applications and websites. | |||||
## Documentation | |||||
The full editor documentation is available online at the following address: | |||||
http://docs.ckeditor.com | |||||
## Installation | |||||
Installing CKEditor is an easy task. Just follow these simple steps: | |||||
1. **Download** the latest version from the CKEditor website: | |||||
http://ckeditor.com. You should have already completed this step, but be | |||||
sure you have the very latest version. | |||||
2. **Extract** (decompress) the downloaded file into the root of your website. | |||||
**Note:** CKEditor is by default installed in the `ckeditor` folder. You can | |||||
place the files in whichever you want though. | |||||
## Checking Your Installation | |||||
The editor comes with a few sample pages that can be used to verify that | |||||
installation proceeded properly. Take a look at the `samples` directory. | |||||
To test your installation, just call the following page at your website: | |||||
http://<your site>/<CKEditor installation path>/samples/index.html | |||||
For example: | |||||
http://www.example.com/ckeditor/samples/index.html |
@ -0,0 +1,10 @@ | |||||
/* | |||||
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | |||||
*/ | |||||
(function(a){if("undefined"==typeof a)throw Error("jQuery should be loaded before CKEditor jQuery adapter.");if("undefined"==typeof CKEDITOR)throw Error("CKEditor should be loaded before CKEditor jQuery adapter.");CKEDITOR.config.jqueryOverrideVal="undefined"==typeof CKEDITOR.config.jqueryOverrideVal?!0:CKEDITOR.config.jqueryOverrideVal;a.extend(a.fn,{ckeditorGet:function(){var a=this.eq(0).data("ckeditorInstance");if(!a)throw"CKEditor is not initialized yet, use ckeditor() with a callback.";return a}, | |||||
ckeditor:function(g,d){if(!CKEDITOR.env.isCompatible)throw Error("The environment is incompatible.");if(!a.isFunction(g)){var m=d;d=g;g=m}var k=[];d=d||{};this.each(function(){var b=a(this),c=b.data("ckeditorInstance"),f=b.data("_ckeditorInstanceLock"),h=this,l=new a.Deferred;k.push(l.promise());if(c&&!f)g&&g.apply(c,[this]),l.resolve();else if(f)c.once("instanceReady",function(){setTimeout(function(){c.element?(c.element.$==h&&g&&g.apply(c,[h]),l.resolve()):setTimeout(arguments.callee,100)},0)}, | |||||
null,null,9999);else{if(d.autoUpdateElement||"undefined"==typeof d.autoUpdateElement&&CKEDITOR.config.autoUpdateElement)d.autoUpdateElementJquery=!0;d.autoUpdateElement=!1;b.data("_ckeditorInstanceLock",!0);c=a(this).is("textarea")?CKEDITOR.replace(h,d):CKEDITOR.inline(h,d);b.data("ckeditorInstance",c);c.on("instanceReady",function(d){var e=d.editor;setTimeout(function(){if(e.element){d.removeListener();e.on("dataReady",function(){b.trigger("dataReady.ckeditor",[e])});e.on("setData",function(a){b.trigger("setData.ckeditor", | |||||
[e,a.data])});e.on("getData",function(a){b.trigger("getData.ckeditor",[e,a.data])},999);e.on("destroy",function(){b.trigger("destroy.ckeditor",[e])});e.on("save",function(){a(h.form).submit();return!1},null,null,20);if(e.config.autoUpdateElementJquery&&b.is("textarea")&&a(h.form).length){var c=function(){b.ckeditor(function(){e.updateElement()})};a(h.form).submit(c);a(h.form).bind("form-pre-serialize",c);b.bind("destroy.ckeditor",function(){a(h.form).unbind("submit",c);a(h.form).unbind("form-pre-serialize", | |||||
c)})}e.on("destroy",function(){b.removeData("ckeditorInstance")});b.removeData("_ckeditorInstanceLock");b.trigger("instanceReady.ckeditor",[e]);g&&g.apply(e,[h]);l.resolve()}else setTimeout(arguments.callee,100)},0)},null,null,9999)}});var f=new a.Deferred;this.promise=f.promise();a.when.apply(this,k).then(function(){f.resolve()});this.editor=this.eq(0).data("ckeditorInstance");return this}});CKEDITOR.config.jqueryOverrideVal&&(a.fn.val=CKEDITOR.tools.override(a.fn.val,function(g){return function(d){if(arguments.length){var m= | |||||
this,k=[],f=this.each(function(){var b=a(this),c=b.data("ckeditorInstance");if(b.is("textarea")&&c){var f=new a.Deferred;c.setData(d,function(){f.resolve()});k.push(f.promise());return!0}return g.call(b,d)});if(k.length){var b=new a.Deferred;a.when.apply(this,k).done(function(){b.resolveWith(m)});return b.promise()}return f}var f=a(this).eq(0),c=f.data("ckeditorInstance");return f.is("textarea")&&c?c.getData():g.call(f)}}))})(window.jQuery); |
@ -0,0 +1,165 @@ | |||||
/** | |||||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||||
* For licensing, see LICENSE.md or http://ckeditor.com/license | |||||
*/ | |||||
/** | |||||
* This file was added automatically by CKEditor builder. | |||||
* You may re-use it at any time to build CKEditor again. | |||||
* | |||||
* If you would like to build CKEditor online again | |||||
* (for example to upgrade), visit one the following links: | |||||
* | |||||
* (1) http://ckeditor.com/builder | |||||
* Visit online builder to build CKEditor from scratch. | |||||
* | |||||
* (2) http://ckeditor.com/builder/2f57fc244039d7273aeb5bb45ddb4866 | |||||
* Visit online builder to build CKEditor, starting with the same setup as before. | |||||
* | |||||
* (3) http://ckeditor.com/builder/download/2f57fc244039d7273aeb5bb45ddb4866 | |||||
* Straight download link to the latest version of CKEditor (Optimized) with the same setup as before. | |||||
* | |||||
* NOTE: | |||||
* This file is not used by CKEditor, you may remove it. | |||||
* Changing this file will not change your CKEditor configuration. | |||||
*/ | |||||
var CKBUILDER_CONFIG = { | |||||
skin: 'moono-lisa', | |||||
preset: 'standard', | |||||
ignore: [ | |||||
'.DS_Store', | |||||
'.bender', | |||||
'.editorconfig', | |||||
'.gitattributes', | |||||
'.gitignore', | |||||
'.idea', | |||||
'.jscsrc', | |||||
'.jshintignore', | |||||
'.jshintrc', | |||||
'.mailmap', | |||||
'.travis.yml', | |||||
'bender-err.log', | |||||
'bender-out.log', | |||||
'bender.ci.js', | |||||
'bender.js', | |||||
'dev', | |||||
'gruntfile.js', | |||||
'less', | |||||
'node_modules', | |||||
'package.json', | |||||
'tests' | |||||
], | |||||
plugins : { | |||||
'a11yhelp' : 1, | |||||
'about' : 1, | |||||
'basicstyles' : 1, | |||||
'blockquote' : 1, | |||||
'clipboard' : 1, | |||||
'contextmenu' : 1, | |||||
'elementspath' : 1, | |||||
'enterkey' : 1, | |||||
'entities' : 1, | |||||
'filebrowser' : 1, | |||||
'floatingspace' : 1, | |||||
'format' : 1, | |||||
'horizontalrule' : 1, | |||||
'htmlwriter' : 1, | |||||
'image' : 1, | |||||
'indentlist' : 1, | |||||
'link' : 1, | |||||
'list' : 1, | |||||
'magicline' : 1, | |||||
'maximize' : 1, | |||||
'pastefromword' : 1, | |||||
'pastetext' : 1, | |||||
'removeformat' : 1, | |||||
'resize' : 1, | |||||
'scayt' : 1, | |||||
'showborders' : 1, | |||||
'sourcearea' : 1, | |||||
'specialchar' : 1, | |||||
'stylescombo' : 1, | |||||
'tab' : 1, | |||||
'table' : 1, | |||||
'tableselection' : 1, | |||||
'tabletools' : 1, | |||||
'toolbar' : 1, | |||||
'undo' : 1, | |||||
'uploadimage' : 1, | |||||
'wsc' : 1, | |||||
'wysiwygarea' : 1 | |||||
}, | |||||
languages : { | |||||
'af' : 1, | |||||
'ar' : 1, | |||||
'az' : 1, | |||||
'bg' : 1, | |||||
'bn' : 1, | |||||
'bs' : 1, | |||||
'ca' : 1, | |||||
'cs' : 1, | |||||
'cy' : 1, | |||||
'da' : 1, | |||||
'de' : 1, | |||||
'de-ch' : 1, | |||||
'el' : 1, | |||||
'en' : 1, | |||||
'en-au' : 1, | |||||
'en-ca' : 1, | |||||
'en-gb' : 1, | |||||
'eo' : 1, | |||||
'es' : 1, | |||||
'es-mx' : 1, | |||||
'et' : 1, | |||||
'eu' : 1, | |||||
'fa' : 1, | |||||
'fi' : 1, | |||||
'fo' : 1, | |||||
'fr' : 1, | |||||
'fr-ca' : 1, | |||||
'gl' : 1, | |||||
'gu' : 1, | |||||
'he' : 1, | |||||
'hi' : 1, | |||||
'hr' : 1, | |||||
'hu' : 1, | |||||
'id' : 1, | |||||
'is' : 1, | |||||
'it' : 1, | |||||
'ja' : 1, | |||||
'ka' : 1, | |||||
'km' : 1, | |||||
'ko' : 1, | |||||
'ku' : 1, | |||||
'lt' : 1, | |||||
'lv' : 1, | |||||
'mk' : 1, | |||||
'mn' : 1, | |||||
'ms' : 1, | |||||
'nb' : 1, | |||||
'nl' : 1, | |||||
'no' : 1, | |||||
'oc' : 1, | |||||
'pl' : 1, | |||||
'pt' : 1, | |||||
'pt-br' : 1, | |||||
'ro' : 1, | |||||
'ru' : 1, | |||||
'si' : 1, | |||||
'sk' : 1, | |||||
'sl' : 1, | |||||
'sq' : 1, | |||||
'sr' : 1, | |||||
'sr-latn' : 1, | |||||
'sv' : 1, | |||||
'th' : 1, | |||||
'tr' : 1, | |||||
'tt' : 1, | |||||
'ug' : 1, | |||||
'uk' : 1, | |||||
'vi' : 1, | |||||
'zh' : 1, | |||||
'zh-cn' : 1 | |||||
} | |||||
}; |
@ -0,0 +1,38 @@ | |||||
/** | |||||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||||
* For licensing, see https://ckeditor.com/legal/ckeditor-oss-license | |||||
*/ | |||||
CKEDITOR.editorConfig = function( config ) { | |||||
// Define changes to default configuration here. | |||||
// For complete reference see: | |||||
// http://docs.ckeditor.com/#!/api/CKEDITOR.config | |||||
// The toolbar groups arrangement, optimized for two toolbar rows. | |||||
config.toolbarGroups = [ | |||||
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, | |||||
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] }, | |||||
{ name: 'links' }, | |||||
{ name: 'insert' }, | |||||
{ name: 'forms' }, | |||||
{ name: 'tools' }, | |||||
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] }, | |||||
{ name: 'others' }, | |||||
'/', | |||||
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, | |||||
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] }, | |||||
{ name: 'styles' }, | |||||
{ name: 'colors' }, | |||||
{ name: 'about' } | |||||
]; | |||||
// Remove some buttons provided by the standard plugins, which are | |||||
// not needed in the Standard(s) toolbar. | |||||
config.removeButtons = 'Underline,Subscript,Superscript'; | |||||
// Set the most common block elements. | |||||
config.format_tags = 'p;h1;h2;h3;pre'; | |||||
// Simplify the dialog windows. | |||||
config.removeDialogTabs = 'image:advanced;link:advanced'; | |||||
}; |
@ -0,0 +1,207 @@ | |||||
/* | |||||
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | |||||
*/ | |||||
body | |||||
{ | |||||
/* Font */ | |||||
font-family: sans-serif, Arial, Verdana, "Trebuchet MS"; | |||||
font-size: 12px; | |||||
/* Text color */ | |||||
color: #333; | |||||
/* Remove the background color to make it transparent */ | |||||
background-color: #fff; | |||||
margin: 20px; | |||||
} | |||||
.cke_editable | |||||
{ | |||||
font-size: 13px; | |||||
line-height: 1.6; | |||||
/* Fix for missing scrollbars with RTL texts. (#10488) */ | |||||
word-wrap: break-word; | |||||
} | |||||
blockquote | |||||
{ | |||||
font-style: italic; | |||||
font-family: Georgia, Times, "Times New Roman", serif; | |||||
padding: 2px 0; | |||||
border-style: solid; | |||||
border-color: #ccc; | |||||
border-width: 0; | |||||
} | |||||
.cke_contents_ltr blockquote | |||||
{ | |||||
padding-left: 20px; | |||||
padding-right: 8px; | |||||
border-left-width: 5px; | |||||
} | |||||
.cke_contents_rtl blockquote | |||||
{ | |||||
padding-left: 8px; | |||||
padding-right: 20px; | |||||
border-right-width: 5px; | |||||
} | |||||
a | |||||
{ | |||||
color: #0782C1; | |||||
} | |||||
ol,ul,dl | |||||
{ | |||||
/* IE7: reset rtl list margin. (#7334) */ | |||||
*margin-right: 0px; | |||||
/* preserved spaces for list items with text direction other than the list. (#6249,#8049)*/ | |||||
padding: 0 40px; | |||||
} | |||||
h1,h2,h3,h4,h5,h6 | |||||
{ | |||||
font-weight: normal; | |||||
line-height: 1.2; | |||||
} | |||||
hr | |||||
{ | |||||
border: 0px; | |||||
border-top: 1px solid #ccc; | |||||
} | |||||
img.right | |||||
{ | |||||
border: 1px solid #ccc; | |||||
float: right; | |||||
margin-left: 15px; | |||||
padding: 5px; | |||||
} | |||||
img.left | |||||
{ | |||||
border: 1px solid #ccc; | |||||
float: left; | |||||
margin-right: 15px; | |||||
padding: 5px; | |||||
} | |||||
pre | |||||
{ | |||||
white-space: pre-wrap; /* CSS 2.1 */ | |||||
word-wrap: break-word; /* IE7 */ | |||||
-moz-tab-size: 4; | |||||
tab-size: 4; | |||||
} | |||||
.marker | |||||
{ | |||||
background-color: Yellow; | |||||
} | |||||
span[lang] | |||||
{ | |||||
font-style: italic; | |||||
} | |||||
figure | |||||
{ | |||||
text-align: center; | |||||
outline: solid 1px #ccc; | |||||
background: rgba(0,0,0,0.05); | |||||
padding: 10px; | |||||
margin: 10px 20px; | |||||
display: inline-block; | |||||
} | |||||
figure > figcaption | |||||
{ | |||||
text-align: center; | |||||
display: block; /* For IE8 */ | |||||
} | |||||
a > img { | |||||
padding: 1px; | |||||
margin: 1px; | |||||
border: none; | |||||
outline: 1px solid #0782C1; | |||||
} | |||||
/* Widget Styles */ | |||||
.code-featured | |||||
{ | |||||
border: 5px solid red; | |||||
} | |||||
.math-featured | |||||
{ | |||||
padding: 20px; | |||||
box-shadow: 0 0 2px rgba(200, 0, 0, 1); | |||||
background-color: rgba(255, 0, 0, 0.05); | |||||
margin: 10px; | |||||
} | |||||
.image-clean | |||||
{ | |||||
border: 0; | |||||
background: none; | |||||
padding: 0; | |||||
} | |||||
.image-clean > figcaption | |||||
{ | |||||
font-size: .9em; | |||||
text-align: right; | |||||
} | |||||
.image-grayscale | |||||
{ | |||||
background-color: white; | |||||
color: #666; | |||||
} | |||||
.image-grayscale img, img.image-grayscale | |||||
{ | |||||
filter: grayscale(100%); | |||||
} | |||||
.embed-240p | |||||
{ | |||||
max-width: 426px; | |||||
max-height: 240px; | |||||
margin:0 auto; | |||||
} | |||||
.embed-360p | |||||
{ | |||||
max-width: 640px; | |||||
max-height: 360px; | |||||
margin:0 auto; | |||||
} | |||||
.embed-480p | |||||
{ | |||||
max-width: 854px; | |||||
max-height: 480px; | |||||
margin:0 auto; | |||||
} | |||||
.embed-720p | |||||
{ | |||||
max-width: 1280px; | |||||
max-height: 720px; | |||||
margin:0 auto; | |||||
} | |||||
.embed-1080p | |||||
{ | |||||
max-width: 1920px; | |||||
max-height: 1080px; | |||||
margin:0 auto; | |||||
} |
@ -0,0 +1,10 @@ | |||||
/* | |||||
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | |||||
*/ | |||||
CKEDITOR.dialog.add("a11yHelp",function(e){var a=e.lang.a11yhelp,b=e.lang.common.keyboard,q=CKEDITOR.tools.getNextId(),d={8:b[8],9:a.tab,13:b[13],16:b[16],17:b[17],18:b[18],19:a.pause,20:a.capslock,27:a.escape,33:a.pageUp,34:a.pageDown,35:b[35],36:b[36],37:a.leftArrow,38:a.upArrow,39:a.rightArrow,40:a.downArrow,45:a.insert,46:b[46],91:a.leftWindowKey,92:a.rightWindowKey,93:a.selectKey,96:a.numpad0,97:a.numpad1,98:a.numpad2,99:a.numpad3,100:a.numpad4,101:a.numpad5,102:a.numpad6,103:a.numpad7,104:a.numpad8, | |||||
105:a.numpad9,106:a.multiply,107:a.add,109:a.subtract,110:a.decimalPoint,111:a.divide,112:a.f1,113:a.f2,114:a.f3,115:a.f4,116:a.f5,117:a.f6,118:a.f7,119:a.f8,120:a.f9,121:a.f10,122:a.f11,123:a.f12,144:a.numLock,145:a.scrollLock,186:a.semiColon,187:a.equalSign,188:a.comma,189:a.dash,190:a.period,191:a.forwardSlash,192:a.graveAccent,219:a.openBracket,220:a.backSlash,221:a.closeBracket,222:a.singleQuote};d[CKEDITOR.ALT]=b[18];d[CKEDITOR.SHIFT]=b[16];d[CKEDITOR.CTRL]=CKEDITOR.env.mac?b[224]:b[17];var k= | |||||
[CKEDITOR.ALT,CKEDITOR.SHIFT,CKEDITOR.CTRL],r=/\$\{(.*?)\}/g,t=function(a,b){var c=e.getCommandKeystroke(b);if(c){for(var l,f,h=[],g=0;g<k.length;g++)f=k[g],l=c/k[g],1<l&&2>=l&&(c-=f,h.push(d[f]));h.push(d[c]||String.fromCharCode(c));c=h.join("+")}else c=a;return c};return{title:a.title,minWidth:600,minHeight:400,contents:[{id:"info",label:e.lang.common.generalTab,expand:!0,elements:[{type:"html",id:"legends",style:"white-space:normal;",focus:function(){this.getElement().focus()},html:function(){for(var b= | |||||
'\x3cdiv class\x3d"cke_accessibility_legend" role\x3d"document" aria-labelledby\x3d"'+q+'_arialbl" tabIndex\x3d"-1"\x3e%1\x3c/div\x3e\x3cspan id\x3d"'+q+'_arialbl" class\x3d"cke_voice_label"\x3e'+a.contents+" \x3c/span\x3e",d=[],c=a.legend,l=c.length,f=0;f<l;f++){for(var h=c[f],g=[],e=h.items,k=e.length,p=0;p<k;p++){var m=e[p],n=CKEDITOR.env.edge&&m.legendEdge?m.legendEdge:m.legend,n=n.replace(r,t);n.match(r)||g.push("\x3cdt\x3e%1\x3c/dt\x3e\x3cdd\x3e%2\x3c/dd\x3e".replace("%1",m.name).replace("%2", | |||||
n))}d.push("\x3ch1\x3e%1\x3c/h1\x3e\x3cdl\x3e%2\x3c/dl\x3e".replace("%1",h.name).replace("%2",g.join("")))}return b.replace("%1",d.join(""))}()+'\x3cstyle type\x3d"text/css"\x3e.cke_accessibility_legend{width:600px;height:400px;padding-right:5px;overflow-y:auto;overflow-x:hidden;}.cke_browser_quirks .cke_accessibility_legend,{height:390px}.cke_accessibility_legend *{white-space:normal;}.cke_accessibility_legend h1{font-size: 20px;border-bottom: 1px solid #AAA;margin: 5px 0px 15px;}.cke_accessibility_legend dl{margin-left: 5px;}.cke_accessibility_legend dt{font-size: 13px;font-weight: bold;}.cke_accessibility_legend dd{margin:10px}\x3c/style\x3e'}]}], | |||||
buttons:[CKEDITOR.dialog.cancelButton]}}); |