├── .gitignore ├── README.md ├── _config.yml ├── about.md ├── config.py ├── create_db.py ├── example.md ├── links.md ├── publish.py ├── requirements.txt ├── run.py └── weblog ├── __init__.py ├── database.py ├── models.py ├── static ├── favicon.ico ├── fonts_googleapi.css └── style.css ├── templates ├── _pagination.html ├── article.html ├── errors.html ├── index.html ├── layout.html ├── page.html ├── tag.html └── tags.html ├── utils.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | ENV/ 3 | temp/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WeBlog 2 | ====== 3 | 4 | A simple blog system based on [Flask](http://flask.pocoo.org/) 5 | 6 | 其他网站推荐 Recommendations 7 | ===== 8 | 9 | [木兰词](https://www.mulanci.org/):中文歌词网站 10 | 11 | [初心阅读](https://www.bemind.site/): 中文短篇阅读推荐 12 | 13 | [Vinyl World](https://www.vinylworld.org/): 关于黑胶唱片、唱片店的爱好者网站 14 | 15 | 16 | Quick Start 17 | ----------- 18 | 19 | ```` 20 | $ git clone https://github.com/ghostrong/weblog.git 21 | $ cd weblog 22 | $ pip install -r requirements.txt 23 | $ python run.py 24 | ```` 25 | 26 | Now, visit `http://127.0.0.1:8888` in a browser. 27 | 28 | Requirements 29 | ------------ 30 | 31 | #### For the server-side 32 | 33 | * Flask 34 | * Flask-SQLAlchemy 35 | * itsdangerous 36 | * Jinja2 37 | * Markdown 38 | * MarkupSafe 39 | * SQLAlchemy 40 | * Werkzeug 41 | 42 | #### For the cline-side (for publishing articles automatically) 43 | 44 | * requests 45 | * lxml 46 | * PyYAML 47 | * Markdown 48 | 49 | 50 | Writing blogs 51 | ------------- 52 | 53 | You should write articles in [markdown](http://daringfireball.net/projects/markdown/), and provide the meta information such as title, tags in [YAML](http://yaml.org/). You should put the meta data in the header lines of the markdown file. Here is an [example](https://raw.githubusercontent.com/ghostrong/weblog/master/example.md). 54 | 55 | #### Meta Data 56 | 57 | * **title** 58 | 59 | You must give the title the the blog. e.g., 60 | 61 | ```` 62 | title: The Zen of Python 63 | ```` 64 | 65 | * **summary** (optional) 66 | 67 | It's the abstract of the article. If you ignore it, the publish helper will generate the 68 | summary snippet from the body automatically. e.g., 69 | 70 | ```` 71 | summary: 72 | Long time Pythoneer Tim Peters succinctly channels the BDFL's 73 | guiding principles for Python's design into 20 aphorisms, only 19 74 | of which have been written down. 75 | ```` 76 | 77 | * **pub_time** (optional) 78 | 79 | You can define the publish datetime manually. Or, the system will assign the current 80 | datetime. The default format is "%Y-%m-%d %H:%M:%S" (such as "2015-06-06 12:40:10"). 81 | You could define the time string format by the value of *TIME_FORMAT* in 82 | [config.py](config.py), 83 | and you should conform to the 84 | [format codes](https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior). e.g., 85 | 86 | ```` 87 | pub_time: 2015-06-06 12:40:10 88 | ```` 89 | 90 | * **tags** (optional) 91 | 92 | It's useful to assign tags to an article. The value type should be list in YAML. e.g., 93 | 94 | ```` 95 | tags: 96 | - python 97 | - programming 98 | ```` 99 | 100 | #### The boundry of meta 101 | 102 | The meta data should be located between '---' and '...'. 103 | The following is an example of meta infomration: 104 | 105 | ```` 106 | --- 107 | title: The Zen of Python 108 | summary: 109 | Long time Pythoneer Tim Peters succinctly channels the BDFL's 110 | guiding principles for Python's design into 20 aphorisms, only 19 111 | of which have been written down. 112 | tags: 113 | - python 114 | - programming 115 | ... 116 | 117 | You should write the body content from here... 118 | ```` 119 | 120 | 121 | Publishing blogs 122 | ---------------- 123 | 124 | We provide a simple script to make the publishing work easy. Run `publish.py` to check the help message. 125 | 126 | ```` 127 | $ python publish 128 | 129 | usage: publish.py [-h] [-p PATH] [-a API] [-t TOKEN] 130 | 131 | optional arguments: 132 | -h, --help show this help message and exit 133 | -p PATH, --path PATH markdown file path/url 134 | -a API, --api API api address 135 | -t TOKEN, --token TOKEN 136 | access token 137 | ```` 138 | 139 | You should provide the markdown file(either file-path or raw-url), the target api, and the access token. In this blog system, the publish url is `/publish`. The access token is the value of *TOKEN* in [config.py](config.py). 140 | **Anyone who know the token could publish articles to your blog system, so keep it secret!!** 141 | 142 | After starting the web server locally, you can publish an article like this: 143 | 144 | ```` 145 | $ python publish.py -a http://127.0.0.1:8888/publish -p example.md 146 | ```` 147 | 148 | 149 | Features 150 | -------- 151 | * Writing blogs in Markdown and YAML 152 | * Browsing blogs by PAGE or TAG 153 | * Neat templates :-) 154 | * Search (TBD) 155 | 156 | 157 | TODO 158 | ---- 159 | * Keyword-based Search 160 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | About Me 2 | ======== 3 | 4 | My name is Xiaosong Rong. I am interested in search engine, data mining, crawler, web development, and many other topics. I am now living in Beijing, China. I just graduated from Peking University. 5 | 6 | If you want to know more about me, you can reach me by: 7 | 8 | * [email](mailto:rongxiaosong@gmail.com) 9 | * [github](https://github.com/ghostrong/) 10 | * [linkedin](https://cn.linkedin.com/pub/xiaosong-rong/45/34/9b5) 11 | * [weibo](weibo.com/rongxs) 12 | 13 | By the way, the nicknames I used include **cedar**, **cedarrong**, **ghostrong**, **马孔多**, and **Rong** :-) 14 | 15 | 16 | About this Website 17 | ------------------ 18 | The website is written in Python, and involves several great open source libraties such as [Flask](flask.pocoo.org/), [Markdown](http://pythonhosted.org/Markdown/) and [Requests](python-requests.org). The webpage templates were *stealed* from [mitsuhiko](http://lucumr.pocoo.org/). Source codes can be found on [github](https://github.com/ghostrong/weblog). 19 | 20 | *Thanks for reading!* 21 | 22 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | 3 | SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/test.db' 4 | 5 | AUTHOR = 'Rong' 6 | 7 | # this is useful for submitting markdow files 8 | # when publish new articles 9 | TIME_FORMAT = '%Y-%m-%d %H:%M:%S' 10 | 11 | TOKEN = '123456' 12 | -------------------------------------------------------------------------------- /create_db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from weblog.database import db 4 | 5 | if __name__ == '__main__': 6 | db.create_all() 7 | -------------------------------------------------------------------------------- /example.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The Zen of Python 3 | summary: 4 | Long time Pythoneer Tim Peters succinctly channels the BDFL's 5 | guiding principles for Python's design into 20 aphorisms, only 19 6 | of which have been written down. 7 | tags: 8 | - python 9 | - programming 10 | ... 11 | 12 | The Zen of Python 13 | ================= 14 | 15 | 16 | The Zen of Python, by Tim Peters 17 | 18 | Beautiful is better than ugly. 19 | Explicit is better than implicit. 20 | Simple is better than complex. 21 | Complex is better than complicated. 22 | Flat is better than nested. 23 | Sparse is better than dense. 24 | Readability counts. 25 | Special cases aren't special enough to break the rules. 26 | Although practicality beats purity. 27 | Errors should never pass silently. 28 | Unless explicitly silenced. 29 | In the face of ambiguity, refuse the temptation to guess. 30 | There should be one-- and preferably only one --obvious way to do it. 31 | Although that way may not be obvious at first unless you're Dutch. 32 | Now is better than never. 33 | Although never is often better than *right* now. 34 | If the implementation is hard to explain, it's a bad idea. 35 | If the implementation is easy to explain, it may be a good idea. 36 | Namespaces are one honking great idea -- let's do more of those! 37 | 38 | [original](https://www.python.org/dev/peps/pep-0020/) -------------------------------------------------------------------------------- /links.md: -------------------------------------------------------------------------------- 1 | Links 2 | ===== 3 | 4 | * [Pocoo](http://www.pocoo.org/) 5 | -------------------------------------------------------------------------------- /publish.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf8 3 | 4 | """ A simple script for summitting articles written 5 | in markdown. 6 | """ 7 | 8 | 9 | import argparse 10 | import json 11 | from StringIO import StringIO 12 | import re 13 | import requests 14 | import yaml 15 | import markdown 16 | from lxml import html 17 | 18 | 19 | def _get_file(path): 20 | if (path.startswith('http://') or path.startswith('https://') or 21 | path.startswith('ftp://')): 22 | r = requests.get(path) 23 | return StringIO(r.content) 24 | return open(path) 25 | 26 | 27 | def _gen_summary(mdtext, n=120): 28 | htmltext = markdown.markdown(mdtext) 29 | tree = html.fromstring(htmltext) 30 | node = tree.xpath('.')[0] 31 | text = re.sub(ur'\s+', ' ', node.text_content()).strip() 32 | return text[:n] + ' ...' 33 | 34 | 35 | def publish(stream, api, token): 36 | """ Publishing a new article from `stream`""" 37 | 38 | headers = [] 39 | for line in stream: 40 | if not line: 41 | break 42 | line = line.rstrip() 43 | headers.append(line) 44 | if line == '...': 45 | break 46 | 47 | cfg = yaml.load(StringIO('\n'.join(headers))) 48 | if not cfg: 49 | raise ValueError('no valid yaml config informations') 50 | if 'title' not in cfg: 51 | raise ValueError('no title found') 52 | if 'tags' in cfg and not isinstance(cfg['tags'], list): 53 | raise ValueError('invalid tags: it should be list') 54 | 55 | bodies = [] 56 | for line in stream: 57 | bodies.append(line.rstrip()) 58 | content = '\n'.join(bodies).strip().decode('utf8') 59 | if not content: 60 | raise ValueError('no content found') 61 | 62 | data = { 63 | 'token': token, 64 | 'title': cfg['title'], 65 | 'summary': cfg.get('summary', None) or _gen_summary(content), 66 | 'content': content, 67 | } 68 | if 'tags' in cfg: 69 | data['tags'] = cfg['tags'] 70 | if 'pub_time' in cfg: 71 | data['pub_time'] = cfg['pub_time'] 72 | 73 | r = requests.post(api, data=data) 74 | assert r.status_code == 200, r.content 75 | 76 | 77 | def main(): 78 | parser = argparse.ArgumentParser() 79 | parser.add_argument('-p', '--path', help='markdown file path/url') 80 | parser.add_argument('-a', '--api', help='api address') 81 | parser.add_argument('-t', '--token', help='access token') 82 | args = parser.parse_args() 83 | 84 | if not args.path or not args.api or not args.token: 85 | parser.print_help() 86 | return 87 | 88 | stream = _get_file(args.path) 89 | publish(stream, args.api, args.token) 90 | 91 | 92 | if __name__ == '__main__': 93 | main() 94 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.0.2 2 | Flask-SQLAlchemy==2.0 3 | itsdangerous==0.24 4 | Jinja2==2.7.3 5 | lxml==3.4.4 6 | Markdown==2.6.2 7 | MarkupSafe==0.23 8 | pyyaml>=4.2b1 9 | requests==2.21.0 10 | SQLAlchemy==1.0.4 11 | Werkzeug==0.10.4 12 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf8 3 | 4 | from weblog import app 5 | 6 | if __name__ == '__main__': 7 | app.run(host='0.0.0.0', port=8888, debug=True) 8 | -------------------------------------------------------------------------------- /weblog/__init__.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | 3 | from flask import Flask 4 | 5 | app = Flask(__name__) 6 | app.config.from_object('config') 7 | from . import utils 8 | from . import views 9 | 10 | -------------------------------------------------------------------------------- /weblog/database.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | 3 | from sqlalchemy.pool import NullPool 4 | from flask.ext.sqlalchemy import SQLAlchemy 5 | from . import app 6 | 7 | 8 | """ 9 | class NullPoolSQLAlchemy(SQLAlchemy): 10 | def apply_driver_hacks(self, app, info, options): 11 | super(NullPoolSQLAlchemy, self).apply_driver_hacks(app, info, options) 12 | options['poolclass'] = NullPool 13 | del options['pool_size'] 14 | 15 | db = NullPoolSQLAlchemy(app) 16 | """ 17 | 18 | db = SQLAlchemy(app) 19 | -------------------------------------------------------------------------------- /weblog/models.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | 3 | from datetime import datetime 4 | from .database import db 5 | 6 | 7 | articles_tags = db.Table( 8 | 'articles_tags', 9 | db.Column('tag_id', db.Integer, db.ForeignKey('tags.id')), 10 | db.Column('article_id', db.Integer, db.ForeignKey('articles.id'))) 11 | 12 | 13 | class Article(db.Model): 14 | __tablename__ = 'articles' 15 | 16 | id = db.Column(db.Integer, primary_key=True) 17 | title = db.Column(db.String(100)) 18 | summary = db.Column(db.String(300)) 19 | content = db.Column(db.Text) 20 | pub_time = db.Column(db.DateTime, default=datetime.now) 21 | tags = db.relationship('Tag', 22 | secondary=articles_tags, 23 | backref=db.backref('articles', lazy='dynamic')) 24 | 25 | def __init__(self, title, summary, content, pub_time=None): 26 | self.title = title 27 | self.summary = summary 28 | self.content = content 29 | if pub_time: 30 | self.pub_time = pub_time 31 | 32 | def save(self): 33 | db.session.add(self) 34 | db.session.commit() 35 | return self 36 | 37 | 38 | class Tag(db.Model): 39 | __tablename__ = 'tags' 40 | 41 | id = db.Column(db.Integer, primary_key=True) 42 | name = db.Column(db.String(50), unique=True) 43 | 44 | def __init__(self, name): 45 | self.name = name.lower() 46 | 47 | def save(self): 48 | db.session.add(self) 49 | db.session.commit() 50 | return self 51 | 52 | 53 | def _get_tag(name): 54 | name = name.lower() 55 | tag = db.session.query(Tag).filter(Tag.name==name).first() 56 | if not tag: 57 | tag = Tag(name) 58 | tag.save() 59 | return tag 60 | 61 | 62 | def create_article(title, summary, content, pub_time=None, tagnames=[]): 63 | article = Article(title, summary, content, pub_time) 64 | for tagname in tagnames: 65 | tag = _get_tag(tagname.lower()) 66 | article.tags.append(tag) 67 | article.save() 68 | return article 69 | -------------------------------------------------------------------------------- /weblog/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/songron/weblog/ef69b39f37bd5ca72313a07e02dcf0f71561dc35/weblog/static/favicon.ico -------------------------------------------------------------------------------- /weblog/static/fonts_googleapi.css: -------------------------------------------------------------------------------- 1 | /* latin */ 2 | @font-face { 3 | font-family: 'Crimson Text'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Crimson Text'), local('CrimsonText-Roman'), url(http://fonts.gstatic.com/s/crimsontext/v6/3IFMwfRa07i-auYR-B-zNRampu5_7CjHW5spxoeN3Vs.woff2) format('woff2'); 7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 8 | } 9 | /* latin */ 10 | @font-face { 11 | font-family: 'Crimson Text'; 12 | font-style: normal; 13 | font-weight: 600; 14 | src: local('Crimson Text Semibold'), local('CrimsonText-Semibold'), url(http://fonts.gstatic.com/s/crimsontext/v6/rEy5tGc5HdXy56Xvd4f3I93uLUHnU24AL_1IdxwhTqs.woff2) format('woff2'); 15 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 16 | } 17 | /* latin */ 18 | @font-face { 19 | font-family: 'Crimson Text'; 20 | font-style: italic; 21 | font-weight: 400; 22 | src: local('Crimson Text Italic'), local('CrimsonText-Italic'), url(http://fonts.gstatic.com/s/crimsontext/v6/a5QZnvmn5amyNI-t2BMkWGfrnYWAzH6tTbHZfcsRIsM.woff2) format('woff2'); 23 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 24 | } 25 | /* latin */ 26 | @font-face { 27 | font-family: 'Crimson Text'; 28 | font-style: italic; 29 | font-weight: 600; 30 | src: local('Crimson Text Semibold Italic'), local('CrimsonText-SemiboldItalic'), url(http://fonts.gstatic.com/s/crimsontext/v6/4j4TR-EfnvCt43InYpUNDEGjWynV6JUy6bTtUoDwWRw.woff2) format('woff2'); 31 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 32 | } 33 | /* latin-ext */ 34 | @font-face { 35 | font-family: 'Merriweather'; 36 | font-style: normal; 37 | font-weight: 300; 38 | src: local('Merriweather Light'), local('Merriweather-Light'), url(http://fonts.gstatic.com/s/merriweather/v8/ZvcMqxEwPfh2qDWBPxn6nrsKtFnhOiVZh9MDlvO1Vys.woff2) format('woff2'); 39 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 40 | } 41 | /* latin */ 42 | @font-face { 43 | font-family: 'Merriweather'; 44 | font-style: normal; 45 | font-weight: 300; 46 | src: local('Merriweather Light'), local('Merriweather-Light'), url(http://fonts.gstatic.com/s/merriweather/v8/ZvcMqxEwPfh2qDWBPxn6nkZRWJQ0UjzR2Uv6RollX_g.woff2) format('woff2'); 47 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 48 | } 49 | /* latin-ext */ 50 | @font-face { 51 | font-family: 'Merriweather'; 52 | font-style: normal; 53 | font-weight: 400; 54 | src: local('Merriweather'), url(http://fonts.gstatic.com/s/merriweather/v8/RFda8w1V0eDZheqfcyQ4EIjoYw3YTyktCCer_ilOlhE.woff2) format('woff2'); 55 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 56 | } 57 | /* latin */ 58 | @font-face { 59 | font-family: 'Merriweather'; 60 | font-style: normal; 61 | font-weight: 400; 62 | src: local('Merriweather'), url(http://fonts.gstatic.com/s/merriweather/v8/RFda8w1V0eDZheqfcyQ4EBampu5_7CjHW5spxoeN3Vs.woff2) format('woff2'); 63 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 64 | } 65 | /* latin-ext */ 66 | @font-face { 67 | font-family: 'Merriweather'; 68 | font-style: normal; 69 | font-weight: 700; 70 | src: local('Merriweather Bold'), local('Merriweather-Bold'), url(http://fonts.gstatic.com/s/merriweather/v8/ZvcMqxEwPfh2qDWBPxn6nkqWMeizceScn2Xpn1ZpsKI.woff2) format('woff2'); 71 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 72 | } 73 | /* latin */ 74 | @font-face { 75 | font-family: 'Merriweather'; 76 | font-style: normal; 77 | font-weight: 700; 78 | src: local('Merriweather Bold'), local('Merriweather-Bold'), url(http://fonts.gstatic.com/s/merriweather/v8/ZvcMqxEwPfh2qDWBPxn6nshHwsiXhsDb0smKjAA7Bek.woff2) format('woff2'); 79 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 80 | } 81 | /* latin-ext */ 82 | @font-face { 83 | font-family: 'Merriweather'; 84 | font-style: italic; 85 | font-weight: 300; 86 | src: local('Merriweather Light Italic'), local('Merriweather-LightItalic'), url(http://fonts.gstatic.com/s/merriweather/v8/EYh7Vl4ywhowqULgRdYwIFPx9KgpCoczUSdnmwUGkhk.woff2) format('woff2'); 87 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 88 | } 89 | /* latin */ 90 | @font-face { 91 | font-family: 'Merriweather'; 92 | font-style: italic; 93 | font-weight: 300; 94 | src: local('Merriweather Light Italic'), local('Merriweather-LightItalic'), url(http://fonts.gstatic.com/s/merriweather/v8/EYh7Vl4ywhowqULgRdYwIB0ue0Sk5cwvYx5tGiUAApw.woff2) format('woff2'); 95 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 96 | } 97 | /* latin-ext */ 98 | @font-face { 99 | font-family: 'Merriweather'; 100 | font-style: italic; 101 | font-weight: 400; 102 | src: local('Merriweather Italic'), local('Merriweather-Italic'), url(http://fonts.gstatic.com/s/merriweather/v8/So5lHxHT37p2SS4-t60SlLbeiSZn9gAT0uu8FgUa5kU.woff2) format('woff2'); 103 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 104 | } 105 | /* latin */ 106 | @font-face { 107 | font-family: 'Merriweather'; 108 | font-style: italic; 109 | font-weight: 400; 110 | src: local('Merriweather Italic'), local('Merriweather-Italic'), url(http://fonts.gstatic.com/s/merriweather/v8/So5lHxHT37p2SS4-t60SlGfrnYWAzH6tTbHZfcsRIsM.woff2) format('woff2'); 111 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 112 | } 113 | /* latin-ext */ 114 | @font-face { 115 | font-family: 'Merriweather'; 116 | font-style: italic; 117 | font-weight: 700; 118 | src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'), url(http://fonts.gstatic.com/s/merriweather/v8/EYh7Vl4ywhowqULgRdYwIFMxop41rUAeuGQqDMZDGyg.woff2) format('woff2'); 119 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 120 | } 121 | /* latin */ 122 | @font-face { 123 | font-family: 'Merriweather'; 124 | font-style: italic; 125 | font-weight: 700; 126 | src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'), url(http://fonts.gstatic.com/s/merriweather/v8/EYh7Vl4ywhowqULgRdYwIFh3o8VkC1exAYQ700cRowo.woff2) format('woff2'); 127 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 128 | } 129 | /* cyrillic-ext */ 130 | @font-face { 131 | font-family: 'Ubuntu Mono'; 132 | font-style: normal; 133 | font-weight: 400; 134 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkfZraR2Tg8w2lzm7kLNL0-w.woff2) format('woff2'); 135 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 136 | } 137 | /* cyrillic */ 138 | @font-face { 139 | font-family: 'Ubuntu Mono'; 140 | font-style: normal; 141 | font-weight: 400; 142 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkV4sYYdJg5dU2qzJEVSuta0.woff2) format('woff2'); 143 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 144 | } 145 | /* greek-ext */ 146 | @font-face { 147 | font-family: 'Ubuntu Mono'; 148 | font-style: normal; 149 | font-weight: 400; 150 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkVBW26QxpSj-_ZKm_xT4hWw.woff2) format('woff2'); 151 | unicode-range: U+1F00-1FFF; 152 | } 153 | /* greek */ 154 | @font-face { 155 | font-family: 'Ubuntu Mono'; 156 | font-style: normal; 157 | font-weight: 400; 158 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkQt_Rm691LTebKfY2ZkKSmI.woff2) format('woff2'); 159 | unicode-range: U+0370-03FF; 160 | } 161 | /* latin-ext */ 162 | @font-face { 163 | font-family: 'Ubuntu Mono'; 164 | font-style: normal; 165 | font-weight: 400; 166 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkaE8kM4xWR1_1bYURRojRGc.woff2) format('woff2'); 167 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 168 | } 169 | /* latin */ 170 | @font-face { 171 | font-family: 'Ubuntu Mono'; 172 | font-style: normal; 173 | font-weight: 400; 174 | src: local('Ubuntu Mono'), local('UbuntuMono-Regular'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ViZhet7Ak-LRXZMXzuAfkYgp9Q8gbYrhqGlRav_IXfk.woff2) format('woff2'); 175 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 176 | } 177 | /* cyrillic-ext */ 178 | @font-face { 179 | font-family: 'Ubuntu Mono'; 180 | font-style: normal; 181 | font-weight: 700; 182 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytgXaAXup5mZlfK6xRLrhsco.woff2) format('woff2'); 183 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 184 | } 185 | /* cyrillic */ 186 | @font-face { 187 | font-family: 'Ubuntu Mono'; 188 | font-style: normal; 189 | font-weight: 700; 190 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytlx-M1I1w5OMiqnVF8xBLhU.woff2) format('woff2'); 191 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 192 | } 193 | /* greek-ext */ 194 | @font-face { 195 | font-family: 'Ubuntu Mono'; 196 | font-style: normal; 197 | font-weight: 700; 198 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytlT7aJLK6nKpn36IMwTcMMc.woff2) format('woff2'); 199 | unicode-range: U+1F00-1FFF; 200 | } 201 | /* greek */ 202 | @font-face { 203 | font-family: 'Ubuntu Mono'; 204 | font-style: normal; 205 | font-weight: 700; 206 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytgn6Wqxo-xwxilDXPU8chVU.woff2) format('woff2'); 207 | unicode-range: U+0370-03FF; 208 | } 209 | /* latin-ext */ 210 | @font-face { 211 | font-family: 'Ubuntu Mono'; 212 | font-style: normal; 213 | font-weight: 700; 214 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytogd9OEPUCN3AdYW0e8tat4.woff2) format('woff2'); 215 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 216 | } 217 | /* latin */ 218 | @font-face { 219 | font-family: 'Ubuntu Mono'; 220 | font-style: normal; 221 | font-weight: 700; 222 | src: local('Ubuntu Mono Bold'), local('UbuntuMono-Bold'), url(http://fonts.gstatic.com/s/ubuntumono/v6/ceqTZGKHipo8pJj4molytv79_ZuUxCigM2DespTnFaw.woff2) format('woff2'); 223 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 224 | } 225 | /* cyrillic-ext */ 226 | @font-face { 227 | font-family: 'Ubuntu Mono'; 228 | font-style: italic; 229 | font-weight: 400; 230 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA5mXQ0gn0-UdQv02W-SySUA.woff2) format('woff2'); 231 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 232 | } 233 | /* cyrillic */ 234 | @font-face { 235 | font-family: 'Ubuntu Mono'; 236 | font-style: italic; 237 | font-weight: 400; 238 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA23PJHrlYDflBZ8jpDuHrUE.woff2) format('woff2'); 239 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 240 | } 241 | /* greek-ext */ 242 | @font-face { 243 | font-family: 'Ubuntu Mono'; 244 | font-style: italic; 245 | font-weight: 400; 246 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKAw7AHm8YTyY_rNtndvmCZcA.woff2) format('woff2'); 247 | unicode-range: U+1F00-1FFF; 248 | } 249 | /* greek */ 250 | @font-face { 251 | font-family: 'Ubuntu Mono'; 252 | font-style: italic; 253 | font-weight: 400; 254 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA4kFOucaQAyNXA33dLaGans.woff2) format('woff2'); 255 | unicode-range: U+0370-03FF; 256 | } 257 | /* latin-ext */ 258 | @font-face { 259 | font-family: 'Ubuntu Mono'; 260 | font-style: italic; 261 | font-weight: 400; 262 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKA1Yo3yjVQ1y6DauKPXl5S54.woff2) format('woff2'); 263 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 264 | } 265 | /* latin */ 266 | @font-face { 267 | font-family: 'Ubuntu Mono'; 268 | font-style: italic; 269 | font-weight: 400; 270 | src: local('Ubuntu Mono Italic'), local('UbuntuMono-Italic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/KAKuHXAHZOeECOWAHsRKAweOulFbQKHxPa89BaxZzA0.woff2) format('woff2'); 271 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 272 | } 273 | /* cyrillic-ext */ 274 | @font-face { 275 | font-family: 'Ubuntu Mono'; 276 | font-style: italic; 277 | font-weight: 700; 278 | src: local('Ubuntu Mono Bold Italic'), local('UbuntuMono-BoldItalic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/n_d8tv_JOIiYyMXR4eaV9dSb_eANQTRHtr9jD-GeTXk.woff2) format('woff2'); 279 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 280 | } 281 | /* cyrillic */ 282 | @font-face { 283 | font-family: 'Ubuntu Mono'; 284 | font-style: italic; 285 | font-weight: 700; 286 | src: local('Ubuntu Mono Bold Italic'), local('UbuntuMono-BoldItalic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/n_d8tv_JOIiYyMXR4eaV9YzXFSCU3bNUuRJbrSKO4n0.woff2) format('woff2'); 287 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 288 | } 289 | /* greek-ext */ 290 | @font-face { 291 | font-family: 'Ubuntu Mono'; 292 | font-style: italic; 293 | font-weight: 700; 294 | src: local('Ubuntu Mono Bold Italic'), local('UbuntuMono-BoldItalic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/n_d8tv_JOIiYyMXR4eaV9bdqdXatrRTGeOcQ_dgj2nk.woff2) format('woff2'); 295 | unicode-range: U+1F00-1FFF; 296 | } 297 | /* greek */ 298 | @font-face { 299 | font-family: 'Ubuntu Mono'; 300 | font-style: italic; 301 | font-weight: 700; 302 | src: local('Ubuntu Mono Bold Italic'), local('UbuntuMono-BoldItalic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/n_d8tv_JOIiYyMXR4eaV9VdvuQB9VlkRwSZGXIQMbVE.woff2) format('woff2'); 303 | unicode-range: U+0370-03FF; 304 | } 305 | /* latin-ext */ 306 | @font-face { 307 | font-family: 'Ubuntu Mono'; 308 | font-style: italic; 309 | font-weight: 700; 310 | src: local('Ubuntu Mono Bold Italic'), local('UbuntuMono-BoldItalic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/n_d8tv_JOIiYyMXR4eaV9QWM0vnr4QAyZa5IfdpZxjY.woff2) format('woff2'); 311 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 312 | } 313 | /* latin */ 314 | @font-face { 315 | font-family: 'Ubuntu Mono'; 316 | font-style: italic; 317 | font-weight: 700; 318 | src: local('Ubuntu Mono Bold Italic'), local('UbuntuMono-BoldItalic'), url(http://fonts.gstatic.com/s/ubuntumono/v6/n_d8tv_JOIiYyMXR4eaV9cyRwA4nzNmLFN68bwzDkMk.woff2) format('woff2'); 319 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 320 | } 321 | -------------------------------------------------------------------------------- /weblog/static/style.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /* fonts */ 4 | @import url(/static/fonts_googleapi.css); 5 | 6 | 7 | /* general style */ 8 | body { font: 17px/25px 'Merriweather', serif; 9 | margin: 0; padding: 0; font-weight: 400; color: black; } 10 | a { color: #398ad5; font-weight: 400; } 11 | a:hover { color: #2771b5; } 12 | 13 | /* headlines */ 14 | h1, h2, h3, h4, h5, h6 { font-family: 'Crimson Text', serif; 15 | font-weight: 400; color: #2771b5; 16 | font-style: italic; 17 | } 18 | h1 a, h2 a, h3 a, h4 a, 19 | h5 a, h6 a { text-decoration: none; } 20 | h1 a:hover, h2 a:hover, 21 | h3 a:hover, h4 a:hover { text-decoration: underline; } 22 | h1 { margin: 15px 0 25px 0; } 23 | h2 { margin: 25px 0 10px 0; } 24 | h3 { margin: 35px 0 10px 0; } 25 | h1 { font-size: 52px; line-height: 56px; } 26 | h2 { font-size: 42px; line-height: 44px; } 27 | h3 { font-size: 36px; line-height: 38px; } 28 | 29 | h1 em { color: black; font-size: 32px; display: block; 30 | margin-left: 25px; margin-top: -15px; } 31 | 32 | /* layout elements */ 33 | div.container { max-width: 880px; margin: 48px auto; padding: 0 40px; } 34 | div.header { float: left; } 35 | div.navigation { float: right; } 36 | div.header, div.navigation { height: 25px; margin-bottom: 42px; } 37 | div.navigation ul { margin: 0; padding: 0; list-style: none; } 38 | div.navigation ul li { display: inline; margin: 0 2px; padding: 0; } 39 | div.body { clear: both; margin: 0 30px; line-height: 1.6; } 40 | div.footer { margin-top: 55px; font-size: 16px; 41 | text-align: right; color: #7b8894; } 42 | div.footer p { margin: 0; } 43 | div.footer a { color: #7b8894; } 44 | 45 | /* margins and stuff */ 46 | p, div.line-block, ul, ol, pre, 47 | table { margin: 15px 0 15px 0; } 48 | dt { margin: 25px 0 16px 0; padding: 0; } 49 | dd { margin: 16px 0 25px 40px; padding: 0; } 50 | ul ol, ol ul, ul ul, ol ol { margin: 10px 0; padding: 0 0 0 40px; } 51 | li { padding: 0; } 52 | h1 + p.date { margin-top: -25px; font-style: italic; } 53 | p.page-date { text-align: right; font-style: italic; margin-top: 30px; } 54 | p.page-tags { text-align: right; font-style: italic; } 55 | blockquote { font-style: italic; } 56 | 57 | /* code formatting. no monospace because of webkit (bug?) */ 58 | pre, code, tt { font-family: 'Ubuntu Mono', 'Consolas', 'Deja Vu Sans Mono', 59 | 'Bitstream Vera Sans Mono', 'Monaco', 'Courier New'; 60 | font-size: 0.9em; } 61 | pre { line-height: 1.45; background: none; padding: 0; } 62 | code, tt { background: #eee; } 63 | 64 | /* tables */ 65 | table { border: 1px solid #ddd; border-collapse: collapse; 66 | background: #fafafa; } 67 | td, th { padding: 2px 12px; border: 1px solid #ddd; } 68 | 69 | /* footnotes */ 70 | table.footnote { margin: 25px 0; background: transparent; border: none; } 71 | table.footnote + table.footnote { margin-top: -35px; } 72 | table.footnote td { border: none; padding: 9px 0 0 0; font-size: 15px; } 73 | table.footnote td.label { padding-right: 10px; } 74 | table.footnote td p { margin: 0; } 75 | table.footnote td p + p { margin-top: 15px; } 76 | 77 | /* blog overview */ 78 | div.entry-overview { margin: 25px 122px 25px 102px; } 79 | div.entry-overview h1, 80 | div.entry-overview div.summary, 81 | div.entry-overview div.summary p { display: inline; line-height: 25px; } 82 | div.entry-overview h1 { margin: 0; font-size: 17px; font-weight: 700; 83 | font-family: 'Merriweather', serif; } 84 | div.entry-overview h1:after { content: " —"; color: black; } 85 | div.entry-overview h1 a { color: #2771b5; } 86 | div.entry-overview div.summary, 87 | div.entry-overview div.date, 88 | div.entry-overview div.summary p { margin: 0; padding: 0; } 89 | div.entry-overview div.detail { margin-left: 140px; } 90 | div.entry-overview div.date { float: left; width: 120px; color: #7b8894; 91 | text-align: right; font-style: italic; 92 | font-size: 14px; } 93 | 94 | /* other alignment things */ 95 | img.align-center { margin: 15px auto; display: block; } 96 | 97 | /* pagination */ 98 | div.pagination { margin: 36px 0 0 0; text-align: center; } 99 | div.pagination strong { font-weight: normal; font-style: italic; } 100 | 101 | /* tags */ 102 | p.tags { text-align: right; margin-top: 35px; } 103 | ul.tagcloud { font-size: 16px; margin: 36px 0; padding: 0; 104 | list-style: none; line-height: 1.45; text-align: justify } 105 | ul.tagcloud li { margin: 0; padding: 0 10px; display: inline; } 106 | 107 | /* latex math */ 108 | span.math img { margin-bottom: -7px; } 109 | 110 | /* ads */ 111 | div.adspace { 112 | text-align: center; 113 | } 114 | 115 | div.adspace iframe { 116 | margin: 25px auto 25px auto; 117 | } 118 | -------------------------------------------------------------------------------- /weblog/templates/_pagination.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /weblog/templates/article.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}{{ entry.title }}{% endblock %} 3 | {% block body %} 4 | {{ entry.title|safe }} 5 |

written on {{ format_date_weekday(entry.pub_time) }} 6 |
7 | 8 | {{ entry.content|safe }} 9 | 10 |

This entry was tagged 11 | {% for tag in entry.tags %} 12 | {%- if not loop.first and not loop.last %}, {% endif -%} 13 | {%- if loop.last and not loop.first %} and {% endif %} 14 | {{ tag.name }} 15 | {%- endfor %} 16 | 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /weblog/templates/errors.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}{{ title }}{% endblock %} 3 | {% block body %} 4 |

{{ title }}

5 |

{{ message }}

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /weblog/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}Blog{% endblock %} 3 | {% block body %} 4 | 5 | {%- for entry in pagination.items %} 6 |
7 |
{{ format_date(entry.pub_time) }}
8 |
9 |

{{ entry.title }}

10 | {% if entry.summary %} 11 |
{{ entry.summary }}
12 | {% endif %} 13 |
14 |
15 | {%- endfor %} 16 | 17 | {% include '_pagination.html' %} 18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /weblog/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block htmlhead %} 7 | {% block title %}Home{% endblock %} | {{ config.AUTHOR }}'s Thoughts and Writings 8 | 9 | {% endblock %} 10 | 11 | 12 |
13 |
14 | {{ config.AUTHOR }}'s Thoughts and Writings 15 |
16 | 24 |
25 | {% block body %}{% endblock %} 26 | 27 | 39 |
40 | 55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /weblog/templates/page.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}{{ title }}{% endblock %} 3 | {% block body %} 4 | {{ content|safe }} 5 | {% if pub_time %} 6 |

Written on {{ format_date_weekday(pub_time) }} 7 | {% endif %} 8 | 9 | {% if tags %} 10 |

This entry was tagged 11 | {% for tag in tags %} 12 | {%- if not loop.first and not loop.last %}, {% endif -%} 13 | {%- if loop.last and not loop.first %} and {% endif %} 14 | {{ tag.name }} 15 | {%- endfor %} 16 | {% endif %} 17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /weblog/templates/tag.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}Entries tagged “{{ tag.name }}”{% endblock %} 3 | {% block body %} 4 |

Entries tagged “{{ tag.name }}”

5 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /weblog/templates/tags.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}Tags{% endblock %} 3 | {% block body %} 4 |

Tags

5 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /weblog/utils.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | 3 | import markdown 4 | from datetime import datetime 5 | from . import app 6 | 7 | 8 | def format_date(dateobj): 9 | return dateobj.strftime( 10 | '%b %d,%Y') 11 | 12 | 13 | def format_date_weekday(dateobj): 14 | return dateobj.strftime( 15 | '%A %b %d,%Y') 16 | 17 | 18 | def format_datetime(dateobj): 19 | return dateobj.strftime( 20 | '%A %b %d,%Y %H:%M:%S') 21 | 22 | 23 | app.jinja_env.globals['format_date'] = format_date 24 | app.jinja_env.globals['format_date_weekday'] = format_date_weekday 25 | app.jinja_env.globals['format_datetime'] = format_datetime 26 | 27 | 28 | def markdown2html(mdtext): 29 | return markdown.markdown(mdtext) 30 | 31 | def load_content(name): 32 | with open('{}.md'.format(name)) as f: 33 | mdtext = f.read().decode('utf8', 'ignore') 34 | return markdown2html(mdtext) 35 | -------------------------------------------------------------------------------- /weblog/views.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | 3 | from datetime import datetime 4 | import json 5 | from flask import render_template, request, abort 6 | from . import app 7 | from .models import (Article, Tag, create_article) 8 | from .utils import markdown2html, load_content 9 | 10 | 11 | @app.errorhandler(404) 12 | def page_not_found(error): 13 | title = unicode(error) 14 | message = error.description 15 | return render_template('errors.html', 16 | title=title, 17 | message=message) 18 | 19 | 20 | @app.errorhandler(500) 21 | def internal_server_error(error): 22 | title = unicode(error) 23 | message = error.description 24 | return render_template('errors.html', 25 | title=title, 26 | message=message) 27 | 28 | 29 | @app.route('/') 30 | def index(): 31 | pagination = Article.query.order_by(Article.id.desc()).paginate(1) 32 | return render_template('index.html', 33 | pagination=pagination) 34 | 35 | 36 | @app.route('/article/') 37 | def show_article(id): 38 | article = Article.query.get_or_404(id) 39 | return render_template('page.html', 40 | title=article.title, 41 | content=article.content, 42 | pub_time=article.pub_time, 43 | tags=article.tags) 44 | 45 | @app.route('/tags') 46 | def show_tags(): 47 | tags = Tag.query.all() 48 | return render_template('tags.html', 49 | tags=tags) 50 | 51 | @app.route('/tag/') 52 | def show_tag(id): 53 | tag = Tag.query.get_or_404(id) 54 | articles = tag.articles.all() 55 | return render_template('tag.html', 56 | tag=tag, 57 | entries=articles) 58 | 59 | 60 | @app.route('/about') 61 | def about(): 62 | content = load_content('about') 63 | return render_template('page.html', 64 | title='About', 65 | content=content) 66 | 67 | 68 | @app.route('/links') 69 | def links(): 70 | content = load_content('links') 71 | return render_template('page.html', 72 | title='Links', 73 | content=content) 74 | 75 | 76 | @app.route('/publish', methods=['GET', 'POST']) 77 | def publish(): 78 | if request.method == 'GET': 79 | abort(404) 80 | 81 | # authorization 82 | token = request.form.get('token', '') 83 | if token != app.config['TOKEN']: 84 | return 'invalid access token', 500 85 | 86 | title = request.form.get('title', None) 87 | if not title: 88 | return 'no title found', 500 89 | 90 | summary = request.form.get('summary', None) 91 | if not summary: 92 | return 'no summary found', 500 93 | 94 | content = request.form.get('content', None) 95 | if not content: 96 | return 'no content found', 500 97 | content = markdown2html(content) 98 | 99 | pub_time = request.form.get('pub_time', None) 100 | if pub_time: 101 | pub_time = datetime.strptime(pub_time, app.config['TIME_FORMAT']) 102 | 103 | tags = request.form.getlist('tags') 104 | 105 | create_article(title, summary, content, pub_time, tags) 106 | return '', 200 107 | --------------------------------------------------------------------------------