{{ entry.title }}
20 | {% if entry.toc_html %} 21 |22 | {{ entry.toc_html|safe }} 23 |
24 | {% endif %} 25 | {{ entry.content|safe }} 26 |
├── demo ├── webhook.py ├── static │ └── favicon.ico ├── widgets │ ├── intro.md │ ├── intro-draft.md │ └── friend-links.md ├── themes │ └── default │ │ ├── templates │ │ ├── header.html │ │ ├── navbar.html │ │ ├── 404.html │ │ ├── custom │ │ │ └── navbar.html │ │ ├── discuss │ │ │ ├── 163gentie.html │ │ │ ├── duoshuo.html │ │ │ └── disqus.html │ │ ├── sidebar.html │ │ ├── footer.html │ │ ├── discuss-thread.html │ │ ├── head.html │ │ ├── searchbar.html │ │ ├── page.html │ │ ├── page-wide.html │ │ ├── layout-wide.html │ │ ├── foot.html │ │ ├── layout.html │ │ ├── archive.html │ │ ├── post.html │ │ └── index.html │ │ └── static │ │ ├── style.css │ │ └── highlight.css ├── pages │ ├── test │ │ └── index.html │ ├── about │ │ └── index.md │ └── about-wide │ │ └── index.md ├── site.json ├── config.py └── posts │ ├── 1994-04-20-feugiat-non.md │ ├── 2012-03-28-huamo-yepelu.md │ ├── 2016-02-05-blandit-vitae-mauris.md │ ├── 1997-08-26-semper.md │ ├── 2017-03-16-viverra.txt │ ├── 2017-03-04-lacinia.md │ ├── 2017-06-02-markdown-demo.md │ ├── 2010-04-01-viverra-imperdiet.md │ ├── 1990-12-30-sollicitudin-aliquam-metus.md │ ├── 1969-09-02-nam-nec-nunc-eros.md │ ├── 1969-10-29-imperdiet-ligula.md │ ├── 1991-02-20-congue-fringilla-sapien.md │ └── 1991-08-06-integer.md ├── MANIFEST.in ├── test-requirements.txt ├── veripress ├── __main__.py ├── model │ ├── __init__.py │ ├── models.py │ └── parsers.py ├── view │ └── __init__.py ├── api │ ├── __init__.py │ └── handlers.py ├── __init__.py └── helpers.py ├── docs ├── themes │ └── clean-doc │ │ ├── templates │ │ ├── navbar.html │ │ ├── header.html │ │ ├── 404.html │ │ ├── custom │ │ │ └── navbar.html │ │ ├── discuss │ │ │ ├── 163gentie.html │ │ │ ├── duoshuo.html │ │ │ └── disqus.html │ │ ├── foot.html │ │ ├── footer.html │ │ ├── discuss-thread.html │ │ ├── head.html │ │ ├── searchbar.html │ │ ├── page.html │ │ ├── archive.html │ │ ├── layout.html │ │ ├── post.html │ │ └── index.html │ │ └── static │ │ ├── style.css │ │ └── highlight.css ├── site.json ├── config.py └── pages │ ├── en │ └── index.md │ ├── index.md │ ├── webhook.md │ ├── deployment.md │ ├── installation.md │ ├── getting-started.md │ ├── theme.md │ ├── configuration-file.md │ ├── making-your-own-theme.md │ └── writing.md ├── veripress_cli ├── defaults │ ├── static │ │ └── favicon.ico │ ├── site.json │ └── config.py ├── __init__.py ├── helpers.py ├── serve.py ├── deploy.py ├── init.py ├── theme.py └── generate.py ├── Dockerfile ├── after-travis-ci-success.sh ├── LICENSE ├── .travis.yml ├── setup.py ├── tests ├── test_app_cache.py ├── test_parsers.py ├── test_helpers.py ├── test_toc.py ├── test_views.py ├── test_api.py ├── test_storage.py └── test_models.py ├── README.md └── .gitignore /demo/webhook.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include veripress_cli/defaults * -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | coverage==4.4.2 2 | pytest==3.2.5 3 | pytest-cov==2.5.1 4 | coveralls==1.2.0 -------------------------------------------------------------------------------- /demo/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verilab/veripress/HEAD/demo/static/favicon.ico -------------------------------------------------------------------------------- /demo/widgets/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | position: sidebar 3 | order: 0 4 | --- 5 | 6 | #### My Name 7 | 8 | Welcome to my blog! 9 | -------------------------------------------------------------------------------- /veripress/__main__.py: -------------------------------------------------------------------------------- 1 | from veripress_cli import main 2 | 3 | if __name__ == '__main__': # pragma: no cover 4 | main() 5 | -------------------------------------------------------------------------------- /demo/widgets/intro-draft.md: -------------------------------------------------------------------------------- 1 | --- 2 | position: header 3 | order: 0 4 | is_draft: true 5 | --- 6 | 7 | This is my blog. Welcome! 8 | -------------------------------------------------------------------------------- /docs/themes/clean-doc/templates/navbar.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /veripress_cli/defaults/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verilab/veripress/HEAD/veripress_cli/defaults/static/favicon.ico -------------------------------------------------------------------------------- /demo/themes/default/templates/header.html: -------------------------------------------------------------------------------- 1 |This is a test page.
9 | 10 | -------------------------------------------------------------------------------- /demo/widgets/friend-links.md: -------------------------------------------------------------------------------- 1 | --- 2 | position: sidebar 3 | order: 1 4 | --- 5 | 6 | #### Friend Links 7 | 8 | - [Project RC](https://stdrc.cc) 9 | - [VeriPress](https://github.com/veripress/veripress) 10 | -------------------------------------------------------------------------------- /docs/site.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "VeriPress Docs", 3 | "subtitle": "Documentation of VeriPress.", 4 | "author": "VeriPress", 5 | "root_url": "https://veripress.github.io", 6 | "timezone": "UTC+08:00", 7 | "language": "zh-hans" 8 | } -------------------------------------------------------------------------------- /demo/themes/default/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block head %} 4 | {{ super() }} 5 |Page Not Found
10 | {% endblock %} -------------------------------------------------------------------------------- /docs/themes/clean-doc/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block head %} 4 | {{ super() }} 5 |Page Not Found
10 | {% endblock %} -------------------------------------------------------------------------------- /demo/site.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "VeriPress Demo", 3 | "subtitle": "Yet another VeriPress blog.", 4 | "author": "My Name", 5 | "email": "someone@example.com", 6 | "root_url": "https://veripress.github.io", 7 | "timezone": "UTC+08:00", 8 | "language": "en" 9 | } -------------------------------------------------------------------------------- /veripress_cli/defaults/site.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Untitled", 3 | "subtitle": "Yet another VeriPress blog.", 4 | "author": "My Name", 5 | "email": "someone@example.com", 6 | "root_url": "https://example.com", 7 | "timezone": "UTC+00:00", 8 | "language": "en" 9 | } -------------------------------------------------------------------------------- /docs/themes/clean-doc/templates/custom/navbar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/config.py: -------------------------------------------------------------------------------- 1 | STORAGE_TYPE = 'file' 2 | THEME = 'clean-doc' 3 | ENTRIES_PER_PAGE = 5 4 | FEED_COUNT = 10 5 | SHOW_TOC = True 6 | TOC_DEPTH = 2 7 | TOC_LOWEST_LEVEL = 3 8 | ALLOW_SEARCH_PAGES = True 9 | PAGE_SOURCE_ACCESSIBLE = False 10 | DISQUS_ENABLED = False 11 | DISQUS_SHORT_NAME = 'veripress-docs' 12 | -------------------------------------------------------------------------------- /demo/themes/default/templates/custom/navbar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/pages/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | author: Richard Chien 4 | created: 2017-03-19 5 | updated: 2017-03-19 6 | language: en 7 | --- 8 | 9 | Coming soon! You can use [Google Translate](https://translate.google.com/translate?hl=en&sl=zh-CN&tl=en&u=https%3A%2F%2Fveripress.github.io%2Fdocs%2Finstallation.html) as a workaround temporarily. 10 | -------------------------------------------------------------------------------- /veripress_cli/defaults/config.py: -------------------------------------------------------------------------------- 1 | STORAGE_TYPE = '{storage_mode}' 2 | THEME = 'default' 3 | CACHE_TYPE = 'simple' 4 | MODE = 'view-only' # mixed|api-only|view-only 5 | ENTRIES_PER_PAGE = 5 6 | FEED_COUNT = 10 7 | SHOW_TOC = True 8 | TOC_DEPTH = 3 # 1~6 9 | TOC_LOWEST_LEVEL = 3 # 1~6 10 | ALLOW_SEARCH_PAGES = True 11 | PAGE_SOURCE_ACCESSIBLE = False 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6.0 2 | MAINTAINER Richard Chien{{ 'There is nothing here.' if archive_type|lower != 'search' else 'No results.' }}
24 | {% else %} 25 | 26 || {{ entry.created.strftime('%Y.%m.%d') if entry.created else '' }} | 32 |{{ entry.title }} | 33 |
{{ 'There is nothing here.' if archive_type|lower != 'search' else 'No results.' }}
24 | {% else %} 25 | 26 || {{ entry.created.strftime('%Y.%m.%d') if entry.created else '' }} | 32 |{{ entry.title }} | 33 |
abc' 50 | assert has_more_content is False 51 | raw_content = 'abc\n---more---\n\ndef' 52 | assert p.parse_preview(raw_content) == ('
abc', True) 53 | assert p.parse_whole(raw_content) == '
abc\n\ndef' 54 | raw_content = 'abc\n------ MoRe --- \n\ndef---more ---' 55 | assert p.parse_whole(raw_content) == '
abc\n\ndef---more ---' 56 | 57 | 58 | def test_md_parser(): 59 | p = MarkdownParser() 60 | assert p.parse_whole('## hello\n\n[link](https://google.com)').strip() \ 61 | == '
There is nothing here.
12 | {% else %} 13 | 14 | {% for entry in entries %} 15 |……
32 | 33 | {% endif %} 34 |There is nothing here.
12 | {% else %} 13 | 14 | {% for entry in entries %} 15 |……
32 | 33 | {% endif %} 34 |very small titlea random paragraph...
very small title',
43 | 'children': []},
44 | {'level': 1, 'id': 'Title', 'text': 'Title', 'inner_html': 'Title',
45 | 'children': [
46 | {'level': 2, 'id': 'Title_1', 'text': 'Title', 'inner_html': 'Title', 'children': [
47 | {'level': 4, 'id': 'Another-title-yes',
48 | 'text': 'Another title, yes!', 'inner_html': 'Another title, yes!',
49 | 'children': []},
50 | {'level': 3, 'id': '中文-标题-Title-amp',
51 | 'text': '中文,标题 Title&', 'inner_html': '中文,标题 Title&',
52 | 'children': []}
53 | ]}
54 | ]},
55 | {'level': 1, 'id': 'Another-h1-1',
56 | 'text': 'Another-h1-1', 'inner_html': 'Another-h1-1',
57 | 'children': [
58 | {'level': 5, 'id': 'a-very-small-title_1',
59 | 'text': 'a very small title', 'inner_html': 'a very small title',
60 | 'children': []}
61 | ]}]
62 | assert parser.toc() == expected_toc
63 |
64 | expected_toc_html = """
65 | {{ some_variable }}
31 | {% endif %} 32 | ``` 33 | 34 | 限于篇幅这里也不重复太多 Jinja2 的文档了,具体的语法请参考 [Template Designer Documentation](http://jinja.pocoo.org/docs/2.9/templates/)。 35 | 36 | 下面将解释渲染模板时「传入的值」。 37 | 38 | ## 渲染模板的 Context 39 | 40 | 渲染模板时有个概念叫 context,也就是在模板渲染时可以接触到的 Python 环境中的函数、对象等。由于基于 Flask,因此所有 Flask 的 context,都可以使用,例如 `request`、`config`、`session`、`url_for()` 等,通过这些,便可以访问到当前的请求 URL、参数、配置文件等,可以参考 [Standard Context](http://flask.pocoo.org/docs/0.12/templating/#standard-context)。 41 | 42 | 除了 Flask 提供的这些,对于不同的模板文件,VeriPress 还提供了该模板可能会需要用到的对象,如下表: 43 | 44 | | 模板 | 额外的 Context 对象 | 说明 | 45 | | --------------- | --------------------------------------- | ---------------------------------------- | 46 | | `index.html` | `entries`、`next_url`、`prev_url` | 分别是当前分页上的文章列表、下一页的 URL、上一页的 URL | 47 | | `post.html` | `entry` | 当前访问的文章 | 48 | | `page.html` | `entry` | 当前访问的自定义页面 | 49 | | `archive.html` | `entries`、`archive_type`、`archive_name` | 分别是当前归档的文章列表、归档类型、归档名称,其中 `/archive/` 页面的归档类型为 `Archive`,名称为 `All` 或类似 `2017`、`2017.3`(分别对应 `/archive/2017/` 和 `/archive/2017/03/` 页面) | 50 | | `tag.html` | 同上 | 归档类型为 `Tag`,归档名称为标签名 | 51 | | `category.html` | 同上 | 归档类型为 `Category`,归档名称为分类名 | 52 | | `search.html` | 同上 | 归档类型为 `Search`,归档名称为搜索关键词加引号 | 53 | 54 | 以上的「文章」「自定义页面」的数据,基本上和 [API 模式](api-mode.html#api-posts-获取文章列表) 获取到的相似,不同之处在于此处每个对象都多了一个 `url` 字段,可以用来直接构造链接,以及,`created` 和 `updated` 字段是 Python `datetime` 对象而不是格式化后的字符串。 55 | 56 | 除了上述的每个模板不同的 context 对象,每个模板内都可以访问 `site` 和 `storage` 两个对象,前者即 `site.json` 中的内容,后者是当前使用的存储类型的数据访问封装对象,一般很少会直接用这个,只有在获取页面部件时有必要使用(因为不是所有页面都需要显示部件,何时显示由主题决定)。由于 `storage` 获取到的数据是最原始的文章、页面、部件的对象,这里不再花费篇幅列出它的方法和获取的对象中的属性了,请直接参考 [model/storages.py](https://github.com/veripress/veripress/blob/master/veripress/model/storages.py) 中的 `Storage` 类和 [model/models.py](https://github.com/veripress/veripress/blob/master/veripress/model/models.py) 中的类定义。 57 | 58 | **鉴于获取页面部件需要使用 `storage` 对象,如果你没有精力或兴趣查看源码,可以直接参考默认主题的 [sidebar.html](https://github.com/veripress/themes/blob/default/templates/sidebar.html) 文件。** 59 | 60 | 在上面的 `sidebar.html` 中你会看到一个 `{{ widget|content|safe }}` 这样的表达式,其中 `widget` 是获取到的页面部件对象,后面两个 `content`、`safe` 是「过滤器」,前者是 VeriPress 提供的,用于把内容的抽象对象中的原始内容直接解析成 HTML 字符串,后者是 Jinja2 自带的,用于将 HTML 代码直接显示而不转义。 61 | 62 | ## 获取特定页面的 URL 63 | 64 | 在主题中你可能需要获取其它某个页面的 URL 来构造链接,可以使用 Flask 提供的 `url_for()` 函数。 65 | 66 | 对于全局或主题中的 `static` 目录的文件,使用 `url_for('static', filename='the-filename')` 来获取。 67 | 68 | 对于 view 模式的其它页面,例如你在导航栏需要提供一个归档页面的链接,使用类似 `url_for('.archive', year=2017)` 的调用。注意 `.archive` 以点号开头,或者也可以使用 `view.archive`。`url_for()` 的其它参数是用来指定 view 函数的参数的,要熟练使用的话,你可能需要对 Flask 的 URL route 规则有一定了解,然后参考 [view/\_\_init\_\_.py](https://github.com/veripress/veripress/blob/master/veripress/view/__init__.py) 文件最底部的 URL 规则。 69 | 70 | ## 适配不同的运行模式 71 | 72 | 如果你打算让主题同时支持动态运行和生成静态页面,可以通过 `config` 的 `GENERATING_STATIC_PAGES` 字段,该字段在执行 `veripress generate` 命令时被设置为 `True`,而动态运行时则不存在,因此你可以通过如下代码来对静态和动态模式: 73 | 74 | ```html 75 | {% if not config.GENERATING_STATIC_PAGES %} 76 | 79 | {% endif %} 80 | ``` 81 | 82 | ## 调试主题 83 | 84 | 制作主题时可能会出现异常(Exception),如果直接显示「500 Internal Error」可能没什么帮助,这时可以使用 `veripress preview --debug` 来预览,`--debug` 选项将开启 Flask 的调试模式,在抛出异常时会将异常信息显示在页面上。 85 | 86 | ## 制作主题时遇到问题? 87 | 88 | 不得不承认这篇关于如何制作主题的文档写得非常简陋,如果你在自己制作过程中遇到不太明确的事情,在这里也找不到的话,首先可以参考官方主题,如果还有疑问(或者对官方主题的写法不太认同),请毫不吝啬地提交 [issue](https://github.com/veripress/veripress/issues/new)。 89 | -------------------------------------------------------------------------------- /tests/test_views.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from veripress import app 5 | 6 | 7 | def test_404(): 8 | with app.test_client() as c: 9 | resp = c.get('/non-exists.html') 10 | assert 'Page Not Found' in resp.data.decode('utf-8') 11 | 12 | 13 | def test_index(): 14 | with app.test_client() as c: 15 | assert c.get('/page/1').headers['Location'] == 'http://localhost/page/1/' 16 | assert c.get('/page/1/').headers['Location'] == 'http://localhost/' 17 | 18 | shutil.move(os.path.join(app.instance_path, 'pages', 'index.mdown'), 19 | os.path.join(app.instance_path, 'pages', 'index2.mdown')) 20 | resp = c.get('/') 21 | assert resp.status_code == 200 22 | assert 'My Blog' in resp.data.decode('utf-8') 23 | assert 'TOC' in resp.data.decode('utf-8') 45 | assert 'TOC HTML
' in resp.data.decode('utf-8') 46 | 47 | resp = c.get('/post/2017/03/09/non-exists/') 48 | assert 'Page Not Found' in resp.data.decode('utf-8') 49 | 50 | app.config['SHOW_TOC'] = False 51 | with app.test_client() as c: 52 | resp = c.get('/post/2017/03/09/my-post/') 53 | assert 'My Post - My Blog' in resp.data.decode('utf-8') 54 | assert 'TOC
' not in resp.data.decode('utf-8') 55 | assert 'TOC HTML
' not in resp.data.decode('utf-8') 56 | app.config['SHOW_TOC'] = True 57 | 58 | 59 | def test_page(): 60 | with app.test_client() as c: 61 | resp = c.get('/abc') 62 | assert resp.status_code == 302 63 | assert resp.headers.get('Location').endswith('/abc.html') 64 | resp = c.get('/a/b') 65 | assert resp.status_code == 302 66 | assert resp.headers.get('Location').endswith('/a/b/') 67 | 68 | resp = c.get('/test-page.txt') 69 | assert resp.content_type == 'text/plain; charset=utf-8' 70 | 71 | resp = c.get('/dddd/') 72 | assert '{}'.format(raw_content)
130 |
131 |
132 | @parser('markdown', ext_names=['md', 'mdown', 'markdown'])
133 | class MarkdownParser(Parser):
134 | """Markdown content parser."""
135 |
136 | _read_more_exp = r''
137 |
138 | _markdown = partial(
139 | markdown.markdown,
140 | output_format='html5',
141 | extensions=[
142 | 'markdown.extensions.extra',
143 | 'markdown.extensions.codehilite'
144 | ],
145 | extension_configs={
146 | 'markdown.extensions.codehilite': {
147 | 'guess_lang': False,
148 | 'css_class': 'highlight',
149 | 'use_pygments': True
150 | }
151 | },
152 | )
153 |
154 | def parse_whole(self, raw_content):
155 | raw_content = self.remove_read_more_sep(raw_content)
156 | return self._markdown(raw_content).strip()
157 |
--------------------------------------------------------------------------------
/tests/test_models.py:
--------------------------------------------------------------------------------
1 | import json
2 | from datetime import datetime, timedelta
3 |
4 | from pytest import raises
5 |
6 | from veripress import app
7 | from veripress.model import CustomJSONEncoder
8 | from veripress.model.models import Base, Page, Post, Widget
9 |
10 |
11 | def test_base_model():
12 | base = Base()
13 | assert base.meta == {}
14 |
15 | base.format = 'TXT'
16 | assert base.format == 'txt' # the 'format' property automatically convert the value to lowercase
17 |
18 | base.meta = {'title': 'Hello world', 'author': 'Richard'}
19 | base.raw_content = 'This is a test content'
20 | assert base.is_draft == False # default value is False
21 | assert base.to_dict() == {
22 | 'meta': {'title': 'Hello world', 'author': 'Richard'},
23 | 'format': 'txt',
24 | 'raw_content': 'This is a test content',
25 | 'is_draft': False
26 | }
27 |
28 | base.meta['is_draft'] = True
29 | assert base.is_draft == True # will change dynamically when meta changes
30 |
31 | base1 = Base()
32 | base1.format = 'txt'
33 | base2 = Base()
34 | base2.format = 'markdown'
35 | assert base1 != base2
36 | base2.format = 'txt'
37 | assert base1 == base2
38 | assert base1 != 'other object type'
39 |
40 |
41 | def test_page_model():
42 | page = Page()
43 | assert page.layout == 'page' # default layout
44 | assert page.title is None
45 | assert page.author == 'My Name' # default value is from site.json
46 | assert page.email == 'my-email@example.com' # like above
47 | assert page.created is None and page.updated is None
48 |
49 | assert hasattr(page, 'unique_key')
50 | assert hasattr(page, 'rel_url')
51 |
52 | dt = datetime.now()
53 | page.meta = {
54 | 'author': 'Richard',
55 | 'email': 'richard@example.com',
56 | 'created': dt
57 | }
58 | assert page.author == 'Richard'
59 | assert page.email == 'richard@example.com'
60 | assert page.created == page.updated == dt
61 |
62 | # test parsing default title from rel_url
63 | page.rel_url = 'index.html'
64 | assert page.title == 'Index'
65 | page.rel_url = 'a-test-/index.html'
66 | assert page.title == 'A Test'
67 | page.rel_url = 'a--test/b-test/c-test.html'
68 | assert page.title == 'C Test'
69 | page.rel_url = 'a-test/b-test/'
70 | assert page.title == 'B Test'
71 | page.rel_url = '测试/index.html'
72 | assert page.title == '测试'
73 |
74 | page.meta['title'] = 'My Page'
75 | assert page.title == 'My Page'
76 |
77 | dt2 = datetime.now() + timedelta(days=2)
78 | page.meta['updated'] = dt2
79 | assert page.created != page.updated
80 |
81 |
82 | def test_post_model():
83 | post = Post()
84 | assert isinstance(post, Page)
85 |
86 | # test parsing default title and created date from rel_url
87 | post.rel_url = '2017/03/10/my-post/'
88 | assert post.created == datetime.strptime('2017/03/10', '%Y/%m/%d')
89 | assert post.title == 'My Post'
90 | post.meta['title'] = 'My First Post'
91 | assert post.title == 'My First Post'
92 |
93 | # test tags and categories with str and list types
94 | assert post.tags == []
95 | assert post.categories == []
96 | post.meta['tags'] = 'VeriPress'
97 | assert post.tags == ['VeriPress']
98 | post.meta['categories'] = 'Dev'
99 | assert post.categories == ['Dev']
100 |
101 | post.meta['tags'] = ['A', 'B']
102 | assert post.tags == ['A', 'B']
103 | post.meta['categories'] = ['A']
104 | assert post.categories == ['A']
105 |
106 |
107 | def test_widget_model():
108 | widget = Widget()
109 | assert widget.position is None
110 | assert widget.order is None
111 | assert not hasattr(widget, 'unique_key')
112 | assert not hasattr(widget, 'rel_url')
113 | assert isinstance(widget, Base)
114 | assert not isinstance(widget, Page)
115 |
116 | widget.meta['position'] = 'sidebar'
117 | widget.meta['order'] = 0
118 | assert widget.position == 'sidebar'
119 | assert widget.order == 0
120 |
121 |
122 | def test_json_encoder():
123 | assert app.json_encoder == CustomJSONEncoder
124 |
125 | page = Page()
126 | page.rel_url = 'my-page/index.html'
127 | page.unique_key = '/my-page/'
128 | page.raw_content = 'This is the raw content.'
129 | page.format = 'txt'
130 | dt = datetime.now()
131 | page.meta = {'title': 'My Page', 'author': 'Richard', 'created': dt}
132 | result = json.dumps(page, cls=CustomJSONEncoder)
133 | assert json.loads(result) == {
134 | 'meta': {'title': 'My Page', 'author': 'Richard', 'created': dt.strftime('%Y-%m-%d %H:%M:%S')},
135 | 'raw_content': 'This is the raw content.',
136 | 'format': 'txt',
137 | 'is_draft': False,
138 | 'unique_key': '/my-page/',
139 | 'rel_url': 'my-page/index.html',
140 | 'layout': 'page',
141 | 'title': 'My Page',
142 | 'author': 'Richard',
143 | 'email': 'my-email@example.com',
144 | 'created': dt.strftime('%Y-%m-%d %H:%M:%S'),
145 | 'updated': dt.strftime('%Y-%m-%d %H:%M:%S')
146 | }
147 |
148 | class NotSupportedClass:
149 | pass
150 |
151 | not_supported = NotSupportedClass()
152 | with raises(TypeError):
153 | json.dumps(not_supported, cls=CustomJSONEncoder)
154 |
--------------------------------------------------------------------------------
/docs/pages/writing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 撰写内容
3 | author: Richard Chien
4 | created: 2017-03-20
5 | updated: 2017-06-02
6 | ---
7 |
8 | VeriPress 支持三种内容形式:文章(post)、自定义页面(page)、页面部件(widget)。其中,文章(post)是指可以通过 `/post/