├── app ├── modules │ ├── __init__.py │ └── blog │ │ ├── __init__.py │ │ ├── rss.py │ │ ├── utils.py │ │ └── views.py ├── static │ ├── img │ │ ├── bg.jpg │ │ ├── bj.jpg.back │ │ └── favicon.gif │ ├── css │ │ ├── custom.css │ │ ├── login.css │ │ ├── fix.css │ │ ├── syntax │ │ │ ├── vs.css │ │ │ ├── bw.css │ │ │ ├── zenburn.css │ │ │ ├── github.css │ │ │ ├── borland.css │ │ │ ├── autumn.css │ │ │ ├── perldoc.css │ │ │ ├── trac.css │ │ │ ├── monokai.css │ │ │ ├── emacs.css │ │ │ ├── friendly.css │ │ │ ├── default.css │ │ │ ├── manni.css │ │ │ ├── colorful.css │ │ │ ├── murphy.css │ │ │ ├── vim.css │ │ │ ├── pastie.css │ │ │ ├── native.css │ │ │ ├── tango.css │ │ │ └── fruity.css │ │ ├── cover.css │ │ ├── messenger.css │ │ └── messenger-theme-ice.css │ └── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 ├── templates │ ├── blog │ │ ├── blog_all_posts.html │ │ ├── blog_page_404.html │ │ ├── blog_page.html │ │ ├── blog_base.html │ │ ├── blog_index.html │ │ ├── blog_explore_all.html │ │ ├── blog_menu.html │ │ └── blog_explore.html │ ├── blog_404.html │ ├── cover.html │ ├── faq.html │ ├── new_post.html │ ├── edit_post.html │ ├── index.html │ ├── settings.html │ └── base.html ├── models │ ├── __init__.py │ ├── mixins.py │ ├── post.py │ └── user.py ├── utils │ └── __init__.py ├── views │ ├── __init__.py │ ├── explore.py │ ├── curl.py │ ├── context.py │ ├── main.py │ ├── settings.py │ └── post.py ├── api │ ├── __init__.py │ ├── token.py │ ├── utils.py │ ├── user.py │ ├── post.py │ └── example.py ├── forms │ ├── __init__.py │ ├── base.py │ ├── login.py │ ├── utils.py │ ├── register.py │ ├── post.py │ └── settings.py └── __init__.py ├── .dockerignore ├── examples ├── gunicorn.example ├── supervisor.example ├── config.py.example ├── manage.py.example └── nginx.example ├── Dockerfile ├── migrations ├── script.py.mako ├── versions │ ├── 547b4a03fba_.py │ ├── 9144c5cad_.py │ ├── b0d89871be_adding_public_blog.py │ ├── 12ca4cba34c_adding_truncate_posts.py │ ├── 3a35afe64d1_adding_paginate_and_post_slug.py │ ├── 50bb91d1615_adding_more_profile_customization.py │ ├── 13234475ad5_adding_syntax_highlighter_choice.py │ └── 2870937c5fa_.py ├── alembic.ini ├── utils.py └── env.py ├── requirements.txt ├── .gitignore ├── LICENSE.md └── README.md /app/modules/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | env/ 2 | .git 3 | .idea 4 | examples 5 | LICENSE.md 6 | README.md 7 | -------------------------------------------------------------------------------- /app/static/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depado/MarkDownBlog/HEAD/app/static/img/bg.jpg -------------------------------------------------------------------------------- /app/static/img/bj.jpg.back: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depado/MarkDownBlog/HEAD/app/static/img/bj.jpg.back -------------------------------------------------------------------------------- /app/static/img/favicon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depado/MarkDownBlog/HEAD/app/static/img/favicon.gif -------------------------------------------------------------------------------- /app/static/css/custom.css: -------------------------------------------------------------------------------- 1 | #opener { 2 | float:left; 3 | margin:-10px -50px 0px 0px; 4 | border-radius:0; 5 | } 6 | -------------------------------------------------------------------------------- /app/templates/blog/blog_all_posts.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_base.html" %} 2 | 3 | {% block content %} 4 | 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .mixins import AuthMixin 4 | from .post import Post 5 | from .user import User 6 | -------------------------------------------------------------------------------- /app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .markdown import HighlighterRenderer, markdown_renderer, ansi_renderer, renderer 4 | -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depado/MarkDownBlog/HEAD/app/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depado/MarkDownBlog/HEAD/app/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depado/MarkDownBlog/HEAD/app/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/static/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/depado/MarkDownBlog/HEAD/app/static/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/views/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .main import index, logout 4 | from .explore import explore 5 | from .post import new 6 | from .settings import settings 7 | -------------------------------------------------------------------------------- /app/api/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .post import PostSchema, post_serializer, post_deserializer 4 | from .user import UserSchema, user_serializer, user_deserializer 5 | from .token import get_auth_token 6 | -------------------------------------------------------------------------------- /app/forms/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .login import LoginForm 4 | from .register import RegisterForm 5 | from .post import NewPostForm, EditPostForm 6 | from .settings import SettingForm, ChangePasswordForm 7 | -------------------------------------------------------------------------------- /app/modules/blog/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import Blueprint 4 | 5 | blueprint = Blueprint('blog', __name__, subdomain="") 6 | 7 | from .rss import rss_feed 8 | from .views import get, index, page 9 | -------------------------------------------------------------------------------- /app/models/mixins.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_login import current_user 4 | 5 | 6 | class AuthMixin(object): 7 | def is_accessible(self): 8 | return not current_user.is_anonymous and current_user.is_superuser 9 | -------------------------------------------------------------------------------- /app/templates/blog/blog_page_404.html: -------------------------------------------------------------------------------- 1 | {% extends "cover.html" %} 2 | 3 | {% block inner_content %} 4 |

404 Article Not Found

5 |
6 |

There is no such article.

7 | Click here to go back. 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /examples/gunicorn.example: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import multiprocessing 4 | 5 | bind = "127.0.0.1:8085" 6 | pidfile = "/var/log/gunicorn/markdownblog.pid" 7 | workers = multiprocessing.cpu_count() * 2 + 1 8 | accesslog = "/var/log/gunicorn/markdownblog_access.log" 9 | errorlog = "/var/log/gunicorn/markdownblog_error.log" 10 | loglevel = "debug" 11 | -------------------------------------------------------------------------------- /app/templates/blog_404.html: -------------------------------------------------------------------------------- 1 | {% extends "cover.html" %} 2 | 3 | {% block inner_content %} 4 |

404 Blog Not Found

5 |
6 |

There is no blog named {{ blog_name }} right now.

7 |

Maybe you could create one ?

8 | Click here to go back. 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /examples/supervisor.example: -------------------------------------------------------------------------------- 1 | [program:markdownblog] 2 | directory=/path/to/MarkDownBlog/ 3 | command=/path/to/MarkDownBlog/env/bin/python /path/to/MarkDownBlog/env/bin/gunicorn app:app -c /path/to/MarkDownBlog/gunicorn.py 4 | user=www-data 5 | autostart=true 6 | autorestart=true 7 | redirect_stderr=true 8 | stdout_logfile=/var/log/supervisor/markdownblog_stdout.log 9 | stderr_logfile=/var/log/supervisor/markdownblog_stderr.log 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | RUN apk add --update python3 python3-dev 4 | RUN apk add gcc libc-dev 5 | 6 | ADD ./requirements.txt /app/requirements.txt 7 | RUN pip3 install -r /app/requirements.txt 8 | RUN apk del gcc libc-dev python3-dev 9 | RUN rm -rf /var/cache/apk/* 10 | 11 | ADD . /app 12 | WORKDIR /app 13 | 14 | RUN mkdir /var/log/gunicorn/ 15 | 16 | EXPOSE 80 17 | ENTRYPOINT ["gunicorn", "app:app", "-c", "/app/gunicorn.py"] 18 | -------------------------------------------------------------------------------- /examples/config.py.example: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | BASEDIR = os.path.abspath(os.path.dirname(__file__)) 6 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASEDIR, 'database/database.db') 7 | SQLALCHEMY_MIGRATE_REPO = os.path.join(BASEDIR, 'db_repository') 8 | CSRF_ENABLED = True 9 | SECRET_KEY = 'random string here' 10 | DEBUG = True 11 | SERVER_NAME = "your hostname !" 12 | LOG_FILE = "log/yourlogfile.log" 13 | 14 | -------------------------------------------------------------------------------- /app/forms/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_wtf import Form 4 | 5 | 6 | class CustomForm(Form): 7 | 8 | def has_been_submitted(self, request): 9 | """ 10 | Returns whether it was this form or not that was submitted. 11 | 12 | :param request: The flask request of the route 13 | :return: True if form has been submitted, False otherwise 14 | """ 15 | return request.method == "POST" and request.form['btn'] == "{}btn".format(getattr(self, "_prefix")) 16 | -------------------------------------------------------------------------------- /app/templates/blog/blog_page.html: -------------------------------------------------------------------------------- 1 | {% from "helpers.html" import render_blog_post, render_blog_profile %} 2 | {% extends "blog/blog_base.html" %} 3 | 4 | {% block inner_content %} 5 |
6 |
7 |
8 | {{ render_blog_profile(blog_user, owner=owner)}} 9 |
10 |
11 | {{ render_blog_post(post, owner=owner, index=False)}} 12 |
13 |
14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = ${repr(up_revision)} 11 | down_revision = ${repr(down_revision)} 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | ${imports if imports else ""} 16 | 17 | def upgrade(): 18 | ${upgrades if upgrades else "pass"} 19 | 20 | 21 | def downgrade(): 22 | ${downgrades if downgrades else "pass"} 23 | -------------------------------------------------------------------------------- /migrations/versions/547b4a03fba_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: 547b4a03fba 4 | Revises: 9144c5cad 5 | Create Date: 2015-02-05 10:02:38.674647 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '547b4a03fba' 11 | down_revision = '9144c5cad' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | from migrations.utils import drop_column_sqlite 17 | 18 | 19 | def upgrade(): 20 | op.add_column('user', sa.Column('blog_round_image', sa.Boolean(), nullable=True)) 21 | 22 | 23 | def downgrade(): 24 | drop_column_sqlite('user', 'blog_round_image') 25 | -------------------------------------------------------------------------------- /examples/manage.py.example: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_script import Manager, Shell 4 | from flask_migrate import Migrate, MigrateCommand 5 | 6 | from app import app, db 7 | from app.models import User 8 | 9 | manager = Manager(app) 10 | 11 | migrate = Migrate(app, db) 12 | manager.add_command('db', MigrateCommand) 13 | 14 | @manager.command 15 | def create_db(): 16 | db.create_all() 17 | user = User(username='Root', password='root', active=True, superuser=True) 18 | user.save() 19 | 20 | manager.add_command('shell', Shell()) 21 | 22 | if __name__ == '__main__': 23 | manager.run() 24 | -------------------------------------------------------------------------------- /migrations/versions/9144c5cad_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: 9144c5cad 4 | Revises: None 5 | Create Date: 2015-02-05 09:53:36.355874 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '9144c5cad' 11 | down_revision = None 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | pass 20 | ### end Alembic commands ### 21 | 22 | 23 | def downgrade(): 24 | ### commands auto generated by Alembic - please adjust! ### 25 | pass 26 | ### end Alembic commands ### 27 | -------------------------------------------------------------------------------- /app/api/token.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import jsonify, g 4 | from flask_httpauth import HTTPBasicAuth 5 | 6 | from app import app, db 7 | from app.models import User 8 | 9 | auth = HTTPBasicAuth() 10 | 11 | @auth.verify_password 12 | def verify_password(username, password): 13 | user = db.session.query(User).filter_by(username=username).first() 14 | if not user or not user.check_password(password): 15 | return False 16 | g.user = user 17 | return True 18 | 19 | @app.route('/api/v1/token') 20 | @auth.login_required 21 | def get_auth_token(): 22 | token = g.user.generate_auth_token() 23 | return jsonify({'token': token.decode('ascii')}) 24 | -------------------------------------------------------------------------------- /app/api/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import g, request 4 | 5 | from flask_restless import ProcessingException 6 | 7 | from app.models import User 8 | 9 | 10 | def auth_required(data=None, **kwargs): 11 | if 'Authorization' in request.headers: 12 | token = request.headers.get('Authorization') 13 | elif data and 'token' in data: 14 | token = data.pop('token', None) 15 | else: 16 | raise ProcessingException(description="Authorization Token Required", code=401) 17 | user = User.verify_auth_token(token) 18 | if not user: 19 | raise ProcessingException(description="Invalid Authorization Token", code=401) 20 | g.user = user 21 | -------------------------------------------------------------------------------- /app/static/css/login.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 40px; 3 | padding-bottom: 40px; 4 | background-color: #eee; 5 | } 6 | 7 | .form-signin { 8 | max-width: 330px; 9 | padding: 15px; 10 | margin: 0 auto; 11 | } 12 | .form-signin .form-signin-heading, 13 | .form-signin .checkbox { 14 | margin-bottom: 10px; 15 | } 16 | .form-signin .checkbox { 17 | font-weight: normal; 18 | } 19 | .form-signin .form-control { 20 | position: relative; 21 | height: auto; 22 | -webkit-box-sizing: border-box; 23 | -moz-box-sizing: border-box; 24 | box-sizing: border-box; 25 | padding: 10px; 26 | font-size: 16px; 27 | } 28 | .form-signin .form-control:focus { 29 | z-index: 2; 30 | } 31 | -------------------------------------------------------------------------------- /app/templates/blog/blog_base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block extra_style %} 3 | 4 | {% endblock %} 5 | {% block content %} 6 | {% include "blog/blog_menu.html" %} 7 | {% block inner_content %}{% endblock %} 8 | {% endblock %} 9 | 10 | {% block extra_js_at_end %} 11 | 17 | 22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | alembic==0.8.3 2 | beautifulsoup4==4.4.1 3 | cffi==1.3.0 4 | Flask==1.0 5 | Flask-HTTPAuth==2.7.0 6 | Flask-Login==0.3.2 7 | Flask-Migrate==1.6.0 8 | Flask-Misaka==0.3.0 9 | Flask-Restless==0.17.0 10 | Flask-Script==2.0.5 11 | Flask-SQLAlchemy==2.1 12 | Flask-WTF==0.12 13 | gunicorn==19.5.0 14 | itsdangerous==0.24 15 | Jinja2==2.11.3 16 | Mako==1.2.2 17 | MarkupSafe==0.23 18 | marshmallow==2.15.1 19 | mimerender==0.5.5 20 | misaka==1.0.2 21 | mistune==2.0.3 22 | pycparser==2.14 23 | Pygments==2.7.4 24 | python-dateutil==2.4.2 25 | python-editor==0.4 26 | python-mimeparse==0.1.4 27 | python-slugify==1.1.4 28 | requests==2.20.0 29 | six==1.10.0 30 | slugify==0.0.1 31 | SQLAlchemy==1.3.0 32 | Unidecode==0.4.18 33 | Werkzeug==0.15.3 34 | wheel==0.38.1 35 | WTForms==2.0.2 36 | -------------------------------------------------------------------------------- /examples/nginx.example: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen 443 ssl; 4 | 5 | ssl_certificate /usr/local/nginx/ssl/nginx.crt; 6 | ssl_certificate_key /usr/local/nginx/ssl/nginx.key; 7 | 8 | server_name ~^www\.(?.+\.)?host\.com$; 9 | return 301 "$scheme://${user}host.com$request_uri"; 10 | } 11 | 12 | server { 13 | listen 80; 14 | listen 443 ssl; 15 | 16 | ssl_certificate /path/to/nginx.crt; 17 | ssl_certificate_key /path/to/nginx.key; 18 | 19 | server_name ~^.+\.host\.com$ host.com; 20 | 21 | location / { 22 | proxy_set_header Host $http_host; 23 | proxy_set_header X-Real-IP $remote_addr; 24 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 25 | proxy_pass http://127.0.0.1:8085; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/templates/blog/blog_index.html: -------------------------------------------------------------------------------- 1 | {% from "helpers.html" import render_blog_post, render_blog_profile, render_paginator %} 2 | {% extends "blog/blog_base.html" %} 3 | 4 | {% block inner_content %} 5 |
6 |
7 |
8 | {{ render_blog_profile(blog_user, owner=owner)}} 9 |
10 |
11 | {% for post in posts %} 12 | {{ render_blog_post(post, owner=owner) }} 13 | {% endfor %} 14 |
15 |
16 | {% if paginate %} 17 |
18 |
19 | {{ render_paginator(blog_user, current_page) }} 20 |
21 |
22 | {% endif %} 23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /app/views/explore.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from sqlalchemy import desc 4 | from flask import render_template 5 | 6 | from app import app 7 | from app.models import User, Post 8 | 9 | 10 | def explore_context(): 11 | latest_users = User.query.order_by(desc(User.register_date)).limit(10).all() 12 | posts = Post.query.join(User).filter(User.blog_public).order_by(desc(Post.pub_date)).limit(10).all() 13 | return dict(posts=posts, latest_users=latest_users) 14 | 15 | 16 | @app.route("/explore") 17 | def explore(): 18 | return render_template("blog/blog_explore.html", **explore_context()) 19 | 20 | 21 | @app.route("/all") 22 | def explore_all(): 23 | posts = Post.query.join(User).filter(User.blog_public).order_by(desc(Post.pub_date)).all() 24 | return render_template("blog/blog_explore_all.html", posts=posts) 25 | -------------------------------------------------------------------------------- /app/templates/blog/blog_explore_all.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_base.html" %} 2 | 3 | {% block inner_content %} 4 |
5 |
6 |
7 |
8 |
All the Articles
9 |
10 | 15 |
16 |
17 |
18 |
19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /migrations/versions/b0d89871be_adding_public_blog.py: -------------------------------------------------------------------------------- 1 | """adding_public_blog 2 | 3 | Revision ID: b0d89871be 4 | Revises: 3a35afe64d1 5 | Create Date: 2015-02-18 11:36:39.504306 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = 'b0d89871be' 11 | down_revision = '3a35afe64d1' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | from sqlalchemy.sql import table, column 16 | 17 | from migrations.utils import drop_column_sqlite 18 | 19 | def upgrade(): 20 | ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('blog_public', sa.Boolean(), nullable=True)) 22 | op.execute(table('user', column('blog_public')).update().values({'blog_public': op.inline_literal(True)})) 23 | ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | ### commands auto generated by Alembic - please adjust! ### 28 | drop_column_sqlite('user', 'blog_public') 29 | ### end Alembic commands ### 30 | -------------------------------------------------------------------------------- /migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [handler_console] 38 | class = StreamHandler 39 | args = (sys.stderr,) 40 | level = NOTSET 41 | formatter = generic 42 | 43 | [formatter_generic] 44 | format = %(levelname)-5.5s [%(name)s] %(message)s 45 | datefmt = %H:%M:%S 46 | -------------------------------------------------------------------------------- /app/forms/login.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms import PasswordField, SubmitField, StringField, BooleanField 4 | from wtforms.validators import DataRequired, ValidationError 5 | 6 | from app import db 7 | from app.models import User 8 | 9 | from .base import CustomForm 10 | 11 | 12 | class LoginForm(CustomForm): 13 | username = StringField('login', validators=[DataRequired()], description={'placeholder': "Username"}) 14 | password = PasswordField('password', validators=[DataRequired()], description={'placeholder': "Password"}) 15 | rememberme = BooleanField('Remember Me') 16 | submit = SubmitField('submit') 17 | 18 | def validate_username(self, field): 19 | user = db.session.query(User).filter_by(username=self.username.data).first() 20 | if user is None: 21 | raise ValidationError('Invalid username or password') 22 | if not user.check_password(self.password.data): 23 | raise ValidationError('Invalid username or password') 24 | -------------------------------------------------------------------------------- /app/views/curl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | 5 | from flask import g, jsonify, request 6 | from flask_httpauth import HTTPBasicAuth 7 | 8 | from app import app, db 9 | from app.models import User, Post 10 | 11 | auth = HTTPBasicAuth() 12 | 13 | @auth.verify_password 14 | def verify_password(username, password): 15 | user = db.session.query(User).filter_by(username=username).first() 16 | if not user or not user.check_password(password): 17 | return False 18 | g.user = user 19 | return True 20 | 21 | 22 | @app.route('/curl/post', methods=['POST']) 23 | @auth.login_required 24 | def curl_post(): 25 | try: 26 | content = request.json.get('content') 27 | title = request.json.get('title') 28 | post = Post(user=g.user, title=title, content=content, pub_date=datetime.now()) 29 | post.save() 30 | return jsonify({'data': 'Hello, %s!' % g.user.username}) 31 | except: 32 | return jsonify({'data': 'Something Went Wrong.'}) 33 | -------------------------------------------------------------------------------- /migrations/versions/12ca4cba34c_adding_truncate_posts.py: -------------------------------------------------------------------------------- 1 | """adding_truncate_posts 2 | 3 | Revision ID: 12ca4cba34c 4 | Revises: b0d89871be 5 | Create Date: 2015-03-12 09:01:20.829600 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '12ca4cba34c' 11 | down_revision = 'b0d89871be' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | from sqlalchemy.sql import table, column 16 | 17 | from migrations.utils import drop_column_sqlite 18 | 19 | def upgrade(): 20 | ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('blog_truncate_posts', sa.Boolean(), nullable=True)) 22 | op.execute(table('user', column('blog_truncate_posts')).update().values( 23 | {'blog_truncate_posts': op.inline_literal(False)}) 24 | ) 25 | ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | ### commands auto generated by Alembic - please adjust! ### 30 | drop_column_sqlite('user', 'blog_truncate_posts') 31 | ### end Alembic commands ### 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | ### Python template 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | manage.py 60 | gunicorn.py 61 | config.py 62 | database 63 | .idea/ 64 | log/ 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/views/context.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import url_for 4 | from flask_login import current_user 5 | 6 | from app import app 7 | 8 | 9 | def generate_bg_css(): 10 | repeat_mode = "no-repeat" 11 | background_cover = True 12 | backgroud_css_template = "background: url('{background_url}') {repeat_mode} center center fixed;" 13 | background_url = url_for('static', filename='img/bg.jpg') 14 | if current_user.is_authenticated: 15 | if current_user.blog_bg and current_user.blog_bg_everywhere: 16 | background_url = current_user.blog_bg 17 | if current_user.blog_bg_repeat: 18 | repeat_mode = "repeat" 19 | background_cover = False 20 | background_css = backgroud_css_template.format(background_url=background_url, repeat_mode=repeat_mode) 21 | return dict(background_css=background_css, background_cover=background_cover) 22 | 23 | 24 | @app.context_processor 25 | def inject_user(): 26 | return dict(user=current_user) 27 | 28 | 29 | @app.context_processor 30 | def inject_background_css(): 31 | return generate_bg_css() 32 | -------------------------------------------------------------------------------- /migrations/versions/3a35afe64d1_adding_paginate_and_post_slug.py: -------------------------------------------------------------------------------- 1 | """adding_paginate_and_post_slug 2 | 3 | Revision ID: 3a35afe64d1 4 | Revises: 2870937c5fa 5 | Create Date: 2015-02-17 11:52:08.612681 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '3a35afe64d1' 11 | down_revision = '2870937c5fa' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | from migrations.utils import drop_column_sqlite 17 | 18 | def upgrade(): 19 | ### commands auto generated by Alembic - please adjust! ### 20 | op.add_column('post', sa.Column('title_slug', sa.String(length=200), nullable=True)) 21 | op.add_column('user', sa.Column('blog_paginate', sa.Boolean(), nullable=True)) 22 | op.add_column('user', sa.Column('blog_paginate_by', sa.Integer(), nullable=True)) 23 | ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | ### commands auto generated by Alembic - please adjust! ### 28 | drop_column_sqlite('user', 'blog_paginate_by') 29 | drop_column_sqlite('user', 'blog_paginate') 30 | drop_column_sqlite('post', 'title_slug') 31 | ### end Alembic commands ### 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Depado 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/modules/blog/rss.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import request, url_for 4 | from sqlalchemy import desc 5 | from werkzeug.contrib.atom import AtomFeed 6 | 7 | from . import blueprint 8 | from .utils import requested_blog_user, make_external 9 | from app.models import Post 10 | 11 | 12 | @blueprint.route("/recent.atom") 13 | def rss_feed(user_slug): 14 | blog_user = requested_blog_user(user_slug) 15 | if blog_user: 16 | feed = AtomFeed( 17 | '{user} Recent Articles'.format(user=blog_user.username), 18 | feed_url=request.url, 19 | url=request.url_root 20 | ) 21 | posts = blog_user.posts.order_by(desc(Post.pub_date)).limit(15).all() 22 | for post in posts: 23 | feed.add(post.title, post.content_as_html(), 24 | content_type='html', 25 | author=post.user.username, 26 | url=make_external(url_for("blog.get", user_slug=user_slug, post_slug=post.title_slug)), 27 | updated=post.pub_date, 28 | published=post.pub_date) 29 | return feed.get_response() 30 | else: 31 | return "" 32 | -------------------------------------------------------------------------------- /app/forms/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import requests 4 | import imghdr 5 | from datetime import datetime 6 | from slugify import slugify 7 | 8 | from wtforms import ValidationError 9 | from flask_login import current_user 10 | 11 | from app.models import Post 12 | 13 | 14 | class ImageUrl(object): 15 | 16 | def __call__(self, form, field): 17 | try: 18 | r = requests.get(field.data, stream=True) 19 | if r.status_code == 200: 20 | if not imghdr.what("image", h=r.content): 21 | raise ValidationError("Not a supported image type or not an image at all") 22 | else: 23 | raise ValidationError("URL is not accessible") 24 | except: 25 | raise ValidationError("The url or the image it points to is invalid") 26 | 27 | 28 | def validate_post_title(title): 29 | """ 30 | Returns whether a title is valid or not. 31 | :param title: The title of the post 32 | :return: Boolean 33 | """ 34 | slugged = slugify("{date}-{title}".format(date=str(datetime.utcnow().date()), title=title)) 35 | return Post.query.filter_by(user=current_user, title_slug=slugged).first() is None 36 | -------------------------------------------------------------------------------- /migrations/versions/50bb91d1615_adding_more_profile_customization.py: -------------------------------------------------------------------------------- 1 | """adding_more_profile_customization 2 | 3 | Revision ID: 50bb91d1615 4 | Revises: 13234475ad5 5 | Create Date: 2015-05-11 21:04:17.237732 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '50bb91d1615' 11 | down_revision = '13234475ad5' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.add_column('user', sa.Column('github_url', sa.String(length=200), nullable=True)) 20 | op.add_column('user', sa.Column('gplus_url', sa.String(length=200), nullable=True)) 21 | op.add_column('user', sa.Column('linkedin_url', sa.String(length=200), nullable=True)) 22 | op.add_column('user', sa.Column('twitter_url', sa.String(length=200), nullable=True)) 23 | ### end Alembic commands ### 24 | 25 | 26 | def downgrade(): 27 | ### commands auto generated by Alembic - please adjust! ### 28 | op.drop_column('user', 'twitter_url') 29 | op.drop_column('user', 'linkedin_url') 30 | op.drop_column('user', 'gplus_url') 31 | op.drop_column('user', 'github_url') 32 | ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /app/templates/cover.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extra_css_resources %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
12 | {% block inner_content %}{% endblock %} 13 |
14 |
15 |
16 |

17 | Author 18 |   Project 19 |    Contact 20 |   FAQ 21 |

22 |
23 |
24 |
25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /app/api/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from marshmallow import Schema, fields 4 | 5 | from app import manager 6 | from app.models import User 7 | 8 | 9 | class UserSchema(Schema): 10 | id = fields.Integer() 11 | username = fields.String() 12 | blog_slug = fields.String() 13 | blog_image = fields.String() 14 | blog_title = fields.String() 15 | blog_description = fields.String() 16 | blog_bg = fields.String() 17 | blog_public = fields.String() 18 | 19 | def make_object(self, data): 20 | return User(api_purpose=True, **data) 21 | 22 | 23 | def user_serializer(instance): 24 | return UserSchema().dump(instance).data 25 | 26 | 27 | def user_deserializer(data): 28 | return UserSchema().load(data).data 29 | 30 | 31 | def get_many_postprocessor(result=None, search_params=None, **kw): 32 | if result: 33 | for user in result['objects']: 34 | new = user_serializer(user_deserializer(user)) 35 | user.clear() 36 | user.update(new) 37 | 38 | manager.create_api( 39 | User, 40 | methods=['GET', ], 41 | postprocessors=dict( 42 | GET_MANY=[get_many_postprocessor,], 43 | ), 44 | url_prefix="/api/v1", 45 | serializer=user_serializer, 46 | deserializer=user_deserializer 47 | ) 48 | -------------------------------------------------------------------------------- /app/forms/register.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from slugify import slugify 4 | 5 | from wtforms import PasswordField, StringField, ValidationError 6 | from wtforms.validators import DataRequired, Length, EqualTo 7 | 8 | from .base import CustomForm 9 | 10 | from app import db 11 | from app.models import User 12 | 13 | 14 | class RegisterForm(CustomForm): 15 | username = StringField( 16 | 'Username', 17 | validators=[DataRequired(), Length(min=4, max=25)], 18 | description={'placeholder': "Username"} 19 | ) 20 | password = PasswordField( 21 | 'Password', 22 | validators=[DataRequired(), Length(min=4), EqualTo('confirm', message='Passwords must match')], 23 | description={'placeholder': "Password"} 24 | ) 25 | confirm = PasswordField( 26 | 'Repeat Password', 27 | validators=[DataRequired()], 28 | description={'placeholder': "Repeat Password"} 29 | ) 30 | 31 | def validate_username(self, field): 32 | if db.session.query(User).filter_by(username=self.username.data).first() is not None: 33 | raise ValidationError("This username is already taken") 34 | if db.session.query(User).filter_by(blog_slug=slugify(self.username.data)).first() is not None: 35 | raise ValidationError("This username is already taken") 36 | -------------------------------------------------------------------------------- /migrations/versions/13234475ad5_adding_syntax_highlighter_choice.py: -------------------------------------------------------------------------------- 1 | """adding_syntax_highlighter_choice 2 | 3 | Revision ID: 13234475ad5 4 | Revises: 12ca4cba34c 5 | Create Date: 2015-03-12 10:28:43.604768 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '13234475ad5' 11 | down_revision = '12ca4cba34c' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | from sqlalchemy.sql import table, column 16 | 17 | from migrations.utils import drop_column_sqlite 18 | 19 | def upgrade(): 20 | ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('user', sa.Column('blog_syntax_highlighter_css', sa.Enum('autumn.css', 'borland.css', 'bw.css', 'colorful.css', 'default.css', 'emacs.css', 'friendly.css', 'fruity.css', 'github.css', 'manni.css', 'monokai.css', 'murphy.css', 'native.css', 'pastie.css', 'perldoc.css', 'tango.css', 'trac.css', 'vim.css', 'vs.css', 'zenburn.css'), nullable=True)) 22 | op.execute(table('user', column('blog_syntax_highlighter_css')).update().values( 23 | {'blog_syntax_highlighter_css': "monokai.css"}) 24 | ) 25 | ### end Alembic commands ### 26 | 27 | 28 | def downgrade(): 29 | ### commands auto generated by Alembic - please adjust! ### 30 | drop_column_sqlite('user', 'blog_syntax_highlighter_css') 31 | ### end Alembic commands ### 32 | -------------------------------------------------------------------------------- /app/static/css/fix.css: -------------------------------------------------------------------------------- 1 | .my-fa-facebook:before { 2 | content: "\f09a"; 3 | } 4 | .my-fa-facebook-square:before { 5 | content: "\f082"; 6 | } 7 | .my-fa-flickr:before { 8 | content: "\f16e"; 9 | } 10 | .my-fa-google-plus-square:before { 11 | content: "\f0d4"; 12 | } 13 | .my-fa-google-plus:before { 14 | content: "\f0d5"; 15 | } 16 | .my-fa-google:before { 17 | content: "\f1a0"; 18 | } 19 | .my-fa-instagram:before { 20 | content: "\f16d"; 21 | } 22 | .my-fa-linkedin:before { 23 | content: "\f0e1"; 24 | } 25 | .my-fa-linkedin-square:before { 26 | content: "\f08c"; 27 | } 28 | .my-fa-pinterest:before { 29 | content: "\f0d2"; 30 | } 31 | .my-fa-pinterest-square:before { 32 | content: "\f0d3"; 33 | } 34 | .my-fa-reddit:before { 35 | content: "\f1a1"; 36 | } 37 | .my-fa-reddit-square:before { 38 | content: "\f1a2"; 39 | } 40 | .my-fa-share-square-o:before { 41 | content: "\f045"; 42 | } 43 | .my-fa-share-alt:before { 44 | content: "\f1e0"; 45 | } 46 | .my-fa-soundcloud:before { 47 | content: "\f1be"; 48 | } 49 | .my-fa-tumblr:before { 50 | content: "\f173"; 51 | } 52 | .my-fa-tumblr-square:before { 53 | content: "\f174"; 54 | } 55 | .my-fa-twitter-square:before { 56 | content: "\f081"; 57 | } 58 | .my-fa-twitter:before { 59 | content: "\f099"; 60 | } 61 | .my-fa-youtube-square:before { 62 | content: "\f166"; 63 | } 64 | .my-fa-youtube:before { 65 | content: "\f167"; 66 | } 67 | .my-fa-youtube-play:before { 68 | content: "\f16a"; 69 | } 70 | -------------------------------------------------------------------------------- /migrations/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from alembic import op 4 | import sqlalchemy as sa 5 | 6 | 7 | def drop_column_sqlite(tablename, columns): 8 | """ column dropping functionality for SQLite """ 9 | 10 | # we need copy to make a deep copy of the column attributes 11 | from copy import copy 12 | 13 | # get the db engine and reflect database tables 14 | engine = op.get_bind() 15 | meta = sa.MetaData(bind=engine) 16 | meta.reflect() 17 | 18 | # create a select statement from the old table 19 | old_table = meta.tables[tablename] 20 | select = sa.sql.select([c for c in old_table.c if c.name not in columns]) 21 | 22 | # get remaining columns without table attribute attached 23 | remaining_columns = [copy(c) for c in old_table.columns 24 | if c.name not in columns] 25 | for column in remaining_columns: 26 | column.table = None 27 | 28 | # create a temporary new table 29 | new_tablename = '{0}_new'.format(tablename) 30 | op.create_table(new_tablename, *remaining_columns) 31 | meta.reflect() 32 | new_table = meta.tables[new_tablename] 33 | 34 | # copy data from old table 35 | insert = sa.sql.insert(new_table).from_select( 36 | [c.name for c in remaining_columns], select) 37 | engine.execute(insert) 38 | 39 | # drop the old table and rename the new table to take the old tables 40 | # position 41 | op.drop_table(tablename) 42 | op.rename_table(new_tablename, tablename) 43 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | from logging.handlers import RotatingFileHandler 5 | 6 | from werkzeug.contrib.fixers import ProxyFix 7 | from flask import Flask 8 | from flask_sqlalchemy import SQLAlchemy 9 | from flask_login import LoginManager 10 | from flask_misaka import Misaka 11 | from flask_restless import APIManager 12 | 13 | # App initialization 14 | app = Flask(__name__) 15 | app.config.from_object('config') 16 | app.wsgi_app = ProxyFix(app.wsgi_app) 17 | 18 | # Jinja2 Setup 19 | app.jinja_env.trim_blocks = True 20 | 21 | # Logging with Rotating File Setup 22 | handler = RotatingFileHandler(app.config.get('LOG_FILE'), maxBytes=10000, backupCount=5) 23 | handler.setLevel(logging.DEBUG) 24 | handler.setFormatter( 25 | logging.Formatter(fmt='%(asctime)s %(name)s[%(process)d] %(levelname)s %(message)s', datefmt='%b %d %H:%M:%S') 26 | ) 27 | app.logger.addHandler(handler) 28 | 29 | # Database Setup 30 | db = SQLAlchemy(app) 31 | 32 | # Markdown Processor Setup 33 | Misaka(app) 34 | 35 | # Login Manager Setup 36 | login_manager = LoginManager() 37 | login_manager.init_app(app) 38 | login_manager.login_view = 'index' 39 | login_manager.session_protection = 'strong' 40 | 41 | manager = APIManager(app, flask_sqlalchemy_db=db) 42 | 43 | from app import views 44 | from app import models 45 | from app import api 46 | 47 | # Blueprint Registering 48 | from app.modules import blog 49 | app.register_blueprint(blog.blueprint) 50 | 51 | from app.views.context import * 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/forms/post.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from slugify import slugify 4 | from datetime import datetime 5 | 6 | from wtforms.fields import StringField, TextAreaField 7 | from wtforms.validators import DataRequired, Length 8 | from wtforms import ValidationError 9 | 10 | from flask_login import current_user 11 | 12 | from .base import CustomForm 13 | from app.models import Post 14 | 15 | 16 | class NewPostForm(CustomForm): 17 | title = StringField('Title', validators=[DataRequired(), Length(min=5)], description={'placeholder': "Title"}) 18 | content = TextAreaField('Post', validators=[DataRequired()], description={'placeholder': "Content (MarkDown)"}) 19 | 20 | def validate_title(self, field): 21 | slugged = slugify("{date}-{title}".format(date=str(datetime.utcnow().date()), title=self.title.data)) 22 | if Post.query.filter_by(user=current_user, title_slug=slugged).first() is not None: 23 | raise ValidationError("You already posted an article with the same title today. " 24 | "(To prevent spam and inaccessible articles, you can't do that.)") 25 | 26 | 27 | class EditPostForm(CustomForm): 28 | """ 29 | The Edition form. Disable the field validation to perform it manually on the instance. 30 | """ 31 | title = StringField('Title', validators=[DataRequired(), Length(min=5)], description={'placeholder': "Title"}) 32 | content = TextAreaField('Post', validators=[DataRequired()], description={'placeholder': "Content (MarkDown)"}) 33 | -------------------------------------------------------------------------------- /migrations/versions/2870937c5fa_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: 2870937c5fa 4 | Revises: 547b4a03fba 5 | Create Date: 2015-02-06 14:21:20.794958 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '2870937c5fa' 11 | down_revision = '547b4a03fba' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | from migrations.utils import drop_column_sqlite 17 | 18 | 19 | def upgrade(): 20 | op.add_column('user', sa.Column('blog_bg', sa.String(length=200), nullable=True)) 21 | op.add_column('user', sa.Column('blog_bg_everywhere', sa.Boolean(), nullable=True)) 22 | op.add_column('user', sa.Column('blog_bg_override', sa.Boolean(), nullable=True)) 23 | op.add_column('user', sa.Column('blog_bg_public', sa.Boolean(), nullable=True)) 24 | op.add_column('user', sa.Column('blog_bg_repeat', sa.Boolean(), nullable=True)) 25 | op.add_column('user', sa.Column('blog_image_rounded', sa.Boolean(), nullable=True)) 26 | op.add_column('user', sa.Column('last_login', sa.DateTime(), nullable=True)) 27 | drop_column_sqlite('user', 'blog_round_image') 28 | 29 | 30 | def downgrade(): 31 | op.add_column('user', sa.Column('blog_round_image', sa.BOOLEAN(), nullable=True)) 32 | drop_column_sqlite('user', 'last_login') 33 | drop_column_sqlite('user', 'blog_image_rounded') 34 | drop_column_sqlite('user', 'blog_bg_repeat') 35 | drop_column_sqlite('user', 'blog_bg_public') 36 | drop_column_sqlite('user', 'blog_bg_override') 37 | drop_column_sqlite('user', 'blog_bg_everywhere') 38 | drop_column_sqlite('user', 'blog_bg') 39 | -------------------------------------------------------------------------------- /app/templates/blog/blog_menu.html: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /app/templates/faq.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_base.html" %} 2 | 3 | {% block inner_content %} 4 |
5 |
6 |
7 |

Frequently Asked Questions

8 |

I guess every website needs one. Am I wrong ?

9 |

Why did you create that platform ?

10 |

Short Answer
Because I wanted to.

11 |

12 | Long Answer
13 | For a long time, I wanted to create myself a blog. I fell in love with the markdown 14 | format and started playing around with markdown processors in Python, and creating my blog using Flask. 15 | And then I told myself "Hey, it looks pretty easy to transform your blog into a blogging platform". Obviously, I was wrong. 16 | I challenged myself, I wanted to have a working blogging platform using the markdown format and a subdomain per user in just 17 | three days. It was a success. Since then I just keep pushing new stuff to the platform along with writing articles on my blog. 18 |

19 |

That project is awesome. Can I run MarkDownBlog on my own server ?

20 |

21 | Of course you can. The code of MarkDownBlog is open-source on GitHub, and is under the MIT license. There is actually 22 | a short tutorial on how to deploy MarkDownBlog on your own server and customizing it a bit in the README.md file.
23 | Note
24 | This was not the primary goal. At all. I'll accept feature requests on GitHub or issues, but keep in mind that this code 25 | wasn't originally made to run elsewhere than on my own server. I won't fix issues for you, I won't add features unless 26 | I find them fun to add and/or useful. As the code is free, you can modify it to suit your needs. 27 |

28 |
29 |
30 |
31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /app/templates/blog/blog_explore.html: -------------------------------------------------------------------------------- 1 | {% extends "blog/blog_base.html" %} 2 | 3 | {% block inner_content %} 4 |
5 |
6 |
7 |
8 |
Recent Articles
9 |
10 | 15 |
16 | 19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 |
Recently Registered Users
27 | 39 |
40 |
41 |
42 |
43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /app/modules/blog/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import functools 4 | from urllib.parse import urljoin 5 | 6 | from flask import url_for, request, render_template 7 | from flask_login import current_user 8 | from jinja2 import utils 9 | 10 | from app.models import User 11 | 12 | 13 | def make_external(url): 14 | return urljoin(request.url_root, url) 15 | 16 | 17 | def requested_blog_user(user_slug): 18 | return User.query.filter_by(blog_slug=user_slug).first() 19 | 20 | 21 | def generate_background_css(blog_user=None): 22 | chosen = None 23 | repeat_mode = "no-repeat" 24 | background_cover = True 25 | backgroud_css_template = "background: url('{background_url}') {repeat_mode} center center fixed;" 26 | background_url = url_for('static', filename='img/bg.jpg') 27 | 28 | if blog_user: 29 | if blog_user.blog_bg and blog_user.blog_bg_public: 30 | if current_user.is_authenticated and current_user.blog_bg and current_user.blog_bg_override: 31 | chosen = current_user 32 | else: 33 | chosen = blog_user 34 | else: 35 | if current_user.is_authenticated and current_user.blog_bg and current_user.blog_bg_everywhere: 36 | chosen = current_user 37 | 38 | if chosen: 39 | background_url = chosen.blog_bg 40 | if chosen.blog_bg_repeat: 41 | repeat_mode = "repeat" 42 | background_cover = False 43 | background_css = backgroud_css_template.format(background_url=background_url, repeat_mode=repeat_mode) 44 | return dict(background_css=background_css, background_cover=background_cover) 45 | 46 | 47 | def generate_syntax_highlighter_css(blog_user=None): 48 | if blog_user: 49 | return url_for("static", filename="css/syntax/{}".format(blog_user.blog_syntax_highlighter_css)) 50 | 51 | 52 | def blog_exists(f): 53 | @functools.wraps(f) 54 | def decorated_function(*args, **kwargs): 55 | user_slug = kwargs.get('user_slug', None) 56 | if user_slug: 57 | blog_user = requested_blog_user(user_slug) 58 | if not blog_user: 59 | return render_template("blog_404.html", blog_name=utils.escape(user_slug)) 60 | return f(*args, **kwargs) 61 | return decorated_function 62 | -------------------------------------------------------------------------------- /migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | from alembic import context 3 | from sqlalchemy import engine_from_config, pool 4 | from logging.config import fileConfig 5 | 6 | # this is the Alembic Config object, which provides 7 | # access to the values within the .ini file in use. 8 | config = context.config 9 | 10 | # Interpret the config file for Python logging. 11 | # This line sets up loggers basically. 12 | fileConfig(config.config_file_name) 13 | 14 | # add your model's MetaData object here 15 | # for 'autogenerate' support 16 | # from myapp import mymodel 17 | # target_metadata = mymodel.Base.metadata 18 | from flask import current_app 19 | config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI')) 20 | target_metadata = current_app.extensions['migrate'].db.metadata 21 | 22 | # other values from the config, defined by the needs of env.py, 23 | # can be acquired: 24 | # my_important_option = config.get_main_option("my_important_option") 25 | # ... etc. 26 | 27 | def run_migrations_offline(): 28 | """Run migrations in 'offline' mode. 29 | 30 | This configures the context with just a URL 31 | and not an Engine, though an Engine is acceptable 32 | here as well. By skipping the Engine creation 33 | we don't even need a DBAPI to be available. 34 | 35 | Calls to context.execute() here emit the given string to the 36 | script output. 37 | 38 | """ 39 | url = config.get_main_option("sqlalchemy.url") 40 | context.configure(url=url) 41 | 42 | with context.begin_transaction(): 43 | context.run_migrations() 44 | 45 | def run_migrations_online(): 46 | """Run migrations in 'online' mode. 47 | 48 | In this scenario we need to create an Engine 49 | and associate a connection with the context. 50 | 51 | """ 52 | engine = engine_from_config( 53 | config.get_section(config.config_ini_section), 54 | prefix='sqlalchemy.', 55 | poolclass=pool.NullPool) 56 | 57 | connection = engine.connect() 58 | context.configure( 59 | connection=connection, 60 | target_metadata=target_metadata 61 | ) 62 | 63 | try: 64 | with context.begin_transaction(): 65 | context.run_migrations() 66 | finally: 67 | connection.close() 68 | 69 | if context.is_offline_mode(): 70 | run_migrations_offline() 71 | else: 72 | run_migrations_online() 73 | 74 | -------------------------------------------------------------------------------- /app/static/css/syntax/vs.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #008000 } /* Comment */ 3 | .code-highlight .highlight .err { border: 1px solid #FF0000 } /* Error */ 4 | .code-highlight .highlight .k { color: #0000ff } /* Keyword */ 5 | .code-highlight .highlight .cm { color: #008000 } /* Comment.Multiline */ 6 | .code-highlight .highlight .cp { color: #0000ff } /* Comment.Preproc */ 7 | .code-highlight .highlight .c1 { color: #008000 } /* Comment.Single */ 8 | .code-highlight .highlight .cs { color: #008000 } /* Comment.Special */ 9 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 10 | .code-highlight .highlight .gh { font-weight: bold } /* Generic.Heading */ 11 | .code-highlight .highlight .gp { font-weight: bold } /* Generic.Prompt */ 12 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 13 | .code-highlight .highlight .gu { font-weight: bold } /* Generic.Subheading */ 14 | .code-highlight .highlight .kc { color: #0000ff } /* Keyword.Constant */ 15 | .code-highlight .highlight .kd { color: #0000ff } /* Keyword.Declaration */ 16 | .code-highlight .highlight .kn { color: #0000ff } /* Keyword.Namespace */ 17 | .code-highlight .highlight .kp { color: #0000ff } /* Keyword.Pseudo */ 18 | .code-highlight .highlight .kr { color: #0000ff } /* Keyword.Reserved */ 19 | .code-highlight .highlight .kt { color: #2b91af } /* Keyword.Type */ 20 | .code-highlight .highlight .s { color: #a31515 } /* Literal.String */ 21 | .code-highlight .highlight .nc { color: #2b91af } /* Name.Class */ 22 | .code-highlight .highlight .ow { color: #0000ff } /* Operator.Word */ 23 | .code-highlight .highlight .sb { color: #a31515 } /* Literal.String.Backtick */ 24 | .code-highlight .highlight .sc { color: #a31515 } /* Literal.String.Char */ 25 | .code-highlight .highlight .sd { color: #a31515 } /* Literal.String.Doc */ 26 | .code-highlight .highlight .s2 { color: #a31515 } /* Literal.String.Double */ 27 | .code-highlight .highlight .se { color: #a31515 } /* Literal.String.Escape */ 28 | .code-highlight .highlight .sh { color: #a31515 } /* Literal.String.Heredoc */ 29 | .code-highlight .highlight .si { color: #a31515 } /* Literal.String.Interpol */ 30 | .code-highlight .highlight .sx { color: #a31515 } /* Literal.String.Other */ 31 | .code-highlight .highlight .sr { color: #a31515 } /* Literal.String.Regex */ 32 | .code-highlight .highlight .s1 { color: #a31515 } /* Literal.String.Single */ 33 | .code-highlight .highlight .ss { color: #a31515 } /* Literal.String.Symbol */ 34 | -------------------------------------------------------------------------------- /app/views/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | 5 | from flask import render_template, redirect, url_for, request, flash 6 | from flask_login import current_user, login_user, logout_user 7 | 8 | from app import app 9 | from app.forms import LoginForm, RegisterForm 10 | from app.models import User 11 | from app.views.explore import explore_context 12 | 13 | 14 | def has_been_submitted(form, request): 15 | return request.method == "POST" and request.form['btn'] == "{}btn".format(getattr(form, "_prefix")) 16 | 17 | 18 | @app.route("/", methods=['GET', 'POST']) 19 | def index(): 20 | """ 21 | TODO: Handle if user is connected 22 | start_div is the div displayed on page load. Useful for forms with errors 23 | """ 24 | 25 | # Uncomment to force https on that page (maybe just for user login ?) 26 | # if not app.config['DEBUG']: # and current_user.is_anonymous() 27 | # if request.scheme == "http": 28 | # return redirect(url_for("index", _scheme="https")) 29 | 30 | if current_user.is_authenticated: 31 | return render_template("blog/blog_explore.html", **explore_context()) 32 | 33 | start_div = "home-div" 34 | login_form = LoginForm(request.form, prefix="login") 35 | register_form = RegisterForm(request.form, prefix="register") 36 | 37 | if login_form.has_been_submitted(request): 38 | if login_form.validate_on_submit(): 39 | user = User.query.filter_by(username=login_form.username.data).first() 40 | login_user(user, remember=login_form.rememberme.data) 41 | user.last_login = datetime.now() 42 | user.save() 43 | flash("Your are now logged in.", category="info") 44 | return redirect(url_for('index')) 45 | else: 46 | start_div = "login-div" 47 | 48 | if register_form.has_been_submitted(request): 49 | if register_form.validate_on_submit(): 50 | new_user = User(username=register_form.username.data, password=register_form.password.data, active=True, 51 | superuser=False) 52 | new_user.save() 53 | return redirect(url_for('index')) 54 | else: 55 | start_div = "registration-div" 56 | 57 | return render_template("index.html", login_form=login_form, register_form=register_form, start_div=start_div) 58 | 59 | 60 | @app.route("/logout", methods=['GET']) 61 | def logout(): 62 | logout_user() 63 | return redirect(url_for("index")) 64 | 65 | 66 | @app.route("/faq", methods=['GET']) 67 | def faq(): 68 | return render_template("faq.html") 69 | -------------------------------------------------------------------------------- /app/api/post.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from marshmallow import Schema, fields 4 | from flask import g 5 | from flask_restless import ProcessingException 6 | 7 | from app import manager 8 | from app.models import Post 9 | 10 | from .user import UserSchema, user_serializer, user_deserializer 11 | from .utils import auth_required 12 | 13 | 14 | class PostSchema(Schema): 15 | """ 16 | The Schema representing a Post. 17 | """ 18 | id = fields.Integer() 19 | title = fields.String() 20 | content = fields.String() 21 | title_slug = fields.String() 22 | pub_date = fields.Date() 23 | user = fields.Nested(UserSchema) 24 | 25 | def make_object(self, data): 26 | return Post(**data) 27 | 28 | 29 | def post_serializer(instance): 30 | return PostSchema().dump(instance).data 31 | 32 | 33 | def post_deserializer(data): 34 | return PostSchema().load(data).data 35 | 36 | 37 | def owner_single(instance_id=None, **kw): 38 | """ 39 | Checks if the current user is the owner of the post. 40 | Raises an exception if not found or the current user isn't the user. 41 | Note that this fucntion should always be associated with the auth_required preprocessor. 42 | 43 | :param instance_id: The instance id of the post 44 | """ 45 | post = Post.query.filter_by(id=instance_id).first() 46 | if post: 47 | if post.user != g.user: 48 | raise ProcessingException(description="Not Authorized", code=401) 49 | else: 50 | raise ProcessingException(description="Not Found", code=404) 51 | 52 | 53 | def post_preprocessor(data=None, **kwargs): 54 | post = post_deserializer(data) 55 | post.user = g.user 56 | data = post_serializer(post) 57 | 58 | 59 | def get_many_postprocessor(result=None, search_params=None, **kw): 60 | if result: 61 | for post in result['objects']: 62 | post['user'] = user_serializer(user_deserializer(post['user'])) 63 | 64 | 65 | manager.create_api( 66 | Post, 67 | methods=['GET', 'POST', 'PATCH', 'PUT', 'DELETE'], 68 | preprocessors=dict( 69 | POST=[auth_required, post_preprocessor], 70 | PATCH_SINGLE=[auth_required, owner_single], 71 | PATCH_MANY=[auth_required, owner_single], 72 | PUT=[auth_required, owner_single], 73 | DELETE_SINGLE=[auth_required, owner_single], 74 | DELETE_MANY=[auth_required, owner_single] 75 | ), 76 | postprocessors=dict( 77 | GET_MANY=[get_many_postprocessor], 78 | ), 79 | url_prefix="/api/v1", 80 | collection_name="article", 81 | serializer=post_serializer, 82 | deserializer=post_deserializer 83 | ) 84 | -------------------------------------------------------------------------------- /app/models/post.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | from slugify import slugify 5 | from bs4 import BeautifulSoup 6 | 7 | from flask import url_for 8 | from flask_login import current_user 9 | 10 | from app import app, db 11 | from app.utils import markdown_renderer, ansi_renderer, renderer 12 | 13 | 14 | class Post(db.Model): 15 | id = db.Column(db.Integer, primary_key=True) 16 | title = db.Column(db.String(200)) 17 | title_slug = db.Column(db.String(200)) 18 | content = db.Column(db.UnicodeText) 19 | pub_date = db.Column(db.DateTime) 20 | 21 | user_id = db.Column(db.Integer, db.ForeignKey('user.id')) 22 | 23 | def __init__(self, **kwargs): 24 | super(Post, self).__init__(**kwargs) 25 | self.pub_date = self.pub_date if self.pub_date is not None else datetime.utcnow() 26 | self.set_title_slug() 27 | 28 | def set_title_slug(self): 29 | self.title_slug = slugify("{date}-{title}".format(date=str(self.pub_date.date()), title=self.title)) 30 | 31 | def save(self): 32 | db.session.add(self) 33 | try: 34 | db.session.commit() 35 | except Exception as e: 36 | app.logger.exception("Something went wrong while saving a post") 37 | db.session.rollback() 38 | return False 39 | return True 40 | 41 | def owner(self): 42 | return current_user == self.user 43 | 44 | def delete(self): 45 | db.session.delete(self) 46 | try: 47 | db.session.commit() 48 | except Exception as e: 49 | app.logger.exception("Something went wrong while deleting a post") 50 | db.session.rollback() 51 | return False 52 | return True 53 | 54 | def content_as_html(self, index=False): 55 | if self.user.blog_truncate_posts and index: 56 | content = markdown_renderer.render(self.content) 57 | try: 58 | truncated = content[:300 + content[300:].index(" ")] 59 | truncate_end = " [...]
Click here to read the full article".format( 60 | url=url_for('blog.get', user_slug=self.user.blog_slug, post_slug=self.title_slug) 61 | ) 62 | return BeautifulSoup(truncated + truncate_end) 63 | except Exception as e: 64 | return content 65 | else: 66 | renderer.reset_toc() 67 | content = markdown_renderer.render(self.content) 68 | return content 69 | 70 | def content_as_ansi(self): 71 | return ansi_renderer.render(self.content) 72 | -------------------------------------------------------------------------------- /app/static/css/syntax/bw.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { border: 1px solid #FF0000 } /* Error */ 4 | .code-highlight .highlight .k { font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .cm { font-style: italic } /* Comment.Multiline */ 6 | .code-highlight .highlight .c1 { font-style: italic } /* Comment.Single */ 7 | .code-highlight .highlight .cs { font-style: italic } /* Comment.Special */ 8 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 9 | .code-highlight .highlight .gh { font-weight: bold } /* Generic.Heading */ 10 | .code-highlight .highlight .gp { font-weight: bold } /* Generic.Prompt */ 11 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 12 | .code-highlight .highlight .gu { font-weight: bold } /* Generic.Subheading */ 13 | .code-highlight .highlight .kc { font-weight: bold } /* Keyword.Constant */ 14 | .code-highlight .highlight .kd { font-weight: bold } /* Keyword.Declaration */ 15 | .code-highlight .highlight .kn { font-weight: bold } /* Keyword.Namespace */ 16 | .code-highlight .highlight .kr { font-weight: bold } /* Keyword.Reserved */ 17 | .code-highlight .highlight .s { font-style: italic } /* Literal.String */ 18 | .code-highlight .highlight .nc { font-weight: bold } /* Name.Class */ 19 | .code-highlight .highlight .ni { font-weight: bold } /* Name.Entity */ 20 | .code-highlight .highlight .ne { font-weight: bold } /* Name.Exception */ 21 | .code-highlight .highlight .nn { font-weight: bold } /* Name.Namespace */ 22 | .code-highlight .highlight .nt { font-weight: bold } /* Name.Tag */ 23 | .code-highlight .highlight .ow { font-weight: bold } /* Operator.Word */ 24 | .code-highlight .highlight .sb { font-style: italic } /* Literal.String.Backtick */ 25 | .code-highlight .highlight .sc { font-style: italic } /* Literal.String.Char */ 26 | .code-highlight .highlight .sd { font-style: italic } /* Literal.String.Doc */ 27 | .code-highlight .highlight .s2 { font-style: italic } /* Literal.String.Double */ 28 | .code-highlight .highlight .se { font-weight: bold; font-style: italic } /* Literal.String.Escape */ 29 | .code-highlight .highlight .sh { font-style: italic } /* Literal.String.Heredoc */ 30 | .code-highlight .highlight .si { font-weight: bold; font-style: italic } /* Literal.String.Interpol */ 31 | .code-highlight .highlight .sx { font-style: italic } /* Literal.String.Other */ 32 | .code-highlight .highlight .sr { font-style: italic } /* Literal.String.Regex */ 33 | .code-highlight .highlight .s1 { font-style: italic } /* Literal.String.Single */ 34 | .code-highlight .highlight .ss { font-style: italic } /* Literal.String.Symbol */ 35 | -------------------------------------------------------------------------------- /app/views/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import request, flash, redirect, url_for, render_template 4 | from flask_login import login_required, current_user 5 | 6 | from app import app 7 | from app.forms import SettingForm, ChangePasswordForm 8 | 9 | 10 | @app.route("/settings", methods=['GET', 'POST']) 11 | @login_required 12 | def settings(): 13 | form = SettingForm(obj=current_user) 14 | change_pwd_form = ChangePasswordForm(prefix='pwd') 15 | 16 | if form.has_been_submitted(request): 17 | if form.validate_on_submit(): 18 | current_user.blog_title = form.blog_title.data 19 | current_user.blog_description = form.blog_description.data 20 | current_user.blog_image = form.blog_image.data 21 | current_user.blog_image_rounded = form.blog_image_rounded.data 22 | current_user.blog_bg = form.blog_bg.data 23 | current_user.blog_bg_public = form.blog_bg_public.data 24 | current_user.blog_bg_everywhere = form.blog_bg_everywhere.data 25 | current_user.blog_bg_override = form.blog_bg_override.data 26 | current_user.blog_bg_repeat = form.blog_bg_repeat.data 27 | current_user.blog_paginate = form.blog_paginate.data 28 | current_user.blog_paginate_by = form.blog_paginate_by.data 29 | current_user.blog_public = form.blog_public.data 30 | current_user.blog_truncate_posts = form.blog_truncate_posts.data 31 | current_user.blog_syntax_highlighter_css = form.blog_syntax_highlighter_css.data 32 | current_user.linkedin_url = form.linkedin_url.data 33 | current_user.gplus_url = form.gplus_url.data 34 | current_user.github_url = form.github_url.data 35 | current_user.twitter_url = form.twitter_url.data 36 | saved = current_user.save() 37 | if saved: 38 | flash("Saved your settings.") 39 | return redirect(url_for("blog.index", user_slug=current_user.blog_slug)) 40 | else: 41 | flash("Something went wrong...") 42 | 43 | elif change_pwd_form.has_been_submitted(request): 44 | if change_pwd_form.validate_on_submit(): 45 | current_user.set_password(change_pwd_form.new_password.data) 46 | saved = current_user.save() 47 | if saved: 48 | flash("Changed your password.") 49 | else: 50 | flash("Something went wrong...") 51 | return render_template("settings.html", form=form, change_pwd_form=change_pwd_form) 52 | 53 | 54 | @app.route("/settings/logs", methods=['GET']) 55 | @login_required 56 | def logs(): 57 | content = "" 58 | with open("/var/log/nginx/access.log") as fd: 59 | for line in fd: 60 | if current_user.blog_slug + ".markdownblog.com" in line and not "mypi" in line: 61 | content += line 62 | return content 63 | -------------------------------------------------------------------------------- /app/templates/new_post.html: -------------------------------------------------------------------------------- 1 | {% from "helpers.html" import render_field, render_content_field, md_help %} 2 | {% extends "blog/blog_base.html" %} 3 | 4 | {% block inner_content %} 5 |
6 |
7 | {{ md_help() }} 8 |
9 |
10 |
11 |
12 | Create a new post for your blog. 13 |
14 |
15 | {{ form.hidden_tag() }} 16 | {{ render_field(form.title) }} 17 | {{ render_content_field(form.content) }} 18 |
19 |
20 | Preview 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 46 |
47 | 63 | {% endblock %} 64 | -------------------------------------------------------------------------------- /app/static/css/syntax/zenburn.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight code,.code-highlight .highlight pre{color:#fdce93;background-color:#3f3f3f}.code-highlight .highlight .hll{background-color:#222}.code-highlight .highlight .c{color:#7f9f7f}.code-highlight .highlight .err{color:#e37170;background-color:#3d3535}.code-highlight .highlight .g{color:#7f9f7f}.code-highlight .highlight .k{color:#f0dfaf}.code-highlight .highlight .l{color:#ccc}.code-highlight .highlight .n{color:#dcdccc}.code-highlight .highlight .o{color:#f0efd0}.code-highlight .highlight .x{color:#ccc}.code-highlight .highlight .p{color:#41706f}.code-highlight .highlight .cm{color:#7f9f7f}.code-highlight .highlight .cp{color:#7f9f7f}.code-highlight .highlight .c1{color:#7f9f7f}.code-highlight .highlight .cs{color:#cd0000;font-weight:bold}.code-highlight .highlight .gd{color:#cd0000}.code-highlight .highlight .ge{color:#ccc;font-style:italic}.code-highlight .highlight .gr{color:red}.code-highlight .highlight .gh{color:#dcdccc;font-weight:bold}.code-highlight .highlight .gi{color:#00cd00}.code-highlight .highlight .go{color:gray}.code-highlight .highlight .gp{color:#dcdccc;font-weight:bold}.code-highlight .highlight .gs{color:#ccc;font-weight:bold}.code-highlight .highlight .gu{color:purple;font-weight:bold}.code-highlight .highlight .gt{color:#0040D0}.code-highlight .highlight .kc{color:#dca3a3}.code-highlight .highlight .kd{color:#ffff86}.code-highlight .highlight .kn{color:#dfaf8f;font-weight:bold}.code-highlight .highlight .kp{color:#cdcf99}.code-highlight .highlight .kr{color:#cdcd00}.code-highlight .highlight .kt{color:#00cd00}.code-highlight .highlight .ld{color:#cc9393}.code-highlight .highlight .m{color:#8cd0d3}.code-highlight .highlight .s{color:#cc9393}.code-highlight .highlight .na{color:#9ac39f}.code-highlight .highlight .nb{color:#efef8f}.code-highlight .highlight .nc{color:#efef8f}.code-highlight .highlight .no{color:#ccc}.code-highlight .highlight .nd{color:#ccc}.code-highlight .highlight .ni{color:#c28182}.code-highlight .highlight .ne{color:#c3bf9f;font-weight:bold}.code-highlight .highlight .nf{color:#efef8f}.code-highlight .highlight .nl{color:#ccc}.code-highlight .highlight .nn{color:#8fbede}.code-highlight .highlight .nx{color:#ccc}.code-highlight .highlight .py{color:#ccc}.code-highlight .highlight .nt{color:#9ac39f}.code-highlight .highlight .nv{color:#dcdccc}.code-highlight .highlight .ow{color:#f0efd0}.code-highlight .highlight .w{color:#ccc}.code-highlight .highlight .mf{color:#8cd0d3}.code-highlight .highlight .mh{color:#8cd0d3}.code-highlight .highlight .mi{color:#8cd0d3}.code-highlight .highlight .mo{color:#8cd0d3}.code-highlight .highlight .sb{color:#cc9393}.code-highlight .highlight .sc{color:#cc9393}.code-highlight .highlight .sd{color:#cc9393}.code-highlight .highlight .s2{color:#cc9393}.code-highlight .highlight .se{color:#cc9393}.code-highlight .highlight .sh{color:#cc9393}.code-highlight .highlight .si{color:#cc9393}.code-highlight .highlight .sx{color:#cc9393}.code-highlight .highlight .sr{color:#cc9393}.code-highlight .highlight .s1{color:#cc9393}.code-highlight .highlight .ss{color:#cc9393}.code-highlight .highlight .bp{color:#efef8f}.code-highlight .highlight .vc{color:#efef8f}.code-highlight .highlight .vg{color:#dcdccc}.code-highlight .highlight .vi{color:#ffffc7}.code-highlight .highlight .il{color:#8cd0d3} 2 | -------------------------------------------------------------------------------- /app/static/css/cover.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Globals 3 | */ 4 | 5 | /* Links */ 6 | a, 7 | a:focus, 8 | a:hover { 9 | color: #fff; 10 | } 11 | 12 | /* Custom default button */ 13 | .btn-default, 14 | .btn-default:hover, 15 | .btn-default:focus { 16 | color: #333; 17 | text-shadow: none; /* Prevent inheritence from `body` */ 18 | background-color: #fff; 19 | border: 1px solid #fff; 20 | } 21 | 22 | 23 | /* 24 | * Base structure 25 | */ 26 | 27 | html, 28 | body { 29 | height: 100%; 30 | } 31 | body { 32 | color: #fff; 33 | text-align: center; 34 | text-shadow: 0 1px 3px rgba(0,0,0,.5); 35 | } 36 | 37 | /* Extra markup and styles for table-esque vertical and horizontal centering */ 38 | .site-wrapper { 39 | display: table; 40 | width: 100%; 41 | height: 100%; /* For at least Firefox */ 42 | min-height: 100%; 43 | } 44 | .site-wrapper-inner { 45 | display: table-cell; 46 | vertical-align: top; 47 | } 48 | .cover-container { 49 | margin-right: auto; 50 | margin-left: auto; 51 | } 52 | 53 | /* Padding for spacing */ 54 | .inner { 55 | padding: 30px; 56 | } 57 | 58 | 59 | /* 60 | * Header 61 | */ 62 | .masthead-brand { 63 | margin-top: 10px; 64 | margin-bottom: 10px; 65 | } 66 | 67 | .masthead-nav > li { 68 | display: inline-block; 69 | } 70 | .masthead-nav > li + li { 71 | margin-left: 20px; 72 | } 73 | .masthead-nav > li > a { 74 | padding-right: 0; 75 | padding-left: 0; 76 | font-size: 16px; 77 | font-weight: bold; 78 | color: #fff; /* IE8 proofing */ 79 | color: rgba(255,255,255,.75); 80 | border-bottom: 2px solid transparent; 81 | } 82 | .masthead-nav > li > a:hover, 83 | .masthead-nav > li > a:focus { 84 | background-color: transparent; 85 | border-bottom-color: #a9a9a9; 86 | border-bottom-color: rgba(255,255,255,.25); 87 | } 88 | .masthead-nav > .active > a, 89 | .masthead-nav > .active > a:hover, 90 | .masthead-nav > .active > a:focus { 91 | color: #fff; 92 | border-bottom-color: #fff; 93 | } 94 | 95 | @media (min-width: 768px) { 96 | .masthead-brand { 97 | float: left; 98 | } 99 | .masthead-nav { 100 | float: right; 101 | } 102 | } 103 | 104 | 105 | /* 106 | * Cover 107 | */ 108 | 109 | .cover { 110 | padding: 0 20px; 111 | } 112 | .cover .btn-lg { 113 | padding: 10px 20px; 114 | font-weight: bold; 115 | } 116 | 117 | 118 | /* 119 | * Footer 120 | */ 121 | 122 | .mastfoot { 123 | color: #999; /* IE8 proofing */ 124 | color: rgba(255,255,255,.5); 125 | } 126 | 127 | 128 | /* 129 | * Affix and center 130 | */ 131 | 132 | @media (min-width: 768px) { 133 | /* Pull out the header and footer */ 134 | .masthead { 135 | position: fixed; 136 | top: 0; 137 | } 138 | .mastfoot { 139 | position: fixed; 140 | bottom: 0; 141 | } 142 | /* Start the vertical centering */ 143 | .site-wrapper-inner { 144 | vertical-align: middle; 145 | } 146 | /* Handle the widths */ 147 | .masthead, 148 | .mastfoot, 149 | .cover-container { 150 | width: 100%; /* Must be percentage or pixels for horizontal alignment */ 151 | } 152 | } 153 | 154 | @media (min-width: 992px) { 155 | .masthead, 156 | .mastfoot, 157 | .cover-container { 158 | width: 700px; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/templates/edit_post.html: -------------------------------------------------------------------------------- 1 | {% from "helpers.html" import render_field, render_content_field, md_help %} 2 | {% extends "blog/blog_base.html" %} 3 | 4 | {% block inner_content %} 5 |
6 |
7 | {{ md_help() }} 8 |
9 |
10 |
11 |
12 | Edit this post. 13 |
14 |
15 | {{ form.hidden_tag() }} 16 | {{ render_field(form.title) }} 17 | {{ render_content_field(form.content) }} 18 |
19 |
20 | Preview 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 46 |
47 | 52 | 53 | 70 | {% endblock %} 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarkDownBlog # 2 | 3 | [![Join the chat at https://gitter.im/Depado/MarkDownBlog](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Depado/MarkDownBlog?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | This repository is associated to the website [markdownblog.com](http://markdownblog.com). 6 | 7 | ## Purpose and Technologies ## 8 | **TLD;DR : A blog-platform engine which uses markdown as the main article format that you can install on your own or use the main website.** 9 | 10 | The main goal of this application was, in the first place, to allow people to easily create a markdown-based blog using the main site. It would create a subdomain for each user and each user could customize their own blog (background, pagination, truncated articles, information about the author). Now some people are asking me how to install this for their own use. This GitHub repository which was basically just used to version the project files is now an open-source project that people can fork, modify, and use. 11 | 12 | This application has been developped using the [Flask](http://flask.pocoo.org/) micro-framework. It uses several plugins such as [Flask-Login](https://flask-login.readthedocs.org/en/latest/) and [Flask-SQLAlchemy](http://pythonhosted.org/Flask-SQLAlchemy/). For more informations used during the development of this project, see the [requirements.txt](https://github.com/Depado/MarkDownBlog/blob/master/requirements.txt) file. 13 | 14 | ## Installation and usage ## 15 | ### Installing ### 16 | To install this application, you'll need a Python environment. For now the application is running in Python 3.4.3 (which is the latest stable revision at the moment). I didn't test the application to run with Python 2.7, but feel free to try and open some issues in case there is something wrong. I strongly recommend you to create a virtualenv to run this application. Installing python libraries system-wide has several cons. (Can't have two different versions, if you upgrade once, it will apply to everything on your system, you need root access, etc...). Here is how you can install the application. 17 | 18 | ```bash 19 | # First of all, clone the repo using the https url or ssh. 20 | $ git clone https://github.com/Depado/MarkDownBlog.git 21 | $ cd MarkDownBlog/ 22 | # Next command can differ, it may be virtualenv-3.4, pyvenv-3.4 or something like that. 23 | $ virtualenv env 24 | # If using fish shell, use 'activate.fish'. 25 | # If 'source' command doesn't exist, use '.' instead. 26 | $ source env/bin/activate 27 | # Install all the libs. 28 | (env)$ pip install -r requirements.txt 29 | # Create the database and log folders. 30 | # If not using SQLite, database folder isn't needed. 31 | (env)$ mkdir database log 32 | # Now you need to create the 'manage.py' file. Refer to the 'manage.py.example' file. Same with the 'config.py' file. 33 | # Modify the content of those files before saving them ! 34 | (env)$ python manage.py create_db 35 | ``` 36 | 37 | Will add further explanation later. 38 | 39 | ### Running in production ### 40 | I recommend using `Gunicorn` with `Supervisor` and `Nginx`. Gunicorn choice may be arguable but I like it because it's simple to setup and runs fine. Will add further explanation later. 41 | 42 | ### License ### 43 | See the [LICENSE.md](https://github.com/Depado/MarkDownBlog/blob/master/LICENSE.md) file for license rights and limitations (MIT). 44 | -------------------------------------------------------------------------------- /app/static/css/syntax/github.css: -------------------------------------------------------------------------------- 1 | .hll { background-color: #ffffcc } 2 | .c { color: #999988; font-style: italic } /* Comment */ 3 | .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 4 | .k { color: #000000; font-weight: bold } /* Keyword */ 5 | .o { color: #000000; font-weight: bold } /* Operator */ 6 | .cm { color: #999988; font-style: italic } /* Comment.Multiline */ 7 | .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ 8 | .c1 { color: #999988; font-style: italic } /* Comment.Single */ 9 | .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ 10 | .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 11 | .ge { color: #000000; font-style: italic } /* Generic.Emph */ 12 | .gr { color: #aa0000 } /* Generic.Error */ 13 | .gh { color: #999999 } /* Generic.Heading */ 14 | .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 15 | .go { color: #888888 } /* Generic.Output */ 16 | .gp { color: #555555 } /* Generic.Prompt */ 17 | .gs { font-weight: bold } /* Generic.Strong */ 18 | .gu { color: #aaaaaa } /* Generic.Subheading */ 19 | .gt { color: #aa0000 } /* Generic.Traceback */ 20 | .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ 21 | .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ 22 | .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ 23 | .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ 24 | .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ 25 | .kt { color: #445588; font-weight: bold } /* Keyword.Type */ 26 | .m { color: #009999 } /* Literal.Number */ 27 | .s { color: #d01040 } /* Literal.String */ 28 | .na { color: #008080 } /* Name.Attribute */ 29 | .nb { color: #0086B3 } /* Name.Builtin */ 30 | .nc { color: #445588; font-weight: bold } /* Name.Class */ 31 | .no { color: #008080 } /* Name.Constant */ 32 | .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ 33 | .ni { color: #800080 } /* Name.Entity */ 34 | .ne { color: #990000; font-weight: bold } /* Name.Exception */ 35 | .nf { color: #990000; font-weight: bold } /* Name.Function */ 36 | .nl { color: #990000; font-weight: bold } /* Name.Label */ 37 | .nn { color: #555555 } /* Name.Namespace */ 38 | .nt { color: #000080 } /* Name.Tag */ 39 | .nv { color: #008080 } /* Name.Variable */ 40 | .ow { color: #000000; font-weight: bold } /* Operator.Word */ 41 | .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .mf { color: #009999 } /* Literal.Number.Float */ 43 | .mh { color: #009999 } /* Literal.Number.Hex */ 44 | .mi { color: #009999 } /* Literal.Number.Integer */ 45 | .mo { color: #009999 } /* Literal.Number.Oct */ 46 | .sb { color: #d01040 } /* Literal.String.Backtick */ 47 | .sc { color: #d01040 } /* Literal.String.Char */ 48 | .sd { color: #d01040 } /* Literal.String.Doc */ 49 | .s2 { color: #d01040 } /* Literal.String.Double */ 50 | .se { color: #d01040 } /* Literal.String.Escape */ 51 | .sh { color: #d01040 } /* Literal.String.Heredoc */ 52 | .si { color: #d01040 } /* Literal.String.Interpol */ 53 | .sx { color: #d01040 } /* Literal.String.Other */ 54 | .sr { color: #009926 } /* Literal.String.Regex */ 55 | .s1 { color: #d01040 } /* Literal.String.Single */ 56 | .ss { color: #990073 } /* Literal.String.Symbol */ 57 | .bp { color: #999999 } /* Name.Builtin.Pseudo */ 58 | .vc { color: #008080 } /* Name.Variable.Class */ 59 | .vg { color: #008080 } /* Name.Variable.Global */ 60 | .vi { color: #008080 } /* Name.Variable.Instance */ 61 | .il { color: #009999 } /* Literal.Number.Integer.Long */ 62 | -------------------------------------------------------------------------------- /app/static/css/messenger.css: -------------------------------------------------------------------------------- 1 | /* line 4, ../../src/sass/messenger.sass */ 2 | ul.messenger { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | /* line 8, ../../src/sass/messenger.sass */ 7 | ul.messenger > li { 8 | list-style: none; 9 | margin: 0; 10 | padding: 0; 11 | } 12 | /* line 14, ../../src/sass/messenger.sass */ 13 | ul.messenger.messenger-empty { 14 | display: none; 15 | } 16 | /* line 17, ../../src/sass/messenger.sass */ 17 | ul.messenger .messenger-message { 18 | overflow: hidden; 19 | *zoom: 1; 20 | } 21 | /* line 20, ../../src/sass/messenger.sass */ 22 | ul.messenger .messenger-message.messenger-hidden { 23 | display: none; 24 | } 25 | /* line 23, ../../src/sass/messenger.sass */ 26 | ul.messenger .messenger-message .messenger-phrase, ul.messenger .messenger-message .messenger-actions a { 27 | padding-right: 5px; 28 | } 29 | /* line 26, ../../src/sass/messenger.sass */ 30 | ul.messenger .messenger-message .messenger-actions { 31 | float: right; 32 | } 33 | /* line 29, ../../src/sass/messenger.sass */ 34 | ul.messenger .messenger-message .messenger-actions a { 35 | cursor: pointer; 36 | text-decoration: underline; 37 | } 38 | /* line 33, ../../src/sass/messenger.sass */ 39 | ul.messenger .messenger-message ul, ul.messenger .messenger-message ol { 40 | margin: 10px 18px 0; 41 | } 42 | /* line 36, ../../src/sass/messenger.sass */ 43 | ul.messenger.messenger-fixed { 44 | position: fixed; 45 | z-index: 10000; 46 | } 47 | /* line 40, ../../src/sass/messenger.sass */ 48 | ul.messenger.messenger-fixed .messenger-message { 49 | min-width: 0; 50 | -webkit-box-sizing: border-box; 51 | -moz-box-sizing: border-box; 52 | box-sizing: border-box; 53 | } 54 | /* line 45, ../../src/sass/messenger.sass */ 55 | ul.messenger.messenger-fixed .message .messenger-actions { 56 | float: left; 57 | } 58 | /* line 48, ../../src/sass/messenger.sass */ 59 | ul.messenger.messenger-fixed.messenger-on-top { 60 | top: 20px; 61 | } 62 | /* line 51, ../../src/sass/messenger.sass */ 63 | ul.messenger.messenger-fixed.messenger-on-bottom { 64 | bottom: 20px; 65 | } 66 | /* line 54, ../../src/sass/messenger.sass */ 67 | ul.messenger.messenger-fixed.messenger-on-top, ul.messenger.messenger-fixed.messenger-on-bottom { 68 | left: 50%; 69 | width: 800px; 70 | margin-left: -400px; 71 | } 72 | @media (max-width: 960px) { 73 | /* line 54, ../../src/sass/messenger.sass */ 74 | ul.messenger.messenger-fixed.messenger-on-top, ul.messenger.messenger-fixed.messenger-on-bottom { 75 | left: 10%; 76 | width: 80%; 77 | margin-left: 0px; 78 | } 79 | } 80 | /* line 64, ../../src/sass/messenger.sass */ 81 | ul.messenger.messenger-fixed.messenger-on-top.messenger-on-right, ul.messenger.messenger-fixed.messenger-on-bottom.messenger-on-right { 82 | right: 20px; 83 | left: auto; 84 | } 85 | /* line 68, ../../src/sass/messenger.sass */ 86 | ul.messenger.messenger-fixed.messenger-on-top.messenger-on-left, ul.messenger.messenger-fixed.messenger-on-bottom.messenger-on-left { 87 | left: 20px; 88 | margin-left: 0px; 89 | } 90 | /* line 72, ../../src/sass/messenger.sass */ 91 | ul.messenger.messenger-fixed.messenger-on-right, ul.messenger.messenger-fixed.messenger-on-left { 92 | width: 350px; 93 | } 94 | /* line 75, ../../src/sass/messenger.sass */ 95 | ul.messenger.messenger-fixed.messenger-on-right .messenger-actions, ul.messenger.messenger-fixed.messenger-on-left .messenger-actions { 96 | float: left; 97 | } 98 | /* line 78, ../../src/sass/messenger.sass */ 99 | ul.messenger .messenger-spinner { 100 | display: none; 101 | } 102 | -------------------------------------------------------------------------------- /app/views/post.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import redirect, url_for, flash, request, render_template 4 | from flask_login import current_user, login_required 5 | 6 | from app import app 7 | from app.models import Post 8 | from app.forms import NewPostForm, EditPostForm 9 | from app.forms.utils import validate_post_title 10 | from app.utils import markdown_renderer 11 | from app.modules.blog.utils import generate_syntax_highlighter_css 12 | 13 | 14 | @app.route('/new', methods=['GET', 'POST']) 15 | @login_required 16 | def new(): 17 | form = NewPostForm(request.form, prefix="post") 18 | if form.has_been_submitted(request): 19 | if form.validate_on_submit(): 20 | new_post = Post(user=current_user, title=form.title.data, content=form.content.data) 21 | status = new_post.save() 22 | if status: 23 | flash("Successfully posted the article") 24 | return redirect(url_for('blog.index', user_slug=current_user.blog_slug)) 25 | else: 26 | flash("Something went wrong...", category="error") 27 | 28 | return render_template("new_post.html", form=form, 29 | syntax_highlighter_css=generate_syntax_highlighter_css(current_user)) 30 | 31 | 32 | @app.route('/edit/', methods=['GET', 'POST']) 33 | @login_required 34 | def edit(post_id): 35 | post = Post.query.get(post_id) 36 | if post is not None: 37 | if post.user == current_user: 38 | form = EditPostForm(obj=post) 39 | if form.has_been_submitted(request) and form.validate_on_submit(): 40 | 41 | if post.title != form.title.data and not validate_post_title(form.title.data): 42 | form.title.errors.append("You already posted an article with the same title today.") 43 | return render_template("edit_post.html", form=form) 44 | 45 | post.title = form.title.data 46 | post.content = form.content.data 47 | post.set_title_slug() 48 | saved = post.save() 49 | if saved: 50 | flash("Successfully saved the post.") 51 | return redirect(url_for("blog.get", user_slug=current_user.blog_slug, post_slug=post.title_slug)) 52 | else: 53 | flash("Something went wrong...") 54 | 55 | return render_template("edit_post.html", form=form, 56 | syntax_highlighter_css=generate_syntax_highlighter_css(current_user)) 57 | else: 58 | # The user trying to edit is not the actual owner 59 | flash("Your are not authorized to do that.") 60 | return redirect(url_for('index')) 61 | else: 62 | # The post has not been found 63 | return render_template("blog/blog_page_404.html", post_id=post_id) 64 | 65 | @app.route("/delete/") 66 | def delete(post_id): 67 | post = Post.query.get(post_id) 68 | if post is not None: 69 | if post.user == current_user: 70 | deleted = post.delete() 71 | if deleted: 72 | flash("The article has been deleted.") 73 | else: 74 | flash("Something went wrong.") 75 | return redirect(url_for('blog.index', user_slug=post.user.blog_slug)) 76 | else: 77 | flash("You don't have the permission to do that.") 78 | return redirect(url_for('blog.index', user_slug=post.user.blog_slug)) 79 | else: 80 | return render_template("blog/blog_page_404.html", post_id=post_id) 81 | 82 | 83 | @app.route("/_parse", methods=['POST']) 84 | def ajax_markdown_parser(): 85 | return markdown_renderer.render(request.json) 86 | 87 | 88 | -------------------------------------------------------------------------------- /app/static/css/syntax/borland.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #008800; font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 4 | .code-highlight .highlight .k { color: #000080; font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .cm { color: #008800; font-style: italic } /* Comment.Multiline */ 6 | .code-highlight .highlight .cp { color: #008080 } /* Comment.Preproc */ 7 | .code-highlight .highlight .c1 { color: #008800; font-style: italic } /* Comment.Single */ 8 | .code-highlight .highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */ 9 | .code-highlight .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 10 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 11 | .code-highlight .highlight .gr { color: #aa0000 } /* Generic.Error */ 12 | .code-highlight .highlight .gh { color: #999999 } /* Generic.Heading */ 13 | .code-highlight .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 14 | .code-highlight .highlight .go { color: #888888 } /* Generic.Output */ 15 | .code-highlight .highlight .gp { color: #555555 } /* Generic.Prompt */ 16 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 17 | .code-highlight .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ 18 | .code-highlight .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 19 | .code-highlight .highlight .kc { color: #000080; font-weight: bold } /* Keyword.Constant */ 20 | .code-highlight .highlight .kd { color: #000080; font-weight: bold } /* Keyword.Declaration */ 21 | .code-highlight .highlight .kn { color: #000080; font-weight: bold } /* Keyword.Namespace */ 22 | .code-highlight .highlight .kp { color: #000080; font-weight: bold } /* Keyword.Pseudo */ 23 | .code-highlight .highlight .kr { color: #000080; font-weight: bold } /* Keyword.Reserved */ 24 | .code-highlight .highlight .kt { color: #000080; font-weight: bold } /* Keyword.Type */ 25 | .code-highlight .highlight .m { color: #0000FF } /* Literal.Number */ 26 | .code-highlight .highlight .s { color: #0000FF } /* Literal.String */ 27 | .code-highlight .highlight .na { color: #FF0000 } /* Name.Attribute */ 28 | .code-highlight .highlight .nt { color: #000080; font-weight: bold } /* Name.Tag */ 29 | .code-highlight .highlight .ow { font-weight: bold } /* Operator.Word */ 30 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 31 | .code-highlight .highlight .mf { color: #0000FF } /* Literal.Number.Float */ 32 | .code-highlight .highlight .mh { color: #0000FF } /* Literal.Number.Hex */ 33 | .code-highlight .highlight .mi { color: #0000FF } /* Literal.Number.Integer */ 34 | .code-highlight .highlight .mo { color: #0000FF } /* Literal.Number.Oct */ 35 | .code-highlight .highlight .sb { color: #0000FF } /* Literal.String.Backtick */ 36 | .code-highlight .highlight .sc { color: #800080 } /* Literal.String.Char */ 37 | .code-highlight .highlight .sd { color: #0000FF } /* Literal.String.Doc */ 38 | .code-highlight .highlight .s2 { color: #0000FF } /* Literal.String.Double */ 39 | .code-highlight .highlight .se { color: #0000FF } /* Literal.String.Escape */ 40 | .code-highlight .highlight .sh { color: #0000FF } /* Literal.String.Heredoc */ 41 | .code-highlight .highlight .si { color: #0000FF } /* Literal.String.Interpol */ 42 | .code-highlight .highlight .sx { color: #0000FF } /* Literal.String.Other */ 43 | .code-highlight .highlight .sr { color: #0000FF } /* Literal.String.Regex */ 44 | .code-highlight .highlight .s1 { color: #0000FF } /* Literal.String.Single */ 45 | .code-highlight .highlight .ss { color: #0000FF } /* Literal.String.Symbol */ 46 | .code-highlight .highlight .il { color: #0000FF } /* Literal.Number.Integer.Long */ 47 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'cover.html' %} 2 | {% from "helpers.html" import render_field %} 3 | 4 | {% block extra_css_resources %} 5 | {{ super() }} 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block extrajs %} 11 | 12 | {% endblock %} 13 | 14 | {% block inner_content %} 15 |
16 |

MarkDownBlog

17 |

MarkDownBlog is a simple blogging platform. And as its name suggests it, you use markdown to write your articles. Isn't that wonderful ? Also you'll have your very own subdomain.
Using bootstrap because I have no skills in design.

18 |

19 | 20 | Register 21 |

22 |
23 |

Explore the blogs !

24 |
25 | 26 |
27 | 28 | 37 | 38 | No Account yet ? Click here to register. 39 | 40 |
41 | 42 | Or click here to go back to the homepage. 43 | 44 |
45 | 46 |
47 | 54 | 55 |
56 | Or click here to go back to the homepage. 57 |
58 | {% endblock %} 59 | 60 | {% block extra_js_at_end %} 61 | 89 | {% endblock %} 90 | -------------------------------------------------------------------------------- /app/modules/blog/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import render_template, redirect, url_for, make_response, abort 4 | from flask_login import current_user 5 | from sqlalchemy import desc 6 | 7 | from . import blueprint 8 | from .utils import requested_blog_user, generate_background_css, blog_exists, generate_syntax_highlighter_css 9 | from app.models import Post 10 | 11 | 12 | @blueprint.route("/") 13 | @blog_exists 14 | def index(user_slug): 15 | blog_user = requested_blog_user(user_slug) 16 | if blog_user.blog_paginate: 17 | posts = blog_user.get_page(0) 18 | current_page = 1 19 | return render_template("blog/blog_index.html", owner=blog_user == current_user, posts=posts, blog_user=blog_user, 20 | syntax_highlighter_css=generate_syntax_highlighter_css(blog_user), 21 | paginate=True, current_page=current_page, **generate_background_css(blog_user)) 22 | else: 23 | posts = blog_user.posts.order_by(desc(Post.pub_date)).all() 24 | return render_template("blog/blog_index.html", owner=blog_user == current_user, posts=posts, blog_user=blog_user, 25 | syntax_highlighter_css=generate_syntax_highlighter_css(blog_user), 26 | **generate_background_css(blog_user)) 27 | 28 | 29 | @blueprint.route("/") 30 | @blog_exists 31 | def page(user_slug, page): 32 | blog_user = requested_blog_user(user_slug) 33 | if not blog_user.blog_paginate or page <= 1: 34 | return redirect(url_for("blog.index", user_slug=user_slug)) 35 | else: 36 | posts = blog_user.get_page(page - 1) 37 | if posts is None: 38 | return redirect(url_for("blog.index", user_slug=user_slug)) 39 | current_page = page 40 | return render_template("blog/blog_index.html", owner=blog_user == current_user, posts=posts, blog_user=blog_user, 41 | paginate=True, current_page=current_page, 42 | syntax_highlighter_css=generate_syntax_highlighter_css(blog_user), 43 | **generate_background_css(blog_user)) 44 | 45 | 46 | @blueprint.route("/") 47 | @blog_exists 48 | def get(user_slug, post_slug): 49 | blog_user = requested_blog_user(user_slug) 50 | post = Post.query.filter_by(title_slug=post_slug).first() 51 | if post is not None: 52 | return render_template("blog/blog_page.html", post=post, owner=blog_user == current_user, blog_user=blog_user, 53 | syntax_highlighter_css=generate_syntax_highlighter_css(blog_user), 54 | **generate_background_css(blog_user)) 55 | else: 56 | return render_template("blog/blog_page_404.html") 57 | 58 | 59 | @blueprint.route("//raw") 60 | @blog_exists 61 | def get_raw(user_slug, post_slug): 62 | post = Post.query.filter_by(title_slug=post_slug).first() 63 | if post is not None: 64 | resp = make_response(post.content) 65 | resp.mimetype = 'text/plain' 66 | return resp 67 | else: 68 | abort(404) 69 | 70 | 71 | @blueprint.route("//ansi") 72 | @blog_exists 73 | def get_ansi(user_slug, post_slug): 74 | post = Post.query.filter_by(title_slug=post_slug).first() 75 | if post is not None: 76 | resp = make_response(post.content_as_ansi()) 77 | resp.mimetype = 'text/plain' 78 | resp.charset = 'utf-8' 79 | return resp 80 | else: 81 | abort(404) 82 | 83 | 84 | @blueprint.route("/all") 85 | @blog_exists 86 | def all_posts(user_slug): 87 | blog_user = requested_blog_user(user_slug) 88 | posts = blog_user.get_all_posts() 89 | return render_template("blog/blog_all_posts.html", posts=posts, owner=blog_user == current_user, 90 | blog_user=blog_user, syntax_highlighter_css=generate_background_css(blog_user), 91 | **generate_background_css(blog_user)) 92 | -------------------------------------------------------------------------------- /app/api/example.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | def get_single_preprocessor(instance_id=None, **kw): 5 | """Accepts a single argument, `instance_id`, the primary key of the 6 | instance of the model to get. 7 | 8 | """ 9 | pass 10 | 11 | 12 | def get_single_postprocessor(result=None, **kw): 13 | """Accepts a single argument, `result`, which is the dictionary 14 | representation of the requested instance of the model. 15 | 16 | """ 17 | pass 18 | 19 | 20 | def get_many_preprocessor(search_params=None, **kw): 21 | """Accepts a single argument, `search_params`, which is a dictionary 22 | containing the search parameters for the request. 23 | 24 | """ 25 | pass 26 | 27 | 28 | def get_many_postprocessor(result=None, search_params=None, **kw): 29 | """Accepts two arguments, `result`, which is the dictionary 30 | representation of the JSON response which will be returned to the 31 | client, and `search_params`, which is a dictionary containing the 32 | search parameters for the request (that produced the specified 33 | `result`). 34 | 35 | """ 36 | pass 37 | 38 | 39 | def patch_single_preprocessor(instance_id=None, data=None, **kw): 40 | """Accepts two arguments, `instance_id`, the primary key of the 41 | instance of the model to patch, and `data`, the dictionary of fields 42 | to change on the instance. 43 | 44 | """ 45 | pass 46 | 47 | 48 | def patch_single_postprocessor(result=None, **kw): 49 | """Accepts a single argument, `result`, which is the dictionary 50 | representation of the requested instance of the model. 51 | 52 | """ 53 | pass 54 | 55 | 56 | def patch_many_preprocessor(search_params=None, data=None, **kw): 57 | """Accepts two arguments: `search_params`, which is a dictionary 58 | containing the search parameters for the request, and `data`, which 59 | is a dictionary representing the fields to change on the matching 60 | instances and the values to which they will be set. 61 | 62 | """ 63 | pass 64 | 65 | 66 | def patch_many_postprocessor(query=None, data=None, search_params=None, 67 | **kw): 68 | """Accepts three arguments: `query`, which is the SQLAlchemy query 69 | which was inferred from the search parameters in the query string, 70 | `data`, which is the dictionary representation of the JSON response 71 | which will be returned to the client, and `search_params`, which is a 72 | dictionary containing the search parameters for the request. 73 | 74 | """ 75 | pass 76 | 77 | 78 | def post_preprocessor(data=None, **kw): 79 | """Accepts a single argument, `data`, which is the dictionary of 80 | fields to set on the new instance of the model. 81 | 82 | """ 83 | pass 84 | 85 | 86 | def post_postprocessor(result=None, **kw): 87 | """Accepts a single argument, `result`, which is the dictionary 88 | representation of the created instance of the model. 89 | 90 | """ 91 | pass 92 | 93 | 94 | def delete_single_preprocessor(instance_id=None, **kw): 95 | """Accepts a single argument, `instance_id`, which is the primary key 96 | of the instance which will be deleted. 97 | 98 | """ 99 | pass 100 | 101 | 102 | def delete_postprocessor(was_deleted=None, **kw): 103 | """Accepts a single argument, `was_deleted`, which represents whether 104 | the instance has been deleted. 105 | 106 | """ 107 | pass 108 | 109 | 110 | def delete_many_preprocessor(search_params=None, **kw): 111 | """Accepts a single argument, `search_params`, which is a dictionary 112 | containing the search parameters for the request. 113 | 114 | """ 115 | pass 116 | 117 | 118 | def delete_many_postprocessor(result=None, search_params=None, **kw): 119 | """Accepts two arguments: `result`, which is the dictionary 120 | representation of which is the dictionary representation of the JSON 121 | response which will be returned to the client, and `search_params`, 122 | which is a dictionary containing the search parameters for the 123 | request. 124 | 125 | """ 126 | pass 127 | 128 | -------------------------------------------------------------------------------- /app/templates/settings.html: -------------------------------------------------------------------------------- 1 | {% from "helpers.html" import render_field %} 2 | {% extends "blog/blog_base.html" %} 3 | 4 | {% block extra_css_resources %} 5 | 6 | {% endblock %} 7 | 8 | {% block extrajs %} 9 | 10 | {% endblock %} 11 | 12 | {% block inner_content %} 13 |
14 |
15 |
16 |
17 |
18 |
19 | Settings 20 |
21 |
22 | 23 | Logs 24 | Help 25 | 26 | {{ form.hidden_tag() }} 27 |

Blog Description

28 | {{ render_field(form.blog_title) }} 29 | {{ render_field(form.blog_description) }} 30 |
31 |

Blog Image Settings

32 | {{ render_field(form.blog_image) }} 33 | {{ render_field(form.blog_image_rounded, toggle=True) }} 34 |
35 |

Blog Background Settings

36 | {{ render_field(form.blog_bg) }} 37 | {{ render_field(form.blog_bg_public, toggle=True) }} 38 | {{ render_field(form.blog_bg_repeat, toggle=True) }} 39 | {{ render_field(form.blog_bg_everywhere, toggle=True) }} 40 | {{ render_field(form.blog_bg_override, toggle=True) }} 41 |
42 |

Blog Global Settings

43 | {{ render_field(form.blog_paginate, toggle=True) }} 44 | {{ render_field(form.blog_paginate_by) }} 45 | {{ render_field(form.blog_public, toggle=True) }} 46 | {{ render_field(form.blog_truncate_posts, toggle=True) }} 47 | {{ render_field(form.blog_syntax_highlighter_css) }} 48 |
49 |

Social

50 | {{ render_field(form.linkedin_url) }} 51 | {{ render_field(form.gplus_url) }} 52 | {{ render_field(form.github_url) }} 53 | {{ render_field(form.twitter_url) }} 54 | 55 |
56 |
57 |
58 |
59 |
60 |
61 | Change Password 62 |
63 |
64 | {{ change_pwd_form.hidden_tag() }} 65 | {{ render_field(change_pwd_form.old_password) }} 66 | {{ render_field(change_pwd_form.new_password) }} 67 | {{ render_field(change_pwd_form.repeat_new_password) }} 68 | 69 |
70 |
71 |
72 |
73 |
74 | Other Informations 75 |
76 |
77 |

RSS Feed

78 |

Here is your personnal RSS Feed url.

79 | {{ url_for("blog.rss_feed", user_slug=current_user.blog_slug) }} 80 |
81 |
82 |
83 |
84 |
85 | {% endblock %} 86 | -------------------------------------------------------------------------------- /app/static/css/syntax/autumn.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #aaaaaa; font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */ 4 | .code-highlight .highlight .k { color: #0000aa } /* Keyword */ 5 | .code-highlight .highlight .cm { color: #aaaaaa; font-style: italic } /* Comment.Multiline */ 6 | .code-highlight .highlight .cp { color: #4c8317 } /* Comment.Preproc */ 7 | .code-highlight .highlight .c1 { color: #aaaaaa; font-style: italic } /* Comment.Single */ 8 | .code-highlight .highlight .cs { color: #0000aa; font-style: italic } /* Comment.Special */ 9 | .code-highlight .highlight .gd { color: #aa0000 } /* Generic.Deleted */ 10 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 11 | .code-highlight .highlight .gr { color: #aa0000 } /* Generic.Error */ 12 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 13 | .code-highlight .highlight .gi { color: #00aa00 } /* Generic.Inserted */ 14 | .code-highlight .highlight .go { color: #888888 } /* Generic.Output */ 15 | .code-highlight .highlight .gp { color: #555555 } /* Generic.Prompt */ 16 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 17 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 18 | .code-highlight .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 19 | .code-highlight .highlight .kc { color: #0000aa } /* Keyword.Constant */ 20 | .code-highlight .highlight .kd { color: #0000aa } /* Keyword.Declaration */ 21 | .code-highlight .highlight .kn { color: #0000aa } /* Keyword.Namespace */ 22 | .code-highlight .highlight .kp { color: #0000aa } /* Keyword.Pseudo */ 23 | .code-highlight .highlight .kr { color: #0000aa } /* Keyword.Reserved */ 24 | .code-highlight .highlight .kt { color: #00aaaa } /* Keyword.Type */ 25 | .code-highlight .highlight .m { color: #009999 } /* Literal.Number */ 26 | .code-highlight .highlight .s { color: #aa5500 } /* Literal.String */ 27 | .code-highlight .highlight .na { color: #1e90ff } /* Name.Attribute */ 28 | .code-highlight .highlight .nb { color: #00aaaa } /* Name.Builtin */ 29 | .code-highlight .highlight .nc { color: #00aa00; text-decoration: underline } /* Name.Class */ 30 | .code-highlight .highlight .no { color: #aa0000 } /* Name.Constant */ 31 | .code-highlight .highlight .nd { color: #888888 } /* Name.Decorator */ 32 | .code-highlight .highlight .ni { color: #800000; font-weight: bold } /* Name.Entity */ 33 | .code-highlight .highlight .nf { color: #00aa00 } /* Name.Function */ 34 | .code-highlight .highlight .nn { color: #00aaaa; text-decoration: underline } /* Name.Namespace */ 35 | .code-highlight .highlight .nt { color: #1e90ff; font-weight: bold } /* Name.Tag */ 36 | .code-highlight .highlight .nv { color: #aa0000 } /* Name.Variable */ 37 | .code-highlight .highlight .ow { color: #0000aa } /* Operator.Word */ 38 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 39 | .code-highlight .highlight .mf { color: #009999 } /* Literal.Number.Float */ 40 | .code-highlight .highlight .mh { color: #009999 } /* Literal.Number.Hex */ 41 | .code-highlight .highlight .mi { color: #009999 } /* Literal.Number.Integer */ 42 | .code-highlight .highlight .mo { color: #009999 } /* Literal.Number.Oct */ 43 | .code-highlight .highlight .sb { color: #aa5500 } /* Literal.String.Backtick */ 44 | .code-highlight .highlight .sc { color: #aa5500 } /* Literal.String.Char */ 45 | .code-highlight .highlight .sd { color: #aa5500 } /* Literal.String.Doc */ 46 | .code-highlight .highlight .s2 { color: #aa5500 } /* Literal.String.Double */ 47 | .code-highlight .highlight .se { color: #aa5500 } /* Literal.String.Escape */ 48 | .code-highlight .highlight .sh { color: #aa5500 } /* Literal.String.Heredoc */ 49 | .code-highlight .highlight .si { color: #aa5500 } /* Literal.String.Interpol */ 50 | .code-highlight .highlight .sx { color: #aa5500 } /* Literal.String.Other */ 51 | .code-highlight .highlight .sr { color: #009999 } /* Literal.String.Regex */ 52 | .code-highlight .highlight .s1 { color: #aa5500 } /* Literal.String.Single */ 53 | .code-highlight .highlight .ss { color: #0000aa } /* Literal.String.Symbol */ 54 | .code-highlight .highlight .bp { color: #00aaaa } /* Name.Builtin.Pseudo */ 55 | .code-highlight .highlight .vc { color: #aa0000 } /* Name.Variable.Class */ 56 | .code-highlight .highlight .vg { color: #aa0000 } /* Name.Variable.Global */ 57 | .code-highlight .highlight .vi { color: #aa0000 } /* Name.Variable.Instance */ 58 | .code-highlight .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ 59 | -------------------------------------------------------------------------------- /app/static/css/syntax/perldoc.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #228B22 } /* Comment */ 3 | .code-highlight .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 4 | .code-highlight .highlight .k { color: #8B008B; font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .cm { color: #228B22 } /* Comment.Multiline */ 6 | .code-highlight .highlight .cp { color: #1e889b } /* Comment.Preproc */ 7 | .code-highlight .highlight .c1 { color: #228B22 } /* Comment.Single */ 8 | .code-highlight .highlight .cs { color: #8B008B; font-weight: bold } /* Comment.Special */ 9 | .code-highlight .highlight .gd { color: #aa0000 } /* Generic.Deleted */ 10 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 11 | .code-highlight .highlight .gr { color: #aa0000 } /* Generic.Error */ 12 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 13 | .code-highlight .highlight .gi { color: #00aa00 } /* Generic.Inserted */ 14 | .code-highlight .highlight .go { color: #888888 } /* Generic.Output */ 15 | .code-highlight .highlight .gp { color: #555555 } /* Generic.Prompt */ 16 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 17 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 18 | .code-highlight .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 19 | .code-highlight .highlight .kc { color: #8B008B; font-weight: bold } /* Keyword.Constant */ 20 | .code-highlight .highlight .kd { color: #8B008B; font-weight: bold } /* Keyword.Declaration */ 21 | .code-highlight .highlight .kn { color: #8B008B; font-weight: bold } /* Keyword.Namespace */ 22 | .code-highlight .highlight .kp { color: #8B008B; font-weight: bold } /* Keyword.Pseudo */ 23 | .code-highlight .highlight .kr { color: #8B008B; font-weight: bold } /* Keyword.Reserved */ 24 | .code-highlight .highlight .kt { color: #a7a7a7; font-weight: bold } /* Keyword.Type */ 25 | .code-highlight .highlight .m { color: #B452CD } /* Literal.Number */ 26 | .code-highlight .highlight .s { color: #CD5555 } /* Literal.String */ 27 | .code-highlight .highlight .na { color: #658b00 } /* Name.Attribute */ 28 | .code-highlight .highlight .nb { color: #658b00 } /* Name.Builtin */ 29 | .code-highlight .highlight .nc { color: #008b45; font-weight: bold } /* Name.Class */ 30 | .code-highlight .highlight .no { color: #00688B } /* Name.Constant */ 31 | .code-highlight .highlight .nd { color: #707a7c } /* Name.Decorator */ 32 | .code-highlight .highlight .ne { color: #008b45; font-weight: bold } /* Name.Exception */ 33 | .code-highlight .highlight .nf { color: #008b45 } /* Name.Function */ 34 | .code-highlight .highlight .nn { color: #008b45; text-decoration: underline } /* Name.Namespace */ 35 | .code-highlight .highlight .nt { color: #8B008B; font-weight: bold } /* Name.Tag */ 36 | .code-highlight .highlight .nv { color: #00688B } /* Name.Variable */ 37 | .code-highlight .highlight .ow { color: #8B008B } /* Operator.Word */ 38 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 39 | .code-highlight .highlight .mf { color: #B452CD } /* Literal.Number.Float */ 40 | .code-highlight .highlight .mh { color: #B452CD } /* Literal.Number.Hex */ 41 | .code-highlight .highlight .mi { color: #B452CD } /* Literal.Number.Integer */ 42 | .code-highlight .highlight .mo { color: #B452CD } /* Literal.Number.Oct */ 43 | .code-highlight .highlight .sb { color: #CD5555 } /* Literal.String.Backtick */ 44 | .code-highlight .highlight .sc { color: #CD5555 } /* Literal.String.Char */ 45 | .code-highlight .highlight .sd { color: #CD5555 } /* Literal.String.Doc */ 46 | .code-highlight .highlight .s2 { color: #CD5555 } /* Literal.String.Double */ 47 | .code-highlight .highlight .se { color: #CD5555 } /* Literal.String.Escape */ 48 | .code-highlight .highlight .sh { color: #1c7e71; font-style: italic } /* Literal.String.Heredoc */ 49 | .code-highlight .highlight .si { color: #CD5555 } /* Literal.String.Interpol */ 50 | .code-highlight .highlight .sx { color: #cb6c20 } /* Literal.String.Other */ 51 | .code-highlight .highlight .sr { color: #1c7e71 } /* Literal.String.Regex */ 52 | .code-highlight .highlight .s1 { color: #CD5555 } /* Literal.String.Single */ 53 | .code-highlight .highlight .ss { color: #CD5555 } /* Literal.String.Symbol */ 54 | .code-highlight .highlight .bp { color: #658b00 } /* Name.Builtin.Pseudo */ 55 | .code-highlight .highlight .vc { color: #00688B } /* Name.Variable.Class */ 56 | .code-highlight .highlight .vg { color: #00688B } /* Name.Variable.Global */ 57 | .code-highlight .highlight .vi { color: #00688B } /* Name.Variable.Instance */ 58 | .code-highlight .highlight .il { color: #B452CD } /* Literal.Number.Integer.Long */ 59 | -------------------------------------------------------------------------------- /app/static/css/syntax/trac.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #999988; font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 4 | .code-highlight .highlight .k { font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .o { font-weight: bold } /* Operator */ 6 | .code-highlight .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ 7 | .code-highlight .highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ 8 | .code-highlight .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ 9 | .code-highlight .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ 10 | .code-highlight .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 11 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 12 | .code-highlight .highlight .gr { color: #aa0000 } /* Generic.Error */ 13 | .code-highlight .highlight .gh { color: #999999 } /* Generic.Heading */ 14 | .code-highlight .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 15 | .code-highlight .highlight .go { color: #888888 } /* Generic.Output */ 16 | .code-highlight .highlight .gp { color: #555555 } /* Generic.Prompt */ 17 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 18 | .code-highlight .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ 19 | .code-highlight .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 20 | .code-highlight .highlight .kc { font-weight: bold } /* Keyword.Constant */ 21 | .code-highlight .highlight .kd { font-weight: bold } /* Keyword.Declaration */ 22 | .code-highlight .highlight .kn { font-weight: bold } /* Keyword.Namespace */ 23 | .code-highlight .highlight .kp { font-weight: bold } /* Keyword.Pseudo */ 24 | .code-highlight .highlight .kr { font-weight: bold } /* Keyword.Reserved */ 25 | .code-highlight .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ 26 | .code-highlight .highlight .m { color: #009999 } /* Literal.Number */ 27 | .code-highlight .highlight .s { color: #bb8844 } /* Literal.String */ 28 | .code-highlight .highlight .na { color: #008080 } /* Name.Attribute */ 29 | .code-highlight .highlight .nb { color: #999999 } /* Name.Builtin */ 30 | .code-highlight .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ 31 | .code-highlight .highlight .no { color: #008080 } /* Name.Constant */ 32 | .code-highlight .highlight .ni { color: #800080 } /* Name.Entity */ 33 | .code-highlight .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ 34 | .code-highlight .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ 35 | .code-highlight .highlight .nn { color: #555555 } /* Name.Namespace */ 36 | .code-highlight .highlight .nt { color: #000080 } /* Name.Tag */ 37 | .code-highlight .highlight .nv { color: #008080 } /* Name.Variable */ 38 | .code-highlight .highlight .ow { font-weight: bold } /* Operator.Word */ 39 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 40 | .code-highlight .highlight .mf { color: #009999 } /* Literal.Number.Float */ 41 | .code-highlight .highlight .mh { color: #009999 } /* Literal.Number.Hex */ 42 | .code-highlight .highlight .mi { color: #009999 } /* Literal.Number.Integer */ 43 | .code-highlight .highlight .mo { color: #009999 } /* Literal.Number.Oct */ 44 | .code-highlight .highlight .sb { color: #bb8844 } /* Literal.String.Backtick */ 45 | .code-highlight .highlight .sc { color: #bb8844 } /* Literal.String.Char */ 46 | .code-highlight .highlight .sd { color: #bb8844 } /* Literal.String.Doc */ 47 | .code-highlight .highlight .s2 { color: #bb8844 } /* Literal.String.Double */ 48 | .code-highlight .highlight .se { color: #bb8844 } /* Literal.String.Escape */ 49 | .code-highlight .highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */ 50 | .code-highlight .highlight .si { color: #bb8844 } /* Literal.String.Interpol */ 51 | .code-highlight .highlight .sx { color: #bb8844 } /* Literal.String.Other */ 52 | .code-highlight .highlight .sr { color: #808000 } /* Literal.String.Regex */ 53 | .code-highlight .highlight .s1 { color: #bb8844 } /* Literal.String.Single */ 54 | .code-highlight .highlight .ss { color: #bb8844 } /* Literal.String.Symbol */ 55 | .code-highlight .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ 56 | .code-highlight .highlight .vc { color: #008080 } /* Name.Variable.Class */ 57 | .code-highlight .highlight .vg { color: #008080 } /* Name.Variable.Global */ 58 | .code-highlight .highlight .vi { color: #008080 } /* Name.Variable.Instance */ 59 | .code-highlight .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ 60 | -------------------------------------------------------------------------------- /app/static/css/syntax/monokai.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight pre { background-color: #49483e } 2 | .code-highlight .highlight .c { color: #75715e } /* Comment */ 3 | .code-highlight .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ 4 | .code-highlight .highlight .k { color: #66d9ef } /* Keyword */ 5 | .code-highlight .highlight .l { color: #ae81ff } /* Literal */ 6 | .code-highlight .highlight .n { color: #f8f8f2 } /* Name */ 7 | .code-highlight .highlight .o { color: #f92672 } /* Operator */ 8 | .code-highlight .highlight .p { color: #f8f8f2 } /* Punctuation */ 9 | .code-highlight .highlight .cm { color: #75715e } /* Comment.Multiline */ 10 | .code-highlight .highlight .cp { color: #75715e } /* Comment.Preproc */ 11 | .code-highlight .highlight .c1 { color: #75715e } /* Comment.Single */ 12 | .code-highlight .highlight .cs { color: #75715e } /* Comment.Special */ 13 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 14 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 15 | .code-highlight .highlight .kc { color: #66d9ef } /* Keyword.Constant */ 16 | .code-highlight .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ 17 | .code-highlight .highlight .kn { color: #f92672 } /* Keyword.Namespace */ 18 | .code-highlight .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ 19 | .code-highlight .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ 20 | .code-highlight .highlight .kt { color: #66d9ef } /* Keyword.Type */ 21 | .code-highlight .highlight .ld { color: #e6db74 } /* Literal.Date */ 22 | .code-highlight .highlight .m { color: #ae81ff } /* Literal.Number */ 23 | .code-highlight .highlight .s { color: #e6db74 } /* Literal.String */ 24 | .code-highlight .highlight .na { color: #a6e22e } /* Name.Attribute */ 25 | .code-highlight .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ 26 | .code-highlight .highlight .nc { color: #a6e22e } /* Name.Class */ 27 | .code-highlight .highlight .no { color: #66d9ef } /* Name.Constant */ 28 | .code-highlight .highlight .nd { color: #a6e22e } /* Name.Decorator */ 29 | .code-highlight .highlight .ni { color: #f8f8f2 } /* Name.Entity */ 30 | .code-highlight .highlight .ne { color: #a6e22e } /* Name.Exception */ 31 | .code-highlight .highlight .nf { color: #a6e22e } /* Name.Function */ 32 | .code-highlight .highlight .nl { color: #f8f8f2 } /* Name.Label */ 33 | .code-highlight .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ 34 | .code-highlight .highlight .nx { color: #a6e22e } /* Name.Other */ 35 | .code-highlight .highlight .py { color: #f8f8f2 } /* Name.Property */ 36 | .code-highlight .highlight .nt { color: #f92672 } /* Name.Tag */ 37 | .code-highlight .highlight .nv { color: #f8f8f2 } /* Name.Variable */ 38 | .code-highlight .highlight .ow { color: #f92672 } /* Operator.Word */ 39 | .code-highlight .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ 40 | .code-highlight .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ 41 | .code-highlight .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ 42 | .code-highlight .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ 43 | .code-highlight .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ 44 | .code-highlight .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ 45 | .code-highlight .highlight .sc { color: #e6db74 } /* Literal.String.Char */ 46 | .code-highlight .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ 47 | .code-highlight .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ 48 | .code-highlight .highlight .se { color: #ae81ff } /* Literal.String.Escape */ 49 | .code-highlight .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ 50 | .code-highlight .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ 51 | .code-highlight .highlight .sx { color: #e6db74 } /* Literal.String.Other */ 52 | .code-highlight .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ 53 | .code-highlight .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ 54 | .code-highlight .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ 55 | .code-highlight .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ 56 | .code-highlight .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ 57 | .code-highlight .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ 58 | .code-highlight .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ 59 | .code-highlight .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ 60 | 61 | .code-highlight .highlight .gh { } /* Generic Heading & Diff Header */ 62 | .code-highlight .highlight .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ 63 | .code-highlight .highlight .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ 64 | .code-highlight .highlight .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ 65 | -------------------------------------------------------------------------------- /app/static/css/messenger-theme-ice.css: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Raleway:400"); 2 | /* line 12, ../../src/sass/messenger-theme-ice.sass */ 3 | ul.messenger-theme-ice { 4 | -moz-user-select: none; 5 | -webkit-user-select: none; 6 | -o-user-select: none; 7 | user-select: none; 8 | font-family: "Raleway", sans-serif; 9 | } 10 | /* line 16, ../../src/sass/messenger-theme-ice.sass */ 11 | ul.messenger-theme-ice .messenger-message { 12 | -webkit-border-radius: 5px; 13 | -moz-border-radius: 5px; 14 | -ms-border-radius: 5px; 15 | -o-border-radius: 5px; 16 | border-radius: 5px; 17 | -webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.14), 0 4px #aaaaaa, 0 5px rgba(0, 0, 0, 0.05); 18 | -moz-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.14), 0 4px #aaaaaa, 0 5px rgba(0, 0, 0, 0.05); 19 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.14), 0 4px #aaaaaa, 0 5px rgba(0, 0, 0, 0.05); 20 | border: 0px; 21 | background-color: #f6f6f6; 22 | position: relative; 23 | margin-bottom: 1.5em; 24 | font-size: 13px; 25 | color: #666666; 26 | font-weight: 500; 27 | padding: 12px 22px; 28 | } 29 | /* line 28, ../../src/sass/messenger-theme-ice.sass */ 30 | ul.messenger-theme-ice .messenger-message .messenger-close { 31 | position: absolute; 32 | top: 0px; 33 | right: 0px; 34 | color: #888888; 35 | opacity: 1; 36 | font-weight: bold; 37 | display: block; 38 | font-size: 20px; 39 | line-height: 20px; 40 | padding: 8px 10px 7px 7px; 41 | cursor: pointer; 42 | background: transparent; 43 | border: 0; 44 | -webkit-appearance: none; 45 | } 46 | /* line 44, ../../src/sass/messenger-theme-ice.sass */ 47 | ul.messenger-theme-ice .messenger-message .messenger-close:hover { 48 | color: #444444; 49 | } 50 | /* line 47, ../../src/sass/messenger-theme-ice.sass */ 51 | ul.messenger-theme-ice .messenger-message .messenger-close:active { 52 | color: #222222; 53 | } 54 | /* line 50, ../../src/sass/messenger-theme-ice.sass */ 55 | ul.messenger-theme-ice .messenger-message .messenger-actions { 56 | float: none; 57 | margin-top: 10px; 58 | } 59 | /* line 54, ../../src/sass/messenger-theme-ice.sass */ 60 | ul.messenger-theme-ice .messenger-message .messenger-actions a { 61 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 62 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 63 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.05); 64 | -webkit-border-radius: 4px; 65 | -moz-border-radius: 4px; 66 | -ms-border-radius: 4px; 67 | -o-border-radius: 4px; 68 | border-radius: 4px; 69 | position: relative; 70 | text-decoration: none; 71 | display: inline-block; 72 | padding: 10px; 73 | color: #888888; 74 | margin-right: 10px; 75 | padding: 3px 10px 5px; 76 | text-transform: capitalize; 77 | } 78 | /* line 66, ../../src/sass/messenger-theme-ice.sass */ 79 | ul.messenger-theme-ice .messenger-message .messenger-actions a:hover, ul.messenger-theme-ice .messenger-message .messenger-actions a:active { 80 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 2px #aaaaaa; 81 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 2px #aaaaaa; 82 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 2px #aaaaaa; 83 | color: #444444; 84 | } 85 | /* line 70, ../../src/sass/messenger-theme-ice.sass */ 86 | ul.messenger-theme-ice .messenger-message .messenger-actions a:active { 87 | -webkit-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 1px #aaaaaa; 88 | -moz-box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 1px #aaaaaa; 89 | box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1), inset 0px 1px rgba(255, 255, 255, 0.15), 0 1px #aaaaaa; 90 | top: 1px; 91 | } 92 | /* line 74, ../../src/sass/messenger-theme-ice.sass */ 93 | ul.messenger-theme-ice .messenger-message .messenger-actions .messenger-phrase { 94 | display: none; 95 | } 96 | /* line 77, ../../src/sass/messenger-theme-ice.sass */ 97 | ul.messenger-theme-ice .messenger-message .messenger-message-inner:before { 98 | display: block; 99 | z-index: 20; 100 | font-weight: bold; 101 | margin-bottom: 2px; 102 | } 103 | /* line 84, ../../src/sass/messenger-theme-ice.sass */ 104 | ul.messenger-theme-ice .messenger-message.alert-success .messenger-message-inner:before { 105 | content: "Success"; 106 | } 107 | /* line 88, ../../src/sass/messenger-theme-ice.sass */ 108 | ul.messenger-theme-ice .messenger-message.alert-error .messenger-message-inner:before { 109 | content: "Error"; 110 | } 111 | /* line 92, ../../src/sass/messenger-theme-ice.sass */ 112 | ul.messenger-theme-ice .messenger-message.alert-info .messenger-message-inner:before { 113 | content: "Information"; 114 | } 115 | -------------------------------------------------------------------------------- /app/static/css/syntax/emacs.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #008800; font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { border: 1px solid #FF0000 } /* Error */ 4 | .code-highlight .highlight .k { color: #AA22FF; font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .o { color: #666666 } /* Operator */ 6 | .code-highlight .highlight .cm { color: #008800; font-style: italic } /* Comment.Multiline */ 7 | .code-highlight .highlight .cp { color: #008800 } /* Comment.Preproc */ 8 | .code-highlight .highlight .c1 { color: #008800; font-style: italic } /* Comment.Single */ 9 | .code-highlight .highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */ 10 | .code-highlight .highlight .gd { color: #A00000 } /* Generic.Deleted */ 11 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 12 | .code-highlight .highlight .gr { color: #FF0000 } /* Generic.Error */ 13 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 14 | .code-highlight .highlight .gi { color: #00A000 } /* Generic.Inserted */ 15 | .code-highlight .highlight .go { color: #808080 } /* Generic.Output */ 16 | .code-highlight .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 17 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 18 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 19 | .code-highlight .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 20 | .code-highlight .highlight .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */ 21 | .code-highlight .highlight .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */ 22 | .code-highlight .highlight .kn { color: #AA22FF; font-weight: bold } /* Keyword.Namespace */ 23 | .code-highlight .highlight .kp { color: #AA22FF } /* Keyword.Pseudo */ 24 | .code-highlight .highlight .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */ 25 | .code-highlight .highlight .kt { color: #00BB00; font-weight: bold } /* Keyword.Type */ 26 | .code-highlight .highlight .m { color: #666666 } /* Literal.Number */ 27 | .code-highlight .highlight .s { color: #BB4444 } /* Literal.String */ 28 | .code-highlight .highlight .na { color: #BB4444 } /* Name.Attribute */ 29 | .code-highlight .highlight .nb { color: #AA22FF } /* Name.Builtin */ 30 | .code-highlight .highlight .nc { color: #0000FF } /* Name.Class */ 31 | .code-highlight .highlight .no { color: #880000 } /* Name.Constant */ 32 | .code-highlight .highlight .nd { color: #AA22FF } /* Name.Decorator */ 33 | .code-highlight .highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ 34 | .code-highlight .highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 35 | .code-highlight .highlight .nf { color: #00A000 } /* Name.Function */ 36 | .code-highlight .highlight .nl { color: #A0A000 } /* Name.Label */ 37 | .code-highlight .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 38 | .code-highlight .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 39 | .code-highlight .highlight .nv { color: #B8860B } /* Name.Variable */ 40 | .code-highlight .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 41 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .code-highlight .highlight .mf { color: #666666 } /* Literal.Number.Float */ 43 | .code-highlight .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 44 | .code-highlight .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 45 | .code-highlight .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 46 | .code-highlight .highlight .sb { color: #BB4444 } /* Literal.String.Backtick */ 47 | .code-highlight .highlight .sc { color: #BB4444 } /* Literal.String.Char */ 48 | .code-highlight .highlight .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */ 49 | .code-highlight .highlight .s2 { color: #BB4444 } /* Literal.String.Double */ 50 | .code-highlight .highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 51 | .code-highlight .highlight .sh { color: #BB4444 } /* Literal.String.Heredoc */ 52 | .code-highlight .highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 53 | .code-highlight .highlight .sx { color: #008000 } /* Literal.String.Other */ 54 | .code-highlight .highlight .sr { color: #BB6688 } /* Literal.String.Regex */ 55 | .code-highlight .highlight .s1 { color: #BB4444 } /* Literal.String.Single */ 56 | .code-highlight .highlight .ss { color: #B8860B } /* Literal.String.Symbol */ 57 | .code-highlight .highlight .bp { color: #AA22FF } /* Name.Builtin.Pseudo */ 58 | .code-highlight .highlight .vc { color: #B8860B } /* Name.Variable.Class */ 59 | .code-highlight .highlight .vg { color: #B8860B } /* Name.Variable.Global */ 60 | .code-highlight .highlight .vi { color: #B8860B } /* Name.Variable.Instance */ 61 | .code-highlight .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ 62 | -------------------------------------------------------------------------------- /app/static/css/syntax/friendly.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #60a0b0; font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { border: 1px solid #FF0000 } /* Error */ 4 | .code-highlight .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .o { color: #666666 } /* Operator */ 6 | .code-highlight .highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ 7 | .code-highlight .highlight .cp { color: #007020 } /* Comment.Preproc */ 8 | .code-highlight .highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ 9 | .code-highlight .highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ 10 | .code-highlight .highlight .gd { color: #A00000 } /* Generic.Deleted */ 11 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 12 | .code-highlight .highlight .gr { color: #FF0000 } /* Generic.Error */ 13 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 14 | .code-highlight .highlight .gi { color: #00A000 } /* Generic.Inserted */ 15 | .code-highlight .highlight .go { color: #808080 } /* Generic.Output */ 16 | .code-highlight .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 17 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 18 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 19 | .code-highlight .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 20 | .code-highlight .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 21 | .code-highlight .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 22 | .code-highlight .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 23 | .code-highlight .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 24 | .code-highlight .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 25 | .code-highlight .highlight .kt { color: #902000 } /* Keyword.Type */ 26 | .code-highlight .highlight .m { color: #40a070 } /* Literal.Number */ 27 | .code-highlight .highlight .s { color: #4070a0 } /* Literal.String */ 28 | .code-highlight .highlight .na { color: #4070a0 } /* Name.Attribute */ 29 | .code-highlight .highlight .nb { color: #007020 } /* Name.Builtin */ 30 | .code-highlight .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 31 | .code-highlight .highlight .no { color: #60add5 } /* Name.Constant */ 32 | .code-highlight .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 33 | .code-highlight .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 34 | .code-highlight .highlight .ne { color: #007020 } /* Name.Exception */ 35 | .code-highlight .highlight .nf { color: #06287e } /* Name.Function */ 36 | .code-highlight .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 37 | .code-highlight .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 38 | .code-highlight .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 39 | .code-highlight .highlight .nv { color: #bb60d5 } /* Name.Variable */ 40 | .code-highlight .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 41 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .code-highlight .highlight .mf { color: #40a070 } /* Literal.Number.Float */ 43 | .code-highlight .highlight .mh { color: #40a070 } /* Literal.Number.Hex */ 44 | .code-highlight .highlight .mi { color: #40a070 } /* Literal.Number.Integer */ 45 | .code-highlight .highlight .mo { color: #40a070 } /* Literal.Number.Oct */ 46 | .code-highlight .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 47 | .code-highlight .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 48 | .code-highlight .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 49 | .code-highlight .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 50 | .code-highlight .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 51 | .code-highlight .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 52 | .code-highlight .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 53 | .code-highlight .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 54 | .code-highlight .highlight .sr { color: #235388 } /* Literal.String.Regex */ 55 | .code-highlight .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 56 | .code-highlight .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 57 | .code-highlight .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 58 | .code-highlight .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 59 | .code-highlight .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 60 | .code-highlight .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 61 | .code-highlight .highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ 62 | -------------------------------------------------------------------------------- /app/static/css/syntax/default.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight { background: #f8f8f8; } 3 | .code-highlight .highlight .c { color: #408080; font-style: italic } /* Comment */ 4 | .code-highlight .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .code-highlight .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 6 | .code-highlight .highlight .o { color: #666666 } /* Operator */ 7 | .code-highlight .highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 8 | .code-highlight .highlight .cp { color: #BC7A00 } /* Comment.Preproc */ 9 | .code-highlight .highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ 10 | .code-highlight .highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ 11 | .code-highlight .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .code-highlight .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .code-highlight .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .code-highlight .highlight .go { color: #808080 } /* Generic.Output */ 17 | .code-highlight .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 18 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .code-highlight .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 21 | .code-highlight .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 22 | .code-highlight .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 23 | .code-highlight .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 24 | .code-highlight .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 25 | .code-highlight .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 26 | .code-highlight .highlight .kt { color: #B00040 } /* Keyword.Type */ 27 | .code-highlight .highlight .m { color: #666666 } /* Literal.Number */ 28 | .code-highlight .highlight .s { color: #BA2121 } /* Literal.String */ 29 | .code-highlight .highlight .na { color: #7D9029 } /* Name.Attribute */ 30 | .code-highlight .highlight .nb { color: #008000 } /* Name.Builtin */ 31 | .code-highlight .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 32 | .code-highlight .highlight .no { color: #880000 } /* Name.Constant */ 33 | .code-highlight .highlight .nd { color: #AA22FF } /* Name.Decorator */ 34 | .code-highlight .highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ 35 | .code-highlight .highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 36 | .code-highlight .highlight .nf { color: #0000FF } /* Name.Function */ 37 | .code-highlight .highlight .nl { color: #A0A000 } /* Name.Label */ 38 | .code-highlight .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 39 | .code-highlight .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 40 | .code-highlight .highlight .nv { color: #19177C } /* Name.Variable */ 41 | .code-highlight .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 42 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .code-highlight .highlight .mf { color: #666666 } /* Literal.Number.Float */ 44 | .code-highlight .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 45 | .code-highlight .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 46 | .code-highlight .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 47 | .code-highlight .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 48 | .code-highlight .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 49 | .code-highlight .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 50 | .code-highlight .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 51 | .code-highlight .highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 52 | .code-highlight .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 53 | .code-highlight .highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 54 | .code-highlight .highlight .sx { color: #008000 } /* Literal.String.Other */ 55 | .code-highlight .highlight .sr { color: #BB6688 } /* Literal.String.Regex */ 56 | .code-highlight .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 57 | .code-highlight .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 58 | .code-highlight .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 59 | .code-highlight .highlight .vc { color: #19177C } /* Name.Variable.Class */ 60 | .code-highlight .highlight .vg { color: #19177C } /* Name.Variable.Global */ 61 | .code-highlight .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 62 | .code-highlight .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ 63 | -------------------------------------------------------------------------------- /app/static/css/syntax/manni.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #0099FF; font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { color: #AA0000; background-color: #FFAAAA } /* Error */ 4 | .code-highlight .highlight .k { color: #006699; font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .o { color: #555555 } /* Operator */ 6 | .code-highlight .highlight .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ 7 | .code-highlight .highlight .cp { color: #009999 } /* Comment.Preproc */ 8 | .code-highlight .highlight .c1 { color: #0099FF; font-style: italic } /* Comment.Single */ 9 | .code-highlight .highlight .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */ 10 | .code-highlight .highlight .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ 11 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 12 | .code-highlight .highlight .gr { color: #FF0000 } /* Generic.Error */ 13 | .code-highlight .highlight .gh { color: #003300; font-weight: bold } /* Generic.Heading */ 14 | .code-highlight .highlight .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ 15 | .code-highlight .highlight .go { color: #AAAAAA } /* Generic.Output */ 16 | .code-highlight .highlight .gp { color: #000099; font-weight: bold } /* Generic.Prompt */ 17 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 18 | .code-highlight .highlight .gu { color: #003300; font-weight: bold } /* Generic.Subheading */ 19 | .code-highlight .highlight .gt { color: #99CC66 } /* Generic.Traceback */ 20 | .code-highlight .highlight .kc { color: #006699; font-weight: bold } /* Keyword.Constant */ 21 | .code-highlight .highlight .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */ 22 | .code-highlight .highlight .kn { color: #006699; font-weight: bold } /* Keyword.Namespace */ 23 | .code-highlight .highlight .kp { color: #006699 } /* Keyword.Pseudo */ 24 | .code-highlight .highlight .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */ 25 | .code-highlight .highlight .kt { color: #007788; font-weight: bold } /* Keyword.Type */ 26 | .code-highlight .highlight .m { color: #FF6600 } /* Literal.Number */ 27 | .code-highlight .highlight .s { color: #CC3300 } /* Literal.String */ 28 | .code-highlight .highlight .na { color: #330099 } /* Name.Attribute */ 29 | .code-highlight .highlight .nb { color: #336666 } /* Name.Builtin */ 30 | .code-highlight .highlight .nc { color: #00AA88; font-weight: bold } /* Name.Class */ 31 | .code-highlight .highlight .no { color: #336600 } /* Name.Constant */ 32 | .code-highlight .highlight .nd { color: #9999FF } /* Name.Decorator */ 33 | .code-highlight .highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ 34 | .code-highlight .highlight .ne { color: #CC0000; font-weight: bold } /* Name.Exception */ 35 | .code-highlight .highlight .nf { color: #CC00FF } /* Name.Function */ 36 | .code-highlight .highlight .nl { color: #9999FF } /* Name.Label */ 37 | .code-highlight .highlight .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */ 38 | .code-highlight .highlight .nt { color: #330099; font-weight: bold } /* Name.Tag */ 39 | .code-highlight .highlight .nv { color: #003333 } /* Name.Variable */ 40 | .code-highlight .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ 41 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .code-highlight .highlight .mf { color: #FF6600 } /* Literal.Number.Float */ 43 | .code-highlight .highlight .mh { color: #FF6600 } /* Literal.Number.Hex */ 44 | .code-highlight .highlight .mi { color: #FF6600 } /* Literal.Number.Integer */ 45 | .code-highlight .highlight .mo { color: #FF6600 } /* Literal.Number.Oct */ 46 | .code-highlight .highlight .sb { color: #CC3300 } /* Literal.String.Backtick */ 47 | .code-highlight .highlight .sc { color: #CC3300 } /* Literal.String.Char */ 48 | .code-highlight .highlight .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ 49 | .code-highlight .highlight .s2 { color: #CC3300 } /* Literal.String.Double */ 50 | .code-highlight .highlight .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */ 51 | .code-highlight .highlight .sh { color: #CC3300 } /* Literal.String.Heredoc */ 52 | .code-highlight .highlight .si { color: #AA0000 } /* Literal.String.Interpol */ 53 | .code-highlight .highlight .sx { color: #CC3300 } /* Literal.String.Other */ 54 | .code-highlight .highlight .sr { color: #33AAAA } /* Literal.String.Regex */ 55 | .code-highlight .highlight .s1 { color: #CC3300 } /* Literal.String.Single */ 56 | .code-highlight .highlight .ss { color: #FFCC33 } /* Literal.String.Symbol */ 57 | .code-highlight .highlight .bp { color: #336666 } /* Name.Builtin.Pseudo */ 58 | .code-highlight .highlight .vc { color: #003333 } /* Name.Variable.Class */ 59 | .code-highlight .highlight .vg { color: #003333 } /* Name.Variable.Global */ 60 | .code-highlight .highlight .vi { color: #003333 } /* Name.Variable.Instance */ 61 | .code-highlight .highlight .il { color: #FF6600 } /* Literal.Number.Integer.Long */ 62 | -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | MarkDownBlog 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {% if syntax_highlighter_css %} 22 | 23 | {% endif %} 24 | 25 | 26 | 27 | {% block extra_css_resources %}{% endblock %} 28 | {% block extra_style %}{% endblock %} 29 | 50 | 51 | 52 | 53 | 54 | 55 | {% block extrajs %}{% endblock %} 56 | 57 | 58 | {% block content %}{% endblock %} 59 | 102 | 103 | {% block extra_js_at_end %}{% endblock %} 104 | 105 | 106 | -------------------------------------------------------------------------------- /app/static/css/syntax/colorful.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #808080 } /* Comment */ 3 | .code-highlight .highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */ 4 | .code-highlight .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .o { color: #303030 } /* Operator */ 6 | .code-highlight .highlight .cm { color: #808080 } /* Comment.Multiline */ 7 | .code-highlight .highlight .cp { color: #507090 } /* Comment.Preproc */ 8 | .code-highlight .highlight .c1 { color: #808080 } /* Comment.Single */ 9 | .code-highlight .highlight .cs { color: #cc0000; font-weight: bold } /* Comment.Special */ 10 | .code-highlight .highlight .gd { color: #A00000 } /* Generic.Deleted */ 11 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 12 | .code-highlight .highlight .gr { color: #FF0000 } /* Generic.Error */ 13 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 14 | .code-highlight .highlight .gi { color: #00A000 } /* Generic.Inserted */ 15 | .code-highlight .highlight .go { color: #808080 } /* Generic.Output */ 16 | .code-highlight .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 17 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 18 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 19 | .code-highlight .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 20 | .code-highlight .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 21 | .code-highlight .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 22 | .code-highlight .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 23 | .code-highlight .highlight .kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */ 24 | .code-highlight .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 25 | .code-highlight .highlight .kt { color: #303090; font-weight: bold } /* Keyword.Type */ 26 | .code-highlight .highlight .m { color: #6000E0; font-weight: bold } /* Literal.Number */ 27 | .code-highlight .highlight .s { background-color: #fff0f0 } /* Literal.String */ 28 | .code-highlight .highlight .na { color: #0000C0 } /* Name.Attribute */ 29 | .code-highlight .highlight .nb { color: #007020 } /* Name.Builtin */ 30 | .code-highlight .highlight .nc { color: #B00060; font-weight: bold } /* Name.Class */ 31 | .code-highlight .highlight .no { color: #003060; font-weight: bold } /* Name.Constant */ 32 | .code-highlight .highlight .nd { color: #505050; font-weight: bold } /* Name.Decorator */ 33 | .code-highlight .highlight .ni { color: #800000; font-weight: bold } /* Name.Entity */ 34 | .code-highlight .highlight .ne { color: #F00000; font-weight: bold } /* Name.Exception */ 35 | .code-highlight .highlight .nf { color: #0060B0; font-weight: bold } /* Name.Function */ 36 | .code-highlight .highlight .nl { color: #907000; font-weight: bold } /* Name.Label */ 37 | .code-highlight .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 38 | .code-highlight .highlight .nt { color: #007000 } /* Name.Tag */ 39 | .code-highlight .highlight .nv { color: #906030 } /* Name.Variable */ 40 | .code-highlight .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ 41 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .code-highlight .highlight .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */ 43 | .code-highlight .highlight .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */ 44 | .code-highlight .highlight .mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */ 45 | .code-highlight .highlight .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */ 46 | .code-highlight .highlight .sb { background-color: #fff0f0 } /* Literal.String.Backtick */ 47 | .code-highlight .highlight .sc { color: #0040D0 } /* Literal.String.Char */ 48 | .code-highlight .highlight .sd { color: #D04020 } /* Literal.String.Doc */ 49 | .code-highlight .highlight .s2 { background-color: #fff0f0 } /* Literal.String.Double */ 50 | .code-highlight .highlight .se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ 51 | .code-highlight .highlight .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ 52 | .code-highlight .highlight .si { background-color: #e0e0e0 } /* Literal.String.Interpol */ 53 | .code-highlight .highlight .sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */ 54 | .code-highlight .highlight .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ 55 | .code-highlight .highlight .s1 { background-color: #fff0f0 } /* Literal.String.Single */ 56 | .code-highlight .highlight .ss { color: #A06000 } /* Literal.String.Symbol */ 57 | .code-highlight .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 58 | .code-highlight .highlight .vc { color: #306090 } /* Name.Variable.Class */ 59 | .code-highlight .highlight .vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */ 60 | .code-highlight .highlight .vi { color: #3030B0 } /* Name.Variable.Instance */ 61 | .code-highlight .highlight .il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */ 62 | -------------------------------------------------------------------------------- /app/forms/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_login import current_user 4 | 5 | from wtforms.fields import StringField, TextAreaField, BooleanField, IntegerField, SelectField, PasswordField 6 | from wtforms.validators import DataRequired, Length, URL, Optional, EqualTo 7 | from wtforms import ValidationError 8 | 9 | from .base import CustomForm 10 | from .utils import ImageUrl 11 | from app.models.user import SYNTAX_HIGHLIGHTER_TUPLE 12 | 13 | 14 | 15 | class ChangePasswordForm(CustomForm): 16 | old_password = PasswordField( 17 | 'Old Password', 18 | validators=[DataRequired(), ], 19 | description={'placeholder': "Old Password"} 20 | ) 21 | new_password = PasswordField( 22 | 'New Password', 23 | validators=[DataRequired(), ], 24 | description={'placeholder': "New Password"} 25 | ) 26 | repeat_new_password = PasswordField( 27 | 'Repeat New Password', 28 | validators=[DataRequired(), EqualTo('new_password', message='Passwords must match')], 29 | description={'placeholder': "Repeat New Password"} 30 | ) 31 | 32 | def validate_old_password(self, field): 33 | if not current_user.check_password(self.old_password.data): 34 | raise ValidationError("Wrong old password") 35 | 36 | 37 | class SettingForm(CustomForm): 38 | blog_title = StringField( 39 | 'Blog Title', 40 | validators=[DataRequired(), Length(min=5, max=50)], 41 | description={'placeholder': "Blog Title"} 42 | ) 43 | blog_description = TextAreaField( 44 | 'Blog Description', 45 | validators=[DataRequired(), Length(min=5, max=200)], 46 | description={'placeholder': "Blog Description"} 47 | ) 48 | blog_image = StringField( 49 | 'Blog Image', 50 | validators=[Optional(), URL(), ImageUrl()], 51 | description={'placeholder': "Blog Image"} 52 | ) 53 | blog_image_rounded = BooleanField( 54 | 'Round Blog Image', 55 | description={ 56 | 'help': "Do you want your blog image to be rounded ?" 57 | } 58 | ) 59 | blog_public = BooleanField( 60 | 'Public Blog', 61 | description={ 62 | 'help': "Can your articles be used on the explore/front page ?" 63 | } 64 | ) 65 | blog_bg = StringField( 66 | 'Blog Background', 67 | validators=[Optional(), URL(), ImageUrl()], 68 | description={'placeholder': "Blog Background"} 69 | ) 70 | blog_bg_public = BooleanField( 71 | 'Blog Background Public', 72 | description={ 73 | 'help': "Can the visitors of your blog see the background ?" 74 | } 75 | ) 76 | blog_bg_repeat = BooleanField( 77 | 'Blog Background Repeat', 78 | description={ 79 | 'help': "Activate if your image is small and you want it to be repeated in the background." 80 | } 81 | ) 82 | blog_bg_everywhere = BooleanField( 83 | 'Blog Background Everywhere', 84 | description={ 85 | 'help': "When you're logged-in, do you want this background to be applied everywhere on markdownblog ?" 86 | } 87 | ) 88 | blog_bg_override = BooleanField( 89 | 'Blog Background Override', 90 | description={ 91 | 'help': "When you're logged-in, do you want your own background to override the other blog's public background ?" 92 | } 93 | ) 94 | blog_paginate = BooleanField( 95 | 'Activate Pagination', 96 | description={ 97 | 'help': "Would you like to paginate your blog posts ?" 98 | } 99 | ) 100 | blog_paginate_by = IntegerField( 101 | 'Articles per Page', 102 | validators=[DataRequired("Even if not using pagination put a value here.")], 103 | description={'placeholder': "Articles per Page"} 104 | ) 105 | blog_truncate_posts = BooleanField( 106 | 'Truncate Articles', 107 | description={ 108 | 'help': "Would you like your articles to be truncated on the article list ?" 109 | } 110 | ) 111 | blog_syntax_highlighter_css = SelectField( 112 | "Syntax Highlighter Theme", 113 | choices=SYNTAX_HIGHLIGHTER_TUPLE, 114 | description={ 115 | 'help': "Which them would you like to apply to your syntax highlighted code ?" 116 | } 117 | ) 118 | linkedin_url = StringField( 119 | 'LinkedIn Profile URL', 120 | validators=[Optional(), URL(), Length(min=5, max=200)], 121 | description={'placeholder': "LinkedIn Profile URL"} 122 | ) 123 | gplus_url = StringField( 124 | 'Google Plus Profile URL', 125 | validators=[Optional(), URL(), Length(min=5, max=200)], 126 | description={'placeholder': "Google Plus Profile URL"} 127 | ) 128 | github_url = StringField( 129 | 'GitHub Profile URL', 130 | validators=[Optional(), URL(), Length(min=5, max=200)], 131 | description={'placeholder': "GitHub Profile URL"} 132 | ) 133 | twitter_url = StringField( 134 | 'Twitter Profile URL', 135 | validators=[Optional(), URL(), Length(min=5, max=200)], 136 | description={'placeholder': "Twitter Profile URL"} 137 | ) 138 | -------------------------------------------------------------------------------- /app/static/css/syntax/murphy.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #606060; font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */ 4 | .code-highlight .highlight .k { color: #208090; font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .o { color: #303030 } /* Operator */ 6 | .code-highlight .highlight .cm { color: #606060; font-style: italic } /* Comment.Multiline */ 7 | .code-highlight .highlight .cp { color: #507090 } /* Comment.Preproc */ 8 | .code-highlight .highlight .c1 { color: #606060; font-style: italic } /* Comment.Single */ 9 | .code-highlight .highlight .cs { color: #c00000; font-weight: bold; font-style: italic } /* Comment.Special */ 10 | .code-highlight .highlight .gd { color: #A00000 } /* Generic.Deleted */ 11 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 12 | .code-highlight .highlight .gr { color: #FF0000 } /* Generic.Error */ 13 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 14 | .code-highlight .highlight .gi { color: #00A000 } /* Generic.Inserted */ 15 | .code-highlight .highlight .go { color: #808080 } /* Generic.Output */ 16 | .code-highlight .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 17 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 18 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 19 | .code-highlight .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 20 | .code-highlight .highlight .kc { color: #208090; font-weight: bold } /* Keyword.Constant */ 21 | .code-highlight .highlight .kd { color: #208090; font-weight: bold } /* Keyword.Declaration */ 22 | .code-highlight .highlight .kn { color: #208090; font-weight: bold } /* Keyword.Namespace */ 23 | .code-highlight .highlight .kp { color: #0080f0; font-weight: bold } /* Keyword.Pseudo */ 24 | .code-highlight .highlight .kr { color: #208090; font-weight: bold } /* Keyword.Reserved */ 25 | .code-highlight .highlight .kt { color: #6060f0; font-weight: bold } /* Keyword.Type */ 26 | .code-highlight .highlight .m { color: #6000E0; font-weight: bold } /* Literal.Number */ 27 | .code-highlight .highlight .s { background-color: #e0e0ff } /* Literal.String */ 28 | .code-highlight .highlight .na { color: #000070 } /* Name.Attribute */ 29 | .code-highlight .highlight .nb { color: #007020 } /* Name.Builtin */ 30 | .code-highlight .highlight .nc { color: #e090e0; font-weight: bold } /* Name.Class */ 31 | .code-highlight .highlight .no { color: #50e0d0; font-weight: bold } /* Name.Constant */ 32 | .code-highlight .highlight .nd { color: #505050; font-weight: bold } /* Name.Decorator */ 33 | .code-highlight .highlight .ni { color: #800000 } /* Name.Entity */ 34 | .code-highlight .highlight .ne { color: #F00000; font-weight: bold } /* Name.Exception */ 35 | .code-highlight .highlight .nf { color: #50e0d0; font-weight: bold } /* Name.Function */ 36 | .code-highlight .highlight .nl { color: #907000; font-weight: bold } /* Name.Label */ 37 | .code-highlight .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 38 | .code-highlight .highlight .nt { color: #007000 } /* Name.Tag */ 39 | .code-highlight .highlight .nv { color: #003060 } /* Name.Variable */ 40 | .code-highlight .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ 41 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 42 | .code-highlight .highlight .mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */ 43 | .code-highlight .highlight .mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */ 44 | .code-highlight .highlight .mi { color: #6060f0; font-weight: bold } /* Literal.Number.Integer */ 45 | .code-highlight .highlight .mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */ 46 | .code-highlight .highlight .sb { background-color: #e0e0ff } /* Literal.String.Backtick */ 47 | .code-highlight .highlight .sc { color: #8080F0 } /* Literal.String.Char */ 48 | .code-highlight .highlight .sd { color: #D04020 } /* Literal.String.Doc */ 49 | .code-highlight .highlight .s2 { background-color: #e0e0ff } /* Literal.String.Double */ 50 | .code-highlight .highlight .se { color: #606060; font-weight: bold; background-color: #e0e0ff } /* Literal.String.Escape */ 51 | .code-highlight .highlight .sh { background-color: #e0e0ff } /* Literal.String.Heredoc */ 52 | .code-highlight .highlight .si { background-color: #e0e0e0 } /* Literal.String.Interpol */ 53 | .code-highlight .highlight .sx { color: #f08080; background-color: #e0e0ff } /* Literal.String.Other */ 54 | .code-highlight .highlight .sr { color: #000000; background-color: #e0e0ff } /* Literal.String.Regex */ 55 | .code-highlight .highlight .s1 { background-color: #e0e0ff } /* Literal.String.Single */ 56 | .code-highlight .highlight .ss { color: #f0c080 } /* Literal.String.Symbol */ 57 | .code-highlight .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 58 | .code-highlight .highlight .vc { color: #c0c0f0 } /* Name.Variable.Class */ 59 | .code-highlight .highlight .vg { color: #f08040 } /* Name.Variable.Global */ 60 | .code-highlight .highlight .vi { color: #a0a0f0 } /* Name.Variable.Instance */ 61 | .code-highlight .highlight .il { color: #6060f0; font-weight: bold } /* Literal.Number.Integer.Long */ 62 | -------------------------------------------------------------------------------- /app/static/css/syntax/vim.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight pre { background-color: #49483e } 2 | .code-highlight .highlight .hll { background-color: #222222 } 3 | .code-highlight .highlight .c { color: #000080 } /* Comment */ 4 | .code-highlight .highlight .err { color: #cccccc; border: 1px solid #FF0000 } /* Error */ 5 | .code-highlight .highlight .g { color: #cccccc } /* Generic */ 6 | .code-highlight .highlight .k { color: #cdcd00 } /* Keyword */ 7 | .code-highlight .highlight .l { color: #cccccc } /* Literal */ 8 | .code-highlight .highlight .n { color: #cccccc } /* Name */ 9 | .code-highlight .highlight .o { color: #3399cc } /* Operator */ 10 | .code-highlight .highlight .x { color: #cccccc } /* Other */ 11 | .code-highlight .highlight .p { color: #cccccc } /* Punctuation */ 12 | .code-highlight .highlight .cm { color: #000080 } /* Comment.Multiline */ 13 | .code-highlight .highlight .cp { color: #000080 } /* Comment.Preproc */ 14 | .code-highlight .highlight .c1 { color: #000080 } /* Comment.Single */ 15 | .code-highlight .highlight .cs { color: #cd0000; font-weight: bold } /* Comment.Special */ 16 | .code-highlight .highlight .gd { color: #cd0000 } /* Generic.Deleted */ 17 | .code-highlight .highlight .ge { color: #cccccc; font-style: italic } /* Generic.Emph */ 18 | .code-highlight .highlight .gr { color: #FF0000 } /* Generic.Error */ 19 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 20 | .code-highlight .highlight .gi { color: #00cd00 } /* Generic.Inserted */ 21 | .code-highlight .highlight .go { color: #808080 } /* Generic.Output */ 22 | .code-highlight .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 23 | .code-highlight .highlight .gs { color: #cccccc; font-weight: bold } /* Generic.Strong */ 24 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 25 | .code-highlight .highlight .gt { color: #0040D0 } /* Generic.Traceback */ 26 | .code-highlight .highlight .kc { color: #cdcd00 } /* Keyword.Constant */ 27 | .code-highlight .highlight .kd { color: #00cd00 } /* Keyword.Declaration */ 28 | .code-highlight .highlight .kn { color: #cd00cd } /* Keyword.Namespace */ 29 | .code-highlight .highlight .kp { color: #cdcd00 } /* Keyword.Pseudo */ 30 | .code-highlight .highlight .kr { color: #cdcd00 } /* Keyword.Reserved */ 31 | .code-highlight .highlight .kt { color: #00cd00 } /* Keyword.Type */ 32 | .code-highlight .highlight .ld { color: #cccccc } /* Literal.Date */ 33 | .code-highlight .highlight .m { color: #cd00cd } /* Literal.Number */ 34 | .code-highlight .highlight .s { color: #cd0000 } /* Literal.String */ 35 | .code-highlight .highlight .na { color: #cccccc } /* Name.Attribute */ 36 | .code-highlight .highlight .nb { color: #cd00cd } /* Name.Builtin */ 37 | .code-highlight .highlight .nc { color: #00cdcd } /* Name.Class */ 38 | .code-highlight .highlight .no { color: #cccccc } /* Name.Constant */ 39 | .code-highlight .highlight .nd { color: #cccccc } /* Name.Decorator */ 40 | .code-highlight .highlight .ni { color: #cccccc } /* Name.Entity */ 41 | .code-highlight .highlight .ne { color: #666699; font-weight: bold } /* Name.Exception */ 42 | .code-highlight .highlight .nf { color: #cccccc } /* Name.Function */ 43 | .code-highlight .highlight .nl { color: #cccccc } /* Name.Label */ 44 | .code-highlight .highlight .nn { color: #cccccc } /* Name.Namespace */ 45 | .code-highlight .highlight .nx { color: #cccccc } /* Name.Other */ 46 | .code-highlight .highlight .py { color: #cccccc } /* Name.Property */ 47 | .code-highlight .highlight .nt { color: #cccccc } /* Name.Tag */ 48 | .code-highlight .highlight .nv { color: #00cdcd } /* Name.Variable */ 49 | .code-highlight .highlight .ow { color: #cdcd00 } /* Operator.Word */ 50 | .code-highlight .highlight .w { color: #cccccc } /* Text.Whitespace */ 51 | .code-highlight .highlight .mf { color: #cd00cd } /* Literal.Number.Float */ 52 | .code-highlight .highlight .mh { color: #cd00cd } /* Literal.Number.Hex */ 53 | .code-highlight .highlight .mi { color: #cd00cd } /* Literal.Number.Integer */ 54 | .code-highlight .highlight .mo { color: #cd00cd } /* Literal.Number.Oct */ 55 | .code-highlight .highlight .sb { color: #cd0000 } /* Literal.String.Backtick */ 56 | .code-highlight .highlight .sc { color: #cd0000 } /* Literal.String.Char */ 57 | .code-highlight .highlight .sd { color: #cd0000 } /* Literal.String.Doc */ 58 | .code-highlight .highlight .s2 { color: #cd0000 } /* Literal.String.Double */ 59 | .code-highlight .highlight .se { color: #cd0000 } /* Literal.String.Escape */ 60 | .code-highlight .highlight .sh { color: #cd0000 } /* Literal.String.Heredoc */ 61 | .code-highlight .highlight .si { color: #cd0000 } /* Literal.String.Interpol */ 62 | .code-highlight .highlight .sx { color: #cd0000 } /* Literal.String.Other */ 63 | .code-highlight .highlight .sr { color: #cd0000 } /* Literal.String.Regex */ 64 | .code-highlight .highlight .s1 { color: #cd0000 } /* Literal.String.Single */ 65 | .code-highlight .highlight .ss { color: #cd0000 } /* Literal.String.Symbol */ 66 | .code-highlight .highlight .bp { color: #cd00cd } /* Name.Builtin.Pseudo */ 67 | .code-highlight .highlight .vc { color: #00cdcd } /* Name.Variable.Class */ 68 | .code-highlight .highlight .vg { color: #00cdcd } /* Name.Variable.Global */ 69 | .code-highlight .highlight .vi { color: #00cdcd } /* Name.Variable.Instance */ 70 | .code-highlight .highlight .il { color: #cd00cd } /* Literal.Number.Integer.Long */ 71 | -------------------------------------------------------------------------------- /app/static/css/syntax/pastie.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #888888 } /* Comment */ 3 | .code-highlight .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 4 | .code-highlight .highlight .k { color: #008800; font-weight: bold } /* Keyword */ 5 | .code-highlight .highlight .cm { color: #888888 } /* Comment.Multiline */ 6 | .code-highlight .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ 7 | .code-highlight .highlight .c1 { color: #888888 } /* Comment.Single */ 8 | .code-highlight .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ 9 | .code-highlight .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 10 | .code-highlight .highlight .ge { font-style: italic } /* Generic.Emph */ 11 | .code-highlight .highlight .gr { color: #aa0000 } /* Generic.Error */ 12 | .code-highlight .highlight .gh { color: #303030 } /* Generic.Heading */ 13 | .code-highlight .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 14 | .code-highlight .highlight .go { color: #888888 } /* Generic.Output */ 15 | .code-highlight .highlight .gp { color: #555555 } /* Generic.Prompt */ 16 | .code-highlight .highlight .gs { font-weight: bold } /* Generic.Strong */ 17 | .code-highlight .highlight .gu { color: #606060 } /* Generic.Subheading */ 18 | .code-highlight .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 19 | .code-highlight .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ 20 | .code-highlight .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ 21 | .code-highlight .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ 22 | .code-highlight .highlight .kp { color: #008800 } /* Keyword.Pseudo */ 23 | .code-highlight .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ 24 | .code-highlight .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ 25 | .code-highlight .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ 26 | .code-highlight .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ 27 | .code-highlight .highlight .na { color: #336699 } /* Name.Attribute */ 28 | .code-highlight .highlight .nb { color: #003388 } /* Name.Builtin */ 29 | .code-highlight .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ 30 | .code-highlight .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ 31 | .code-highlight .highlight .nd { color: #555555 } /* Name.Decorator */ 32 | .code-highlight .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ 33 | .code-highlight .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ 34 | .code-highlight .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ 35 | .code-highlight .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ 36 | .code-highlight .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ 37 | .code-highlight .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ 38 | .code-highlight .highlight .nv { color: #336699 } /* Name.Variable */ 39 | .code-highlight .highlight .ow { color: #008800 } /* Operator.Word */ 40 | .code-highlight .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 41 | .code-highlight .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ 42 | .code-highlight .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ 43 | .code-highlight .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ 44 | .code-highlight .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ 45 | .code-highlight .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ 46 | .code-highlight .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ 47 | .code-highlight .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ 48 | .code-highlight .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ 49 | .code-highlight .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ 50 | .code-highlight .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ 51 | .code-highlight .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ 52 | .code-highlight .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ 53 | .code-highlight .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ 54 | .code-highlight .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ 55 | .code-highlight .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ 56 | .code-highlight .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ 57 | .code-highlight .highlight .vc { color: #336699 } /* Name.Variable.Class */ 58 | .code-highlight .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ 59 | .code-highlight .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ 60 | .code-highlight .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ 61 | -------------------------------------------------------------------------------- /app/static/css/syntax/native.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight pre { background-color: #49483e } 2 | .code-highlight .highlight .hll { background-color: #404040 } 3 | .code-highlight .highlight .c { color: #999999; font-style: italic } /* Comment */ 4 | .code-highlight .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 5 | .code-highlight .highlight .g { color: #d0d0d0 } /* Generic */ 6 | .code-highlight .highlight .k { color: #6ab825; font-weight: bold } /* Keyword */ 7 | .code-highlight .highlight .l { color: #d0d0d0 } /* Literal */ 8 | .code-highlight .highlight .n { color: #d0d0d0 } /* Name */ 9 | .code-highlight .highlight .o { color: #d0d0d0 } /* Operator */ 10 | .code-highlight .highlight .x { color: #d0d0d0 } /* Other */ 11 | .code-highlight .highlight .p { color: #d0d0d0 } /* Punctuation */ 12 | .code-highlight .highlight .cm { color: #999999; font-style: italic } /* Comment.Multiline */ 13 | .code-highlight .highlight .cp { color: #cd2828; font-weight: bold } /* Comment.Preproc */ 14 | .code-highlight .highlight .c1 { color: #999999; font-style: italic } /* Comment.Single */ 15 | .code-highlight .highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */ 16 | .code-highlight .highlight .gd { color: #d22323 } /* Generic.Deleted */ 17 | .code-highlight .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ 18 | .code-highlight .highlight .gr { color: #d22323 } /* Generic.Error */ 19 | .code-highlight .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ 20 | .code-highlight .highlight .gi { color: #589819 } /* Generic.Inserted */ 21 | .code-highlight .highlight .go { color: #cccccc } /* Generic.Output */ 22 | .code-highlight .highlight .gp { color: #aaaaaa } /* Generic.Prompt */ 23 | .code-highlight .highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */ 24 | .code-highlight .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ 25 | .code-highlight .highlight .gt { color: #d22323 } /* Generic.Traceback */ 26 | .code-highlight .highlight .kc { color: #6ab825; font-weight: bold } /* Keyword.Constant */ 27 | .code-highlight .highlight .kd { color: #6ab825; font-weight: bold } /* Keyword.Declaration */ 28 | .code-highlight .highlight .kn { color: #6ab825; font-weight: bold } /* Keyword.Namespace */ 29 | .code-highlight .highlight .kp { color: #6ab825 } /* Keyword.Pseudo */ 30 | .code-highlight .highlight .kr { color: #6ab825; font-weight: bold } /* Keyword.Reserved */ 31 | .code-highlight .highlight .kt { color: #6ab825; font-weight: bold } /* Keyword.Type */ 32 | .code-highlight .highlight .ld { color: #d0d0d0 } /* Literal.Date */ 33 | .code-highlight .highlight .m { color: #3677a9 } /* Literal.Number */ 34 | .code-highlight .highlight .s { color: #ed9d13 } /* Literal.String */ 35 | .code-highlight .highlight .na { color: #bbbbbb } /* Name.Attribute */ 36 | .code-highlight .highlight .nb { color: #24909d } /* Name.Builtin */ 37 | .code-highlight .highlight .nc { color: #447fcf; text-decoration: underline } /* Name.Class */ 38 | .code-highlight .highlight .no { color: #40ffff } /* Name.Constant */ 39 | .code-highlight .highlight .nd { color: #ffa500 } /* Name.Decorator */ 40 | .code-highlight .highlight .ni { color: #d0d0d0 } /* Name.Entity */ 41 | .code-highlight .highlight .ne { color: #bbbbbb } /* Name.Exception */ 42 | .code-highlight .highlight .nf { color: #447fcf } /* Name.Function */ 43 | .code-highlight .highlight .nl { color: #d0d0d0 } /* Name.Label */ 44 | .code-highlight .highlight .nn { color: #447fcf; text-decoration: underline } /* Name.Namespace */ 45 | .code-highlight .highlight .nx { color: #d0d0d0 } /* Name.Other */ 46 | .code-highlight .highlight .py { color: #d0d0d0 } /* Name.Property */ 47 | .code-highlight .highlight .nt { color: #6ab825; font-weight: bold } /* Name.Tag */ 48 | .code-highlight .highlight .nv { color: #40ffff } /* Name.Variable */ 49 | .code-highlight .highlight .ow { color: #6ab825; font-weight: bold } /* Operator.Word */ 50 | .code-highlight .highlight .w { color: #666666 } /* Text.Whitespace */ 51 | .code-highlight .highlight .mf { color: #3677a9 } /* Literal.Number.Float */ 52 | .code-highlight .highlight .mh { color: #3677a9 } /* Literal.Number.Hex */ 53 | .code-highlight .highlight .mi { color: #3677a9 } /* Literal.Number.Integer */ 54 | .code-highlight .highlight .mo { color: #3677a9 } /* Literal.Number.Oct */ 55 | .code-highlight .highlight .sb { color: #ed9d13 } /* Literal.String.Backtick */ 56 | .code-highlight .highlight .sc { color: #ed9d13 } /* Literal.String.Char */ 57 | .code-highlight .highlight .sd { color: #ed9d13 } /* Literal.String.Doc */ 58 | .code-highlight .highlight .s2 { color: #ed9d13 } /* Literal.String.Double */ 59 | .code-highlight .highlight .se { color: #ed9d13 } /* Literal.String.Escape */ 60 | .code-highlight .highlight .sh { color: #ed9d13 } /* Literal.String.Heredoc */ 61 | .code-highlight .highlight .si { color: #ed9d13 } /* Literal.String.Interpol */ 62 | .code-highlight .highlight .sx { color: #ffa500 } /* Literal.String.Other */ 63 | .code-highlight .highlight .sr { color: #ed9d13 } /* Literal.String.Regex */ 64 | .code-highlight .highlight .s1 { color: #ed9d13 } /* Literal.String.Single */ 65 | .code-highlight .highlight .ss { color: #ed9d13 } /* Literal.String.Symbol */ 66 | .code-highlight .highlight .bp { color: #24909d } /* Name.Builtin.Pseudo */ 67 | .code-highlight .highlight .vc { color: #40ffff } /* Name.Variable.Class */ 68 | .code-highlight .highlight .vg { color: #40ffff } /* Name.Variable.Global */ 69 | .code-highlight .highlight .vi { color: #40ffff } /* Name.Variable.Instance */ 70 | .code-highlight .highlight .il { color: #3677a9 } /* Literal.Number.Integer.Long */ 71 | -------------------------------------------------------------------------------- /app/static/css/syntax/tango.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight .hll { background-color: #ffffcc } 2 | .code-highlight .highlight .c { color: #8f5902; font-style: italic } /* Comment */ 3 | .code-highlight .highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ 4 | .code-highlight .highlight .g { color: #000000 } /* Generic */ 5 | .code-highlight .highlight .k { color: #204a87; font-weight: bold } /* Keyword */ 6 | .code-highlight .highlight .l { color: #000000 } /* Literal */ 7 | .code-highlight .highlight .n { color: #000000 } /* Name */ 8 | .code-highlight .highlight .o { color: #ce5c00; font-weight: bold } /* Operator */ 9 | .code-highlight .highlight .x { color: #000000 } /* Other */ 10 | .code-highlight .highlight .p { color: #000000; font-weight: bold } /* Punctuation */ 11 | .code-highlight .highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ 12 | .code-highlight .highlight .cp { color: #8f5902; font-style: italic } /* Comment.Preproc */ 13 | .code-highlight .highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ 14 | .code-highlight .highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ 15 | .code-highlight .highlight .gd { color: #a40000 } /* Generic.Deleted */ 16 | .code-highlight .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ 17 | .code-highlight .highlight .gr { color: #ef2929 } /* Generic.Error */ 18 | .code-highlight .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 19 | .code-highlight .highlight .gi { color: #00A000 } /* Generic.Inserted */ 20 | .code-highlight .highlight .go { color: #000000; font-style: italic } /* Generic.Output */ 21 | .code-highlight .highlight .gp { color: #8f5902 } /* Generic.Prompt */ 22 | .code-highlight .highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ 23 | .code-highlight .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 24 | .code-highlight .highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ 25 | .code-highlight .highlight .kc { color: #204a87; font-weight: bold } /* Keyword.Constant */ 26 | .code-highlight .highlight .kd { color: #204a87; font-weight: bold } /* Keyword.Declaration */ 27 | .code-highlight .highlight .kn { color: #204a87; font-weight: bold } /* Keyword.Namespace */ 28 | .code-highlight .highlight .kp { color: #204a87; font-weight: bold } /* Keyword.Pseudo */ 29 | .code-highlight .highlight .kr { color: #204a87; font-weight: bold } /* Keyword.Reserved */ 30 | .code-highlight .highlight .kt { color: #204a87; font-weight: bold } /* Keyword.Type */ 31 | .code-highlight .highlight .ld { color: #000000 } /* Literal.Date */ 32 | .code-highlight .highlight .m { color: #0000cf; font-weight: bold } /* Literal.Number */ 33 | .code-highlight .highlight .s { color: #4e9a06 } /* Literal.String */ 34 | .code-highlight .highlight .na { color: #c4a000 } /* Name.Attribute */ 35 | .code-highlight .highlight .nb { color: #204a87 } /* Name.Builtin */ 36 | .code-highlight .highlight .nc { color: #000000 } /* Name.Class */ 37 | .code-highlight .highlight .no { color: #000000 } /* Name.Constant */ 38 | .code-highlight .highlight .nd { color: #5c35cc; font-weight: bold } /* Name.Decorator */ 39 | .code-highlight .highlight .ni { color: #ce5c00 } /* Name.Entity */ 40 | .code-highlight .highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ 41 | .code-highlight .highlight .nf { color: #000000 } /* Name.Function */ 42 | .code-highlight .highlight .nl { color: #f57900 } /* Name.Label */ 43 | .code-highlight .highlight .nn { color: #000000 } /* Name.Namespace */ 44 | .code-highlight .highlight .nx { color: #000000 } /* Name.Other */ 45 | .code-highlight .highlight .py { color: #000000 } /* Name.Property */ 46 | .code-highlight .highlight .nt { color: #204a87; font-weight: bold } /* Name.Tag */ 47 | .code-highlight .highlight .nv { color: #000000 } /* Name.Variable */ 48 | .code-highlight .highlight .ow { color: #204a87; font-weight: bold } /* Operator.Word */ 49 | .code-highlight .highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ 50 | .code-highlight .highlight .mf { color: #0000cf; font-weight: bold } /* Literal.Number.Float */ 51 | .code-highlight .highlight .mh { color: #0000cf; font-weight: bold } /* Literal.Number.Hex */ 52 | .code-highlight .highlight .mi { color: #0000cf; font-weight: bold } /* Literal.Number.Integer */ 53 | .code-highlight .highlight .mo { color: #0000cf; font-weight: bold } /* Literal.Number.Oct */ 54 | .code-highlight .highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ 55 | .code-highlight .highlight .sc { color: #4e9a06 } /* Literal.String.Char */ 56 | .code-highlight .highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ 57 | .code-highlight .highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ 58 | .code-highlight .highlight .se { color: #4e9a06 } /* Literal.String.Escape */ 59 | .code-highlight .highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ 60 | .code-highlight .highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ 61 | .code-highlight .highlight .sx { color: #4e9a06 } /* Literal.String.Other */ 62 | .code-highlight .highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ 63 | .code-highlight .highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ 64 | .code-highlight .highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ 65 | .code-highlight .highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ 66 | .code-highlight .highlight .vc { color: #000000 } /* Name.Variable.Class */ 67 | .code-highlight .highlight .vg { color: #000000 } /* Name.Variable.Global */ 68 | .code-highlight .highlight .vi { color: #000000 } /* Name.Variable.Instance */ 69 | .code-highlight .highlight .il { color: #0000cf; font-weight: bold } /* Literal.Number.Integer.Long */ 70 | -------------------------------------------------------------------------------- /app/static/css/syntax/fruity.css: -------------------------------------------------------------------------------- 1 | .code-highlight .highlight pre { background-color: #49483e } 2 | .code-highlight .highlight .hll { background-color: #333333 } 3 | .code-highlight .highlight .c { color: #008800; font-style: italic; background-color: #0f140f } /* Comment */ 4 | .code-highlight .highlight .err { color: #ffffff } /* Error */ 5 | .code-highlight .highlight .g { color: #ffffff } /* Generic */ 6 | .code-highlight .highlight .k { color: #fb660a; font-weight: bold } /* Keyword */ 7 | .code-highlight .highlight .l { color: #ffffff } /* Literal */ 8 | .code-highlight .highlight .n { color: #ffffff } /* Name */ 9 | .code-highlight .highlight .o { color: #ffffff } /* Operator */ 10 | .code-highlight .highlight .x { color: #ffffff } /* Other */ 11 | .code-highlight .highlight .p { color: #ffffff } /* Punctuation */ 12 | .code-highlight .highlight .cm { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Multiline */ 13 | .code-highlight .highlight .cp { color: #ff0007; font-weight: bold; font-style: italic; background-color: #0f140f } /* Comment.Preproc */ 14 | .code-highlight .highlight .c1 { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Single */ 15 | .code-highlight .highlight .cs { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Special */ 16 | .code-highlight .highlight .gd { color: #ffffff } /* Generic.Deleted */ 17 | .code-highlight .highlight .ge { color: #ffffff } /* Generic.Emph */ 18 | .code-highlight .highlight .gr { color: #ffffff } /* Generic.Error */ 19 | .code-highlight .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ 20 | .code-highlight .highlight .gi { color: #ffffff } /* Generic.Inserted */ 21 | .code-highlight .highlight .go { color: #444444; background-color: #222222 } /* Generic.Output */ 22 | .code-highlight .highlight .gp { color: #ffffff } /* Generic.Prompt */ 23 | .code-highlight .highlight .gs { color: #ffffff } /* Generic.Strong */ 24 | .code-highlight .highlight .gu { color: #ffffff; font-weight: bold } /* Generic.Subheading */ 25 | .code-highlight .highlight .gt { color: #ffffff } /* Generic.Traceback */ 26 | .code-highlight .highlight .kc { color: #fb660a; font-weight: bold } /* Keyword.Constant */ 27 | .code-highlight .highlight .kd { color: #fb660a; font-weight: bold } /* Keyword.Declaration */ 28 | .code-highlight .highlight .kn { color: #fb660a; font-weight: bold } /* Keyword.Namespace */ 29 | .code-highlight .highlight .kp { color: #fb660a } /* Keyword.Pseudo */ 30 | .code-highlight .highlight .kr { color: #fb660a; font-weight: bold } /* Keyword.Reserved */ 31 | .code-highlight .highlight .kt { color: #cdcaa9; font-weight: bold } /* Keyword.Type */ 32 | .code-highlight .highlight .ld { color: #ffffff } /* Literal.Date */ 33 | .code-highlight .highlight .m { color: #0086f7; font-weight: bold } /* Literal.Number */ 34 | .code-highlight .highlight .s { color: #0086d2 } /* Literal.String */ 35 | .code-highlight .highlight .na { color: #ff0086; font-weight: bold } /* Name.Attribute */ 36 | .code-highlight .highlight .nb { color: #ffffff } /* Name.Builtin */ 37 | .code-highlight .highlight .nc { color: #ffffff } /* Name.Class */ 38 | .code-highlight .highlight .no { color: #0086d2 } /* Name.Constant */ 39 | .code-highlight .highlight .nd { color: #ffffff } /* Name.Decorator */ 40 | .code-highlight .highlight .ni { color: #ffffff } /* Name.Entity */ 41 | .code-highlight .highlight .ne { color: #ffffff } /* Name.Exception */ 42 | .code-highlight .highlight .nf { color: #ff0086; font-weight: bold } /* Name.Function */ 43 | .code-highlight .highlight .nl { color: #ffffff } /* Name.Label */ 44 | .code-highlight .highlight .nn { color: #ffffff } /* Name.Namespace */ 45 | .code-highlight .highlight .nx { color: #ffffff } /* Name.Other */ 46 | .code-highlight .highlight .py { color: #ffffff } /* Name.Property */ 47 | .code-highlight .highlight .nt { color: #fb660a; font-weight: bold } /* Name.Tag */ 48 | .code-highlight .highlight .nv { color: #fb660a } /* Name.Variable */ 49 | .code-highlight .highlight .ow { color: #ffffff } /* Operator.Word */ 50 | .code-highlight .highlight .w { color: #888888 } /* Text.Whitespace */ 51 | .code-highlight .highlight .mf { color: #0086f7; font-weight: bold } /* Literal.Number.Float */ 52 | .code-highlight .highlight .mh { color: #0086f7; font-weight: bold } /* Literal.Number.Hex */ 53 | .code-highlight .highlight .mi { color: #0086f7; font-weight: bold } /* Literal.Number.Integer */ 54 | .code-highlight .highlight .mo { color: #0086f7; font-weight: bold } /* Literal.Number.Oct */ 55 | .code-highlight .highlight .sb { color: #0086d2 } /* Literal.String.Backtick */ 56 | .code-highlight .highlight .sc { color: #0086d2 } /* Literal.String.Char */ 57 | .code-highlight .highlight .sd { color: #0086d2 } /* Literal.String.Doc */ 58 | .code-highlight .highlight .s2 { color: #0086d2 } /* Literal.String.Double */ 59 | .code-highlight .highlight .se { color: #0086d2 } /* Literal.String.Escape */ 60 | .code-highlight .highlight .sh { color: #0086d2 } /* Literal.String.Heredoc */ 61 | .code-highlight .highlight .si { color: #0086d2 } /* Literal.String.Interpol */ 62 | .code-highlight .highlight .sx { color: #0086d2 } /* Literal.String.Other */ 63 | .code-highlight .highlight .sr { color: #0086d2 } /* Literal.String.Regex */ 64 | .code-highlight .highlight .s1 { color: #0086d2 } /* Literal.String.Single */ 65 | .code-highlight .highlight .ss { color: #0086d2 } /* Literal.String.Symbol */ 66 | .code-highlight .highlight .bp { color: #ffffff } /* Name.Builtin.Pseudo */ 67 | .code-highlight .highlight .vc { color: #fb660a } /* Name.Variable.Class */ 68 | .code-highlight .highlight .vg { color: #fb660a } /* Name.Variable.Global */ 69 | .code-highlight .highlight .vi { color: #fb660a } /* Name.Variable.Instance */ 70 | .code-highlight .highlight .il { color: #0086f7; font-weight: bold } /* Literal.Number.Integer.Long */ 71 | -------------------------------------------------------------------------------- /app/models/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | from slugify import slugify 5 | from sqlalchemy import desc 6 | from itsdangerous import TimedJSONWebSignatureSerializer as Serializer 7 | from itsdangerous import SignatureExpired, BadSignature 8 | from werkzeug.security import generate_password_hash, check_password_hash 9 | 10 | from app.models import Post 11 | from app.utils import markdown_renderer 12 | from app import app, db, login_manager 13 | 14 | SYNTAX_HIGHLIGHTER_CHOICES = [ 15 | 'autumn.css', 'borland.css', 'bw.css', 'colorful.css', 'default.css', 'emacs.css', 'friendly.css', 'fruity.css', 16 | 'github.css', 'manni.css', 'monokai.css', 'murphy.css', 'native.css', 'pastie.css', 'perldoc.css', 'tango.css', 17 | 'trac.css', 'vim.css', 'vs.css', 'zenburn.css' 18 | ] 19 | SYNTAX_HIGHLIGHTER_TUPLE = [(raw, raw.capitalize()[:-4]) for raw in SYNTAX_HIGHLIGHTER_CHOICES] 20 | 21 | 22 | class User(db.Model): 23 | """ 24 | User with blog model. Integrates with Flask-Login. 25 | 26 | id: The ID of the user. 27 | username: The username of the user (can contain any characters) 28 | password: Encrypted password 29 | superuser: Boolean to tell if the user is a superuser 30 | active: Boolean to tell if the user is active (ability to login and operate on the app) 31 | register_date: The date the user registered 32 | last_login: The date the user last logged in the app 33 | 34 | blog_slug: The slugified username. Used as subdomain 35 | blog_title: The title of the blog that can be set in the user's settings 36 | blog_description: The description of the blog that can be set in the user's settings 37 | blog_image: The image (avatar) of the blog that can be set in the user's settings using an URL 38 | blog_image_rounded: Tells whether the image of the blog should be rounded or not (user setting) 39 | 40 | blog_bg: The URL of the background of the user 41 | blog_bg_public: Tells if the background should be displayed to everyone or only the user 42 | blog_bg_repeat: Should the background be repeated ? Useful for small images 43 | blog_bg_everywhere: Use the background image everywhere on markdownblog instead of the default one 44 | blog_bg_override: Use the background everywhere even on other blogs 45 | 46 | posts: The posts associated to this blog/user 47 | """ 48 | id = db.Column(db.Integer, primary_key=True) 49 | username = db.Column(db.String(50), unique=True) 50 | password = db.Column(db.String(54)) 51 | superuser = db.Column(db.Boolean()) 52 | active = db.Column(db.Boolean()) 53 | register_date = db.Column(db.DateTime()) 54 | last_login = db.Column(db.DateTime()) 55 | linkedin_url = db.Column(db.String(200), default="") 56 | gplus_url = db.Column(db.String(200), default="") 57 | github_url = db.Column(db.String(200), default="") 58 | twitter_url = db.Column(db.String(200), default="") 59 | 60 | # Blog Related Informations 61 | blog_slug = db.Column(db.String(50), unique=True) 62 | blog_title = db.Column(db.String(50), default="Untitled Blog") 63 | blog_description = db.Column(db.String(200), default="No Description") 64 | blog_public = db.Column(db.Boolean(), default=True) 65 | 66 | # Blog Images Related 67 | blog_image = db.Column(db.String(200)) 68 | blog_image_rounded = db.Column(db.Boolean(), default=False) 69 | blog_bg = db.Column(db.String(200)) 70 | blog_bg_public = db.Column(db.Boolean(), default=False) 71 | blog_bg_repeat = db.Column(db.Boolean(), default=False) 72 | blog_bg_everywhere = db.Column(db.Boolean(), default=False) 73 | blog_bg_override = db.Column(db.Boolean(), default=False) 74 | 75 | # Blog Pagination 76 | blog_paginate = db.Column(db.Boolean(), default=False) 77 | blog_paginate_by = db.Column(db.Integer(), default=10) 78 | 79 | # Blog Design 80 | blog_truncate_posts = db.Column(db.Boolean(), default=False) 81 | blog_syntax_highlighter_css = db.Column(db.Enum(*SYNTAX_HIGHLIGHTER_CHOICES), default='monokai.css') 82 | 83 | posts = db.relationship('Post', backref='user', lazy='dynamic') 84 | 85 | def __init__(self, active=True, superuser=False, api_purpose=False, **kwargs): 86 | """ 87 | :param username: The username of the user, will become the blog subdomain once slugified. 88 | :param password: The raw password to be encrypted and stored. 89 | :param active: To change once postfix is setup and app can send mails. 90 | :param superuser: Set if the user is a superuser (currently no use for that) 91 | :param api_purpose: If using this with Marshmallow, do not bother to generate a password. 92 | """ 93 | super(User, self).__init__(active=active, superuser=superuser, **kwargs) 94 | now = datetime.utcnow() 95 | if not api_purpose: 96 | self.set_password(self.password) 97 | self.register_date = now 98 | self.last_login = now 99 | self.blog_slug = slugify(self.username) 100 | 101 | @property 102 | def total_pages(self): 103 | """ 104 | Property that returns the minimum number of pages to display all the articles (posts) 105 | :return: int 106 | """ 107 | count = self.posts.count() 108 | if count % self.blog_paginate_by == 0: 109 | return int(count / self.blog_paginate_by) 110 | else: 111 | return int(count / self.blog_paginate_by) + 1 112 | 113 | @property 114 | def pages_as_list(self): 115 | """ 116 | Returns a list of integers representing the available pages 117 | """ 118 | return list(range(1, self.total_pages + 1)) 119 | 120 | def get_page(self, page): 121 | if not page < self.total_pages: 122 | return None 123 | else: 124 | return list(self.posts.order_by(desc(Post.pub_date)).all())[self.blog_paginate_by * page: self.blog_paginate_by * (page+1)] 125 | 126 | def get_all_posts(self): 127 | return list(self.posts.order_by(desc(Post.pub_date)).all()) 128 | 129 | def save(self): 130 | db.session.add(self) 131 | try: 132 | db.session.commit() 133 | except Exception as e: 134 | app.logger.exception("Something went wrong while saving a user") 135 | db.session.rollback() 136 | return False 137 | return True 138 | 139 | def delete(self): 140 | db.session.delete(self) 141 | try: 142 | db.session.commit() 143 | except Exception as e: 144 | app.logger.exception("Something went wrong while deleting a user") 145 | db.session.rollback() 146 | return False 147 | return True 148 | 149 | def is_superuser(self): 150 | return self.superuser 151 | 152 | def set_password(self, password): 153 | self.password = generate_password_hash(password) 154 | 155 | def check_password(self, password): 156 | return check_password_hash(self.password, password) 157 | 158 | @property 159 | def is_authenticated(self): 160 | return True 161 | 162 | @property 163 | def is_active(self): 164 | return self.active 165 | 166 | @property 167 | def is_anonymous(self): 168 | return False 169 | 170 | def get_id(self): 171 | return self.id 172 | 173 | def generate_auth_token(self, expiration=6000): 174 | s = Serializer(app.config['SECRET_KEY'], expires_in=expiration) 175 | return s.dumps({'id': self.id}) 176 | 177 | @staticmethod 178 | def verify_auth_token(token): 179 | s = Serializer(app.config['SECRET_KEY']) 180 | try: 181 | data = s.loads(token) 182 | except SignatureExpired: 183 | return None 184 | except BadSignature: 185 | return None 186 | user = User.query.get(data['id']) 187 | return user 188 | 189 | def description_as_html(self): 190 | return markdown_renderer.render(self.blog_description) 191 | 192 | def __repr__(self): 193 | return self.username 194 | 195 | def __str__(self): 196 | return self.username 197 | 198 | 199 | @login_manager.user_loader 200 | def load_user(user_id): 201 | return db.session.query(User).get(user_id) 202 | --------------------------------------------------------------------------------