diff --git a/CHANGELOG.md b/CHANGELOG.md index 696ed1d..8311294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## v1.1 2021.03.28 + +### Added + +- **owl** switched to `markdown` module. +- `markdown_alerts` extension. Short admonition syntax. +- `markdown_del_ins` extension. Deleted and inserted text support. + +### Changed + +- Fixed `PASSWORD_FILE` detecting. +- Fixed path resolving. Now uses `os.path.join()`. + +### Removed + +- `markdown2`. Replaced by `markdown` module. + ## v1.0 2021.03.23 ### Added @@ -27,4 +44,4 @@ ## v0.1 2020.08.15 -First version released. +First version released. \ No newline at end of file diff --git a/README.md b/README.md index def1b19..fd2ee5c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # owl -![](https://img.shields.io/badge/owl-v1.0-%2300f) +![](https://img.shields.io/badge/owl-v1.1-blueviolet) **owl** — is the minimalistic kn**owl**edge base web app written in Python. @@ -22,13 +22,11 @@ App is now available at [http://localhost:5000/](http://localhost:5000/). This solution is suitable for creating documentation or maintaining a personal knowledge base. -New in `v1.0`: -- This is brand new owl! -- New frontend and refactored backend. -- Bootstrap 5 -- Optional authentication. +New in `v1.1`: +- Switched to [Python-Markdown](https://github.com/Python-Markdown/markdown). +- Added some new Markdown extensions. -See [CHANGELOG.md](CHANGELOG.md) +See [CHANGELOG.md](CHANGELOG.md). # License diff --git a/config.py b/config.py index e564299..bc1aa78 100644 --- a/config.py +++ b/config.py @@ -1,16 +1,32 @@ class Config(object): DEBUG = False - SECRET_KEY = 'top_secret' + SECRET_KEY = None PASSWORD_FILE = '.pw' SIGN_IN = False # Enable or disable authentication MARKDOWN_ROOT = 'docs/' # Path to your .md files MARKDOWN_DOWLOADS = True - # See https://github.com/trentm/python-markdown2/wiki/Extras - MARKDOWN2_EXTRAS = [ - 'fenced-code-blocks', - 'markdown-in-html', - 'code-friendly', - 'header-ids', - 'strike', - 'tables' - ] \ No newline at end of file + MARKDOWN_EXTRAS = [ + 'admonition', + 'attr_list', + 'codehilite', + 'def_list', + 'fenced_code', + 'md_in_html', + 'markdown_alerts', + 'markdown_del_ins', + 'tables', + 'toc' + ] + MARKDOWN_EXTRAS_CONFIGS = { + 'markdown_alerts': { + 'info': 'alert alert-info', + 'note': 'alert alert-primary', + 'tip': 'alert alert-success', + 'success': 'alert alert-success', + 'warning': 'alert alert-warning', + 'danger': 'alert alert-danger' + }, + 'toc': { + 'toc_depth': '2-5' + } + } \ No newline at end of file diff --git a/docs/CONTENTS.md b/docs/CONTENTS.md index 5587640..73d0295 100644 --- a/docs/CONTENTS.md +++ b/docs/CONTENTS.md @@ -1,3 +1,3 @@ -### Contents +# @v@ -- [Home](/) +- [Home](/) \ No newline at end of file diff --git a/docs/HOME.md b/docs/HOME.md index 9de7ccf..e6e6df8 100644 --- a/docs/HOME.md +++ b/docs/HOME.md @@ -1,6 +1,16 @@ -# @v@ owl took off! +# owl took off! -Read the [Docs](https://owl.gch.icu/docs/) to get started. +
+ ,   ,
+ /\ /\
+((@v@))
+((;;; (\
+ \ ;;; \'
+  ,V.V  `
+ `` ``
+
-Also there is project's [git repository](https://gitea.gch.icu/gd/owl) ([mirror](https://github.com/gechandesu/owl)). +Read the [Docs](https://owl.gch.icu/docs/){target="_blank"} to get started. + +Also there is project's [git repository](https://gitea.gch.icu/gd/owl) ([mirror on GitHub](https://github.com/gechandesu/owl)). diff --git a/owl.py b/owl.py index 2c244df..ec36d61 100644 --- a/owl.py +++ b/owl.py @@ -1,10 +1,12 @@ +__version__ = '1.1' + import os import re from functools import wraps from datetime import timedelta import pygments -from markdown2 import Markdown +from markdown import Markdown from flask import Flask from flask import request @@ -30,31 +32,35 @@ def read_file(filepath: str) -> str: except IOError: return 'Error: Cannot read file: {}'.format(filepath) +def get_path(path: str) -> str: + return os.path.join(app.config['MARKDOWN_ROOT'], path) + def render_html(filepath: str) -> str: - markdown = Markdown(extras=app.config['MARKDOWN2_EXTRAS']) - return markdown.convert( - read_file( - app.config['MARKDOWN_ROOT'] + filepath - ) - ) + html = Markdown( + extensions = app.config['MARKDOWN_EXTRAS'], + extension_configs = app.config['MARKDOWN_EXTRAS_CONFIGS'] + ) + return html.convert( + read_file(get_path(filepath)) + ) def parse_title_from_markdown(filepath: str) -> str: # This function parses article title from first level heading. # It returns the occurrence of the first heading, and there # can be nothing before it except empty lines and spaces. - article = read_file(app.config['MARKDOWN_ROOT'] + filepath) + article = read_file(get_path(filepath)) pattern = re.compile(r'^\s*#\s.*') if pattern.search(article): return pattern.search(article).group().strip()[2:] else: - return 'Error: Cannot parse title from file:'.format(filepath) + return 'Error: Cannot parse title from file: {}'.format(filepath) def parse_content_links(filepath: str) -> list: # This function returns a list of links from a Markdown file. # Only links contained in the list (ul ol li) are parsed. r = re.compile(r'(.*(-|\+|\*|\d).?\[.*\])(\(.*\))', re.MULTILINE) links = [] - for tpl in r.findall(read_file(app.config['MARKDOWN_ROOT'] + filepath)): + for tpl in r.findall(read_file(get_path(filepath))): for item in tpl: if re.match(r'\(.*\)', item): if item == '(/)': @@ -67,8 +73,8 @@ def parse_content_links(filepath: str) -> list: return links def check_password(password: str) -> bool: - if os.path.exists('.pw'): - pw_hash = read_file('.pw') + if os.path.exists(app.config['PASSWORD_FILE']): + pw_hash = read_file(app.config['PASSWORD_FILE']) return bcrypt.check_password_hash(pw_hash, password) else: return False @@ -129,7 +135,7 @@ def index(): @app.route('//') @login_required def get_article(path): - if os.path.exists(app.config['MARKDOWN_ROOT'] + path + '.md'): + if os.path.exists(get_path(path) + '.md'): return render_template( 'index.j2', title = parse_title_from_markdown(path + '.md'), @@ -144,7 +150,7 @@ def get_article(path): @app.route('/.md') @login_required def download_article(path): - if os.path.exists(app.config['MARKDOWN_ROOT'] + path + '.md') \ + if os.path.exists(get_path(path) + '.md') \ and app.config['MARKDOWN_DOWLOADS']: return send_from_directory( app.config['MARKDOWN_ROOT'], diff --git a/requirements.txt b/requirements.txt index c8afa2f..785fa4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ flask>=1.1 flask-bcrypt>=0.7 -markdown2>=2.3 -pygments>=2.6 \ No newline at end of file +pygments>=2.6 +markdown>=3.3.4 +markdown-alerts>=0.1 +markdown-del-ins>=1.0.0 diff --git a/static/css/style.css b/static/css/style.css index a09f927..fe288be 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,6 +1,6 @@ body { font-family: 'Ubuntu', sans-serif; - font-size: 19px; + font-size: 18px; } h1, @@ -41,10 +41,28 @@ blockquote { blockquote p { margin: 0; } +dl dt { + padding:0; + margin-top:16px; + font-size:1em; + font-style:italic; + font-weight:bold +} + +dl dd { + padding:0 2rem; + margin-bottom:16px +} + +/* Delete margin for admonitions */ +.alert p:last-child { + margin: 0; + padding: 0; +} + /* Details and summary */ details, summary { - display: block; margin: 1rem 0; transition: 200ms linear; } @@ -54,37 +72,13 @@ summary { transition: .3s; } -/* Hide defaul marker */ -details > summary { list-style: none; } -details summary::-webkit-details-marker { display: none; } - details[open] summary ~ * { animation: open 0.3s ease-in-out; } @keyframes open { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -details summary:before { - content: '+'; - font-family: 'Ubuntu Mono'; - font-size: 20px; - display: inline-flex; - padding: 0 0.3rem; -} - -details[open] summary:before { - content: '-'; - font-family: 'Ubuntu Mono'; - font-size: 20px; - display: inline-flex; - padding: 0 0.3rem; + 0% { opacity: 0; } + 100% { opacity: 1; } } /* Code styling */ @@ -92,7 +86,7 @@ details[open] summary:before { code, pre { font-family: 'Ubuntu Mono', monospace; - font-size: 19px; + font-size: 18px; color: #d0d0d0; } @@ -111,7 +105,7 @@ code { border-radius: 6px; } -.raw-pre { +.raw { color: unset; background: unset; } @@ -168,15 +162,6 @@ code { text-align: center; } -/* Header bar */ - -.header { - display: block; - position: fixed; - height: 5rem; - background: unset; -} - /* Sidebar */ .sidebar-toggle-btn { @@ -227,6 +212,13 @@ code { .sidebar a:hover { text-decoration: underline; } +.sidebar h1, +.sidebar h2, +.sidebar h3, +.sidebar h4 { + margin-left: 1rem; +} + .sidebar ul, .sidebar ol, .sidebar li { @@ -301,7 +293,8 @@ article.wide { max-width: 980px; } @media (max-width: 1200px) { .header { background: #ffffff; } .sidebar { left: -300px; } - .sidebar-toggle-btn { left: 1rem; } + .sidebar-toggle-btn { left: 0.5rem; } + .signout-btn { right: 0.5rem; } .content { margin-left: 0px; } .sidebar.hide { left: 0px; } .sidebar-toggle-btn.click { left: 316px; } diff --git a/templates/base.j2 b/templates/base.j2 index 612b559..258c70f 100644 --- a/templates/base.j2 +++ b/templates/base.j2 @@ -23,12 +23,6 @@ - {% if session['logged_in'] %} -
- -
- {% endif %} - {% block content %} No content here {% endblock %} diff --git a/templates/index.j2 b/templates/index.j2 index 50f830e..3b4d2f8 100644 --- a/templates/index.j2 +++ b/templates/index.j2 @@ -6,34 +6,38 @@ {% block content %} -
- + {# Sidebar toggle button #} + {# Sign out button #} + {% if session['logged_in'] %} +
+ +
+ {% endif %}
- + {# Page main content #}
- {# CURRENT PATH {{ current_path }} #}
{{ article | safe }}
- + {# Pagination #} {% for link in links %} {% if link == current_path %}