├── log.txt ├── db ├── snote-bak.db └── snote-dev.db ├── app ├── static │ ├── materialize │ │ ├── css │ │ │ ├── gen.bat │ │ │ ├── components │ │ │ │ ├── _mixins.scss │ │ │ │ ├── _icons-material-design.scss │ │ │ │ ├── forms │ │ │ │ │ ├── _forms.scss │ │ │ │ │ ├── _file-input.scss │ │ │ │ │ ├── _switches.scss │ │ │ │ │ ├── _select.scss │ │ │ │ │ ├── _radio-buttons.scss │ │ │ │ │ ├── _range.scss │ │ │ │ │ ├── _checkboxes.css │ │ │ │ │ ├── _input-fields.scss │ │ │ │ │ └── _checkboxes.scss │ │ │ │ ├── _chips.scss │ │ │ │ ├── _carousel.scss │ │ │ │ ├── _table_of_contents.scss │ │ │ │ ├── _tooltip.scss │ │ │ │ ├── _materialbox.scss │ │ │ │ ├── _toast.scss │ │ │ │ ├── _dropdown.scss │ │ │ │ ├── _tabs.scss │ │ │ │ ├── _modal.scss │ │ │ │ ├── _slider.scss │ │ │ │ ├── _typography.scss │ │ │ │ ├── _collapsible.scss │ │ │ │ ├── _roboto.scss │ │ │ │ ├── _cards.scss │ │ │ │ ├── _sideNav.scss │ │ │ │ ├── _grid.scss │ │ │ │ ├── date_picker │ │ │ │ │ ├── _default.time.scss │ │ │ │ │ ├── _default.scss │ │ │ │ │ └── _default.date.scss │ │ │ │ ├── _navbar.scss │ │ │ │ ├── _buttons.scss │ │ │ │ ├── _waves.scss │ │ │ │ └── _normalize.scss │ │ │ └── materialize.scss │ │ ├── font │ │ │ ├── roboto │ │ │ │ ├── Roboto-Bold.eot │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ ├── Roboto-Thin.eot │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ ├── Roboto-Bold.woff │ │ │ │ ├── Roboto-Bold.woff2 │ │ │ │ ├── Roboto-Light.eot │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ ├── Roboto-Light.woff │ │ │ │ ├── Roboto-Medium.eot │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ ├── Roboto-Thin.woff │ │ │ │ ├── Roboto-Thin.woff2 │ │ │ │ ├── Roboto-Light.woff2 │ │ │ │ ├── Roboto-Medium.woff │ │ │ │ ├── Roboto-Medium.woff2 │ │ │ │ ├── Roboto-Regular.eot │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ ├── Roboto-Regular.woff │ │ │ │ └── Roboto-Regular.woff2 │ │ │ └── material-design-icons │ │ │ │ ├── Material-Design-Icons.eot │ │ │ │ ├── Material-Design-Icons.ttf │ │ │ │ ├── Material-Design-Icons.woff │ │ │ │ └── Material-Design-Icons.woff2 │ │ └── fonts │ │ │ └── roboto │ │ │ ├── Roboto-Bold.eot │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-Bold.woff │ │ │ ├── Roboto-Light.eot │ │ │ ├── Roboto-Light.ttf │ │ │ ├── Roboto-Thin.eot │ │ │ ├── Roboto-Thin.ttf │ │ │ ├── Roboto-Thin.woff │ │ │ ├── Roboto-Bold.woff2 │ │ │ ├── Roboto-Light.woff │ │ │ ├── Roboto-Light.woff2 │ │ │ ├── Roboto-Medium.eot │ │ │ ├── Roboto-Medium.ttf │ │ │ ├── Roboto-Medium.woff │ │ │ ├── Roboto-Medium.woff2 │ │ │ ├── Roboto-Regular.eot │ │ │ ├── Roboto-Regular.ttf │ │ │ ├── Roboto-Regular.woff │ │ │ ├── Roboto-Thin.woff2 │ │ │ └── Roboto-Regular.woff2 │ ├── main │ │ ├── images │ │ │ ├── bg.jpg │ │ │ └── bg - 副本.jpg │ │ ├── js │ │ │ └── main.js │ │ └── css │ │ │ └── main.css │ └── simditor │ │ ├── styles │ │ ├── simditor.scss │ │ └── fonticon.scss │ │ ├── images │ │ └── image.png │ │ └── scripts │ │ ├── module.min.js │ │ ├── hotkeys.min.js │ │ ├── simditor-html.js │ │ ├── uploader.min.js │ │ ├── module.js │ │ ├── hotkeys.js │ │ └── uploader.js ├── note │ ├── __init__.py │ ├── form.py │ └── views.py ├── user │ ├── __init__.py │ ├── form.py │ └── views.py ├── main │ ├── __init__.py │ ├── views.py │ └── errors.py ├── templates │ ├── inc │ │ ├── resource_simditor.html │ │ ├── resource_materialize.html │ │ ├── menu.html │ │ └── header.html │ ├── 404.html │ ├── 500.html │ ├── layout │ │ └── layout.html │ ├── detail.html │ ├── login.html │ ├── macro │ │ └── main.html │ ├── signup.html │ ├── index.html │ └── write.html ├── helper.py ├── upload.py ├── crypt.py ├── __init__.py └── models.py ├── .gitignore ├── requirements.txt ├── README.md ├── config ├── __init__.py ├── testing.py └── development.py ├── manage.py └── install.py /log.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db/snote-bak.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/db/snote-bak.db -------------------------------------------------------------------------------- /db/snote-dev.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/db/snote-dev.db -------------------------------------------------------------------------------- /app/static/materialize/css/gen.bat: -------------------------------------------------------------------------------- 1 | sass -t compressed materialize.scss materialize.min.css -------------------------------------------------------------------------------- /app/static/main/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/main/images/bg.jpg -------------------------------------------------------------------------------- /app/static/simditor/styles/simditor.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import 'fonticon'; 4 | @import 'editor'; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/* 3 | .vscode/ 4 | *.pyc 5 | .tmp/* 6 | config/production.py 7 | db/*.db 8 | .sass-cache -------------------------------------------------------------------------------- /app/static/main/images/bg - 副本.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/main/images/bg - 副本.jpg -------------------------------------------------------------------------------- /app/static/simditor/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/simditor/images/image.png -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /app/note/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | 4 | note = Blueprint('note', __name__) 5 | 6 | from . import views 7 | -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /app/user/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | 4 | user = Blueprint('user', __name__) 5 | 6 | from . import views 7 | -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /app/static/materialize/font/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /app/main/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Blueprint 3 | 4 | main = Blueprint('main', __name__) 5 | 6 | from . import views, errors 7 | -------------------------------------------------------------------------------- /app/static/materialize/fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /app/templates/inc/resource_simditor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/static/materialize/font/material-design-icons/Material-Design-Icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/material-design-icons/Material-Design-Icons.eot -------------------------------------------------------------------------------- /app/static/materialize/font/material-design-icons/Material-Design-Icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/material-design-icons/Material-Design-Icons.ttf -------------------------------------------------------------------------------- /app/static/materialize/font/material-design-icons/Material-Design-Icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/material-design-icons/Material-Design-Icons.woff -------------------------------------------------------------------------------- /app/static/materialize/font/material-design-icons/Material-Design-Icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keelii/snote/HEAD/app/static/materialize/font/material-design-icons/Material-Design-Icons.woff2 -------------------------------------------------------------------------------- /app/static/materialize/css/components/_mixins.scss: -------------------------------------------------------------------------------- 1 | // @mixin box-shadow-2($args1, $args2) { 2 | // -webkit-box-shadow: $args1, $args2; 3 | // -moz-box-shadow: $args1, $args2; 4 | // box-shadow: $args1, $args2; 5 | // } -------------------------------------------------------------------------------- /app/templates/inc/resource_materialize.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_icons-material-design.scss: -------------------------------------------------------------------------------- 1 | /* This is needed for some mobile phones to display the Google Icon font properly */ 2 | .material-icons { 3 | text-rendering: optimizeLegibility; 4 | font-feature-settings: 'liga'; 5 | } 6 | -------------------------------------------------------------------------------- /app/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.html' %} 2 | 3 | {% block body %} 4 | {% include 'inc/header.html' %} 5 | 6 |
7 | 8 |

Page not found.

9 |

Return to home page.

10 | 11 |
12 | 13 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.html' %} 2 | 3 | {% block body %} 4 | {% include 'inc/header.html' %} 5 | 6 |
7 | 8 |

Internal Server Error.

9 |

Return to home page.

10 | 11 |
12 | 13 | {% endblock %} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dominate==2.1.16 2 | flask==0.10.1 3 | Flask-Bootstrap==3.3.5.7 4 | Flask-Login==0.3.2 5 | Flask-Moment==0.5.1 6 | Flask-Script==2.0.5 7 | Flask-SQLAlchemy==2.1 8 | Flask-WTF==0.12 9 | itsdangerous==0.24 10 | Jinja2==2.8 11 | MarkupSafe==0.23 12 | pycrypto==2.6.1 13 | SQLAlchemy==1.0.11 14 | visitor==0.1.2 15 | Werkzeug==0.11.3 16 | WTForms==2.1 17 | -------------------------------------------------------------------------------- /app/main/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import request, render_template, url_for, flash, abort, redirect 3 | from ..models import Note 4 | from . import main as MAIN 5 | from flask.ext.login import current_user 6 | from .. import db 7 | 8 | # Home 9 | @MAIN.route('/') 10 | def index(): 11 | return redirect(url_for('note.show_public_notes')) 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple note 2 | 3 | ## Installation 4 | 5 | ```bash 6 | pip install Flask qiniu 7 | pip install -r requirements.txt 8 | # Copy your production config file 9 | cp config/development.py config/production.py 10 | python ./manage.py install 11 | python ./manage.py runserver -h 127.0.0.1 -p 4096 12 | ``` 13 | 14 | ## 新版 15 | 16 | 基于 Flask 开发的一个基于网页的 WYSIWYG(所见即所得) 富文本编辑器应用 [wtdf.io](https://wtdf.io/) 17 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from development import Config as DevelopmentConfig 3 | from testing import Config as TestingConfig 4 | 5 | try: 6 | from production import Config as ProductionConfig 7 | except (RuntimeError, NameError, ImportError): 8 | print 'Production config not found, please run install shell' 9 | 10 | config = { 11 | 'development': ProductionConfig, 12 | 'testing': TestingConfig, 13 | 'production': ProductionConfig, 14 | } 15 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_forms.scss: -------------------------------------------------------------------------------- 1 | // Remove Focus Boxes 2 | select:focus { 3 | outline: $select-focus; 4 | } 5 | 6 | button:focus { 7 | outline: none; 8 | background-color: $button-background-focus; 9 | } 10 | 11 | label { 12 | font-size: $label-font-size; 13 | color: $input-border-color; 14 | } 15 | 16 | @import 'input-fields'; 17 | @import 'radio-buttons'; 18 | @import 'checkboxes'; 19 | @import 'switches'; 20 | @import 'select'; 21 | @import 'file-input'; 22 | @import 'range'; 23 | -------------------------------------------------------------------------------- /app/main/errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import render_template, jsonify 3 | from . import main as MAIN 4 | 5 | @MAIN.app_errorhandler(404) 6 | def page_not_found(error): 7 | return render_template('404.html', title='page not found'); 8 | 9 | @MAIN.app_errorhandler(500) 10 | def page_not_found(error): 11 | return render_template('500.html', title='internal Server Error'); 12 | 13 | @MAIN.app_errorhandler(413) 14 | def request_entity_too_large(error): 15 | result = dict(success=False, msg=u'文件太大,不能超过2M', file_path='') 16 | return jsonify(result) -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/evn python 2 | import os 3 | from app import create_app, db 4 | from app.models import User, Note 5 | from flask.ext.script import Manager, Shell 6 | from install import install_app 7 | 8 | app = create_app(os.getenv('FLASK_CONFIG') or 'production') 9 | 10 | manager = Manager(app) 11 | 12 | @manager.command 13 | def install(): 14 | install_app(app, db) 15 | 16 | def make_shell_context(): 17 | return dict(app=app, db=db, User=User, Note=Note) 18 | 19 | manager.add_command('shell', Shell(make_context=make_shell_context)) 20 | 21 | if __name__ == '__main__': 22 | manager.run() 23 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_chips.scss: -------------------------------------------------------------------------------- 1 | .chip { 2 | display: inline-block; 3 | height: 32px; 4 | font-size: 13px; 5 | font-weight: 500; 6 | color: rgba(0,0,0,.6); 7 | line-height: 32px; 8 | padding: 0 12px; 9 | border-radius: 16px; 10 | background-color: $chip-bg-color; 11 | 12 | img { 13 | float: left; 14 | margin: 0 8px 0 -12px; 15 | height: 32px; 16 | width: 32px; 17 | border-radius: 50%; 18 | } 19 | 20 | i.material-icons { 21 | cursor: pointer; 22 | float: right; 23 | font-size: 16px; 24 | line-height: 32px; 25 | padding-left: 8px; 26 | } 27 | } -------------------------------------------------------------------------------- /app/helper.py: -------------------------------------------------------------------------------- 1 | import os, glob 2 | from flask import url_for 3 | from flask.ext.login import current_user 4 | 5 | # Helpers 6 | def getNoteUrl(note): 7 | if note.public: 8 | return '/note/{0}'.format(note.id) 9 | else: 10 | return '/{0}/{1}'.format(current_user.nick_name, note.id) 11 | 12 | def emptyDir(dir): 13 | files = glob.glob(os.path.join(dir, '*')) 14 | for f in files: 15 | print f 16 | os.remove(f) 17 | 18 | class Helper(): 19 | @staticmethod 20 | def init_app(app): 21 | @app.context_processor 22 | def utility_processor(): 23 | return dict( getNoteUrl=getNoteUrl ) 24 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_carousel.scss: -------------------------------------------------------------------------------- 1 | .carousel { 2 | overflow: hidden; 3 | position: relative; 4 | width: 100%; 5 | height: 400px; 6 | perspective: 500px; 7 | transform-style: preserve-3d; 8 | transform-origin: 0% 50%; 9 | 10 | .carousel-item { 11 | width: 200px; 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | 16 | img { 17 | width: 100%; 18 | } 19 | } 20 | 21 | &.carousel-slider { 22 | top: 0; 23 | left: 0; 24 | height: 0; 25 | 26 | .carousel-item { 27 | width: 100%; 28 | height: 100%; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/upload.py: -------------------------------------------------------------------------------- 1 | from qiniu import Auth, put_file, etag, urlsafe_base64_encode 2 | import qiniu.config 3 | 4 | class Upload(): 5 | 6 | def __init__(self, access_key, secret_key, bucket_name): 7 | self.access_key = access_key 8 | self.secret_key = secret_key 9 | self.bucket_name = bucket_name 10 | 11 | self.auth = Auth(access_key, secret_key) 12 | 13 | def send_file(self, filename, local_file): 14 | self.token = self.auth.upload_token(self.bucket_name, filename, 3600) 15 | ret, info = put_file(self.token, filename, local_file) 16 | 17 | if info.status_code == 200 and not info.exception: 18 | return ret 19 | else: 20 | return None 21 | -------------------------------------------------------------------------------- /app/static/main/js/main.js: -------------------------------------------------------------------------------- 1 | function setBackTop() { 2 | var mainFloating = $('#main-floating'); 3 | var backTopFloating = $('#back-top-floating'); 4 | 5 | backTopFloating.bind('click', function() { 6 | $('body').animate({ 7 | scrollTop: 0 8 | }, 500); 9 | }); 10 | 11 | $(window).scroll(function() { 12 | var sTop = $('body').scrollTop() || $('html').scrollTop(); 13 | 14 | if (sTop > 500) { 15 | mainFloating.hide(); 16 | backTopFloating.show(); 17 | } else { 18 | mainFloating.show(); 19 | backTopFloating.hide(); 20 | } 21 | }); 22 | } 23 | 24 | $(document).ready(function() { 25 | Materialize.updateTextFields(); 26 | setBackTop(); 27 | }); -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_file-input.scss: -------------------------------------------------------------------------------- 1 | /* File Input 2 | ========================================================================== */ 3 | 4 | .file-field { 5 | position: relative; 6 | 7 | .file-path-wrapper { 8 | overflow: hidden; 9 | padding-left: 10px; 10 | } 11 | 12 | input.file-path { width: 100%; } 13 | 14 | .btn { 15 | float: left; 16 | height: $input-height; 17 | line-height: $input-height; 18 | } 19 | 20 | span { 21 | cursor: pointer; 22 | } 23 | 24 | input[type=file] { 25 | position: absolute; 26 | top: 0; 27 | right: 0; 28 | left: 0; 29 | bottom: 0; 30 | width: 100%; 31 | margin: 0; 32 | padding: 0; 33 | font-size: 20px; 34 | cursor: pointer; 35 | opacity: 0; 36 | filter: alpha(opacity=0); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_table_of_contents.scss: -------------------------------------------------------------------------------- 1 | /*************** 2 | Nav List 3 | ***************/ 4 | .table-of-contents { 5 | &.fixed { 6 | position: fixed; 7 | } 8 | 9 | li { 10 | padding: 2px 0; 11 | } 12 | a { 13 | display: inline-block; 14 | font-weight: 300; 15 | color: #757575; 16 | padding-left: 20px; 17 | height: 1.5rem; 18 | line-height: 1.5rem; 19 | letter-spacing: .4; 20 | display: inline-block; 21 | 22 | &:hover { 23 | color: lighten(#757575, 20%); 24 | padding-left: 19px; 25 | border-left: 1px solid lighten(color("materialize-red", "base"),10%); 26 | } 27 | &.active { 28 | font-weight: 500; 29 | padding-left: 18px; 30 | border-left: 2px solid lighten(color("materialize-red", "base"),10%); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_tooltip.scss: -------------------------------------------------------------------------------- 1 | .material-tooltip { 2 | padding: 10px 8px; 3 | font-size: 1rem; 4 | z-index: 2000; 5 | background-color: transparent; 6 | border-radius: 2px; 7 | color: #fff; 8 | min-height: 36px; 9 | line-height: 120%; 10 | opacity: 0; 11 | display: none; 12 | position: absolute; 13 | text-align: center; 14 | max-width: calc(100% - 4px); 15 | overflow: hidden; 16 | left:0; 17 | top:0; 18 | pointer-events: none; 19 | will-change: top, left; 20 | } 21 | 22 | .backdrop { 23 | position: absolute; 24 | opacity: 0; 25 | display: none; 26 | height: 7px; 27 | width: 14px; 28 | border-radius: 0 0 14px 14px; 29 | background-color: #323232; 30 | z-index: -1; 31 | transform-origin: 50% 10%; 32 | 33 | will-change: transform, opacity; 34 | } 35 | -------------------------------------------------------------------------------- /app/note/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from wtforms import Form, BooleanField, TextField, TextAreaField, PasswordField 3 | from wtforms.validators import Length, Required 4 | 5 | class CreateNoteForm(Form): 6 | title = TextField('Title', validators=[ 7 | Required(), 8 | Length(min=1, max=100) 9 | ]) 10 | content = TextAreaField(' ', validators=[ 11 | Required() 12 | ], render_kw={"id": "editor"}) 13 | public = BooleanField('Public', render_kw={"title": "Others can view."}) 14 | 15 | class EditNoteForm(Form): 16 | title = TextField('Title', validators=[ 17 | Required(), 18 | Length(min=1, max=100) 19 | ]) 20 | content = TextAreaField(' ', validators=[ 21 | Required() 22 | ], render_kw={"id": "editor"}) 23 | public = BooleanField('Public', render_kw={"title": "Others can view."}) 24 | -------------------------------------------------------------------------------- /app/crypt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import hashlib 3 | from binascii import b2a_hex, a2b_hex 4 | from Crypto.Cipher import AES 5 | from Crypto import Random 6 | 7 | class MyCrypt(): 8 | def __init__(self, key): 9 | self.key = hashlib.md5(key).hexdigest() 10 | self.iv = b'0000000000000000' 11 | self.mode = AES.MODE_CBC 12 | 13 | def encrypt(self, text): 14 | cryptor = AES.new(self.key, self.mode, self.iv) 15 | length = 16 16 | count = len(text) 17 | add = length - (count % length) 18 | text = text + ('\0' * add) 19 | self.ciphertext = cryptor.encrypt(text) 20 | return b2a_hex(self.ciphertext) 21 | 22 | def decrypt(self, text): 23 | cryptor = AES.new(self.key, self.mode, self.iv) 24 | plain_text = cryptor.decrypt(a2b_hex(text)) 25 | return plain_text.rstrip('\0') 26 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_materialbox.scss: -------------------------------------------------------------------------------- 1 | .materialboxed { 2 | display: block; 3 | cursor: zoom-in; 4 | position: relative; 5 | transition: opacity .4s; 6 | 7 | &:hover { 8 | &:not(.active) { 9 | opacity: .8; 10 | } 11 | will-change: left, top, width, height; 12 | } 13 | } 14 | 15 | .materialboxed.active { 16 | cursor: zoom-out; 17 | } 18 | 19 | #materialbox-overlay { 20 | position:fixed; 21 | top:0; 22 | left:0; 23 | right: 0; 24 | bottom: 0; 25 | background-color: #292929; 26 | z-index: 1000; 27 | 28 | will-change: opacity; 29 | } 30 | .materialbox-caption { 31 | position: fixed; 32 | display: none; 33 | color: #fff; 34 | line-height: 50px; 35 | bottom: 0; 36 | width: 100%; 37 | text-align: center; 38 | padding: 0% 15%; 39 | height: 50px; 40 | z-index: 1000; 41 | -webkit-font-smoothing: antialiased; 42 | } -------------------------------------------------------------------------------- /app/templates/layout/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title | title }} - simple note 6 | 7 | 8 | {% block robots %}{% endblock %} 9 | 10 | 11 | {% include 'inc/resource_materialize.html' %} 12 | {% include 'inc/resource_simditor.html' %} 13 | 14 | 15 | {% block editor_script %}{% endblock %} 16 | 17 | 18 | 19 | {% block body %} 20 | {% endblock %} 21 | 22 | {% block main_script %} 23 | 24 | 25 | {% endblock %} 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/user/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from wtforms import Form, BooleanField, TextField, TextAreaField, RadioField, PasswordField 3 | from wtforms.validators import Length, Required, EqualTo, Email 4 | 5 | class SignupForm(Form): 6 | email = TextField('Email', validators=[ 7 | Email(), 8 | Required(), 9 | Length(min=5, max=40) 10 | ]) 11 | nick_name = TextField('Nickname', validators=[ 12 | Length(min=3, max=25), 13 | ]) 14 | password = PasswordField('Password', validators=[ 15 | Required(), 16 | Length(min=6, max=20), 17 | EqualTo('confirm', message='Passwords must match') 18 | ]) 19 | confirm = PasswordField('Confirm password') 20 | 21 | class LoginForm(Form): 22 | email = TextField('Email', validators=[ 23 | Email(), 24 | Required(), 25 | Length(min=5, max=40) 26 | ]) 27 | password = PasswordField('Password', validators=[ 28 | Required(), 29 | Length(min=6, max=20) 30 | ]) 31 | remember = BooleanField('remember') 32 | -------------------------------------------------------------------------------- /app/templates/detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.html' %} 2 | 3 | {% block htmlclass %}{% endblock %} 4 | 5 | {% block robots %} 6 | {% if not note.public %} 7 | 8 | {% endif %} 9 | {% endblock %} 10 | 11 | {% block body %} 12 | 13 |
14 |
15 |

{{ note.title }}

16 |
17 |
18 | perm_identity {{ user.nick_name }}  19 | date_range {{ note.created_at.strftime("%Y-%m-%d %H:%M:%S") }} 20 |
21 |
{{ note.content | safe }}
22 | 23 |
24 | 25 | 26 | 27 | {% include 'inc/menu.html' %} 28 | 29 |
30 | 33 |
34 | 35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Flask, Blueprint 3 | from flask.ext.login import LoginManager 4 | from flask.ext.sqlalchemy import SQLAlchemy 5 | from config import config 6 | from helper import Helper 7 | from upload import Upload 8 | 9 | login_manager = LoginManager() 10 | login_manager.login_view = 'user.login' 11 | db = SQLAlchemy() 12 | 13 | def create_app(config_name): 14 | app = Flask(__name__, static_url_path='') 15 | 16 | app.config.from_object(config[config_name]) 17 | 18 | # Configurations & Extensions 19 | config[config_name].init_app(app) 20 | login_manager.init_app(app) 21 | db.init_app(app) 22 | Helper.init_app(app) 23 | 24 | @app.teardown_request 25 | def shutdown_session(exception=None): 26 | db.session.remove() 27 | 28 | # Register Blueprint 29 | from .main import main as main_blueprint 30 | from .user import user as user_blueprint 31 | from .note import note as note_blueprint 32 | 33 | app.register_blueprint(main_blueprint) 34 | app.register_blueprint(user_blueprint) 35 | app.register_blueprint(note_blueprint) 36 | 37 | return app 38 | -------------------------------------------------------------------------------- /app/static/materialize/css/materialize.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | // Mixins 4 | // @import "components/prefixer"; 5 | @import "components/mixins"; 6 | @import "components/color"; 7 | 8 | // Variables; 9 | @import "components/variables"; 10 | 11 | // Reset 12 | @import "components/normalize"; 13 | 14 | // components 15 | @import "components/global"; 16 | @import "components/icons-material-design"; 17 | @import "components/grid"; 18 | @import "components/navbar"; 19 | @import "components/roboto"; 20 | @import "components/typography"; 21 | @import "components/cards"; 22 | @import "components/toast"; 23 | @import "components/tabs"; 24 | @import "components/tooltip"; 25 | @import "components/buttons"; 26 | @import "components/dropdown"; 27 | @import "components/waves"; 28 | @import "components/modal"; 29 | @import "components/collapsible"; 30 | @import "components/chips"; 31 | @import "components/materialbox"; 32 | @import "components/forms/forms"; 33 | @import "components/table_of_contents"; 34 | @import "components/sideNav"; 35 | @import "components/preloader"; 36 | @import "components/slider"; 37 | @import "components/carousel"; 38 | @import "components/date_picker/default"; 39 | @import "components/date_picker/default.date"; 40 | @import "components/date_picker/default.time"; 41 | -------------------------------------------------------------------------------- /config/testing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | basedir = os.path.abspath(os.path.dirname(__file__)) 4 | 5 | def getDatabasePath(name): 6 | return 'sqlite:///' + os.path.join(basedir, 'db', name) 7 | 8 | def getTmpDir(dirname): 9 | return os.path.join(basedir, dirname) 10 | 11 | class Config: 12 | DEBUG = True 13 | SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') \ 14 | or getDatabasePath('snote-dev.db') 15 | 16 | # !!! Do not show anyone else !!! 17 | SECRET_KEY = os.environ.get('SECRET_KEY') or '1234567890abcdef' 18 | 19 | MAX_CONTENT_LENGTH = os.environ.get('MAX_CONTENT_LENGTH') or 2 * 1024 * 1024 20 | 21 | SQLALCHEMY_COMMIT_ON_TEARDOWN = True 22 | SQLALCHEMY_TRACK_MODIFICATIONS = True 23 | 24 | # Note 25 | NOTE_NUM_PER_PAGE = 12 26 | 27 | # TMP directory 28 | TMP_DIR = getTmpDir('.tmp') 29 | QINIU_DOMAIN = 'http://****.clouddn.com/' 30 | QN_ACCESS_KEY = 'qiniu access key' 31 | QN_SECRET_KEY = 'qiniu secret key' 32 | QN_BUCKET_NAME = 'qiniu bucket name' 33 | 34 | @staticmethod 35 | def init_app(app): 36 | # maximum allowed 2 megabytes 37 | app.config['MAX_CONTENT_LENGTH'] = Config.MAX_CONTENT_LENGTH 38 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Config.SQLALCHEMY_TRACK_MODIFICATIONS 39 | -------------------------------------------------------------------------------- /config/development.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | basedir = os.path.abspath(os.path.dirname(__file__)) 4 | 5 | def getDatabasePath(name): 6 | return 'sqlite:///' + os.path.join(basedir, os.path.pardir, 'db', name) 7 | 8 | def getTmpDir(dirname): 9 | return os.path.join(basedir, os.path.pardir, dirname) 10 | 11 | class Config: 12 | DEBUG = True 13 | SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') \ 14 | or getDatabasePath('snote-dev.db') 15 | 16 | # !!! Do not show anyone else !!! 17 | SECRET_KEY = os.environ.get('SECRET_KEY') or '1234567890abcdef' 18 | 19 | MAX_CONTENT_LENGTH = os.environ.get('MAX_CONTENT_LENGTH') or 2 * 1024 * 1024 20 | 21 | SQLALCHEMY_COMMIT_ON_TEARDOWN = True 22 | SQLALCHEMY_TRACK_MODIFICATIONS = True 23 | 24 | # Note 25 | NOTE_NUM_PER_PAGE = 12 26 | 27 | # TMP directory 28 | TMP_DIR = getTmpDir('.tmp') 29 | QINIU_DOMAIN = 'http://****.clouddn.com/' 30 | QN_ACCESS_KEY = 'qiniu access key' 31 | QN_SECRET_KEY = 'qiniu secret key' 32 | QN_BUCKET_NAME = 'qiniu bucket name' 33 | 34 | @staticmethod 35 | def init_app(app): 36 | # maximum allowed 2 megabytes 37 | app.config['MAX_CONTENT_LENGTH'] = Config.MAX_CONTENT_LENGTH 38 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Config.SQLALCHEMY_TRACK_MODIFICATIONS -------------------------------------------------------------------------------- /app/static/materialize/css/components/_toast.scss: -------------------------------------------------------------------------------- 1 | #toast-container { 2 | display:block; 3 | position: fixed; 4 | z-index: 10000; 5 | 6 | @media #{$small-and-down} { 7 | min-width: 100%; 8 | bottom: 0%; 9 | } 10 | @media #{$medium-only} { 11 | left: 5%; 12 | bottom: 7%; 13 | max-width: 90%; 14 | } 15 | @media #{$large-and-up} { 16 | top: 10%; 17 | right: 7%; 18 | max-width: 86%; 19 | } 20 | } 21 | 22 | .toast { 23 | @extend .z-depth-1; 24 | border-radius: 2px; 25 | top: 0; 26 | width: auto; 27 | clear: both; 28 | margin-top: 10px; 29 | position: relative; 30 | max-width:100%; 31 | height: auto; 32 | min-height: $toast-height; 33 | line-height: 1.5em; 34 | word-break: break-all; 35 | background-color: $toast-color; 36 | padding: 10px 25px; 37 | font-size: 1.1rem; 38 | font-weight: 300; 39 | color: $toast-text-color; 40 | 41 | display: flex; 42 | align-items: center; 43 | justify-content: space-between; 44 | 45 | .btn, .btn-flat { 46 | margin: 0; 47 | margin-left: 3rem; 48 | } 49 | 50 | &.rounded{ 51 | border-radius: 24px; 52 | } 53 | 54 | @media #{$small-and-down} { 55 | width:100%; 56 | border-radius: 0; 57 | } 58 | @media #{$medium-only} { 59 | float: left; 60 | } 61 | @media #{$large-and-up} { 62 | float: right; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_dropdown.scss: -------------------------------------------------------------------------------- 1 | .dropdown-content { 2 | @extend .z-depth-1; 3 | background-color: $dropdown-bg-color; 4 | margin: 0; 5 | display: none; 6 | min-width: 100px; 7 | max-height: 650px; 8 | overflow-y: auto; 9 | opacity: 0; 10 | position: absolute; 11 | z-index: 999; 12 | will-change: width, height; 13 | 14 | li { 15 | clear: both; 16 | color: $off-black; 17 | cursor: pointer; 18 | min-height: $dropdown-item-height; 19 | line-height: 1.5rem; 20 | width: 100%; 21 | text-align: left; 22 | text-transform: none; 23 | 24 | &:hover, &.active, &.selected { 25 | background-color: $dropdown-hover-bg-color; 26 | } 27 | 28 | &.active.selected { 29 | background-color: darken($dropdown-hover-bg-color, 5%); 30 | } 31 | 32 | &.divider { 33 | min-height: 0; 34 | height: 1px; 35 | } 36 | 37 | & > a, & > span { 38 | font-size: 16px; 39 | color: $dropdown-color; 40 | display: block; 41 | line-height: 22px; 42 | padding: (($dropdown-item-height - 22) / 2) 16px; 43 | } 44 | 45 | & > span > label { 46 | top: 1px; 47 | left: 3px; 48 | height: 18px; 49 | } 50 | 51 | // Icon alignment override 52 | & > a > i { 53 | height: inherit; 54 | line-height: inherit; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_tabs.scss: -------------------------------------------------------------------------------- 1 | .tabs { 2 | display: flex; 3 | position: relative; 4 | overflow-x: auto; 5 | overflow-y: hidden; 6 | height: 48px; 7 | background-color: $tabs-bg-color; 8 | margin: 0 auto; 9 | width: 100%; 10 | white-space: nowrap; 11 | 12 | .tab { 13 | -webkit-box-flex: 1; 14 | -webkit-flex-grow: 1; 15 | -ms-flex-positive: 1; 16 | flex-grow: 1; 17 | display: block; 18 | float: left; 19 | text-align: center; 20 | line-height: 48px; 21 | height: 48px; 22 | padding: 0; 23 | margin: 0; 24 | text-transform: uppercase; 25 | text-overflow: ellipsis; 26 | overflow: hidden; 27 | letter-spacing: .8px; 28 | width: 15%; 29 | min-width: 80px; 30 | 31 | a { 32 | color: $tabs-text-color; 33 | display: block; 34 | width: 100%; 35 | height: 100%; 36 | text-overflow: ellipsis; 37 | overflow: hidden; 38 | transition: color .28s ease; 39 | &:hover { 40 | color: lighten($tabs-text-color, 20%); 41 | } 42 | } 43 | 44 | &.disabled a { 45 | color: lighten($tabs-text-color, 20%); 46 | cursor: default; 47 | } 48 | } 49 | .indicator { 50 | position: absolute; 51 | bottom: 0; 52 | height: 2px; 53 | background-color: $tabs-underline-color; 54 | will-change: left, right; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def copy_file(source, dest, buffer_size=1024*1024): 4 | while True: 5 | copy_buffer = source.read(buffer_size) 6 | if not copy_buffer: 7 | break 8 | dest.write(copy_buffer) 9 | 10 | def install_app(app, db): 11 | print '' 12 | print 'Simple note prepareing install' 13 | print '-' * 20 14 | 15 | print 'Creating database boilerplate' 16 | 17 | db.create_all() 18 | 19 | print 'Database created successfully' 20 | print '-' * 20 21 | 22 | print 'Make template directory' 23 | 24 | # prepare tmp directory 25 | if not os.path.isdir(app.config['TMP_DIR']): 26 | os.mkdir(app.config['TMP_DIR']) 27 | 28 | print 'Template directory made successfully' 29 | print '-' * 20 30 | 31 | print 'Creating configuration files' 32 | 33 | basedir = os.path.abspath(os.path.dirname(__file__)) 34 | config_dir = os.path.join(basedir, 'config') 35 | development_config = os.path.join(config_dir, 'development.py') 36 | production_config = os.path.join(config_dir, 'production.py') 37 | 38 | 39 | with open(development_config, 'rb') as src, open(production_config, 'wb') as dest: 40 | copy_file(src, dest) 41 | 42 | print 'Production config file created,' 43 | print 'Please set the SECRET_KEY and SQLALCHEMY_DATABASE_URI' 44 | print 'and other important fields.' 45 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_modal.scss: -------------------------------------------------------------------------------- 1 | .modal { 2 | @extend .z-depth-4; 3 | 4 | display: none; 5 | position: fixed; 6 | left: 0; 7 | right: 0; 8 | background-color: #fafafa; 9 | padding: 0; 10 | max-height: 70%; 11 | width: 55%; 12 | margin: auto; 13 | overflow-y: auto; 14 | 15 | border-radius: 2px; 16 | will-change: top, opacity; 17 | 18 | @media #{$medium-and-down} { 19 | width: 80%; 20 | } 21 | 22 | h1,h2,h3,h4 { 23 | margin-top: 0; 24 | } 25 | 26 | .modal-content { 27 | padding: 24px; 28 | } 29 | .modal-close { 30 | cursor: pointer; 31 | } 32 | 33 | .modal-footer { 34 | border-radius: 0 0 2px 2px; 35 | background-color: #fafafa; 36 | padding: 4px 6px; 37 | height: 56px; 38 | width: 100%; 39 | 40 | .btn, .btn-flat { 41 | float: right; 42 | margin: 6px 0; 43 | } 44 | } 45 | } 46 | .lean-overlay { 47 | position: fixed; 48 | z-index:999; 49 | top: -100px; 50 | left: 0; 51 | bottom: 0; 52 | right: 0; 53 | height: 125%; 54 | width: 100%; 55 | background: #000; 56 | display: none; 57 | 58 | will-change: opacity; 59 | } 60 | 61 | // Modal with fixed action footer 62 | .modal.modal-fixed-footer { 63 | padding: 0; 64 | height: 70%; 65 | 66 | .modal-content { 67 | position: absolute; 68 | height: calc(100% - 56px); 69 | max-height: 100%; 70 | width: 100%; 71 | overflow-y: auto; 72 | } 73 | 74 | .modal-footer { 75 | border-top: 1px solid rgba(0,0,0,.1); 76 | position: absolute; 77 | bottom: 0; 78 | } 79 | } 80 | 81 | // Modal Bottom Sheet Style 82 | .modal.bottom-sheet { 83 | top: auto; 84 | bottom: -100%; 85 | margin: 0; 86 | width: 100%; 87 | max-height: 45%; 88 | border-radius: 0; 89 | will-change: bottom, opacity; 90 | } 91 | -------------------------------------------------------------------------------- /app/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.html' %} 2 | {% from 'macro/main.html' import get_flash_message_html, render_field %} 3 | 4 | {% block body %} 5 | {% include 'inc/header.html' %} 6 | 7 |
8 |
9 | 10 |
11 |
12 | {% with messages = get_flashed_messages(with_categories=true) %} 13 | {% if messages %} 14 |
15 | {% for category, message in messages %} 16 |
17 | {{ get_flash_message_html(category, message) }} 18 |
19 | {% endfor %} 20 |
21 | {% endif %} 22 | {% endwith %} 23 | 24 |
25 |
26 | {{ render_field(form.email) }} 27 |
28 |
29 |
30 |
31 | {{ render_field(form.password) }} 32 |
33 |
34 |
35 |
36 | {{ render_field(form.remember) }} 37 |
38 |
39 |
40 |
41 | 42 |  new guy? Sign up 43 |
44 |
45 |
46 |
47 | 48 |
49 | 50 | {% include 'inc/menu.html' %} 51 | 52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_slider.scss: -------------------------------------------------------------------------------- 1 | .slider { 2 | position: relative; 3 | height: 400px; 4 | width: 100%; 5 | 6 | // Fullscreen slider 7 | &.fullscreen { 8 | height: 100%; 9 | width: 100%; 10 | position: absolute; 11 | top: 0; 12 | left: 0; 13 | right: 0; 14 | bottom: 0; 15 | 16 | ul.slides { 17 | height: 100%; 18 | } 19 | 20 | ul.indicators { 21 | z-index: 2; 22 | bottom: 30px; 23 | } 24 | } 25 | 26 | .slides { 27 | background-color: $slider-bg-color; 28 | margin: 0; 29 | height: 400px; 30 | 31 | li { 32 | opacity: 0; 33 | position: absolute; 34 | top: 0; 35 | left: 0; 36 | z-index: 1; 37 | width: 100%; 38 | height: inherit; 39 | overflow: hidden; 40 | 41 | img { 42 | height: 100%; 43 | width: 100%; 44 | background-size: cover; 45 | background-position: center; 46 | } 47 | 48 | .caption { 49 | color: #fff; 50 | position: absolute; 51 | top: 15%; 52 | left: 15%; 53 | width: 70%; 54 | opacity: 0; 55 | 56 | p { color: $slider-bg-color-light; } 57 | } 58 | 59 | &.active { 60 | z-index: 2; 61 | } 62 | } 63 | } 64 | 65 | 66 | .indicators { 67 | position: absolute; 68 | text-align: center; 69 | left: 0; 70 | right: 0; 71 | bottom: 0; 72 | margin: 0; 73 | 74 | .indicator-item { 75 | display: inline-block; 76 | position: relative; 77 | cursor: pointer; 78 | height: 16px; 79 | width: 16px; 80 | margin: 0 12px; 81 | background-color: $slider-bg-color-light; 82 | 83 | transition: background-color .3s; 84 | border-radius: 50%; 85 | 86 | &.active { 87 | background-color: $slider-indicator-color; 88 | } 89 | } 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /app/static/materialize/css/components/_typography.scss: -------------------------------------------------------------------------------- 1 | 2 | a { 3 | text-decoration: none; 4 | } 5 | 6 | html{ 7 | line-height: 1.5; 8 | 9 | @media only screen and (min-width: 0) { 10 | font-size: 14px; 11 | } 12 | 13 | @media only screen and (min-width: $medium-screen) { 14 | font-size: 14.5px; 15 | } 16 | 17 | @media only screen and (min-width: $large-screen) { 18 | font-size: 15px; 19 | } 20 | 21 | font-family: "Roboto", sans-serif; 22 | font-weight: normal; 23 | color: $off-black; 24 | } 25 | h1, h2, h3, h4, h5, h6 { 26 | font-weight: 400; 27 | line-height: 1.1; 28 | } 29 | 30 | // Header Styles 31 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; } 32 | h1 { font-size: $h1-fontsize; line-height: 110%; margin: ($h1-fontsize / 2) 0 ($h1-fontsize / 2.5) 0;} 33 | h2 { font-size: $h2-fontsize; line-height: 110%; margin: ($h2-fontsize / 2) 0 ($h2-fontsize / 2.5) 0;} 34 | h3 { font-size: $h3-fontsize; line-height: 110%; margin: ($h3-fontsize / 2) 0 ($h3-fontsize / 2.5) 0;} 35 | h4 { font-size: $h4-fontsize; line-height: 110%; margin: ($h4-fontsize / 2) 0 ($h4-fontsize / 2.5) 0;} 36 | h5 { font-size: $h5-fontsize; line-height: 110%; margin: ($h5-fontsize / 2) 0 ($h5-fontsize / 2.5) 0;} 37 | h6 { font-size: $h6-fontsize; line-height: 110%; margin: ($h6-fontsize / 2) 0 ($h6-fontsize / 2.5) 0;} 38 | 39 | // Text Styles 40 | em { font-style: italic; } 41 | strong { font-weight: 500; } 42 | small { font-size: 75%; } 43 | .light { font-weight: 300; } 44 | .thin { font-weight: 200; } 45 | 46 | 47 | .flow-text{ 48 | font-weight: 300; 49 | $i: 0; 50 | @while $i <= $intervals { 51 | @media only screen and (min-width : 360 + ($i * $interval-size)) { 52 | font-size: 1.2rem * (1 + (.02 * $i)); 53 | } 54 | $i: $i + 1; 55 | } 56 | 57 | // Handle below 360px screen 58 | @media only screen and (max-width: 360px) { 59 | font-size: 1.2rem; 60 | } 61 | } -------------------------------------------------------------------------------- /app/templates/macro/main.html: -------------------------------------------------------------------------------- 1 | {% macro render_field(field) %} 2 | {{ field(**kwargs)|safe }} 3 | {{ field.label }} 4 | 5 | {% if field.errors %} 6 | 11 | {% endif %} 12 | {% endmacro %} 13 | 14 | {% macro get_flash_message_html(type, msg) -%} 15 | {% if type == 'success' %} 16 |
17 | done {{ msg | safe }} 18 |
19 | {% elif type == 'warning' %} 20 |
21 | warning {{ msg | safe }} 22 |
23 | {% else %} 24 |
25 | error {{ msg | safe }} 26 |
27 | {% endif %} 28 | {%- endmacro %} 29 | 30 | {% macro render_pagination(pagination, endpoint) -%} 31 | 52 | {%- endmacro %} -------------------------------------------------------------------------------- /app/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.html' %} 2 | {% from 'macro/main.html' import get_flash_message_html, render_field %} 3 | 4 | {% block body %} 5 | {% include 'inc/header.html' %} 6 | 7 |
8 |
9 | 10 |
11 |
12 | {% with messages = get_flashed_messages(with_categories=true) %} 13 | {% if messages %} 14 |
15 | {% for category, message in messages %} 16 |
17 | {{ get_flash_message_html(category, message) }} 18 |
19 | {% endfor %} 20 |
21 | {% endif %} 22 | {% endwith %} 23 | 24 |
25 |
26 | {{ render_field(form.email) }} 27 |
28 |
29 |
30 |
31 | {{ render_field(form.nick_name) }} 32 |
33 |
34 |
35 |
36 | {{ render_field(form.password) }} 37 |
38 |
39 |
40 |
41 | {{ render_field(form.confirm) }} 42 |
43 |
44 |
45 |
46 | 47 |  have an account? Log in 48 |
49 |
50 |
51 |
52 | 53 |
54 | 55 | {% endblock %} 56 | 57 | {% include 'inc/menu.html' %} -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_switches.scss: -------------------------------------------------------------------------------- 1 | /* Switch 2 | ========================================================================== */ 3 | 4 | .switch, 5 | .switch * { 6 | -webkit-user-select: none; 7 | -moz-user-select: none; 8 | -khtml-user-select: none; 9 | -ms-user-select: none; 10 | } 11 | 12 | .switch label { 13 | cursor: pointer; 14 | } 15 | 16 | .switch label input[type=checkbox] { 17 | opacity: 0; 18 | width: 0; 19 | height: 0; 20 | 21 | &:checked + .lever { 22 | background-color: $switch-checked-lever-bg; 23 | 24 | &:after { 25 | background-color: $switch-bg-color; 26 | left: 24px; 27 | } 28 | } 29 | } 30 | 31 | .switch label .lever { 32 | content: ""; 33 | display: inline-block; 34 | position: relative; 35 | width: 40px; 36 | height: 15px; 37 | background-color: $switch-unchecked-lever-bg; 38 | border-radius: $switch-radius; 39 | margin-right: 10px; 40 | transition: background 0.3s ease; 41 | vertical-align: middle; 42 | margin: 0 16px; 43 | 44 | &:after { 45 | content: ""; 46 | position: absolute; 47 | display: inline-block; 48 | width: 21px; 49 | height: 21px; 50 | background-color: $switch-unchecked-bg; 51 | border-radius: 21px; 52 | box-shadow: 0 1px 3px 1px rgba(0,0,0,.4); 53 | left: -5px; 54 | top: -3px; 55 | transition: left 0.3s ease, background .3s ease, box-shadow 0.1s ease; 56 | } 57 | } 58 | 59 | // Switch active style 60 | input[type=checkbox]:checked:not(:disabled) ~ .lever:active::after, 61 | input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::after { 62 | box-shadow: 0 1px 3px 1px rgba(0,0,0,.4), 0 0 0 15px transparentize($switch-bg-color, .9); 63 | } 64 | 65 | input[type=checkbox]:not(:disabled) ~ .lever:active:after, 66 | input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::after { 67 | box-shadow: 0 1px 3px 1px rgba(0,0,0,.4), 0 0 0 15px rgba(0, 0, 0, .08); 68 | } 69 | 70 | // Disabled Styles 71 | .switch input[type=checkbox][disabled] + .lever { 72 | cursor: default; 73 | } 74 | 75 | .switch label input[type=checkbox][disabled] + .lever:after, 76 | .switch label input[type=checkbox][disabled]:checked + .lever:after { 77 | background-color: $input-disabled-solid-color; 78 | } 79 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_collapsible.scss: -------------------------------------------------------------------------------- 1 | .collapsible { 2 | border-top: 1px solid $collapsible-border-color; 3 | border-right: 1px solid $collapsible-border-color; 4 | border-left: 1px solid $collapsible-border-color; 5 | margin: $element-top-margin 0 $element-bottom-margin 0; 6 | @extend .z-depth-1; 7 | } 8 | 9 | .collapsible-header { 10 | display: block; 11 | cursor: pointer; 12 | min-height: $collapsible-height; 13 | line-height: $collapsible-height; 14 | padding: 0 1rem; 15 | background-color: $collapsible-header-color; 16 | border-bottom: 1px solid $collapsible-border-color; 17 | 18 | i { 19 | width: 2rem; 20 | font-size: 1.6rem; 21 | line-height: $collapsible-height; 22 | display: block; 23 | float: left; 24 | text-align: center; 25 | margin-right: 1rem; 26 | } 27 | } 28 | 29 | .collapsible-body { 30 | display: none; 31 | border-bottom: 1px solid $collapsible-border-color; 32 | box-sizing: border-box; 33 | 34 | p { 35 | margin: 0; 36 | padding: 2rem; 37 | } 38 | } 39 | 40 | // sideNav collapsible styling 41 | .side-nav, 42 | .side-nav.fixed { 43 | 44 | .collapsible { 45 | border: none; 46 | box-shadow: none; 47 | 48 | li { padding: 0; } 49 | } 50 | 51 | .collapsible-header { 52 | background-color: transparent; 53 | border: none; 54 | line-height: inherit; 55 | height: inherit; 56 | padding: 0 $sidenav-padding-right; 57 | 58 | &:hover { background-color: rgba(0,0,0,.05); } 59 | i { line-height: inherit; } 60 | } 61 | 62 | .collapsible-body { 63 | border: 0; 64 | background-color: $collapsible-header-color; 65 | 66 | li a { 67 | padding: 0 (7.5px + $sidenav-padding-right) 68 | 0 (15px + $sidenav-padding-right); 69 | } 70 | } 71 | 72 | } 73 | 74 | // Popout Collapsible 75 | 76 | .collapsible.popout { 77 | border: none; 78 | box-shadow: none; 79 | > li { 80 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); 81 | // transform: scaleX(.92); 82 | margin: 0 24px; 83 | transition: margin .35s cubic-bezier(0.250, 0.460, 0.450, 0.940); 84 | } 85 | > li.active { 86 | box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15); 87 | margin: 16px 0; 88 | // transform: scaleX(1); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_roboto.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Roboto"; 3 | src: local(Roboto Thin), url('#{$roboto-font-path}Roboto-Thin.eot'); 4 | src: url("#{$roboto-font-path}Roboto-Thin.eot?#iefix") format('embedded-opentype'), 5 | url("#{$roboto-font-path}Roboto-Thin.woff2") format("woff2"), 6 | url("#{$roboto-font-path}Roboto-Thin.woff") format("woff"), 7 | url("#{$roboto-font-path}Roboto-Thin.ttf") format("truetype"); 8 | 9 | font-weight: 200; 10 | } 11 | @font-face { 12 | font-family: "Roboto"; 13 | src: local(Roboto Light), url('#{$roboto-font-path}Roboto-Light.eot'); 14 | src: url("#{$roboto-font-path}Roboto-Light.eot?#iefix") format('embedded-opentype'), 15 | url("#{$roboto-font-path}Roboto-Light.woff2") format("woff2"), 16 | url("#{$roboto-font-path}Roboto-Light.woff") format("woff"), 17 | url("#{$roboto-font-path}Roboto-Light.ttf") format("truetype"); 18 | font-weight: 300; 19 | } 20 | 21 | @font-face { 22 | font-family: "Roboto"; 23 | src: local(Roboto Regular), url('#{$roboto-font-path}Roboto-Regular.eot'); 24 | src: url("#{$roboto-font-path}Roboto-Regular.eot?#iefix") format('embedded-opentype'), 25 | url("#{$roboto-font-path}Roboto-Regular.woff2") format("woff2"), 26 | url("#{$roboto-font-path}Roboto-Regular.woff") format("woff"), 27 | url("#{$roboto-font-path}Roboto-Regular.ttf") format("truetype"); 28 | font-weight: 400; 29 | } 30 | 31 | @font-face { 32 | font-family: "Roboto"; 33 | src: url('#{$roboto-font-path}Roboto-Medium.eot'); 34 | src: url("#{$roboto-font-path}Roboto-Medium.eot?#iefix") format('embedded-opentype'), 35 | url("#{$roboto-font-path}Roboto-Medium.woff2") format("woff2"), 36 | url("#{$roboto-font-path}Roboto-Medium.woff") format("woff"), 37 | url("#{$roboto-font-path}Roboto-Medium.ttf") format("truetype"); 38 | font-weight: 500; 39 | } 40 | 41 | @font-face { 42 | font-family: "Roboto"; 43 | src: url('#{$roboto-font-path}Roboto-Bold.eot'); 44 | src: url("#{$roboto-font-path}Roboto-Bold.eot?#iefix") format('embedded-opentype'), 45 | url("#{$roboto-font-path}Roboto-Bold.woff2") format("woff2"), 46 | url("#{$roboto-font-path}Roboto-Bold.woff") format("woff"), 47 | url("#{$roboto-font-path}Roboto-Bold.ttf") format("truetype"); 48 | font-weight: 700; 49 | } 50 | -------------------------------------------------------------------------------- /app/templates/inc/menu.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 8 |
9 | {% if isHome %} 10 | 11 | add 12 | 13 | {% else %} 14 | {% if isDetail and current_user.is_authenticated and current_user.id == user.id %} 15 | 16 | edit 17 | 18 | {% elif isEdit %} 19 | 20 | home 21 | 22 | {% else %} 23 | 24 | menu 25 | 26 | {% endif %} 27 | {% endif %} 28 |
29 | 30 | 47 |
48 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.html' %} 2 | {% from 'macro/main.html' import render_pagination %} 3 | 4 | {% block body %} 5 | 6 | {% include 'inc/header.html' %} 7 | 8 |
9 |
10 | 11 |
12 | {% for note in notes %} 13 |
14 |
15 |
16 | 17 | {{ note.title }} 18 | 19 |

{{ note.content | striptags | truncate(length=500) }}

20 |
21 |
22 | 23 | perm_identity 24 | {{ note.user.getDisplayName() }} 25 | 26 | 30 | 31 | 32 | {% if current_user.is_authenticated and current_user.id == note.user_id %} 33 | 34 | edit 35 | 36 | 37 | delete 38 | 39 | {% endif %} 40 |
41 |
42 |
43 | {% endfor %} 44 |
45 |
46 | 47 |
48 |
49 | {% if isSearch %} 50 | {{ render_pagination(pagination, 'note.show_search_notes', keyword=keyword) }} 51 | {% elif isUserNote %} 52 | {{ render_pagination(pagination, 'note.show_user_notes', user=current_user.nick_name) }} 53 | {% else %} 54 | {{ render_pagination(pagination, 'note.show_public_notes') }} 55 | {% endif %} 56 |
57 |
58 | 59 | 60 | {% include 'inc/menu.html' %} 61 | 62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /app/user/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import request, render_template, url_for, flash, abort, redirect 3 | from ..models import User 4 | from . import user as USER 5 | from flask.ext.login import current_user, login_required, login_user, logout_user 6 | from form import SignupForm, LoginForm 7 | 8 | from .. import login_manager 9 | 10 | # login 11 | @login_manager.user_loader 12 | def load_user(id): 13 | user = User.query.filter_by(id=id).first() 14 | return user 15 | 16 | @login_manager.unauthorized_handler 17 | def unauthorized(): 18 | return redirect(url_for('user.login')) 19 | 20 | @USER.route('/login', methods=['GET', 'POST']) 21 | def login(): 22 | form = LoginForm(request.form) 23 | result = ''; 24 | 25 | if request.method == 'POST' and form.validate(): 26 | u = User.query.filter_by(email=form.email.data).first() 27 | 28 | if u == None: 29 | flash('User not exists.', 'warning') 30 | else: 31 | user = User(u.email, '', u.nick_name) 32 | user.id = u.id 33 | user.password_hash = u.password_hash 34 | if user.verify_password(form.password.data): 35 | login_user(user, remember=form.remember.data) 36 | # flash('Hi~ %s, you just login success.' % user.getDisplayName(), 'warning') 37 | 38 | return redirect(url_for('main.index')) 39 | else: 40 | flash('password incorrect', 'error') 41 | 42 | return render_template('login.html', title='login', form=form); 43 | 44 | @USER.route('/signup', methods=['GET', 'POST']) 45 | def signup(): 46 | form = SignupForm(request.form) 47 | 48 | def showMessage(type): 49 | if type == 'success': 50 | flash('Your account has been created. write a new note?'.format(url_for('note.write')), 'success') 51 | if type == 'exists': 52 | flash('This Email address is exists.', 'warning') 53 | if type == 'error': 54 | flash('Something error when save.', 'error') 55 | 56 | if request.method == 'POST' and form.validate(): 57 | user = User( 58 | form.email.data, 59 | form.password.data, 60 | form.nick_name.data) 61 | result = user.create() 62 | 63 | showMessage(result) 64 | login_user(user, remember=True) 65 | 66 | return render_template('signup.html', title='signup', form=form) 67 | 68 | @USER.route('/logout') 69 | @login_required 70 | def logout(): 71 | logout_user() 72 | return redirect(url_for('main.index')) 73 | -------------------------------------------------------------------------------- /app/static/simditor/scripts/module.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"function"==typeof define&&define.amd? 2 | // AMD. Register as an anonymous module unless amdModuleId is set 3 | define("simple-module",["jquery"],function(c){return a.Module=b(c)}):"object"==typeof exports?module.exports=b(require("jquery")):a.SimpleModule=b(jQuery)}(this,function(a){var b,c=[].slice;return b=function(){function b(b){var c,d,e,f,g,h,i;if(this.opts=a.extend({},this.opts,b),(c=this.constructor)._connectedClasses||(c._connectedClasses=[]),g=function(){var a,b,c,e;for(c=this.constructor._connectedClasses,e=[],a=0,b=c.length;b>a;a++)d=c[a],i=d.pluginName.charAt(0).toLowerCase()+d.pluginName.slice(1),d.prototype._connected&&(d.prototype._module=this),e.push(this[i]=new d);return e}.call(this),this._connected)this.opts=a.extend({},this.opts,this._module.opts);else for(this._init(),e=0,h=g.length;h>e;e++)f=g[e],"function"==typeof f._init&&f._init();this.trigger("initialized")}return b.extend=function(a){var b,c,d;if(null!=a&&"object"==typeof a){for(b in a)d=a[b],"included"!==b&&"extended"!==b&&(this[b]=d);return null!=(c=a.extended)?c.call(this):void 0}},b.include=function(a){var b,c,d;if(null!=a&&"object"==typeof a){for(b in a)d=a[b],"included"!==b&&"extended"!==b&&(this.prototype[b]=d);return null!=(c=a.included)?c.call(this):void 0}},b.connect=function(a){if("function"==typeof a){if(!a.pluginName)throw new Error("Module.connect: cannot connect plugin without pluginName");return a.prototype._connected=!0,this._connectedClasses||(this._connectedClasses=[]),this._connectedClasses.push(a),a.pluginName?this[a.pluginName]=a:void 0}},b.prototype.opts={},b.prototype._init=function(){},b.prototype.on=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).on.apply(d,b),this},b.prototype.one=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).one.apply(d,b),this},b.prototype.off=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).off.apply(d,b),this},b.prototype.trigger=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).trigger.apply(d,b),this},b.prototype.triggerHandler=function(){var b,d;return b=1<=arguments.length?c.call(arguments,0):[],(d=a(this)).triggerHandler.apply(d,b)},b.prototype._t=function(){var a,b;return a=1<=arguments.length?c.call(arguments,0):[],(b=this.constructor)._t.apply(b,a)},b._t=function(){var a,b,d,e;return b=arguments[0],a=2<=arguments.length?c.call(arguments,1):[],e=(null!=(d=this.i18n[this.locale])?d[b]:void 0)||"",a.length>0?(e=e.replace(/([^%]|^)%(?:(\d+)\$)?s/g,function(b,c,d){return d?c+a[parseInt(d)-1]:c+a.shift()}),e.replace(/%%s/g,"%s")):e},b.i18n={"zh-CN":{}},b.locale="zh-CN",b}()}); -------------------------------------------------------------------------------- /app/templates/inc/header.html: -------------------------------------------------------------------------------- 1 | 8 | 52 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_select.scss: -------------------------------------------------------------------------------- 1 | /* Select Field 2 | ========================================================================== */ 3 | 4 | .input-field select { display: none; } 5 | select.browser-default { display: block; } 6 | 7 | .input-field select { 8 | background-color: $select-background; 9 | width: 100%; 10 | padding: $select-padding; 11 | border: $select-border; 12 | border-radius: $select-radius; 13 | height: $input-height; 14 | } 15 | 16 | .select-label { 17 | position: absolute; 18 | } 19 | 20 | .select-wrapper { 21 | position: relative; 22 | 23 | input.select-dropdown { 24 | position: relative; 25 | cursor: pointer; 26 | background-color: transparent; 27 | border: none; 28 | border-bottom: $input-border; 29 | outline: none; 30 | height: $input-height; 31 | line-height: $input-height; 32 | width: 100%; 33 | font-size: $input-font-size; 34 | margin: $input-margin; 35 | padding: 0; 36 | display: block; 37 | } 38 | 39 | span.caret { 40 | color: initial; 41 | position: absolute; 42 | right: 0; 43 | top: 16px; 44 | font-size: 10px; 45 | &.disabled { 46 | color: $input-disabled-color; 47 | } 48 | } 49 | 50 | & + label { 51 | position: absolute; 52 | top: -14px; 53 | font-size: $label-font-size; 54 | } 55 | } 56 | 57 | // Disabled styles 58 | .input-field select:disabled { 59 | color: rgba(0,0,0,.3); 60 | } 61 | 62 | .select-wrapper input.select-dropdown:disabled { 63 | color: rgba(0,0,0,.3); 64 | cursor: default; 65 | -webkit-user-select: none; /* webkit (safari, chrome) browsers */ 66 | -moz-user-select: none; /* mozilla browsers */ 67 | -ms-user-select: none; /* IE10+ */ 68 | border-bottom: 1px solid rgba(0,0,0,.3); 69 | } 70 | 71 | .select-wrapper i { 72 | color: $select-disabled-color; 73 | } 74 | 75 | .select-dropdown li.disabled, 76 | .select-dropdown li.disabled > span, 77 | .select-dropdown li.optgroup { 78 | color: $select-disabled-color; 79 | background-color: transparent; 80 | } 81 | 82 | // Prefix Icons 83 | .prefix ~ .select-wrapper { 84 | margin-left: 3rem; 85 | width: 92%; 86 | width: calc(100% - 3rem); 87 | } 88 | 89 | .prefix ~ label { margin-left: 3rem; } 90 | 91 | // Icons 92 | .select-dropdown li { 93 | img { 94 | height: $dropdown-item-height - 10; 95 | width: $dropdown-item-height - 10; 96 | margin: 5px 15px; 97 | float: right; 98 | } 99 | } 100 | 101 | // Optgroup styles 102 | .select-dropdown li.optgroup { 103 | border-top: 1px solid $dropdown-hover-bg-color; 104 | 105 | &.selected > span { 106 | color: rgba(0, 0, 0, .7); 107 | } 108 | 109 | & > span { 110 | color: rgba(0, 0, 0, .4); 111 | } 112 | 113 | & ~ li.optgroup-option { 114 | padding-left: 1rem; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_cards.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | .card-panel { 4 | transition: box-shadow .25s; 5 | padding: $card-padding; 6 | margin: $element-top-margin 0 $element-bottom-margin 0; 7 | border-radius: 2px; 8 | @extend .z-depth-1; 9 | background-color: $card-bg-color; 10 | } 11 | 12 | .card { 13 | position: relative; 14 | margin: $element-top-margin 0 $element-bottom-margin 0; 15 | background-color: $card-bg-color; 16 | transition: box-shadow .25s; 17 | border-radius: 2px; 18 | @extend .z-depth-1; 19 | 20 | 21 | .card-title { 22 | font-size: 24px; 23 | font-weight: 300; 24 | &.activator { 25 | cursor: pointer; 26 | } 27 | } 28 | 29 | // Card Sizes 30 | &.small, &.medium, &.large { 31 | position: relative; 32 | 33 | .card-image { 34 | max-height: 60%; 35 | overflow: hidden; 36 | } 37 | .card-content { 38 | max-height: 40%; 39 | overflow: hidden; 40 | } 41 | .card-action { 42 | position: absolute; 43 | bottom: 0; 44 | left: 0; 45 | right: 0; 46 | } 47 | } 48 | 49 | &.small { 50 | height: 300px; 51 | } 52 | 53 | &.medium { 54 | height: 400px; 55 | } 56 | 57 | &.large { 58 | height: 500px; 59 | } 60 | 61 | 62 | .card-image { 63 | position: relative; 64 | 65 | // Image background for content 66 | img { 67 | display: block; 68 | border-radius: 2px 2px 0 0; 69 | position: relative; 70 | left: 0; 71 | right: 0; 72 | top: 0; 73 | bottom: 0; 74 | width: 100%; 75 | } 76 | 77 | .card-title { 78 | color: $card-bg-color; 79 | position: absolute; 80 | bottom: 0; 81 | left: 0; 82 | padding: $card-padding; 83 | } 84 | 85 | } 86 | 87 | .card-content { 88 | padding: $card-padding; 89 | border-radius: 0 0 2px 2px; 90 | 91 | p { 92 | margin: 0; 93 | color: inherit; 94 | } 95 | .card-title { 96 | line-height: 48px; 97 | } 98 | } 99 | 100 | .card-action { 101 | position: relative; 102 | background-color: inherit; 103 | border-top: 1px solid rgba(160,160,160,.2); 104 | padding: $card-padding; 105 | z-index: 2; 106 | 107 | a:not(.btn):not(.btn-large):not(.btn-floating) { 108 | color: $card-link-color; 109 | margin-right: $card-padding; 110 | transition: color .3s ease; 111 | text-transform: uppercase; 112 | 113 | &:hover { color: $card-link-color-light; } 114 | } 115 | 116 | & + .card-reveal { 117 | z-index: 1; 118 | padding-bottom: 64px; 119 | } 120 | } 121 | 122 | .card-reveal { 123 | padding: $card-padding; 124 | position: absolute; 125 | background-color: $card-bg-color; 126 | width: 100%; 127 | overflow-y: auto; 128 | top: 100%; 129 | height: 100%; 130 | z-index: 3; 131 | display: none; 132 | 133 | .card-title { 134 | cursor: pointer; 135 | display: block; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_sideNav.scss: -------------------------------------------------------------------------------- 1 | .side-nav { 2 | position: fixed; 3 | width: 240px; 4 | left: 0; 5 | top: 0; 6 | margin: 0; 7 | transform: translateX(-100%); 8 | height: 100%; 9 | height: calc(100% + 60px); 10 | height: -moz-calc(100%); //Temporary Firefox Fix 11 | padding-bottom: 60px; 12 | background-color: $sidenav-bg-color; 13 | z-index: 999; 14 | backface-visibility: hidden; 15 | overflow-y: auto; 16 | will-change: transform; 17 | backface-visibility: hidden; 18 | transform: translateX(-105%); 19 | 20 | @extend .z-depth-1; 21 | 22 | // Right Align 23 | &.right-aligned { 24 | right: 0; 25 | transform: translateX(105%); 26 | left: auto; 27 | transform: translateX(100%); 28 | } 29 | 30 | .collapsible { 31 | margin: 0; 32 | } 33 | 34 | 35 | li { 36 | float: none; 37 | line-height: $sidenav-item-height; 38 | 39 | &.active { background-color: rgba(0,0,0,.05); } 40 | } 41 | 42 | a { 43 | color: $sidenav-font-color; 44 | display: block; 45 | font-size: 1rem; 46 | height: $sidenav-item-height; 47 | line-height: $sidenav-item-height; 48 | padding: 0 $sidenav-padding-right; 49 | 50 | &:hover { background-color: rgba(0,0,0,.05);} 51 | 52 | &.btn, &.btn-large, &.btn-flat, &.btn-floating { 53 | margin: 10px 15px; 54 | } 55 | 56 | &.btn, 57 | &.btn-large, 58 | &.btn-floating { color: $button-raised-color; } 59 | &.btn-flat { color: $button-flat-color; } 60 | 61 | &.btn:hover, 62 | &.btn-large:hover { background-color: lighten($button-raised-background, 5%); } 63 | &.btn-floating:hover { background-color: $button-raised-background; } 64 | } 65 | } 66 | 67 | 68 | // Touch interaction 69 | .drag-target { 70 | height: 100%; 71 | width: 10px; 72 | position: fixed; 73 | top: 0; 74 | z-index: 998; 75 | } 76 | 77 | 78 | // Hidden side-nav for all sizes 79 | .side-nav.fixed { 80 | a { 81 | display: block; 82 | padding: 0 $sidenav-padding-right; 83 | color: $sidenav-font-color; 84 | } 85 | } 86 | 87 | 88 | // Fixed side-nav shown 89 | .side-nav.fixed { 90 | left: 0; 91 | transform: translateX(0); 92 | position: fixed; 93 | 94 | // Right Align 95 | &.right-aligned { 96 | right: 0; 97 | left: auto; 98 | } 99 | } 100 | 101 | // Fixed sideNav hide on smaller 102 | @media #{$medium-and-down} { 103 | .side-nav.fixed { 104 | transform: translateX(-105%); 105 | 106 | &.right-aligned { 107 | transform: translateX(105%); 108 | } 109 | } 110 | } 111 | 112 | 113 | .side-nav .collapsible-body li.active, 114 | .side-nav.fixed .collapsible-body li.active { 115 | background-color: $primary-color; 116 | a { 117 | color: $sidenav-bg-color; 118 | } 119 | } 120 | 121 | 122 | #sidenav-overlay { 123 | position: fixed; 124 | top: 0; 125 | left: 0; 126 | right: 0; 127 | 128 | height: 120vh; 129 | background-color: rgba(0,0,0,.5); 130 | z-index: 997; 131 | 132 | will-change: opacity; 133 | } 134 | -------------------------------------------------------------------------------- /app/static/simditor/scripts/hotkeys.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"function"==typeof define&&define.amd? 2 | // AMD. Register as an anonymous module unless amdModuleId is set 3 | define("simple-hotkeys",["jquery","simple-module"],function(c,d){return a.hotkeys=b(c,d)}):"object"==typeof exports? 4 | // Node. Does not work with strict CommonJS, but 5 | // only CommonJS-like environments that support module.exports, 6 | // like Node. 7 | module.exports=b(require("jquery"),require("simple-module")):(a.simple=a.simple||{},a.simple.hotkeys=b(jQuery,SimpleModule))}(this,function(a,b){var c,d,e=function(a,b){function c(){this.constructor=a}for(var d in b)f.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},f={}.hasOwnProperty;return c=function(b){function c(){return c.__super__.constructor.apply(this,arguments)}return e(c,b),c.count=0,c.keyNameMap={8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Spacebar",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",45:"Insert",46:"Del",91:"Meta",93:"Meta",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"Multiply",107:"Add",109:"Subtract",110:"Decimal",111:"Divide",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",124:"F13",125:"F14",126:"F15",127:"F16",128:"F17",129:"F18",130:"F19",131:"F20",132:"F21",133:"F22",134:"F23",135:"F24",59:";",61:"=",186:";",187:"=",188:",",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},c.aliases={escape:"esc","delete":"del","return":"enter",ctrl:"control",space:"spacebar",ins:"insert",cmd:"meta",command:"meta",wins:"meta",windows:"meta"},c.normalize=function(a){var b,c,d,e,f,g;for(f=a.toLowerCase().replace(/\s+/gi,"").split("+"),b=c=0,g=f.length;g>c;b=++c)d=f[b],f[b]=this.aliases[d]||d;return e=f.pop(),f.sort().push(e),f.join("_")},c.prototype.opts={el:document},c.prototype._init=function(){return this.id=++this.constructor.count,this._map={},this._delegate="string"==typeof this.opts.el?document:this.opts.el,a(this._delegate).on("keydown.simple-hotkeys-"+this.id,this.opts.el,function(a){return function(b){var c;return null!=(c=a._getHander(b))?c.call(a,b):void 0}}(this))},c.prototype._getHander=function(a){var b,c;if(b=this.constructor.keyNameMap[a.which])return c="",a.altKey&&(c+="alt_"),a.ctrlKey&&(c+="control_"),a.metaKey&&(c+="meta_"),a.shiftKey&&(c+="shift_"),c+=b.toLowerCase(),this._map[c]},c.prototype.respondTo=function(a){return"string"==typeof a?null!=this._map[this.constructor.normalize(a)]:null!=this._getHander(a)},c.prototype.add=function(a,b){return this._map[this.constructor.normalize(a)]=b,this},c.prototype.remove=function(a){return delete this._map[this.constructor.normalize(a)],this},c.prototype.destroy=function(){return a(this._delegate).off(".simple-hotkeys-"+this.id),this._map={},this},c}(b),d=function(a){return new c(a)}}); -------------------------------------------------------------------------------- /app/static/materialize/css/components/_grid.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | margin: 0 auto; 3 | max-width: 1280px; 4 | width: 90%; 5 | } 6 | @media #{$medium-and-up} { 7 | .container { 8 | width: 85%; 9 | } 10 | } 11 | @media #{$large-and-up} { 12 | .container { 13 | width: 70%; 14 | } 15 | } 16 | .container .row { 17 | margin-left: (-1 * $gutter-width / 2); 18 | margin-right: (-1 * $gutter-width / 2); 19 | } 20 | 21 | .section { 22 | padding-top: 1rem; 23 | padding-bottom: 1rem; 24 | 25 | &.no-pad { 26 | padding: 0; 27 | } 28 | &.no-pad-bot { 29 | padding-bottom: 0; 30 | } 31 | &.no-pad-top { 32 | padding-top: 0; 33 | } 34 | } 35 | 36 | 37 | .row { 38 | margin-left: auto; 39 | margin-right: auto; 40 | margin-bottom: 20px; 41 | 42 | // Clear floating children 43 | &:after { 44 | content: ""; 45 | display: table; 46 | clear: both; 47 | } 48 | 49 | .col { 50 | float: left; 51 | box-sizing: border-box; 52 | padding: 0 $gutter-width / 2; 53 | 54 | &[class*="push-"], 55 | &[class*="pull-"] { 56 | position: relative; 57 | } 58 | 59 | $i: 1; 60 | @while $i <= $num-cols { 61 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 62 | &.s#{$i} { 63 | width: $perc; 64 | margin-left: auto; 65 | left: auto; 66 | right: auto; 67 | } 68 | $i: $i + 1; 69 | } 70 | 71 | $i: 1; 72 | @while $i <= $num-cols { 73 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 74 | &.offset-s#{$i} { 75 | margin-left: $perc; 76 | } 77 | &.pull-s#{$i} { 78 | right: $perc; 79 | } 80 | &.push-s#{$i} { 81 | left: $perc; 82 | } 83 | $i: $i + 1; 84 | } 85 | 86 | @media #{$medium-and-up} { 87 | 88 | $i: 1; 89 | @while $i <= $num-cols { 90 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 91 | &.m#{$i} { 92 | width: $perc; 93 | margin-left: auto; 94 | left: auto; 95 | right: auto; 96 | } 97 | $i: $i + 1 98 | } 99 | 100 | $i: 1; 101 | @while $i <= $num-cols { 102 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 103 | &.offset-m#{$i} { 104 | margin-left: $perc; 105 | } 106 | &.pull-m#{$i} { 107 | right: $perc; 108 | } 109 | &.push-m#{$i} { 110 | left: $perc; 111 | } 112 | $i: $i + 1; 113 | } 114 | } 115 | 116 | @media #{$large-and-up} { 117 | 118 | $i: 1; 119 | @while $i <= $num-cols { 120 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 121 | &.l#{$i} { 122 | width: $perc; 123 | margin-left: auto; 124 | left: auto; 125 | right: auto; 126 | } 127 | $i: $i + 1; 128 | } 129 | 130 | $i: 1; 131 | @while $i <= $num-cols { 132 | $perc: unquote((100 / ($num-cols / $i)) + "%"); 133 | &.offset-l#{$i} { 134 | margin-left: $perc; 135 | } 136 | &.pull-l#{$i} { 137 | right: $perc; 138 | } 139 | &.push-l#{$i} { 140 | left: $perc; 141 | } 142 | $i: $i + 1; 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_radio-buttons.scss: -------------------------------------------------------------------------------- 1 | /* Radio Buttons 2 | ========================================================================== */ 3 | 4 | // Remove default Radio Buttons 5 | [type="radio"]:not(:checked), 6 | [type="radio"]:checked { 7 | position: absolute; 8 | left: -9999px; 9 | opacity: 0; 10 | } 11 | 12 | [type="radio"]:not(:checked) + label, 13 | [type="radio"]:checked + label { 14 | position: relative; 15 | padding-left: 35px; 16 | cursor: pointer; 17 | display: inline-block; 18 | height: 25px; 19 | line-height: 25px; 20 | font-size: 1rem; 21 | transition: .28s ease; 22 | 23 | -khtml-user-select: none; /* webkit (konqueror) browsers */ 24 | user-select: none; 25 | } 26 | 27 | [type="radio"] + label:before, 28 | [type="radio"] + label:after { 29 | content: ''; 30 | position: absolute; 31 | left: 0; 32 | top: 0; 33 | margin: 4px; 34 | width: 16px; 35 | height: 16px; 36 | z-index: 0; 37 | transition: .28s ease; 38 | } 39 | 40 | /* Unchecked styles */ 41 | [type="radio"]:not(:checked) + label:before, 42 | [type="radio"]:not(:checked) + label:after, 43 | [type="radio"]:checked + label:before, 44 | [type="radio"]:checked + label:after, 45 | [type="radio"].with-gap:checked + label:before, 46 | [type="radio"].with-gap:checked + label:after { 47 | border-radius: 50%; 48 | } 49 | 50 | [type="radio"]:not(:checked) + label:before, 51 | [type="radio"]:not(:checked) + label:after { 52 | border: 2px solid $radio-empty-color; 53 | } 54 | 55 | [type="radio"]:not(:checked) + label:after { 56 | z-index: -1; 57 | transform: scale(0); 58 | } 59 | 60 | /* Checked styles */ 61 | [type="radio"]:checked + label:before { 62 | border: 2px solid transparent; 63 | } 64 | 65 | [type="radio"]:checked + label:after, 66 | [type="radio"].with-gap:checked + label:before, 67 | [type="radio"].with-gap:checked + label:after { 68 | border: $radio-border; 69 | } 70 | 71 | [type="radio"]:checked + label:after, 72 | [type="radio"].with-gap:checked + label:after { 73 | background-color: $radio-fill-color; 74 | z-index: 0; 75 | } 76 | 77 | [type="radio"]:checked + label:after { 78 | transform: scale(1.02); 79 | } 80 | 81 | /* Radio With gap */ 82 | [type="radio"].with-gap:checked + label:after { 83 | transform: scale(.5); 84 | } 85 | 86 | /* Focused styles */ 87 | [type="radio"].tabbed:focus + label:before { 88 | box-shadow: 0 0 0 10px rgba(0,0,0,.1); 89 | } 90 | 91 | /* Disabled Radio With gap */ 92 | [type="radio"].with-gap:disabled:checked + label:before { 93 | border: 2px solid $input-disabled-color; 94 | } 95 | 96 | [type="radio"].with-gap:disabled:checked + label:after { 97 | border: none; 98 | background-color: $input-disabled-color; 99 | } 100 | 101 | /* Disabled style */ 102 | [type="radio"]:disabled:not(:checked) + label:before, 103 | [type="radio"]:disabled:checked + label:before { 104 | background-color: transparent; 105 | border-color: $input-disabled-color; 106 | } 107 | 108 | [type="radio"]:disabled + label { 109 | color: $input-disabled-color; 110 | } 111 | 112 | [type="radio"]:disabled:not(:checked) + label:before { 113 | border-color: $input-disabled-color; 114 | } 115 | 116 | [type="radio"]:disabled:checked + label:after { 117 | background-color: $input-disabled-color; 118 | border-color: $input-disabled-solid-color; 119 | } 120 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/date_picker/_default.time.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | $BASE-TIME-PICKER 3 | ========================================================================== */ 4 | /** 5 | * The list of times. 6 | */ 7 | .picker__list { 8 | list-style: none; 9 | padding: 0.75em 0 4.2em; 10 | margin: 0; 11 | } 12 | /** 13 | * The times on the clock. 14 | */ 15 | .picker__list-item { 16 | border-bottom: 1px solid #dddddd; 17 | border-top: 1px solid #dddddd; 18 | margin-bottom: -1px; 19 | position: relative; 20 | background: #ffffff; 21 | padding: .75em 1.25em; 22 | } 23 | @media (min-height: 46.75em) { 24 | .picker__list-item { 25 | padding: .5em 1em; 26 | } 27 | } 28 | /* Hovered time */ 29 | .picker__list-item:hover { 30 | cursor: pointer; 31 | color: #000000; 32 | background: #b1dcfb; 33 | border-color: #0089ec; 34 | z-index: 10; 35 | } 36 | /* Highlighted and hovered/focused time */ 37 | .picker__list-item--highlighted { 38 | border-color: #0089ec; 39 | z-index: 10; 40 | } 41 | .picker__list-item--highlighted:hover, 42 | .picker--focused .picker__list-item--highlighted { 43 | cursor: pointer; 44 | color: #000000; 45 | background: #b1dcfb; 46 | } 47 | /* Selected and hovered/focused time */ 48 | .picker__list-item--selected, 49 | .picker__list-item--selected:hover, 50 | .picker--focused .picker__list-item--selected { 51 | background: #0089ec; 52 | color: #ffffff; 53 | z-index: 10; 54 | } 55 | /* Disabled time */ 56 | .picker__list-item--disabled, 57 | .picker__list-item--disabled:hover, 58 | .picker--focused .picker__list-item--disabled { 59 | background: #f5f5f5; 60 | border-color: #f5f5f5; 61 | color: #dddddd; 62 | cursor: default; 63 | border-color: #dddddd; 64 | z-index: auto; 65 | } 66 | /** 67 | * The clear button 68 | */ 69 | .picker--time .picker__button--clear { 70 | display: block; 71 | width: 80%; 72 | margin: 1em auto 0; 73 | padding: 1em 1.25em; 74 | background: none; 75 | border: 0; 76 | font-weight: 500; 77 | font-size: .67em; 78 | text-align: center; 79 | text-transform: uppercase; 80 | color: #666; 81 | } 82 | .picker--time .picker__button--clear:hover, 83 | .picker--time .picker__button--clear:focus { 84 | color: #000000; 85 | background: #b1dcfb; 86 | background: #ee2200; 87 | border-color: #ee2200; 88 | cursor: pointer; 89 | color: #ffffff; 90 | outline: none; 91 | } 92 | .picker--time .picker__button--clear:before { 93 | top: -0.25em; 94 | color: #666; 95 | font-size: 1.25em; 96 | font-weight: bold; 97 | } 98 | .picker--time .picker__button--clear:hover:before, 99 | .picker--time .picker__button--clear:focus:before { 100 | color: #ffffff; 101 | } 102 | 103 | /* ========================================================================== 104 | $DEFAULT-TIME-PICKER 105 | ========================================================================== */ 106 | /** 107 | * The frame the bounds the time picker. 108 | */ 109 | .picker--time .picker__frame { 110 | min-width: 256px; 111 | max-width: 320px; 112 | } 113 | /** 114 | * The picker box. 115 | */ 116 | .picker--time .picker__box { 117 | font-size: 1em; 118 | background: #f2f2f2; 119 | padding: 0; 120 | } 121 | @media (min-height: 40.125em) { 122 | .picker--time .picker__box { 123 | margin-bottom: 5em; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_range.scss: -------------------------------------------------------------------------------- 1 | /* Range 2 | ========================================================================== */ 3 | 4 | .range-field { 5 | position: relative; 6 | } 7 | 8 | input[type=range], 9 | input[type=range] + .thumb { 10 | @extend .no-select; 11 | cursor: pointer; 12 | } 13 | 14 | input[type=range] { 15 | position: relative; 16 | background-color: transparent; 17 | border: none; 18 | outline: none; 19 | width: 100%; 20 | margin: 15px 0; 21 | padding: 0; 22 | 23 | &:focus { 24 | outline: none; 25 | } 26 | } 27 | 28 | input[type=range] + .thumb { 29 | position: absolute; 30 | border: none; 31 | height: 0; 32 | width: 0; 33 | border-radius: 50%; 34 | background-color: $radio-fill-color; 35 | top: 10px; 36 | margin-left: -6px; 37 | 38 | transform-origin: 50% 50%; 39 | transform: rotate(-45deg); 40 | 41 | .value { 42 | display: block; 43 | width: 30px; 44 | text-align: center; 45 | color: $radio-fill-color; 46 | font-size: 0; 47 | transform: rotate(45deg); 48 | } 49 | 50 | &.active { 51 | border-radius: 50% 50% 50% 0; 52 | 53 | .value { 54 | color: $input-background; 55 | margin-left: -1px; 56 | margin-top: 8px; 57 | font-size: 10px; 58 | } 59 | } 60 | } 61 | 62 | // WebKit 63 | input[type=range] { 64 | -webkit-appearance: none; 65 | } 66 | 67 | input[type=range]::-webkit-slider-runnable-track { 68 | height: $track-height; 69 | background: #c2c0c2; 70 | border: none; 71 | } 72 | 73 | input[type=range]::-webkit-slider-thumb { 74 | -webkit-appearance: none; 75 | border: none; 76 | height: $range-height; 77 | width: $range-width; 78 | border-radius: 50%; 79 | background-color: $radio-fill-color; 80 | transform-origin: 50% 50%; 81 | margin: -5px 0 0 0; 82 | transition: .3s; 83 | } 84 | 85 | input[type=range]:focus::-webkit-slider-runnable-track { 86 | background: #ccc; 87 | } 88 | 89 | // FireFox 90 | input[type=range] { 91 | /* fix for FF unable to apply focus style bug */ 92 | border: 1px solid white; 93 | 94 | /*required for proper track sizing in FF*/ 95 | } 96 | 97 | input[type=range]::-moz-range-track { 98 | height: $track-height; 99 | background: #ddd; 100 | border: none; 101 | } 102 | 103 | input[type=range]::-moz-range-thumb { 104 | border: none; 105 | height: $range-height; 106 | width: $range-width; 107 | border-radius: 50%; 108 | background: $radio-fill-color; 109 | margin-top: -5px; 110 | } 111 | 112 | // hide the outline behind the border 113 | input[type=range]:-moz-focusring { 114 | outline: 1px solid #fff; 115 | outline-offset: -1px; 116 | } 117 | 118 | input[type=range]:focus::-moz-range-track { 119 | background: #ccc; 120 | } 121 | 122 | // IE 10+ 123 | input[type=range]::-ms-track { 124 | height: $track-height; 125 | 126 | // remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead 127 | background: transparent; 128 | 129 | // leave room for the larger thumb to overflow with a transparent border */ 130 | border-color: transparent; 131 | border-width: 6px 0; 132 | 133 | /*remove default tick marks*/ 134 | color: transparent; 135 | } 136 | 137 | input[type=range]::-ms-fill-lower { 138 | background: #777; 139 | } 140 | 141 | input[type=range]::-ms-fill-upper { 142 | background: #ddd; 143 | } 144 | 145 | input[type=range]::-ms-thumb { 146 | border: none; 147 | height: $range-height; 148 | width: $range-width; 149 | border-radius: 50%; 150 | background: $radio-fill-color; 151 | } 152 | 153 | input[type=range]:focus::-ms-fill-lower { 154 | background: #888; 155 | } 156 | 157 | input[type=range]:focus::-ms-fill-upper { 158 | background: #ccc; 159 | } 160 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_navbar.scss: -------------------------------------------------------------------------------- 1 | nav { 2 | color: $navbar-font-color; 3 | @extend .z-depth-1; 4 | background-color: $primary-color; 5 | width: 100%; 6 | height: $navbar-height-mobile; 7 | line-height: $navbar-height-mobile; 8 | 9 | a { color: $navbar-font-color; } 10 | 11 | i, 12 | [class^="mdi-"], [class*="mdi-"], 13 | i.material-icons { 14 | display: block; 15 | font-size: 2rem; 16 | height: $navbar-height-mobile; 17 | line-height: $navbar-height-mobile; 18 | } 19 | 20 | .nav-wrapper { 21 | position: relative; 22 | height: 100%; 23 | } 24 | 25 | @media #{$large-and-up} { 26 | a.button-collapse { display: none; } 27 | } 28 | 29 | 30 | // Collapse button 31 | .button-collapse { 32 | float: left; 33 | position: relative; 34 | z-index: 1; 35 | height: $navbar-height-mobile; 36 | 37 | i { 38 | font-size: 2.7rem; 39 | height: $navbar-height-mobile; 40 | line-height: $navbar-height-mobile; 41 | } 42 | } 43 | 44 | 45 | // Logo 46 | .brand-logo { 47 | position: absolute; 48 | color: $navbar-font-color; 49 | display: inline-block; 50 | font-size: $navbar-brand-font-size; 51 | padding: 0; 52 | white-space: nowrap; 53 | 54 | &.center { 55 | left: 50%; 56 | transform: translateX(-50%); 57 | } 58 | 59 | @media #{$medium-and-down} { 60 | left: 50%; 61 | transform: translateX(-50%); 62 | 63 | &.left, &.right { 64 | padding: 0; 65 | transform: none; 66 | } 67 | 68 | &.left { left: 0.5rem; } 69 | &.right { 70 | right: 0.5rem; 71 | left: auto; 72 | } 73 | } 74 | 75 | &.right { 76 | right: 0.5rem; 77 | padding: 0; 78 | } 79 | } 80 | 81 | 82 | // Navbar Links 83 | ul { 84 | margin: 0; 85 | 86 | li { 87 | transition: background-color .3s; 88 | float: left; 89 | padding: 0; 90 | 91 | &.active { 92 | background-color: rgba(0,0,0,.1); 93 | } 94 | } 95 | a { 96 | transition: background-color .3s; 97 | font-size: 1rem; 98 | color: $navbar-font-color; 99 | display: inline-block; 100 | padding: 0 15px; 101 | cursor: pointer; 102 | 103 | &.btn, &.btn-large, &.btn-flat, &.btn-floating { 104 | margin-top: -2px; 105 | margin-left: 15px; 106 | margin-right: 15px; 107 | } 108 | 109 | &:hover { 110 | background-color: rgba(0,0,0,.1); 111 | } 112 | } 113 | 114 | &.left { 115 | float: left; 116 | } 117 | } 118 | 119 | // Navbar Search Form 120 | .input-field { 121 | margin: 0; 122 | 123 | input { 124 | height: 100%; 125 | font-size: 1.2rem; 126 | border: none; 127 | padding-left: 2rem; 128 | 129 | &:focus, &[type=text]:valid, &[type=password]:valid, 130 | &[type=email]:valid, &[type=url]:valid, &[type=date]:valid { 131 | border: none; 132 | box-shadow: none; 133 | } 134 | } 135 | label { 136 | top: 0; 137 | left: 0; 138 | 139 | i { 140 | color: rgba(255,255,255,.7); 141 | transition: color .3s; 142 | } 143 | &.active i { color: $navbar-font-color; } 144 | &.active { 145 | transform: translateY(0); 146 | } 147 | } 148 | 149 | } 150 | 151 | } 152 | 153 | // Fixed Navbar 154 | .navbar-fixed { 155 | position: relative; 156 | height: $navbar-height-mobile; 157 | z-index: 998; 158 | 159 | nav { 160 | position: fixed; 161 | } 162 | } 163 | @media #{$medium-and-up} { 164 | nav, nav .nav-wrapper i, nav a.button-collapse, nav a.button-collapse i { 165 | height: $navbar-height; 166 | line-height: $navbar-height; 167 | } 168 | .navbar-fixed { 169 | height: $navbar-height; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /app/static/simditor/scripts/simditor-html.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define('simditor-html', ["jquery","simditor","html_beautify"], function (a0,b1,c2) { 5 | return (root['HTMLButton'] = factory(a0,b1,c2)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery"),require("simditor"),require("js_beautify")); 12 | } else { 13 | root['SimditorHTML'] = factory(jQuery,Simditor,html_beautify); 14 | } 15 | }(this, function ($, Simditor, beautify) { 16 | 17 | var HTMLButton, 18 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 19 | hasProp = {}.hasOwnProperty, 20 | slice = [].slice; 21 | 22 | HTMLButton = (function(superClass) { 23 | extend(HTMLButton, superClass); 24 | 25 | function HTMLButton() { 26 | return HTMLButton.__super__.constructor.apply(this, arguments); 27 | } 28 | 29 | HTMLButton.prototype.name = 'html'; 30 | 31 | HTMLButton.prototype.icon = 'html5'; 32 | 33 | HTMLButton.prototype.needFocus = false; 34 | 35 | HTMLButton.prototype._init = function() { 36 | HTMLButton.__super__._init.call(this); 37 | this.editor.textarea.on('focus', (function(_this) { 38 | return function(e) { 39 | return _this.editor.el.addClass('focus').removeClass('error'); 40 | }; 41 | })(this)); 42 | this.editor.textarea.on('blur', (function(_this) { 43 | return function(e) { 44 | _this.editor.el.removeClass('focus'); 45 | return _this.editor.setValue(_this.editor.textarea.val()); 46 | }; 47 | })(this)); 48 | return this.editor.textarea.on('input', (function(_this) { 49 | return function(e) { 50 | return _this._resizeTextarea(); 51 | }; 52 | })(this)); 53 | }; 54 | 55 | HTMLButton.prototype.status = function() {}; 56 | 57 | HTMLButton.prototype.command = function() { 58 | var button, i, len, ref; 59 | this.editor.blur(); 60 | this.editor.el.toggleClass('simditor-html'); 61 | this.editor.htmlMode = this.editor.el.hasClass('simditor-html'); 62 | if (this.editor.htmlMode) { 63 | this.editor.hidePopover(); 64 | this.editor.textarea.val(this.beautifyHTML(this.editor.textarea.val())); 65 | this._resizeTextarea(); 66 | } else { 67 | this.editor.setValue(this.editor.textarea.val()); 68 | } 69 | ref = this.editor.toolbar.buttons; 70 | for (i = 0, len = ref.length; i < len; i++) { 71 | button = ref[i]; 72 | if (button.name === 'html') { 73 | button.setActive(this.editor.htmlMode); 74 | } else { 75 | button.setDisabled(this.editor.htmlMode); 76 | } 77 | } 78 | return null; 79 | }; 80 | 81 | HTMLButton.prototype.beautifyHTML = function() { 82 | var args; 83 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 84 | if (beautify.html) { 85 | return beautify.html.apply(beautify, args); 86 | } else { 87 | return beautify.apply(null, args); 88 | } 89 | }; 90 | 91 | HTMLButton.prototype._resizeTextarea = function() { 92 | this._textareaPadding || (this._textareaPadding = this.editor.textarea.innerHeight() - this.editor.textarea.height()); 93 | return this.editor.textarea.height(this.editor.textarea[0].scrollHeight - this._textareaPadding); 94 | }; 95 | 96 | return HTMLButton; 97 | 98 | })(Simditor.Button); 99 | 100 | Simditor.Toolbar.addButton(HTMLButton); 101 | 102 | return HTMLButton; 103 | 104 | })); 105 | -------------------------------------------------------------------------------- /app/static/simditor/scripts/uploader.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"function"==typeof define&&define.amd? 2 | // AMD. Register as an anonymous module unless amdModuleId is set 3 | define("simple-uploader",["jquery","simple-module"],function(c,d){return a.uploader=b(c,d)}):"object"==typeof exports? 4 | // Node. Does not work with strict CommonJS, but 5 | // only CommonJS-like environments that support module.exports, 6 | // like Node. 7 | module.exports=b(require("jquery"),require("simple-module")):(a.simple=a.simple||{},a.simple.uploader=b(jQuery,SimpleModule))}(this,function(a,b){var c,d,e=function(a,b){function c(){this.constructor=a}for(var d in b)f.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},f={}.hasOwnProperty;return c=function(b){function c(){return c.__super__.constructor.apply(this,arguments)}return e(c,b),c.count=0,c.prototype.opts={url:"",params:null,fileKey:"upload_file",connectionCount:3},c.prototype._init=function(){return this.files=[],this.queue=[],this.id=++c.count,this.on("uploadcomplete",function(b){return function(c,d){return b.files.splice(a.inArray(d,b.files),1),b.queue.length>0&&b.files.lengthe;e++)d=b[e],this.upload(d,c);else a(b).is("input:file")?(f=a(b).attr("name"),f&&(c.fileKey=f),this.upload(a.makeArray(a(b)[0].files),c)):b.id&&b.obj||(b=this.getFile(b));if(b&&b.obj){if(a.extend(b,c),this.files.length>=this.opts.connectionCount)return void this.queue.push(b);if(this.triggerHandler("beforeupload",[b])!==!1)return this.files.push(b),this._xhrUpload(b),this.uploading=!0}}},c.prototype.getFile=function(a){var b,c,d;return a instanceof window.File||a instanceof window.Blob?(b=null!=(c=a.fileName)?c:a.name,{id:this.generateId(),url:this.opts.url,params:this.opts.params,fileKey:this.opts.fileKey,name:b,size:null!=(d=a.fileSize)?d:a.size,ext:b?b.split(".").pop().toLowerCase():"",obj:a}):null},c.prototype._xhrUpload=function(b){var c,d,e,f;if(c=new FormData,c.append(b.fileKey,b.obj),c.append("original_filename",b.name),b.params){e=b.params;for(d in e)f=e[d],c.append(d,f)}return b.xhr=a.ajax({url:b.url,data:c,processData:!1,contentType:!1,type:"POST",headers:{"X-File-Name":encodeURIComponent(b.name)},xhr:function(){var b;return b=a.ajaxSettings.xhr(),b&&(b.upload.onprogress=function(a){return function(b){return a.progress(b)}}(this)),b},progress:function(a){return function(c){return c.lengthComputable?a.trigger("uploadprogress",[b,c.loaded,c.total]):void 0}}(this),error:function(a){return function(c,d,e){return a.trigger("uploaderror",[b,c,d])}}(this),success:function(c){return function(d){return c.trigger("uploadprogress",[b,b.size,b.size]),c.trigger("uploadsuccess",[b,d]),a(document).trigger("uploadsuccess",[b,d,c])}}(this),complete:function(a){return function(c,d){return a.trigger("uploadcomplete",[b,c.responseText])}}(this)})},c.prototype.cancel=function(a){var b,c,d,e;if(!a.id)for(e=this.files,c=0,d=e.length;d>c;c++)if(b=e[c],b.id===1*a){a=b;break}return this.trigger("uploadcancel",[a]),a.xhr&&a.xhr.abort(),a.xhr=null},c.prototype.readImageFile=function(b,c){var d,e;if(a.isFunction(c))return e=new Image,e.onload=function(){return c(e)},e.onerror=function(){return c()},window.FileReader&&FileReader.prototype.readAsDataURL&&/^image/.test(b.type)?(d=new FileReader,d.onload=function(a){return e.src=a.target.result},d.readAsDataURL(b)):c()},c.prototype.destroy=function(){var b,c,d,e;for(this.queue.length=0,e=this.files,c=0,d=e.length;d>c;c++)b=e[c],this.cancel(b);return a(window).off(".uploader-"+this.id),a(document).off(".uploader-"+this.id)},c.i18n={"zh-CN":{leaveConfirm:"正在上传文件,如果离开上传会自动取消"}},c.locale="zh-CN",c}(b),d=function(a){return new c(a)}}); -------------------------------------------------------------------------------- /app/static/materialize/css/components/_buttons.scss: -------------------------------------------------------------------------------- 1 | // shared styles 2 | .btn, 3 | .btn-flat { 4 | border: $button-border; 5 | border-radius: $button-radius; 6 | display: inline-block; 7 | height: $button-height; 8 | line-height: $button-height; 9 | outline: 0; 10 | padding: $button-padding; 11 | text-transform: uppercase; 12 | vertical-align: middle; 13 | // Gets rid of tap active state 14 | -webkit-tap-highlight-color: transparent; 15 | } 16 | 17 | // Disabled shared style 18 | .btn.disabled, 19 | .btn-floating.disabled, 20 | .btn-large.disabled, 21 | .btn:disabled 22 | .btn-large:disabled, 23 | .btn-floating:disabled { 24 | background-color: $button-disabled-background !important; 25 | box-shadow: none; 26 | color: $button-disabled-color !important; 27 | cursor: default; 28 | 29 | * { 30 | pointer-events: none; 31 | } 32 | 33 | &:hover { 34 | background-color: $button-disabled-background !important; 35 | color: $button-disabled-color !important; 36 | } 37 | } 38 | 39 | // Shared icon styles 40 | .btn, 41 | .btn-floating, 42 | .btn-large, 43 | .btn-flat { 44 | i { 45 | font-size: $button-font-size; 46 | line-height: inherit; 47 | } 48 | } 49 | 50 | // Raised Button 51 | .btn { 52 | text-decoration: none; 53 | color: $button-raised-color; 54 | background-color: $button-raised-background; 55 | text-align: center; 56 | letter-spacing: .5px; 57 | @extend .z-depth-1; 58 | transition: .2s ease-out; 59 | cursor: pointer; 60 | 61 | &:hover { 62 | background-color: $button-raised-background-hover; 63 | @extend .z-depth-1-half; 64 | } 65 | } 66 | 67 | // Floating button 68 | .btn-floating { 69 | display: inline-block; 70 | color: $button-floating-color; 71 | position: relative; 72 | overflow: hidden; 73 | z-index: 1; 74 | width: $button-floating-size; 75 | height: $button-floating-size; 76 | line-height: $button-floating-size; 77 | padding: 0; 78 | background-color: $button-floating-background; 79 | border-radius: $button-floating-radius; 80 | @extend .z-depth-1; 81 | transition: .3s; 82 | cursor: pointer; 83 | vertical-align: middle; 84 | 85 | i { 86 | width: inherit; 87 | display: inline-block; 88 | text-align: center; 89 | color: $button-floating-color; 90 | font-size: $button-large-icon-font-size; 91 | line-height: $button-floating-size; 92 | } 93 | 94 | &:hover { 95 | background-color: $button-floating-background-hover; 96 | @extend .z-depth-1-half; 97 | } 98 | 99 | &:before { 100 | border-radius: 0; 101 | } 102 | 103 | &.btn-large { 104 | width: $button-floating-large-size; 105 | height: $button-floating-large-size; 106 | i { 107 | line-height: $button-floating-large-size; 108 | } 109 | } 110 | } 111 | 112 | // button fix 113 | button.btn-floating { 114 | border: $button-border; 115 | } 116 | 117 | // Fixed Action Button 118 | .fixed-action-btn { 119 | &.active { 120 | ul { 121 | visibility: visible; 122 | } 123 | } 124 | 125 | &.horizontal { 126 | padding: 0 0 0 15px; 127 | 128 | ul { 129 | text-align: right; 130 | right: 64px; 131 | top: 50%; 132 | transform: translateY(-50%); 133 | height: 100%; 134 | left: auto; 135 | width: 500px; /*width 100% only goes to width of button container */ 136 | 137 | li { 138 | display: inline-block; 139 | margin: 15px 15px 0 0; 140 | } 141 | } 142 | } 143 | 144 | position: fixed; 145 | right: 23px; 146 | bottom: 23px; 147 | padding-top: 15px; 148 | margin-bottom: 0; 149 | z-index: 998; 150 | 151 | ul { 152 | left: 0; 153 | right: 0; 154 | text-align: center; 155 | position: absolute; 156 | bottom: 64px; 157 | margin: 0; 158 | visibility: hidden; 159 | 160 | li { 161 | margin-bottom: 15px; 162 | } 163 | 164 | a.btn-floating { 165 | opacity: 0; 166 | } 167 | } 168 | } 169 | 170 | // Flat button 171 | .btn-flat { 172 | box-shadow: none; 173 | background-color: transparent; 174 | color: $button-flat-color; 175 | cursor: pointer; 176 | 177 | &.disabled { 178 | color: $button-flat-disabled-color; 179 | cursor: default; 180 | } 181 | } 182 | 183 | // Large button 184 | .btn-large { 185 | @extend .btn; 186 | height: $button-large-height; 187 | line-height: $button-large-height; 188 | 189 | i { 190 | font-size: $button-large-icon-font-size; 191 | } 192 | } 193 | 194 | // Block button 195 | .btn-block { 196 | display: block; 197 | } 198 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_waves.scss: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Waves v0.6.0 4 | * http://fian.my.id/Waves 5 | * 6 | * Copyright 2014 Alfiana E. Sibuea and other contributors 7 | * Released under the MIT license 8 | * https://github.com/fians/Waves/blob/master/LICENSE 9 | */ 10 | 11 | 12 | .waves-effect { 13 | position: relative; 14 | cursor: pointer; 15 | display: inline-block; 16 | overflow: hidden; 17 | -webkit-user-select: none; 18 | -moz-user-select: none; 19 | -ms-user-select: none; 20 | user-select: none; 21 | -webkit-tap-highlight-color: transparent; 22 | // white-space: nowrap; 23 | // outline: 0; 24 | 25 | vertical-align: middle; 26 | // cursor: pointer; 27 | // border: none; 28 | // outline: none; 29 | // color: inherit; 30 | // background-color: rgba(0, 0, 0, 0); 31 | // font-size: 1em; 32 | // line-height:1em; 33 | // text-align: center; 34 | // text-decoration: none; 35 | z-index: 1; 36 | will-change: opacity, transform; 37 | transition: all .3s ease-out; 38 | 39 | .waves-ripple { 40 | position: absolute; 41 | border-radius: 50%; 42 | width: 20px; 43 | height: 20px; 44 | margin-top:-10px; 45 | margin-left:-10px; 46 | opacity: 0; 47 | 48 | background: rgba(0,0,0,0.2); 49 | // $gradient: rgba(0,0,0,0.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%; 50 | // background: -webkit-radial-gradient($gradient); 51 | // background: -o-radial-gradient($gradient); 52 | // background: -moz-radial-gradient($gradient); 53 | // background: radial-gradient($gradient); 54 | transition: all 0.7s ease-out; 55 | transition-property: transform, opacity; 56 | transform: scale(0); 57 | pointer-events: none; 58 | } 59 | 60 | // Waves Colors 61 | &.waves-light .waves-ripple { 62 | background-color: rgba(255, 255, 255, 0.45); 63 | } 64 | 65 | &.waves-red .waves-ripple { 66 | background-color: rgba(244, 67, 54, .70); 67 | } 68 | &.waves-yellow .waves-ripple { 69 | background-color: rgba(255, 235, 59, .70); 70 | } 71 | &.waves-orange .waves-ripple { 72 | background-color: rgba(255, 152, 0, .70); 73 | } 74 | &.waves-purple .waves-ripple { 75 | background-color: rgba(156, 39, 176, 0.70); 76 | } 77 | &.waves-green .waves-ripple { 78 | background-color: rgba(76, 175, 80, 0.70); 79 | } 80 | &.waves-teal .waves-ripple { 81 | background-color: rgba(0, 150, 136, 0.70); 82 | } 83 | 84 | // Style input button bug. 85 | input[type="button"], input[type="reset"], input[type="submit"] { 86 | border: 0; 87 | font-style: normal; 88 | font-size: inherit; 89 | text-transform: inherit; 90 | background: none; 91 | } 92 | 93 | } 94 | 95 | .waves-notransition { 96 | transition: none #{"!important"}; 97 | } 98 | 99 | .waves-circle { 100 | transform: translateZ(0); 101 | -webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%); 102 | } 103 | 104 | // .waves-button, 105 | // .waves-button:hover, 106 | // .waves-button:visited, 107 | // .waves-button-input { 108 | // white-space: nowrap; 109 | // vertical-align: middle; 110 | // cursor: pointer; 111 | // border: none; 112 | // outline: none; 113 | // color: inherit; 114 | // background-color: rgba(0, 0, 0, 0); 115 | // font-size: 1em; 116 | // line-height:1em; 117 | // text-align: center; 118 | // text-decoration: none; 119 | // z-index: 1; 120 | // } 121 | 122 | // .waves-button { 123 | // padding: 0.85em 1.1em; 124 | // border-radius: 0.2em; 125 | // } 126 | 127 | // .waves-button-input { 128 | // margin: 0; 129 | // padding: 0.85em 1.1em; 130 | // } 131 | 132 | .waves-input-wrapper { 133 | border-radius: 0.2em; 134 | vertical-align: bottom; 135 | 136 | // &.waves-button { 137 | // padding: 0; 138 | // } 139 | 140 | .waves-button-input { 141 | position: relative; 142 | top: 0; 143 | left: 0; 144 | z-index: 1; 145 | } 146 | } 147 | 148 | .waves-circle { 149 | text-align: center; 150 | width: 2.5em; 151 | height: 2.5em; 152 | line-height: 2.5em; 153 | border-radius: 50%; 154 | -webkit-mask-image: none; 155 | } 156 | 157 | // .waves-float { 158 | // -webkit-mask-image: none; 159 | // @include box-shadow(0px 1px 1.5px 1px rgba(0, 0, 0, 0.12)); 160 | 161 | // &:active { 162 | // @include box-shadow(0px 8px 20px 1px rgba(0, 0, 0, 0.30)); 163 | // } 164 | // } 165 | 166 | .waves-block { 167 | display: block; 168 | } 169 | 170 | /* Firefox Bug: link not triggered */ 171 | a.waves-effect .waves-ripple { 172 | z-index: -1; 173 | } -------------------------------------------------------------------------------- /app/templates/write.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.html' %} 2 | {% from 'macro/main.html' import get_flash_message_html, render_field %} 3 | 4 | {% block editor_script %} 5 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% endblock %} 22 | {% block body %} 23 | 24 | {% include 'inc/header.html' %} 25 | 26 |
27 | {% with messages = get_flashed_messages(with_categories=true) %} 28 | {% if messages %} 29 |
30 |
31 | {% for category, message in messages %} 32 |
33 | {{ get_flash_message_html(category, message) }} 34 |
35 | {% endfor %} 36 |
37 | {% endif %} 38 | {% endwith %} 39 |

Write one

40 |
41 | 42 |
43 |
44 | {{ render_field(form.title) }} 45 |
46 |
47 |
48 |
49 | {{ render_field(form.content) }} 50 |
51 | 93 |
94 |
95 |
96 | {{ render_field(form.public) }} 97 |
98 |
99 | 100 |
101 |
102 | 103 |
104 |
105 |
106 | 107 |
108 | 109 | 110 | {% include 'inc/menu.html' %} 111 | 112 | {% endblock %} 113 | -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import current_app 3 | from flask.ext.login import UserMixin 4 | from datetime import datetime 5 | from werkzeug.security import generate_password_hash, check_password_hash 6 | 7 | from . import db 8 | from crypt import MyCrypt 9 | 10 | class Note(db.Model, UserMixin): 11 | __tablename__ = 'notes' 12 | 13 | id = db.Column(db.Integer, primary_key=True, index=True) 14 | title = db.Column(db.String(100)) 15 | content = db.Column(db.Text) 16 | public = db.Column(db.Boolean()) 17 | created_at = db.Column(db.DateTime) 18 | updated_at = db.Column(db.DateTime) 19 | 20 | user_id = db.Column(db.Integer, db.ForeignKey('users.id')) 21 | user = db.relationship('User', backref=db.backref('notes', lazy='dynamic')) 22 | 23 | def __init__(self, title, content, public, user): 24 | self.title = title 25 | self.content = content 26 | self.public = public 27 | self.user = user 28 | 29 | def gen_time(self): 30 | if self.created_at is None: 31 | self.created_at = datetime.now() 32 | if self.updated_at is None: 33 | self.updated_at = datetime.now() 34 | 35 | def encryptContent(self): 36 | ec = MyCrypt(current_app.config['SECRET_KEY']) 37 | if not self.public: 38 | self.content = ec.encrypt(self.content.encode('utf-8')) 39 | 40 | def decryptContent(self): 41 | ec = MyCrypt(current_app.config['SECRET_KEY']) 42 | if not self.public: 43 | self.content = ec.decrypt(self.content).decode('utf-8') 44 | 45 | def printInfo(self): 46 | print '--'*30 47 | print ' title | content | public ' 48 | print ' {0} | {1} | {2} '.format(self.title, self.content[:10], self.public) 49 | print '--'*30 50 | 51 | @staticmethod 52 | def getIndexNotes(): 53 | notes = Note.query.filter_by(public=1).order_by(Note.created_at.desc()) 54 | 55 | for note in notes: 56 | if not note.public: 57 | note.decryptContent() 58 | 59 | return notes 60 | 61 | @staticmethod 62 | def getSearchNotes(keyword): 63 | pattern = u'%{0}%'.format(keyword) 64 | notes = Note.query.filter_by(public=1).filter(Note.title.like(pattern)).order_by(Note.created_at.desc()) 65 | 66 | for note in notes: 67 | if not note.public: 68 | note.decryptContent() 69 | 70 | return notes 71 | 72 | @staticmethod 73 | def getUserNotes(user_id): 74 | notes = Note.query.filter_by(user_id=user_id).order_by(Note.created_at.desc()) 75 | 76 | for note in notes: 77 | if not note.public: 78 | note.decryptContent() 79 | 80 | return notes 81 | 82 | def create(self): 83 | self.gen_time() 84 | self.encryptContent() 85 | 86 | # self.printInfo(); 87 | 88 | try: 89 | db.session.add(self) 90 | db.session.commit() 91 | except Exception, e: 92 | print e 93 | return 'error' 94 | 95 | return 'success' 96 | 97 | def update(self): 98 | self.updated_at = datetime.now() 99 | self.encryptContent() 100 | 101 | # self.printInfo(); 102 | 103 | try: 104 | db.session.add(self) 105 | db.session.commit() 106 | except Exception, e: 107 | print e 108 | return 'error' 109 | 110 | return 'success' 111 | 112 | def delete(self): 113 | try: 114 | db.session.delete(self) 115 | db.session.commit() 116 | except Exception, e: 117 | print e 118 | return 'error' 119 | 120 | return 'success' 121 | 122 | def __repr__(self): 123 | return '' % self.title 124 | 125 | class User(db.Model, UserMixin): 126 | __tablename__ = 'users' 127 | 128 | id = db.Column(db.Integer, primary_key=True, index=True) 129 | nick_name = db.Column(db.String(25)) 130 | email = db.Column(db.String(40), unique=True,) 131 | password_hash = db.Column(db.String(120)) 132 | created_at = db.Column(db.DateTime) 133 | 134 | def __init__(self, email, password, nick_name): 135 | self.email = email 136 | self.password = password 137 | self.nick_name = nick_name 138 | 139 | self.gen_password() 140 | 141 | def exists(self, email): 142 | return User.query.filter_by(email=email).first() != None 143 | 144 | def getDisplayName(self): 145 | if self.nick_name != None: 146 | return self.nick_name 147 | else: 148 | return self.email.split('@')[0] 149 | 150 | def create(self): 151 | if self.exists(self.email): 152 | return 'exists' 153 | 154 | self.created_at = datetime.now(); 155 | 156 | try: 157 | db.session.add(self) 158 | db.session.commit() 159 | except Exception, e: 160 | print e 161 | return 'error' 162 | 163 | return 'success' 164 | 165 | def gen_password(self): 166 | self.password_hash = generate_password_hash(self.password) 167 | 168 | def verify_password(self, password): 169 | return check_password_hash(self.password_hash, password) 170 | 171 | def __repr__(self): 172 | return '' % self.email 173 | -------------------------------------------------------------------------------- /app/static/simditor/scripts/module.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define('simple-module', ["jquery"], function (a0) { 5 | return (root['Module'] = factory(a0)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery")); 12 | } else { 13 | root['SimpleModule'] = factory(jQuery); 14 | } 15 | }(this, function ($) { 16 | 17 | var Module, 18 | slice = [].slice; 19 | 20 | Module = (function() { 21 | Module.extend = function(obj) { 22 | var key, ref, val; 23 | if (!((obj != null) && typeof obj === 'object')) { 24 | return; 25 | } 26 | for (key in obj) { 27 | val = obj[key]; 28 | if (key !== 'included' && key !== 'extended') { 29 | this[key] = val; 30 | } 31 | } 32 | return (ref = obj.extended) != null ? ref.call(this) : void 0; 33 | }; 34 | 35 | Module.include = function(obj) { 36 | var key, ref, val; 37 | if (!((obj != null) && typeof obj === 'object')) { 38 | return; 39 | } 40 | for (key in obj) { 41 | val = obj[key]; 42 | if (key !== 'included' && key !== 'extended') { 43 | this.prototype[key] = val; 44 | } 45 | } 46 | return (ref = obj.included) != null ? ref.call(this) : void 0; 47 | }; 48 | 49 | Module.connect = function(cls) { 50 | if (typeof cls !== 'function') { 51 | return; 52 | } 53 | if (!cls.pluginName) { 54 | throw new Error('Module.connect: cannot connect plugin without pluginName'); 55 | return; 56 | } 57 | cls.prototype._connected = true; 58 | if (!this._connectedClasses) { 59 | this._connectedClasses = []; 60 | } 61 | this._connectedClasses.push(cls); 62 | if (cls.pluginName) { 63 | return this[cls.pluginName] = cls; 64 | } 65 | }; 66 | 67 | Module.prototype.opts = {}; 68 | 69 | function Module(opts) { 70 | var base, cls, i, instance, instances, len, name; 71 | this.opts = $.extend({}, this.opts, opts); 72 | (base = this.constructor)._connectedClasses || (base._connectedClasses = []); 73 | instances = (function() { 74 | var i, len, ref, results; 75 | ref = this.constructor._connectedClasses; 76 | results = []; 77 | for (i = 0, len = ref.length; i < len; i++) { 78 | cls = ref[i]; 79 | name = cls.pluginName.charAt(0).toLowerCase() + cls.pluginName.slice(1); 80 | if (cls.prototype._connected) { 81 | cls.prototype._module = this; 82 | } 83 | results.push(this[name] = new cls()); 84 | } 85 | return results; 86 | }).call(this); 87 | if (this._connected) { 88 | this.opts = $.extend({}, this.opts, this._module.opts); 89 | } else { 90 | this._init(); 91 | for (i = 0, len = instances.length; i < len; i++) { 92 | instance = instances[i]; 93 | if (typeof instance._init === "function") { 94 | instance._init(); 95 | } 96 | } 97 | } 98 | this.trigger('initialized'); 99 | } 100 | 101 | Module.prototype._init = function() {}; 102 | 103 | Module.prototype.on = function() { 104 | var args, ref; 105 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 106 | (ref = $(this)).on.apply(ref, args); 107 | return this; 108 | }; 109 | 110 | Module.prototype.one = function() { 111 | var args, ref; 112 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 113 | (ref = $(this)).one.apply(ref, args); 114 | return this; 115 | }; 116 | 117 | Module.prototype.off = function() { 118 | var args, ref; 119 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 120 | (ref = $(this)).off.apply(ref, args); 121 | return this; 122 | }; 123 | 124 | Module.prototype.trigger = function() { 125 | var args, ref; 126 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 127 | (ref = $(this)).trigger.apply(ref, args); 128 | return this; 129 | }; 130 | 131 | Module.prototype.triggerHandler = function() { 132 | var args, ref; 133 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 134 | return (ref = $(this)).triggerHandler.apply(ref, args); 135 | }; 136 | 137 | Module.prototype._t = function() { 138 | var args, ref; 139 | args = 1 <= arguments.length ? slice.call(arguments, 0) : []; 140 | return (ref = this.constructor)._t.apply(ref, args); 141 | }; 142 | 143 | Module._t = function() { 144 | var args, key, ref, result; 145 | key = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; 146 | result = ((ref = this.i18n[this.locale]) != null ? ref[key] : void 0) || ''; 147 | if (!(args.length > 0)) { 148 | return result; 149 | } 150 | result = result.replace(/([^%]|^)%(?:(\d+)\$)?s/g, function(p0, p, position) { 151 | if (position) { 152 | return p + args[parseInt(position) - 1]; 153 | } else { 154 | return p + args.shift(); 155 | } 156 | }); 157 | return result.replace(/%%s/g, '%s'); 158 | }; 159 | 160 | Module.i18n = { 161 | 'zh-CN': {} 162 | }; 163 | 164 | Module.locale = 'zh-CN'; 165 | 166 | return Module; 167 | 168 | })(); 169 | 170 | return Module; 171 | 172 | })); 173 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/date_picker/_default.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | $BASE-PICKER 3 | ========================================================================== */ 4 | /** 5 | * Note: the root picker element should *NOT* be styled more than what's here. 6 | */ 7 | .picker { 8 | font-size: 16px; 9 | text-align: left; 10 | line-height: 1.2; 11 | color: #000000; 12 | position: absolute; 13 | z-index: 10000; 14 | -webkit-user-select: none; 15 | -moz-user-select: none; 16 | -ms-user-select: none; 17 | user-select: none; 18 | } 19 | /** 20 | * The picker input element. 21 | */ 22 | .picker__input { 23 | cursor: default; 24 | } 25 | /** 26 | * When the picker is opened, the input element is "activated". 27 | */ 28 | .picker__input.picker__input--active { 29 | border-color: #0089ec; 30 | } 31 | /** 32 | * The holder is the only "scrollable" top-level container element. 33 | */ 34 | .picker__holder { 35 | width: 100%; 36 | overflow-y: auto; 37 | -webkit-overflow-scrolling: touch; 38 | } 39 | 40 | /*! 41 | * Default mobile-first, responsive styling for pickadate.js 42 | * Demo: http://amsul.github.io/pickadate.js 43 | */ 44 | /** 45 | * Note: the root picker element should *NOT* be styled more than what's here. 46 | */ 47 | /** 48 | * Make the holder and frame fullscreen. 49 | */ 50 | .picker__holder, 51 | .picker__frame { 52 | bottom: 0; 53 | left: 0; 54 | right: 0; 55 | top: 100%; 56 | } 57 | /** 58 | * The holder should overlay the entire screen. 59 | */ 60 | .picker__holder { 61 | position: fixed; 62 | -webkit-transition: background 0.15s ease-out, top 0s 0.15s; 63 | -moz-transition: background 0.15s ease-out, top 0s 0.15s; 64 | transition: background 0.15s ease-out, top 0s 0.15s; 65 | -webkit-backface-visibility: hidden; 66 | } 67 | /** 68 | * The frame that bounds the box contents of the picker. 69 | */ 70 | .picker__frame { 71 | position: absolute; 72 | margin: 0 auto; 73 | min-width: 256px; 74 | 75 | // picker width 76 | width: 300px; 77 | max-height: 350px; 78 | 79 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 80 | filter: alpha(opacity=0); 81 | -moz-opacity: 0; 82 | opacity: 0; 83 | -webkit-transition: all 0.15s ease-out; 84 | -moz-transition: all 0.15s ease-out; 85 | transition: all 0.15s ease-out; 86 | } 87 | @media (min-height: 28.875em) { 88 | .picker__frame { 89 | overflow: visible; 90 | top: auto; 91 | bottom: -100%; 92 | max-height: 80%; 93 | } 94 | } 95 | @media (min-height: 40.125em) { 96 | .picker__frame { 97 | margin-bottom: 7.5%; 98 | } 99 | } 100 | /** 101 | * The wrapper sets the stage to vertically align the box contents. 102 | */ 103 | .picker__wrap { 104 | display: table; 105 | width: 100%; 106 | height: 100%; 107 | } 108 | @media (min-height: 28.875em) { 109 | .picker__wrap { 110 | display: block; 111 | } 112 | } 113 | /** 114 | * The box contains all the picker contents. 115 | */ 116 | .picker__box { 117 | background: #ffffff; 118 | display: table-cell; 119 | vertical-align: middle; 120 | } 121 | //@media (min-height: 26.5em) { 122 | // .picker__box { 123 | //// font-size: 1.25em; 124 | // } 125 | //} 126 | @media (min-height: 28.875em) { 127 | .picker__box { 128 | display: block; 129 | 130 | // picker header font-size 131 | // font-size: 1rem; 132 | 133 | border: 1px solid #777777; 134 | border-top-color: #898989; 135 | border-bottom-width: 0; 136 | -webkit-border-radius: 5px 5px 0 0; 137 | -moz-border-radius: 5px 5px 0 0; 138 | border-radius: 5px 5px 0 0; 139 | -webkit-box-shadow: 0 12px 36px 16px rgba(0, 0, 0, 0.24); 140 | -moz-box-shadow: 0 12px 36px 16px rgba(0, 0, 0, 0.24); 141 | box-shadow: 0 12px 36px 16px rgba(0, 0, 0, 0.24); 142 | } 143 | } 144 | //@media (min-height: 40.125em) { 145 | // .picker__box { 146 | // font-size: 1.1rem; 147 | // border-bottom-width: 1px; 148 | // -webkit-border-radius: 5px; 149 | // -moz-border-radius: 5px; 150 | // border-radius: 5px; 151 | // } 152 | //} 153 | /** 154 | * When the picker opens... 155 | */ 156 | .picker--opened .picker__holder { 157 | top: 0; 158 | background: transparent; 159 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E000000,endColorstr=#1E000000)"; 160 | zoom: 1; 161 | background: rgba(0, 0, 0, 0.32); 162 | -webkit-transition: background 0.15s ease-out; 163 | -moz-transition: background 0.15s ease-out; 164 | transition: background 0.15s ease-out; 165 | } 166 | .picker--opened .picker__frame { 167 | top: 0; 168 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; 169 | filter: alpha(opacity=100); 170 | -moz-opacity: 1; 171 | opacity: 1; 172 | } 173 | @media (min-height: 35.875em) { 174 | .picker--opened .picker__frame { 175 | top: 10%; 176 | bottom: auto; 177 | } 178 | } 179 | /** 180 | * For `large` screens, transform into an inline picker. 181 | */ 182 | 183 | /* ========================================================================== 184 | CUSTOM MATERIALIZE STYLES 185 | ========================================================================== */ 186 | 187 | .picker__input.picker__input--active { 188 | border-color: color("blue", "lighten-5"); 189 | } 190 | 191 | .picker__frame { 192 | margin: 0 auto; 193 | max-width: 325px; 194 | } 195 | 196 | @media (min-height: 38.875em) { 197 | .picker--opened .picker__frame { 198 | top: 10%; 199 | bottom: auto; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_checkboxes.css: -------------------------------------------------------------------------------- 1 | /* 2 | Error: $color: 'color("materialize-red", "lighten-2")' is not a color for `lighten' 3 | on line 37 of ../_variables.scss 4 | from line 3 of _checkboxes.scss 5 | 6 | 32: font-size: 1rem; 7 | 33: 8 | 34: -webkit-user-select: none; /* webkit (safari, chrome) browsers *\/ 9 | 35: -moz-user-select: none; /* mozilla browsers *\/ 10 | 36: -khtml-user-select: none; /* webkit (konqueror) browsers *\/ 11 | 37: -ms-user-select: none; /* IE10+ *\/ 12 | 38: } 13 | 39: 14 | 40: /* checkbox aspect *\/ 15 | 41: + label:before, 16 | 42: &:not(.filled-in) + label:after { 17 | 18 | Backtrace: 19 | ../_variables.scss:37 20 | _checkboxes.scss:3 21 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/script/tree/funcall.rb:303:in `reformat_argument_error' 22 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/script/tree/funcall.rb:146:in `rescue in _perform' 23 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/script/tree/funcall.rb:123:in `_perform' 24 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/script/tree/node.rb:50:in `perform' 25 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:469:in `visit_variable' 26 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/base.rb:36:in `visit' 27 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:158:in `block in visit' 28 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:79:in `block in with_base' 29 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:115:in `with_frame' 30 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:79:in `with_base' 31 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:158:in `visit' 32 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:326:in `block (2 levels) in visit_import' 33 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:326:in `map' 34 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:326:in `block in visit_import' 35 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:88:in `block in with_import' 36 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:115:in `with_frame' 37 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:88:in `with_import' 38 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:323:in `visit_import' 39 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/base.rb:36:in `visit' 40 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:158:in `block in visit' 41 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:79:in `block in with_base' 42 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:115:in `with_frame' 43 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/stack.rb:79:in `with_base' 44 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:158:in `visit' 45 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/base.rb:52:in `block in visit_children' 46 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/base.rb:52:in `map' 47 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/base.rb:52:in `visit_children' 48 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:167:in `block in visit_children' 49 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:179:in `with_environment' 50 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:166:in `visit_children' 51 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/base.rb:36:in `block in visit' 52 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:186:in `visit_root' 53 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/base.rb:36:in `visit' 54 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:157:in `visit' 55 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/visitors/perform.rb:8:in `visit' 56 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/root_node.rb:36:in `css_tree' 57 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/tree/root_node.rb:29:in `render_with_sourcemap' 58 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/engine.rb:378:in `_render_with_sourcemap' 59 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/engine.rb:295:in `render_with_sourcemap' 60 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/exec/sass_scss.rb:412:in `run' 61 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/exec/sass_scss.rb:63:in `process_result' 62 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/exec/base.rb:52:in `parse' 63 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/lib/sass/exec/base.rb:19:in `parse!' 64 | C:/Ruby193/lib/ruby/gems/1.9.1/gems/sass-3.4.19/bin/sass:13:in `' 65 | C:/Ruby193/bin/sass:23:in `load' 66 | C:/Ruby193/bin/sass:23:in `
' 67 | */ 68 | body:before { 69 | white-space: pre; 70 | font-family: monospace; 71 | content: "Error: $color: 'color(\"materialize-red\", \"lighten-2\")' is not a color for `lighten'\A on line 37 of ../_variables.scss\A from line 3 of _checkboxes.scss\A \A 32: font-size: 1rem;\A 33: \A 34: -webkit-user-select: none; /* webkit (safari, chrome) browsers */\A 35: -moz-user-select: none; /* mozilla browsers */\A 36: -khtml-user-select: none; /* webkit (konqueror) browsers */\A 37: -ms-user-select: none; /* IE10+ */\A 38: }\A 39: \A 40: /* checkbox aspect */\A 41: + label:before,\A 42: &:not(.filled-in) + label:after {"; } 72 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_input-fields.scss: -------------------------------------------------------------------------------- 1 | /* Text Inputs + Textarea 2 | ========================================================================== */ 3 | 4 | /* Style Placeholders */ 5 | 6 | ::-webkit-input-placeholder { 7 | color: $placeholder-text-color; 8 | } 9 | 10 | :-moz-placeholder { /* Firefox 18- */ 11 | color: $placeholder-text-color; 12 | } 13 | 14 | ::-moz-placeholder { /* Firefox 19+ */ 15 | color: $placeholder-text-color; 16 | } 17 | 18 | :-ms-input-placeholder { 19 | color: $placeholder-text-color; 20 | } 21 | 22 | /* Text inputs */ 23 | 24 | input:not([type]), 25 | input[type=text], 26 | input[type=password], 27 | input[type=email], 28 | input[type=url], 29 | input[type=time], 30 | input[type=date], 31 | input[type=datetime], 32 | input[type=datetime-local], 33 | input[type=tel], 34 | input[type=number], 35 | input[type=search], 36 | textarea.materialize-textarea { 37 | 38 | // General Styles 39 | background-color: transparent; 40 | border: none; 41 | border-bottom: $input-border; 42 | border-radius: 0; 43 | outline: none; 44 | height: $input-height; 45 | width: 100%; 46 | font-size: $input-font-size; 47 | margin: $input-margin; 48 | padding: $input-padding; 49 | box-shadow: none; 50 | box-sizing: content-box; 51 | transition: $input-transition; 52 | 53 | // Disabled input style 54 | &:disabled, 55 | &[readonly="readonly"] { 56 | color: $input-disabled-color; 57 | border-bottom: $input-disabled-border; 58 | } 59 | 60 | // Disabled label style 61 | &:disabled+label, 62 | &[readonly="readonly"]+label { 63 | color: $input-disabled-color; 64 | } 65 | 66 | // Focused input style 67 | &:focus:not([readonly]) { 68 | border-bottom: 1px solid $input-focus-color; 69 | box-shadow: 0 1px 0 0 $input-focus-color; 70 | } 71 | 72 | // Focused label style 73 | &:focus:not([readonly])+label { 74 | color: $input-focus-color; 75 | } 76 | 77 | // Valid Input Style 78 | &.valid, 79 | &:focus.valid { 80 | border-bottom: 1px solid $input-success-color; 81 | box-shadow: 0 1px 0 0 $input-success-color; 82 | } 83 | 84 | // Custom Success Message 85 | &.valid + label:after, 86 | &:focus.valid + label:after { 87 | content: attr(data-success); 88 | color: $input-success-color; 89 | opacity: 1; 90 | } 91 | 92 | // Invalid Input Style 93 | &.invalid, 94 | &:focus.invalid { 95 | border-bottom: $input-invalid-border; 96 | box-shadow: 0 1px 0 0 $input-error-color; 97 | } 98 | 99 | // Custom Error message 100 | &.invalid + label:after, 101 | &:focus.invalid + label:after { 102 | content: attr(data-error); 103 | color: $input-error-color; 104 | opacity: 1; 105 | } 106 | 107 | // Full width label when using validate for error messages 108 | &.validate + label { 109 | width: 100%; 110 | pointer-events: none; 111 | } 112 | 113 | // Form Message Shared Styles 114 | & + label:after { 115 | display: block; 116 | content: ""; 117 | position: absolute; 118 | top: 65px; 119 | opacity: 0; 120 | transition: .2s opacity ease-out, .2s color ease-out; 121 | } 122 | } 123 | 124 | // Styling for input field wrapper 125 | .input-field { 126 | position: relative; 127 | margin-top: 1rem; 128 | 129 | label { 130 | color: $input-border-color; 131 | position: absolute; 132 | top: 0.8rem; 133 | left: $gutter-width / 2; 134 | font-size: 1rem; 135 | cursor: text; 136 | transition: .2s ease-out; 137 | } 138 | 139 | label.active { 140 | font-size: $label-font-size; 141 | transform: translateY(-140%); 142 | } 143 | 144 | // Prefix Icons 145 | .prefix { 146 | position: absolute; 147 | width: $input-height; 148 | font-size: 2rem; 149 | transition: color .2s; 150 | 151 | &.active { color: $input-focus-color; } 152 | } 153 | 154 | .prefix ~ input, 155 | .prefix ~ textarea { 156 | margin-left: 3rem; 157 | width: 92%; 158 | width: calc(100% - 3rem); 159 | } 160 | 161 | .prefix ~ textarea { padding-top: .8rem; } 162 | .prefix ~ label { margin-left: 3rem; } 163 | 164 | @media #{$medium-and-down} { 165 | .prefix ~ input { 166 | width: 86%; 167 | width: calc(100% - 3rem); 168 | } 169 | } 170 | 171 | @media #{$small-and-down} { 172 | .prefix ~ input { 173 | width: 80%; 174 | width: calc(100% - 3rem); 175 | } 176 | } 177 | } 178 | 179 | 180 | /* Search Field */ 181 | 182 | .input-field input[type=search] { 183 | display: block; 184 | line-height: inherit; 185 | padding-left: 4rem; 186 | width: calc(100% - 4rem); 187 | 188 | &:focus { 189 | background-color: $input-background; 190 | border: 0; 191 | box-shadow: none; 192 | color: #444; 193 | 194 | & + label i, 195 | & ~ .mdi-navigation-close, 196 | & ~ .material-icons { 197 | color: #444; 198 | } 199 | } 200 | 201 | & + label { 202 | left: 1rem; 203 | } 204 | 205 | & ~ .mdi-navigation-close, 206 | & ~ .material-icons { 207 | position: absolute; 208 | top: 0; 209 | right: 1rem; 210 | color: transparent; 211 | cursor: pointer; 212 | font-size: 2rem; 213 | transition: .3s color; 214 | } 215 | } 216 | 217 | 218 | /* Textarea */ 219 | 220 | // Default textarea 221 | textarea { 222 | width: 100%; 223 | height: $input-height; 224 | background-color: transparent; 225 | 226 | &.materialize-textarea { 227 | overflow-y: hidden; /* prevents scroll bar flash */ 228 | padding: 1.6rem 0; /* prevents text jump on Enter keypress */ 229 | resize: none; 230 | min-height: $input-height; 231 | } 232 | } 233 | 234 | // For textarea autoresize 235 | .hiddendiv { 236 | display: none; 237 | white-space: pre-wrap; 238 | word-wrap: break-word; 239 | overflow-wrap: break-word; /* future version of deprecated 'word-wrap' */ 240 | padding-top: 1.2rem; /* prevents text jump on Enter keypress */ 241 | } 242 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/forms/_checkboxes.scss: -------------------------------------------------------------------------------- 1 | /* Checkboxes 2 | ========================================================================== */ 3 | 4 | /* CUSTOM CSS CHECKBOXES */ 5 | form p { 6 | margin-bottom: 10px; 7 | text-align: left; 8 | } 9 | 10 | form p:last-child { 11 | margin-bottom: 0; 12 | } 13 | 14 | /* Remove default checkbox */ 15 | .input-field [type="checkbox"]:not(:checked), 16 | [type="checkbox"]:checked { 17 | position: absolute; 18 | left: -9999px; 19 | opacity: 0; 20 | } 21 | 22 | // Checkbox Styles 23 | .input-field [type="checkbox"] { 24 | // Text Label Style 25 | + label { 26 | position: relative; 27 | padding-left: 35px; 28 | cursor: pointer; 29 | display: inline-block; 30 | height: 25px; 31 | line-height: 25px; 32 | font-size: 1rem; 33 | 34 | -webkit-user-select: none; /* webkit (safari, chrome) browsers */ 35 | -moz-user-select: none; /* mozilla browsers */ 36 | -khtml-user-select: none; /* webkit (konqueror) browsers */ 37 | -ms-user-select: none; /* IE10+ */ 38 | } 39 | 40 | /* checkbox aspect */ 41 | + label:before, 42 | &:not(.filled-in) + label:after { 43 | content: ''; 44 | position: absolute; 45 | top: 0; 46 | left: 0; 47 | width: 18px; 48 | height: 18px; 49 | z-index: 0; 50 | border: 2px solid $radio-empty-color; 51 | border-radius: 1px; 52 | margin-top: 2px; 53 | transition: .2s; 54 | } 55 | 56 | &:not(.filled-in) + label:after { 57 | border: 0; 58 | transform: scale(0); 59 | } 60 | 61 | &:not(:checked):disabled + label:before { 62 | border: none; 63 | background-color: $input-disabled-color; 64 | } 65 | 66 | // Focused styles 67 | &.tabbed:focus + label:after { 68 | transform: scale(1); 69 | border: 0; 70 | border-radius: 50%; 71 | box-shadow: 0 0 0 10px rgba(0,0,0,.1); 72 | background-color: rgba(0,0,0,.1); 73 | } 74 | } 75 | 76 | .input-field [type="checkbox"]:checked { 77 | + label:before { 78 | top: -4px; 79 | left: -5px; 80 | width: 12px; 81 | height: 22px; 82 | border-top: 2px solid transparent; 83 | border-left: 2px solid transparent; 84 | border-right: $radio-border; 85 | border-bottom: $radio-border; 86 | transform: rotate(40deg); 87 | backface-visibility: hidden; 88 | transform-origin: 100% 100%; 89 | } 90 | 91 | &:disabled + label:before { 92 | border-right: 2px solid $input-disabled-color; 93 | border-bottom: 2px solid $input-disabled-color; 94 | } 95 | } 96 | 97 | /* Indeterminate checkbox */ 98 | .input-field [type="checkbox"]:indeterminate { 99 | +label:before { 100 | top: -11px; 101 | left: -12px; 102 | width: 10px; 103 | height: 22px; 104 | border-top: none; 105 | border-left: none; 106 | border-right: $radio-border; 107 | border-bottom: none; 108 | transform: rotate(90deg); 109 | backface-visibility: hidden; 110 | transform-origin: 100% 100%; 111 | } 112 | 113 | // Disabled indeterminate 114 | &:disabled + label:before { 115 | border-right: 2px solid $input-disabled-color; 116 | background-color: transparent; 117 | } 118 | } 119 | 120 | // Filled in Style 121 | .input-field [type="checkbox"].filled-in { 122 | // General 123 | + label:after { 124 | border-radius: 2px; 125 | } 126 | 127 | + label:before, 128 | + label:after { 129 | content: ''; 130 | left: 0; 131 | position: absolute; 132 | /* .1s delay is for check animation */ 133 | transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s; 134 | z-index: 1; 135 | } 136 | 137 | // Unchecked style 138 | &:not(:checked) + label:before { 139 | width: 0; 140 | height: 0; 141 | border: 3px solid transparent; 142 | left: 6px; 143 | top: 10px; 144 | 145 | -webkit-transform: rotateZ(37deg); 146 | transform: rotateZ(37deg); 147 | -webkit-transform-origin: 20% 40%; 148 | transform-origin: 100% 100%; 149 | } 150 | 151 | &:not(:checked) + label:after { 152 | height: 20px; 153 | width: 20px; 154 | background-color: transparent; 155 | border: 2px solid $radio-empty-color; 156 | top: 0px; 157 | z-index: 0; 158 | } 159 | 160 | // Checked style 161 | &:checked { 162 | + label:before { 163 | top: 0; 164 | left: 1px; 165 | width: 8px; 166 | height: 13px; 167 | border-top: 2px solid transparent; 168 | border-left: 2px solid transparent; 169 | border-right: 2px solid $input-background; 170 | border-bottom: 2px solid $input-background; 171 | -webkit-transform: rotateZ(37deg); 172 | transform: rotateZ(37deg); 173 | 174 | -webkit-transform-origin: 100% 100%; 175 | transform-origin: 100% 100%; 176 | } 177 | 178 | + label:after { 179 | top: 0; 180 | width: 20px; 181 | height: 20px; 182 | border: 2px solid $secondary-color; 183 | background-color: $secondary-color; 184 | z-index: 0; 185 | } 186 | } 187 | 188 | // Focused styles 189 | &.tabbed:focus + label:after { 190 | border-radius: 2px; 191 | border-color: $radio-empty-color; 192 | background-color: rgba(0,0,0,.1); 193 | } 194 | 195 | &.tabbed:checked:focus + label:after { 196 | border-radius: 2px; 197 | background-color: $secondary-color; 198 | border-color: $secondary-color; 199 | } 200 | 201 | // Disabled style 202 | &:disabled:not(:checked) + label:before { 203 | background-color: transparent; 204 | border: 2px solid transparent; 205 | } 206 | 207 | &:disabled:not(:checked) + label:after { 208 | border-color: transparent; 209 | background-color: $input-disabled-solid-color; 210 | } 211 | 212 | &:disabled:checked + label:before { 213 | background-color: transparent; 214 | } 215 | 216 | &:disabled:checked + label:after { 217 | background-color: $input-disabled-solid-color; 218 | border-color: $input-disabled-solid-color; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /app/static/simditor/scripts/hotkeys.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define('simple-hotkeys', ["jquery","simple-module"], function ($, SimpleModule) { 5 | return (root['hotkeys'] = factory($, SimpleModule)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery"),require("simple-module")); 12 | } else { 13 | root.simple = root.simple || {}; 14 | root.simple['hotkeys'] = factory(jQuery,SimpleModule); 15 | } 16 | }(this, function ($, SimpleModule) { 17 | 18 | var Hotkeys, hotkeys, 19 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 20 | hasProp = {}.hasOwnProperty; 21 | 22 | Hotkeys = (function(superClass) { 23 | extend(Hotkeys, superClass); 24 | 25 | function Hotkeys() { 26 | return Hotkeys.__super__.constructor.apply(this, arguments); 27 | } 28 | 29 | Hotkeys.count = 0; 30 | 31 | Hotkeys.keyNameMap = { 32 | 8: "Backspace", 33 | 9: "Tab", 34 | 13: "Enter", 35 | 16: "Shift", 36 | 17: "Control", 37 | 18: "Alt", 38 | 19: "Pause", 39 | 20: "CapsLock", 40 | 27: "Esc", 41 | 32: "Spacebar", 42 | 33: "PageUp", 43 | 34: "PageDown", 44 | 35: "End", 45 | 36: "Home", 46 | 37: "Left", 47 | 38: "Up", 48 | 39: "Right", 49 | 40: "Down", 50 | 45: "Insert", 51 | 46: "Del", 52 | 91: "Meta", 53 | 93: "Meta", 54 | 48: "0", 55 | 49: "1", 56 | 50: "2", 57 | 51: "3", 58 | 52: "4", 59 | 53: "5", 60 | 54: "6", 61 | 55: "7", 62 | 56: "8", 63 | 57: "9", 64 | 65: "A", 65 | 66: "B", 66 | 67: "C", 67 | 68: "D", 68 | 69: "E", 69 | 70: "F", 70 | 71: "G", 71 | 72: "H", 72 | 73: "I", 73 | 74: "J", 74 | 75: "K", 75 | 76: "L", 76 | 77: "M", 77 | 78: "N", 78 | 79: "O", 79 | 80: "P", 80 | 81: "Q", 81 | 82: "R", 82 | 83: "S", 83 | 84: "T", 84 | 85: "U", 85 | 86: "V", 86 | 87: "W", 87 | 88: "X", 88 | 89: "Y", 89 | 90: "Z", 90 | 96: "0", 91 | 97: "1", 92 | 98: "2", 93 | 99: "3", 94 | 100: "4", 95 | 101: "5", 96 | 102: "6", 97 | 103: "7", 98 | 104: "8", 99 | 105: "9", 100 | 106: "Multiply", 101 | 107: "Add", 102 | 109: "Subtract", 103 | 110: "Decimal", 104 | 111: "Divide", 105 | 112: "F1", 106 | 113: "F2", 107 | 114: "F3", 108 | 115: "F4", 109 | 116: "F5", 110 | 117: "F6", 111 | 118: "F7", 112 | 119: "F8", 113 | 120: "F9", 114 | 121: "F10", 115 | 122: "F11", 116 | 123: "F12", 117 | 124: "F13", 118 | 125: "F14", 119 | 126: "F15", 120 | 127: "F16", 121 | 128: "F17", 122 | 129: "F18", 123 | 130: "F19", 124 | 131: "F20", 125 | 132: "F21", 126 | 133: "F22", 127 | 134: "F23", 128 | 135: "F24", 129 | 59: ";", 130 | 61: "=", 131 | 186: ";", 132 | 187: "=", 133 | 188: ",", 134 | 190: ".", 135 | 191: "/", 136 | 192: "`", 137 | 219: "[", 138 | 220: "\\", 139 | 221: "]", 140 | 222: "'" 141 | }; 142 | 143 | Hotkeys.aliases = { 144 | "escape": "esc", 145 | "delete": "del", 146 | "return": "enter", 147 | "ctrl": "control", 148 | "space": "spacebar", 149 | "ins": "insert", 150 | "cmd": "meta", 151 | "command": "meta", 152 | "wins": "meta", 153 | "windows": "meta" 154 | }; 155 | 156 | Hotkeys.normalize = function(shortcut) { 157 | var i, j, key, keyname, keys, len; 158 | keys = shortcut.toLowerCase().replace(/\s+/gi, "").split("+"); 159 | for (i = j = 0, len = keys.length; j < len; i = ++j) { 160 | key = keys[i]; 161 | keys[i] = this.aliases[key] || key; 162 | } 163 | keyname = keys.pop(); 164 | keys.sort().push(keyname); 165 | return keys.join("_"); 166 | }; 167 | 168 | Hotkeys.prototype.opts = { 169 | el: document 170 | }; 171 | 172 | Hotkeys.prototype._init = function() { 173 | this.id = ++this.constructor.count; 174 | this._map = {}; 175 | this._delegate = typeof this.opts.el === "string" ? document : this.opts.el; 176 | return $(this._delegate).on("keydown.simple-hotkeys-" + this.id, this.opts.el, (function(_this) { 177 | return function(e) { 178 | var ref; 179 | return (ref = _this._getHander(e)) != null ? ref.call(_this, e) : void 0; 180 | }; 181 | })(this)); 182 | }; 183 | 184 | Hotkeys.prototype._getHander = function(e) { 185 | var keyname, shortcut; 186 | if (!(keyname = this.constructor.keyNameMap[e.which])) { 187 | return; 188 | } 189 | shortcut = ""; 190 | if (e.altKey) { 191 | shortcut += "alt_"; 192 | } 193 | if (e.ctrlKey) { 194 | shortcut += "control_"; 195 | } 196 | if (e.metaKey) { 197 | shortcut += "meta_"; 198 | } 199 | if (e.shiftKey) { 200 | shortcut += "shift_"; 201 | } 202 | shortcut += keyname.toLowerCase(); 203 | return this._map[shortcut]; 204 | }; 205 | 206 | Hotkeys.prototype.respondTo = function(subject) { 207 | if (typeof subject === 'string') { 208 | return this._map[this.constructor.normalize(subject)] != null; 209 | } else { 210 | return this._getHander(subject) != null; 211 | } 212 | }; 213 | 214 | Hotkeys.prototype.add = function(shortcut, handler) { 215 | this._map[this.constructor.normalize(shortcut)] = handler; 216 | return this; 217 | }; 218 | 219 | Hotkeys.prototype.remove = function(shortcut) { 220 | delete this._map[this.constructor.normalize(shortcut)]; 221 | return this; 222 | }; 223 | 224 | Hotkeys.prototype.destroy = function() { 225 | $(this._delegate).off(".simple-hotkeys-" + this.id); 226 | this._map = {}; 227 | return this; 228 | }; 229 | 230 | return Hotkeys; 231 | 232 | })(SimpleModule); 233 | 234 | hotkeys = function(opts) { 235 | return new Hotkeys(opts); 236 | }; 237 | 238 | return hotkeys; 239 | 240 | })); 241 | 242 | -------------------------------------------------------------------------------- /app/static/main/css/main.css: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 350px; 3 | padding: 100px 0; 4 | background:url(../images/bg.jpg) center center no-repeat; 5 | } 6 | .footer span { 7 | color:#4CAF50; 8 | } 9 | .footer a { 10 | color: #ccc; 11 | } 12 | .footer a:hover { 13 | color:#999; 14 | } 15 | .footer { 16 | padding-top: 5px; 17 | border-top:1px solid #ccc; 18 | color:#ccc; 19 | font-size: 12px; 20 | } 21 | .container { 22 | margin-bottom: 50px; 23 | } 24 | .note-body ul, .note-body ul li { 25 | list-style-type: disc; 26 | } 27 | body { 28 | color:#666; 29 | } 30 | #global-nav .input-field input[type=text] { 31 | width: 150px; 32 | border: none; 33 | border-bottom: 1px solid #ef9a9a; 34 | color:#fff; 35 | } 36 | .pagination li { 37 | padding: 0; 38 | } 39 | .pagination li a { 40 | display: block; 41 | padding: 0 10px 42 | } 43 | .search-input { 44 | width: 210px; 45 | } 46 | .search-input a { 47 | width: 50px; 48 | } 49 | .nav .nav-wrapper { 50 | padding-left: 20px; 51 | } 52 | .tab { 53 | padding: 10px 0; 54 | } 55 | .material-icons { 56 | vertical-align: middle; 57 | } 58 | .fixed-action-btn .material-icons { 59 | vertical-align: top; 60 | } 61 | .note-card .desc { 62 | height: 4.2em; 63 | overflow: hidden; 64 | } 65 | .note-card .card-action span { 66 | margin-right: 1em; 67 | } 68 | .note-card .card-action em { 69 | font-style: normal; 70 | } 71 | .note-card .material-icons { 72 | font-size: 20px; 73 | } 74 | 75 | .flash-message { 76 | padding: 1em; 77 | width: 100%; 78 | } 79 | .error-message { 80 | padding: 1em; 81 | } 82 | .error-message .material-icons { 83 | vertical-align: middle; 84 | } 85 | 86 | p code { 87 | display:inline; 88 | font-size:13px; 89 | margin: 0 2px; 90 | padding: 1px 5px; 91 | border: 1px solid #eaeaea; 92 | background-color: #f8f8f8; 93 | border-radius:2px; 94 | } 95 | code, pre { 96 | display:block; 97 | font-size:12px; 98 | color:#000; 99 | white-space: pre; 100 | font-family: Monaco, Consolas, "Lucida Console", monospace; 101 | background:#fafafa; 102 | padding:5px 1em; 103 | border:1px solid #eee; 104 | border-radius:5px; 105 | } 106 | pre { 107 | margin-bottom:1.5em; 108 | } 109 | pre code { 110 | border:none; 111 | } 112 | 113 | .meta-info { 114 | color: #999; 115 | } 116 | .meta-info .material-icons { 117 | font-size: 20px; 118 | } 119 | .meta-info span { 120 | display:inline-block; 121 | height: 24px; 122 | line-height: 24px; 123 | } 124 | 125 | /* type */ 126 | .note-body body { 127 | font: 14px/160% "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Verdana, Tahoma, sans-serif; 128 | color:#666; 129 | } 130 | 131 | /* Typography */ 132 | .note-body a { 133 | color: #690; 134 | text-decoration:none; 135 | } 136 | .note-body a:hover { 137 | text-decoration: underline; 138 | } 139 | 140 | .note-body h1, .note-body h2, .note-body h3, .note-body h4, .note-body h5, .note-body h6 { 141 | color:#000; 142 | font-weight:normal; 143 | line-height:100%; 144 | } 145 | .note-body h1 { 146 | font-size:24px; 147 | } 148 | .note-body h2 { 149 | font-size:22px; 150 | } 151 | .note-body h3 { 152 | font-size:20px; 153 | } 154 | .note-body h4 { 155 | font-size:18px; 156 | } 157 | .note-body h5 { 158 | font-size:16px; 159 | } 160 | .note-body h6 { 161 | font-size:14px; 162 | font-weight:bold; 163 | } 164 | 165 | .note-body ul, .note-body ol { 166 | margin-bottom:1.5em; 167 | padding-left:1.5em; 168 | } 169 | .note-body strong { 170 | font-weight:bold; 171 | } 172 | .note-body em { 173 | font-style:italic; 174 | padding:0 2px; 175 | } 176 | .note-body p { 177 | color:#666; 178 | margin-bottom:1.5em; 179 | } 180 | .note-body p code { 181 | display:inline; 182 | font-size:13px; 183 | margin: 0 2px; 184 | padding: 1px 5px; 185 | border: 1px solid #eaeaea; 186 | background-color: #f8f8f8; 187 | border-radius:2px; 188 | } 189 | .note-body code, .note-body pre { 190 | display:block; 191 | font-size:12px; 192 | color:#000; 193 | white-space: pre; 194 | font-family: Monaco, Consolas, "Lucida Console", monospace; 195 | background:#fafafa; 196 | padding:5px 1em; 197 | border:1px solid #eee; 198 | border-radius:5px; 199 | } 200 | .note-body pre { 201 | margin-bottom:1.5em; 202 | } 203 | .note-body pre code { 204 | border:none; 205 | padding: 5px 0; 206 | } 207 | .note-body blockquote { 208 | display:block; 209 | padding:0 0 0 1em; 210 | margin:0; 211 | border-left:1px solid #bbb; 212 | color:#999; 213 | margin-bottom:1.5em; 214 | } 215 | .note-body blockquote p { 216 | color:#999; 217 | } 218 | .note-body abbr { 219 | border-bottom:1px dotted #bbb; 220 | } 221 | 222 | /* highlight - them > default.css */ 223 | 224 | /* 225 | 226 | Original highlight.js style (c) Ivan Sagalaev 227 | 228 | */ 229 | .hljs { 230 | display: block; 231 | overflow-x: auto; 232 | padding: 0.5em; 233 | background: #F0F0F0; 234 | } 235 | 236 | 237 | /* Base color: saturation 0; */ 238 | 239 | .hljs, 240 | .hljs-subst { 241 | color: #444; 242 | } 243 | 244 | .hljs-comment { 245 | color: #888888; 246 | } 247 | 248 | .hljs-keyword, 249 | .hljs-attribute, 250 | .hljs-selector-tag, 251 | .hljs-meta-keyword, 252 | .hljs-doctag, 253 | .hljs-name { 254 | font-weight: bold; 255 | } 256 | 257 | 258 | /* User color: hue: 0 */ 259 | 260 | .hljs-type, 261 | .hljs-string, 262 | .hljs-number, 263 | .hljs-selector-id, 264 | .hljs-selector-class, 265 | .hljs-quote, 266 | .hljs-template-tag, 267 | .hljs-deletion { 268 | color: #880000; 269 | } 270 | 271 | .hljs-title, 272 | .hljs-section { 273 | color: #880000; 274 | font-weight: bold; 275 | } 276 | 277 | .hljs-regexp, 278 | .hljs-symbol, 279 | .hljs-variable, 280 | .hljs-template-variable, 281 | .hljs-link, 282 | .hljs-selector-attr, 283 | .hljs-selector-pseudo { 284 | color: #BC6060; 285 | } 286 | 287 | 288 | /* Language color: hue: 90; */ 289 | 290 | .hljs-literal { 291 | color: #78A960; 292 | } 293 | 294 | .hljs-built_in, 295 | .hljs-bullet, 296 | .hljs-code, 297 | .hljs-addition { 298 | color: #397300; 299 | } 300 | 301 | 302 | /* Meta color: hue: 200 */ 303 | 304 | .hljs-meta { 305 | color: #1f7199; 306 | } 307 | 308 | .hljs-meta-string { 309 | color: #4d99bf; 310 | } 311 | 312 | 313 | /* Misc effects */ 314 | 315 | .hljs-emphasis { 316 | font-style: italic; 317 | } 318 | 319 | .hljs-strong { 320 | font-weight: bold; 321 | } 322 | .note-title h1 { 323 | font-size: 30px; 324 | } -------------------------------------------------------------------------------- /app/note/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | from datetime import datetime 4 | from flask import request, render_template, url_for, flash, abort, redirect, current_app, jsonify 5 | from flask.ext.login import current_user, login_required 6 | from flask.ext.sqlalchemy import Pagination 7 | from werkzeug import secure_filename 8 | from form import CreateNoteForm, EditNoteForm 9 | from . import note as NOTE 10 | from ..models import Note, User 11 | from ..helper import getNoteUrl, emptyDir 12 | from ..upload import Upload 13 | 14 | @NOTE.route('/upload', methods=['POST']) 15 | def upload_image(): 16 | success = False 17 | msg = u'上传出错' 18 | file_path = '' 19 | filename_prefix = 'note___{0}___{1}'.format(current_user.id, datetime.now().strftime("%Y-%m-%d_%H-%M-%S")) 20 | 21 | ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) 22 | tmp_dir = current_app.config['TMP_DIR'] 23 | max_size = current_app.config['MAX_CONTENT_LENGTH'] 24 | qiniu_domain = current_app.config['QINIU_DOMAIN'] 25 | 26 | def allowed_file(filename): 27 | return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS 28 | 29 | if request.method == 'POST': 30 | file = request.files['file'] 31 | 32 | if not file: 33 | success = False 34 | msg = u'空文件' 35 | if not allowed_file(file.filename): 36 | success = False 37 | msg = u'文件格式不支持' 38 | 39 | if file and allowed_file(file.filename): 40 | filename = secure_filename(file.filename) 41 | realname = filename_prefix + str('___') + filename 42 | abs_file_name = os.path.join(tmp_dir, realname) 43 | 44 | file.save(abs_file_name) 45 | size = os.stat(abs_file_name).st_size 46 | 47 | if size > max_size: 48 | success = false 49 | msg = u'文件太大,不能超过2M' 50 | else: 51 | uploader = Upload( 52 | current_app.config['QN_ACCESS_KEY'], 53 | current_app.config['QN_SECRET_KEY'], 54 | current_app.config['QN_BUCKET_NAME'] 55 | ) 56 | upload_result = uploader.send_file(realname, abs_file_name) 57 | 58 | if upload_result: 59 | success = True 60 | msg = u'文件上传成功' 61 | file_path = qiniu_domain + realname 62 | else: 63 | success = False 64 | msg = u'文件上传 cdn 失败,请检查 access or secret key 是否正确配置' 65 | 66 | # cleanup 67 | emptyDir(tmp_dir) 68 | result = dict(success=success, msg=msg, file_path=file_path) 69 | 70 | return jsonify(result) 71 | 72 | @NOTE.route('/notes', defaults={'page': 1}) 73 | @NOTE.route('/page/') 74 | def show_public_notes(page=1): 75 | pagination = Note.getIndexNotes().paginate( 76 | page=page, 77 | per_page=current_app.config['NOTE_NUM_PER_PAGE'], 78 | error_out=True 79 | ) 80 | 81 | return render_template('index.html', title='home', isHome=True, 82 | notes=pagination.items, pagination=pagination, page=page) 83 | 84 | @NOTE.route('/notes/', defaults={'page': 1}) 85 | @NOTE.route('/notes//page/') 86 | @login_required 87 | def show_user_notes(user, page): 88 | if user != current_user.nick_name: 89 | return render_template('404.html', title='page not found') 90 | 91 | pagination = Note.getUserNotes(current_user.id).paginate( 92 | page=page, 93 | per_page=current_app.config['NOTE_NUM_PER_PAGE'], 94 | error_out=True 95 | ) 96 | 97 | return render_template('index.html', title='home', 98 | isHome=True, 99 | isUserNote=True, 100 | notes=pagination.items, pagination=pagination, page=page) 101 | 102 | @NOTE.route('/search') 103 | def show_search_notes(): 104 | page = int(request.args.get('page') or 1) 105 | keyword = request.args.get('keyword') 106 | 107 | if not keyword: 108 | return render_template('404.html', title='page not found') 109 | 110 | pagination = Note.getSearchNotes(keyword).paginate( 111 | page=page, 112 | per_page=current_app.config['NOTE_NUM_PER_PAGE'], 113 | error_out=True 114 | ) 115 | 116 | return render_template('index.html', title='search', 117 | isHome=True, 118 | isSearch=True, 119 | keyword=keyword, 120 | notes=pagination.items, pagination=pagination, page=page); 121 | 122 | @NOTE.route('/note/') 123 | def show_note(id): 124 | note = Note.query.filter_by(id=id).first() 125 | 126 | if note == None: 127 | return render_template('404.html', title='page not found') 128 | 129 | user = User.query.filter_by(id=note.user_id).first() 130 | 131 | return render_template('detail.html', title=note.title, 132 | user=user, 133 | note=note, 134 | isDetail=True) 135 | 136 | @NOTE.route('//') 137 | @login_required 138 | def show_user_note(user, id): 139 | note = Note.query.filter_by(id=id).first() 140 | note.decryptContent() 141 | 142 | # current user must be the note's author 143 | if user != current_user.nick_name or note == None: 144 | return render_template('404.html', title='page not found') 145 | 146 | return render_template('detail.html', title=note.title, note=note, isDetail=True, user=current_user) 147 | 148 | @NOTE.route('/write', methods=['GET', 'POST']) 149 | @login_required 150 | def write(): 151 | form = CreateNoteForm(request.form) 152 | if request.method == 'POST' and form.validate(): 153 | note = Note( 154 | form.title.data, 155 | form.content.data, 156 | form.public.data, 157 | current_user 158 | ) 159 | result = note.create() 160 | if result == 'success': 161 | flash(u'You`v add a new note. 「{1}」'.format(getNoteUrl(note), form.title.data), result) 162 | else: 163 | flash('Saving with an error.', result) 164 | 165 | return render_template('write.html', title='write', form=form, isWrite=True) 166 | 167 | @NOTE.route('/edit/', methods=['GET', 'POST']) 168 | @login_required 169 | def edit(id): 170 | form = EditNoteForm(request.form) 171 | note = Note.query.filter_by(id=id).first() 172 | note.decryptContent() 173 | 174 | # current user must be the note's author 175 | if note.user_id != current_user.id or note == None: 176 | return render_template('404.html', title='page not found') 177 | 178 | if request.method == 'GET': 179 | form.title.data = note.title 180 | form.content.data = note.content 181 | form.public.data = note.public 182 | 183 | if request.method == 'POST' and form.validate(): 184 | note.title = form.title.data 185 | note.content = form.content.data 186 | note.public = form.public.data 187 | 188 | result = note.update() 189 | if result == 'success': 190 | flash(u'You`v edit your note. 「{1}」'.format(getNoteUrl(note), form.title.data), result) 191 | else: 192 | flash('Saving with an error.', result) 193 | 194 | return render_template('write.html', title='edit', note=note, form=form, isEdit=True) 195 | 196 | @NOTE.route('/delete/', methods=['GET', 'POST']) 197 | @login_required 198 | def delete(id): 199 | form = EditNoteForm(request.form) 200 | note = Note.query.filter_by(id=id).first() 201 | 202 | # current user must be the note's author 203 | if note.user_id != current_user.id or note == None: 204 | return render_template('404.html', title='page not found') 205 | 206 | result = note.delete() 207 | 208 | if result == 'success': 209 | return redirect(url_for('main.index')) 210 | else: 211 | return render_template('500.html', title='delete note error') 212 | -------------------------------------------------------------------------------- /app/static/simditor/scripts/uploader.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | // AMD. Register as an anonymous module unless amdModuleId is set 4 | define('simple-uploader', ["jquery","simple-module"], function ($, SimpleModule) { 5 | return (root['uploader'] = factory($, SimpleModule)); 6 | }); 7 | } else if (typeof exports === 'object') { 8 | // Node. Does not work with strict CommonJS, but 9 | // only CommonJS-like environments that support module.exports, 10 | // like Node. 11 | module.exports = factory(require("jquery"),require("simple-module")); 12 | } else { 13 | root.simple = root.simple || {}; 14 | root.simple['uploader'] = factory(jQuery,SimpleModule); 15 | } 16 | }(this, function ($, SimpleModule) { 17 | 18 | var Uploader, uploader, 19 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 20 | hasProp = {}.hasOwnProperty; 21 | 22 | Uploader = (function(superClass) { 23 | extend(Uploader, superClass); 24 | 25 | function Uploader() { 26 | return Uploader.__super__.constructor.apply(this, arguments); 27 | } 28 | 29 | Uploader.count = 0; 30 | 31 | Uploader.prototype.opts = { 32 | url: '', 33 | params: null, 34 | fileKey: 'upload_file', 35 | connectionCount: 3 36 | }; 37 | 38 | Uploader.prototype._init = function() { 39 | this.files = []; 40 | this.queue = []; 41 | this.id = ++Uploader.count; 42 | this.on('uploadcomplete', (function(_this) { 43 | return function(e, file) { 44 | _this.files.splice($.inArray(file, _this.files), 1); 45 | if (_this.queue.length > 0 && _this.files.length < _this.opts.connectionCount) { 46 | return _this.upload(_this.queue.shift()); 47 | } else { 48 | return _this.uploading = false; 49 | } 50 | }; 51 | })(this)); 52 | return $(window).on('beforeunload.uploader-' + this.id, (function(_this) { 53 | return function(e) { 54 | if (!_this.uploading) { 55 | return; 56 | } 57 | e.originalEvent.returnValue = _this._t('leaveConfirm'); 58 | return _this._t('leaveConfirm'); 59 | }; 60 | })(this)); 61 | }; 62 | 63 | Uploader.prototype.generateId = (function() { 64 | var id; 65 | id = 0; 66 | return function() { 67 | return id += 1; 68 | }; 69 | })(); 70 | 71 | Uploader.prototype.upload = function(file, opts) { 72 | var f, i, key, len; 73 | if (opts == null) { 74 | opts = {}; 75 | } 76 | if (file == null) { 77 | return; 78 | } 79 | if ($.isArray(file) || file instanceof FileList) { 80 | for (i = 0, len = file.length; i < len; i++) { 81 | f = file[i]; 82 | this.upload(f, opts); 83 | } 84 | } else if ($(file).is('input:file')) { 85 | key = $(file).attr('name'); 86 | if (key) { 87 | opts.fileKey = key; 88 | } 89 | this.upload($.makeArray($(file)[0].files), opts); 90 | } else if (!file.id || !file.obj) { 91 | file = this.getFile(file); 92 | } 93 | if (!(file && file.obj)) { 94 | return; 95 | } 96 | $.extend(file, opts); 97 | if (this.files.length >= this.opts.connectionCount) { 98 | this.queue.push(file); 99 | return; 100 | } 101 | if (this.triggerHandler('beforeupload', [file]) === false) { 102 | return; 103 | } 104 | this.files.push(file); 105 | this._xhrUpload(file); 106 | return this.uploading = true; 107 | }; 108 | 109 | Uploader.prototype.getFile = function(fileObj) { 110 | var name, ref, ref1; 111 | if (fileObj instanceof window.File || fileObj instanceof window.Blob) { 112 | name = (ref = fileObj.fileName) != null ? ref : fileObj.name; 113 | } else { 114 | return null; 115 | } 116 | return { 117 | id: this.generateId(), 118 | url: this.opts.url, 119 | params: this.opts.params, 120 | fileKey: this.opts.fileKey, 121 | name: name, 122 | size: (ref1 = fileObj.fileSize) != null ? ref1 : fileObj.size, 123 | ext: name ? name.split('.').pop().toLowerCase() : '', 124 | obj: fileObj 125 | }; 126 | }; 127 | 128 | Uploader.prototype._xhrUpload = function(file) { 129 | var formData, k, ref, v; 130 | formData = new FormData(); 131 | formData.append(file.fileKey, file.obj); 132 | formData.append("original_filename", file.name); 133 | if (file.params) { 134 | ref = file.params; 135 | for (k in ref) { 136 | v = ref[k]; 137 | formData.append(k, v); 138 | } 139 | } 140 | return file.xhr = $.ajax({ 141 | url: file.url, 142 | data: formData, 143 | processData: false, 144 | contentType: false, 145 | type: 'POST', 146 | headers: { 147 | 'X-File-Name': encodeURIComponent(file.name) 148 | }, 149 | xhr: function() { 150 | var req; 151 | req = $.ajaxSettings.xhr(); 152 | if (req) { 153 | req.upload.onprogress = (function(_this) { 154 | return function(e) { 155 | return _this.progress(e); 156 | }; 157 | })(this); 158 | } 159 | return req; 160 | }, 161 | progress: (function(_this) { 162 | return function(e) { 163 | if (!e.lengthComputable) { 164 | return; 165 | } 166 | return _this.trigger('uploadprogress', [file, e.loaded, e.total]); 167 | }; 168 | })(this), 169 | error: (function(_this) { 170 | return function(xhr, status, err) { 171 | return _this.trigger('uploaderror', [file, xhr, status]); 172 | }; 173 | })(this), 174 | success: (function(_this) { 175 | return function(result) { 176 | _this.trigger('uploadprogress', [file, file.size, file.size]); 177 | _this.trigger('uploadsuccess', [file, result]); 178 | return $(document).trigger('uploadsuccess', [file, result, _this]); 179 | }; 180 | })(this), 181 | complete: (function(_this) { 182 | return function(xhr, status) { 183 | return _this.trigger('uploadcomplete', [file, xhr.responseText]); 184 | }; 185 | })(this) 186 | }); 187 | }; 188 | 189 | Uploader.prototype.cancel = function(file) { 190 | var f, i, len, ref; 191 | if (!file.id) { 192 | ref = this.files; 193 | for (i = 0, len = ref.length; i < len; i++) { 194 | f = ref[i]; 195 | if (f.id === file * 1) { 196 | file = f; 197 | break; 198 | } 199 | } 200 | } 201 | this.trigger('uploadcancel', [file]); 202 | if (file.xhr) { 203 | file.xhr.abort(); 204 | } 205 | return file.xhr = null; 206 | }; 207 | 208 | Uploader.prototype.readImageFile = function(fileObj, callback) { 209 | var fileReader, img; 210 | if (!$.isFunction(callback)) { 211 | return; 212 | } 213 | img = new Image(); 214 | img.onload = function() { 215 | return callback(img); 216 | }; 217 | img.onerror = function() { 218 | return callback(); 219 | }; 220 | if (window.FileReader && FileReader.prototype.readAsDataURL && /^image/.test(fileObj.type)) { 221 | fileReader = new FileReader(); 222 | fileReader.onload = function(e) { 223 | return img.src = e.target.result; 224 | }; 225 | return fileReader.readAsDataURL(fileObj); 226 | } else { 227 | return callback(); 228 | } 229 | }; 230 | 231 | Uploader.prototype.destroy = function() { 232 | var file, i, len, ref; 233 | this.queue.length = 0; 234 | ref = this.files; 235 | for (i = 0, len = ref.length; i < len; i++) { 236 | file = ref[i]; 237 | this.cancel(file); 238 | } 239 | $(window).off('.uploader-' + this.id); 240 | return $(document).off('.uploader-' + this.id); 241 | }; 242 | 243 | Uploader.i18n = { 244 | 'zh-CN': { 245 | leaveConfirm: '正在上传文件,如果离开上传会自动取消' 246 | } 247 | }; 248 | 249 | Uploader.locale = 'zh-CN'; 250 | 251 | return Uploader; 252 | 253 | })(SimpleModule); 254 | 255 | uploader = function(opts) { 256 | return new Uploader(opts); 257 | }; 258 | 259 | return uploader; 260 | 261 | })); 262 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS and IE text size adjust after device orientation change, 6 | * without disabling user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability of focused elements when they are also in an 95 | * active/hover state. 96 | */ 97 | 98 | a:active, 99 | a:hover { 100 | outline: 0; 101 | } 102 | 103 | /* Text-level semantics 104 | ========================================================================== */ 105 | 106 | /** 107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 108 | */ 109 | 110 | abbr[title] { 111 | border-bottom: 1px dotted; 112 | } 113 | 114 | /** 115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bold; 121 | } 122 | 123 | /** 124 | * Address styling not present in Safari and Chrome. 125 | */ 126 | 127 | dfn { 128 | font-style: italic; 129 | } 130 | 131 | /** 132 | * Address variable `h1` font-size and margin within `section` and `article` 133 | * contexts in Firefox 4+, Safari, and Chrome. 134 | */ 135 | 136 | h1 { 137 | font-size: 2em; 138 | margin: 0.67em 0; 139 | } 140 | 141 | /** 142 | * Address styling not present in IE 8/9. 143 | */ 144 | 145 | mark { 146 | background: #ff0; 147 | color: #000; 148 | } 149 | 150 | /** 151 | * Address inconsistent and variable font size in all browsers. 152 | */ 153 | 154 | small { 155 | font-size: 80%; 156 | } 157 | 158 | /** 159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 160 | */ 161 | 162 | sub, 163 | sup { 164 | font-size: 75%; 165 | line-height: 0; 166 | position: relative; 167 | vertical-align: baseline; 168 | } 169 | 170 | sup { 171 | top: -0.5em; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | /* Embedded content 179 | ========================================================================== */ 180 | 181 | /** 182 | * Remove border when inside `a` element in IE 8/9/10. 183 | */ 184 | 185 | img { 186 | border: 0; 187 | } 188 | 189 | /** 190 | * Correct overflow not hidden in IE 9/10/11. 191 | */ 192 | 193 | svg:not(:root) { 194 | overflow: hidden; 195 | } 196 | 197 | /* Grouping content 198 | ========================================================================== */ 199 | 200 | /** 201 | * Address margin not present in IE 8/9 and Safari. 202 | */ 203 | 204 | figure { 205 | margin: 1em 40px; 206 | } 207 | 208 | /** 209 | * Address differences between Firefox and other browsers. 210 | */ 211 | 212 | hr { 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. 354 | */ 355 | 356 | input[type="search"] { 357 | -webkit-appearance: textfield; /* 1 */ 358 | box-sizing: content-box; /* 2 */ 359 | } 360 | 361 | /** 362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 363 | * Safari (but not Chrome) clips the cancel button when the search input has 364 | * padding (and `textfield` appearance). 365 | */ 366 | 367 | input[type="search"]::-webkit-search-cancel-button, 368 | input[type="search"]::-webkit-search-decoration { 369 | -webkit-appearance: none; 370 | } 371 | 372 | /** 373 | * Define consistent border, margin, and padding. 374 | */ 375 | 376 | fieldset { 377 | border: 1px solid #c0c0c0; 378 | margin: 0 2px; 379 | padding: 0.35em 0.625em 0.75em; 380 | } 381 | 382 | /** 383 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 385 | */ 386 | 387 | legend { 388 | border: 0; /* 1 */ 389 | padding: 0; /* 2 */ 390 | } 391 | 392 | /** 393 | * Remove default vertical scrollbar in IE 8/9/10/11. 394 | */ 395 | 396 | textarea { 397 | overflow: auto; 398 | } 399 | 400 | /** 401 | * Don't inherit the `font-weight` (applied by a rule above). 402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 403 | */ 404 | 405 | optgroup { 406 | font-weight: bold; 407 | } 408 | 409 | /* Tables 410 | ========================================================================== */ 411 | 412 | /** 413 | * Remove most spacing between table cells. 414 | */ 415 | 416 | table { 417 | border-collapse: collapse; 418 | border-spacing: 0; 419 | } 420 | 421 | td, 422 | th { 423 | padding: 0; 424 | } 425 | -------------------------------------------------------------------------------- /app/static/materialize/css/components/date_picker/_default.date.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | $BASE-DATE-PICKER 3 | ========================================================================== */ 4 | /** 5 | * The picker box. 6 | */ 7 | .picker__box { 8 | padding: 0 1em; 9 | } 10 | /** 11 | * The header containing the month and year stuff. 12 | */ 13 | .picker__header { 14 | text-align: center; 15 | position: relative; 16 | margin-top: .75em; 17 | } 18 | /** 19 | * The month and year labels. 20 | */ 21 | .picker__month, 22 | .picker__year { 23 | // font-weight: 500; 24 | display: inline-block; 25 | margin-left: .25em; 26 | margin-right: .25em; 27 | } 28 | /** 29 | * The month and year selectors. 30 | */ 31 | .picker__select--month, 32 | .picker__select--year { 33 | 34 | height: 2em; 35 | padding: 0; 36 | margin-left: .25em; 37 | margin-right: .25em; 38 | } 39 | 40 | // Modified 41 | .picker__select--month.browser-default { 42 | display: inline; 43 | background-color: #FFFFFF; 44 | width: 40%; 45 | } 46 | .picker__select--year.browser-default { 47 | display: inline; 48 | background-color: #FFFFFF; 49 | width: 26%; 50 | } 51 | .picker__select--month:focus, 52 | .picker__select--year:focus { 53 | border-color: $datepicker-focus; 54 | } 55 | /** 56 | * The month navigation buttons. 57 | */ 58 | .picker__nav--prev, 59 | .picker__nav--next { 60 | position: absolute; 61 | padding: .5em 1.25em; 62 | width: 1em; 63 | height: 1em; 64 | box-sizing: content-box; 65 | top: -0.25em; 66 | } 67 | //@media (min-width: 24.5em) { 68 | // .picker__nav--prev, 69 | // .picker__nav--next { 70 | // top: -0.33em; 71 | // } 72 | //} 73 | .picker__nav--prev { 74 | left: -1em; 75 | padding-right: 1.25em; 76 | } 77 | //@media (min-width: 24.5em) { 78 | // .picker__nav--prev { 79 | // padding-right: 1.5em; 80 | // } 81 | //} 82 | .picker__nav--next { 83 | right: -1em; 84 | padding-left: 1.25em; 85 | } 86 | //@media (min-width: 24.5em) { 87 | // .picker__nav--next { 88 | // padding-left: 1.5em; 89 | // } 90 | //} 91 | 92 | .picker__nav--disabled, 93 | .picker__nav--disabled:hover, 94 | .picker__nav--disabled:before, 95 | .picker__nav--disabled:before:hover { 96 | cursor: default; 97 | background: none; 98 | border-right-color: #f5f5f5; 99 | border-left-color: #f5f5f5; 100 | } 101 | /** 102 | * The calendar table of dates 103 | */ 104 | .picker__table { 105 | text-align: center; 106 | border-collapse: collapse; 107 | border-spacing: 0; 108 | table-layout: fixed; 109 | font-size: 1rem; 110 | width: 100%; 111 | margin-top: .75em; 112 | margin-bottom: .5em; 113 | } 114 | 115 | 116 | 117 | .picker__table th, .picker__table td { 118 | text-align: center; 119 | } 120 | 121 | 122 | 123 | 124 | 125 | 126 | .picker__table td { 127 | margin: 0; 128 | padding: 0; 129 | } 130 | /** 131 | * The weekday labels 132 | */ 133 | .picker__weekday { 134 | width: 14.285714286%; 135 | font-size: .75em; 136 | padding-bottom: .25em; 137 | color: #999999; 138 | font-weight: 500; 139 | /* Increase the spacing a tad */ 140 | } 141 | @media (min-height: 33.875em) { 142 | .picker__weekday { 143 | padding-bottom: .5em; 144 | } 145 | } 146 | /** 147 | * The days on the calendar 148 | */ 149 | 150 | .picker__day--today { 151 | position: relative; 152 | color: #595959; 153 | letter-spacing: -.3; 154 | padding: .75rem 0; 155 | font-weight: 400; 156 | border: 1px solid transparent; 157 | 158 | } 159 | 160 | //.picker__day--today:before { 161 | // content: " "; 162 | // position: absolute; 163 | // top: 2px; 164 | // right: 2px; 165 | // width: 0; 166 | // height: 0; 167 | // border-top: 0.5em solid #0059bc; 168 | // border-left: .5em solid transparent; 169 | //} 170 | .picker__day--disabled:before { 171 | border-top-color: #aaaaaa; 172 | } 173 | 174 | 175 | .picker__day--infocus:hover{ 176 | cursor: pointer; 177 | color: #000; 178 | font-weight: 500; 179 | } 180 | 181 | .picker__day--outfocus { 182 | display: none; 183 | padding: .75rem 0; 184 | color: #fff; 185 | 186 | } 187 | .picker__day--outfocus:hover { 188 | cursor: pointer; 189 | color: #dddddd; 190 | // background: #b1dcfb; 191 | font-weight: 500; 192 | } 193 | 194 | 195 | .picker__day--highlighted { 196 | // border-color: #0089ec; 197 | } 198 | .picker__day--highlighted:hover, 199 | .picker--focused .picker__day--highlighted { 200 | cursor: pointer; 201 | // color: #000000; 202 | // background: #b1dcfb; 203 | // font-weight: 500; 204 | } 205 | .picker__day--selected, 206 | .picker__day--selected:hover, 207 | .picker--focused .picker__day--selected { 208 | 209 | 210 | // Circle background 211 | border-radius: 50%; 212 | transform: scale(.75); 213 | background: #0089ec; 214 | color: #ffffff; 215 | } 216 | .picker__day--disabled, 217 | .picker__day--disabled:hover, 218 | .picker--focused .picker__day--disabled { 219 | background: #f5f5f5; 220 | border-color: #f5f5f5; 221 | color: #dddddd; 222 | cursor: default; 223 | } 224 | .picker__day--highlighted.picker__day--disabled, 225 | .picker__day--highlighted.picker__day--disabled:hover { 226 | background: #bbbbbb; 227 | } 228 | /** 229 | * The footer containing the "today", "clear", and "close" buttons. 230 | */ 231 | .picker__footer { 232 | text-align: center; 233 | display: flex; 234 | align-items: center; 235 | justify-content: space-between; 236 | } 237 | .picker__button--today, 238 | .picker__button--clear, 239 | .picker__button--close { 240 | border: 1px solid #ffffff; 241 | background: #ffffff; 242 | font-size: .8em; 243 | padding: .66em 0; 244 | font-weight: bold; 245 | width: 33%; 246 | display: inline-block; 247 | vertical-align: bottom; 248 | } 249 | .picker__button--today:hover, 250 | .picker__button--clear:hover, 251 | .picker__button--close:hover { 252 | cursor: pointer; 253 | color: #000000; 254 | background: #b1dcfb; 255 | border-bottom-color: #b1dcfb; 256 | } 257 | .picker__button--today:focus, 258 | .picker__button--clear:focus, 259 | .picker__button--close:focus { 260 | background: #b1dcfb; 261 | border-color: $datepicker-focus; 262 | outline: none; 263 | } 264 | .picker__button--today:before, 265 | .picker__button--clear:before, 266 | .picker__button--close:before { 267 | position: relative; 268 | display: inline-block; 269 | height: 0; 270 | } 271 | .picker__button--today:before, 272 | .picker__button--clear:before { 273 | content: " "; 274 | margin-right: .45em; 275 | } 276 | .picker__button--today:before { 277 | top: -0.05em; 278 | width: 0; 279 | border-top: 0.66em solid #0059bc; 280 | border-left: .66em solid transparent; 281 | } 282 | .picker__button--clear:before { 283 | top: -0.25em; 284 | width: .66em; 285 | border-top: 3px solid #ee2200; 286 | } 287 | .picker__button--close:before { 288 | content: "\D7"; 289 | top: -0.1em; 290 | vertical-align: top; 291 | font-size: 1.1em; 292 | margin-right: .35em; 293 | color: #777777; 294 | } 295 | .picker__button--today[disabled], 296 | .picker__button--today[disabled]:hover { 297 | background: #f5f5f5; 298 | border-color: #f5f5f5; 299 | color: #dddddd; 300 | cursor: default; 301 | } 302 | .picker__button--today[disabled]:before { 303 | border-top-color: #aaaaaa; 304 | } 305 | 306 | /* ========================================================================== 307 | CUSTOM MATERIALIZE STYLES 308 | ========================================================================== */ 309 | .picker__box { 310 | border-radius: 2px; 311 | overflow: hidden; 312 | } 313 | 314 | .picker__date-display { 315 | text-align: center; 316 | background-color: $datepicker-date-bg; 317 | color: #fff; 318 | padding-bottom: 15px; 319 | font-weight: 300; 320 | } 321 | 322 | .picker__nav--prev:hover, 323 | .picker__nav--next:hover { 324 | cursor: pointer; 325 | color: #000000; 326 | background: $datepicker-selected-outfocus; 327 | } 328 | 329 | .picker__weekday-display { 330 | background-color: $datepicker-weekday-bg; 331 | padding: 10px; 332 | font-weight: 200; 333 | letter-spacing: .5; 334 | font-size: 1rem; 335 | margin-bottom: 15px; 336 | } 337 | 338 | .picker__month-display { 339 | text-transform: uppercase; 340 | font-size: 2rem; 341 | } 342 | .picker__day-display { 343 | 344 | font-size: 4.5rem; 345 | font-weight: 400; 346 | } 347 | .picker__year-display { 348 | font-size: 1.8rem; 349 | color: $datepicker-year; 350 | } 351 | 352 | .picker__box { 353 | padding: 0; 354 | } 355 | .picker__calendar-container { 356 | padding: 0 1rem; 357 | 358 | thead { 359 | border: none; 360 | } 361 | } 362 | 363 | // Calendar 364 | .picker__table { 365 | margin-top: 0; 366 | margin-bottom: .5em; 367 | } 368 | 369 | .picker__day--infocus { 370 | color: #595959; 371 | letter-spacing: -.3; 372 | padding: .75rem 0; 373 | font-weight: 400; 374 | border: 1px solid transparent; 375 | } 376 | 377 | //Today style 378 | .picker__day.picker__day--today { 379 | color: $datepicker-selected; 380 | } 381 | 382 | .picker__day.picker__day--today.picker__day--selected { 383 | color: #fff; 384 | } 385 | 386 | // Table Header 387 | .picker__weekday { 388 | font-size: .9rem; 389 | } 390 | 391 | 392 | .picker__day--selected, 393 | .picker__day--selected:hover, 394 | .picker--focused .picker__day--selected { 395 | // Circle background 396 | border-radius: 50%; 397 | transform: scale(.9); 398 | background-color: $datepicker-selected; 399 | &.picker__day--outfocus { 400 | background-color: $datepicker-selected-outfocus; 401 | } 402 | color: #ffffff; 403 | } 404 | 405 | .picker__footer { 406 | text-align: right; 407 | padding: 5px 10px; 408 | } 409 | 410 | // Materialize modified 411 | .picker__close, .picker__today { 412 | font-size: 1.1rem; 413 | padding: 0 1rem; 414 | color: $datepicker-selected; 415 | } 416 | 417 | //month nav buttons 418 | .picker__nav--prev:before, 419 | .picker__nav--next:before { 420 | content: " "; 421 | border-top: .5em solid transparent; 422 | border-bottom: .5em solid transparent; 423 | border-right: 0.75em solid #676767; 424 | width: 0; 425 | height: 0; 426 | display: block; 427 | margin: 0 auto; 428 | } 429 | .picker__nav--next:before { 430 | border-right: 0; 431 | border-left: 0.75em solid #676767; 432 | } 433 | button.picker__today:focus, button.picker__clear:focus, button.picker__close:focus { 434 | background-color: $datepicker-selected-outfocus; 435 | } 436 | -------------------------------------------------------------------------------- /app/static/simditor/styles/fonticon.scss: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'Simditor';src:url(data:application/font-woff;charset=utf-8;base64,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) format('woff');font-weight:normal;font-style:normal}.simditor-icon{display:inline-block;font:normal normal normal 14px/1 'Simditor';font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0,0)}.simditor-icon-code:before{content:'\f000'}.simditor-icon-bold:before{content:'\f001'}.simditor-icon-italic:before{content:'\f002'}.simditor-icon-underline:before{content:'\f003'}.simditor-icon-times:before{content:'\f004'}.simditor-icon-strikethrough:before{content:'\f005'}.simditor-icon-list-ol:before{content:'\f006'}.simditor-icon-list-ul:before{content:'\f007'}.simditor-icon-quote-left:before{content:'\f008'}.simditor-icon-table:before{content:'\f009'}.simditor-icon-link:before{content:'\f00a'}.simditor-icon-picture-o:before{content:'\f00b'}.simditor-icon-minus:before{content:'\f00c'}.simditor-icon-indent:before{content:'\f00d'}.simditor-icon-outdent:before{content:'\f00e'}.simditor-icon-unlink:before{content:'\f00f'}.simditor-icon-caret-down:before{content:'\f010'}.simditor-icon-caret-right:before{content:'\f011'}.simditor-icon-upload:before{content:'\f012'}.simditor-icon-undo:before{content:'\f013'}.simditor-icon-smile-o:before{content:'\f014'}.simditor-icon-tint:before{content:'\f015'}.simditor-icon-font:before{content:'\f016'}.simditor-icon-html5:before{content:'\f017'}.simditor-icon-mark:before{content:'\f018'}.simditor-icon-align-center:before{content:'\f019'}.simditor-icon-align-left:before{content:'\f01a'}.simditor-icon-align-right:before{content:'\f01b'}.simditor-icon-font-minus:before{content:'\f01c'}.simditor-icon-markdown:before{content:'\f01d'}.simditor-icon-checklist:before{content:'\f01e'} --------------------------------------------------------------------------------