version advance + some refactoring
This commit is contained in:
parent
ded90d1ac4
commit
910e01b691
6 changed files with 49 additions and 72 deletions
|
|
@ -1,5 +1,14 @@
|
||||||
# What’s New
|
# What’s New
|
||||||
|
|
||||||
|
## 0.9
|
||||||
|
|
||||||
|
+ Removed `markdown_katex` dependency, and therefore support for math.
|
||||||
|
It is bloat; moreover, it ships executables with it, negatively impacting the lightweightness of the app.
|
||||||
|
+ Added support for `.env` (dotenv) file.
|
||||||
|
+ Now a database URL is required. For example, `[database]directory = /path/to/data/` becomes
|
||||||
|
`[database]url = sqlite:////path/to/data/data.sqlite` (site.conf) or
|
||||||
|
`DATABASE_URL=sqlite:////path/to/data/data.sqlite` (.env).
|
||||||
|
|
||||||
## 0.8
|
## 0.8
|
||||||
|
|
||||||
+ Schema changes:
|
+ Schema changes:
|
||||||
|
|
|
||||||
15
README.md
15
README.md
|
|
@ -24,16 +24,13 @@ suitable as a community or team knowledge base.
|
||||||
+ **Peewee** ORM.
|
+ **Peewee** ORM.
|
||||||
+ **Markdown** for page rendering.
|
+ **Markdown** for page rendering.
|
||||||
+ **Python-I18n**.
|
+ **Python-I18n**.
|
||||||
|
* **Python-Dotenv**.
|
||||||
+ The database drivers needed for the type of database.
|
+ The database drivers needed for the type of database.
|
||||||
|
|
||||||
### Optional requirements
|
|
||||||
|
|
||||||
* **Markdown-KaTeX** if you want to display math inside pages.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
+ Clone this repository: `git clone https://github.com/sakuragasaki46/salvi`
|
+ Clone this repository: `git clone https://github.com/sakuragasaki46/salvi`
|
||||||
+ Edit site.conf with the needed parameters. An example site.conf:
|
+ Edit site.conf (old way) or .env (new way) with the needed parameters. An example site.conf with SQLite:
|
||||||
|
|
||||||
```
|
```
|
||||||
[site]
|
[site]
|
||||||
|
|
@ -41,6 +38,13 @@ name = Salvi
|
||||||
|
|
||||||
[database]
|
[database]
|
||||||
directory = /path/to/database/
|
directory = /path/to/database/
|
||||||
|
```
|
||||||
|
|
||||||
|
An example .env with MySQL:
|
||||||
|
|
||||||
|
```
|
||||||
|
APP_NAME=Salvi
|
||||||
|
DATABASE_URL=mysql://root:root@localhost/salvi
|
||||||
```
|
```
|
||||||
|
|
||||||
+ Run `python3 -m app_init` to initialize the database and create the administrator user.
|
+ Run `python3 -m app_init` to initialize the database and create the administrator user.
|
||||||
|
|
@ -51,6 +55,7 @@ directory = /path/to/database/
|
||||||
|
|
||||||
+ The whole application is untested.
|
+ The whole application is untested.
|
||||||
+ If you forget the password, there is currently no way to reset it.
|
+ If you forget the password, there is currently no way to reset it.
|
||||||
|
+ This app comes with no content. It means, you have to write it yourself.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
||||||
73
app.py
73
app.py
|
|
@ -21,7 +21,7 @@ from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
from peewee import *
|
from peewee import *
|
||||||
from playhouse.db_url import connect as dbconnect
|
from playhouse.db_url import connect as dbconnect
|
||||||
import calendar, datetime, hashlib, html, importlib, json, markdown, os, random, \
|
import datetime, hashlib, html, importlib, json, markdown, os, random, \
|
||||||
re, sys, warnings
|
re, sys, warnings
|
||||||
from functools import lru_cache, partial
|
from functools import lru_cache, partial
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
@ -29,8 +29,9 @@ from configparser import ConfigParser
|
||||||
import i18n
|
import i18n
|
||||||
import gzip
|
import gzip
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
|
import dotenv
|
||||||
|
|
||||||
__version__ = '0.8'
|
__version__ = '0.9-dev'
|
||||||
|
|
||||||
#### CONSTANTS ####
|
#### CONSTANTS ####
|
||||||
|
|
||||||
|
|
@ -45,11 +46,17 @@ PING_RE = r'(?<!\w)@(' + USERNAME_RE + r')'
|
||||||
|
|
||||||
#### GENERAL CONFIG ####
|
#### GENERAL CONFIG ####
|
||||||
|
|
||||||
CONFIG_FILE = os.environ.get('SALVI_CONF', APP_BASE_DIR + '/site.conf')
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
|
CONFIG_FILE = os.getenv('SALVI_CONF', APP_BASE_DIR + '/site.conf')
|
||||||
|
|
||||||
|
# security check: one may specify only configuration files INSIDE
|
||||||
|
# the code directory.
|
||||||
|
if not os.path.abspath(CONFIG_FILE).startswith(APP_BASE_DIR):
|
||||||
|
raise OSError("Invalid configuration file")
|
||||||
|
|
||||||
DEFAULT_CONF = {
|
DEFAULT_CONF = {
|
||||||
('site', 'title'): 'Salvi',
|
('site', 'title'): os.getenv('APP_NAME', 'Salvi'),
|
||||||
('database', 'directory'): APP_BASE_DIR + "/database",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_cfp = ConfigParser()
|
_cfp = ConfigParser()
|
||||||
|
|
@ -69,15 +76,6 @@ else:
|
||||||
print('Uh oh, site.conf not found.')
|
print('Uh oh, site.conf not found.')
|
||||||
exit(-1)
|
exit(-1)
|
||||||
|
|
||||||
#### OPTIONAL IMPORTS ####
|
|
||||||
|
|
||||||
markdown_katex = None
|
|
||||||
try:
|
|
||||||
if _getconf('appearance', 'math') != 'off':
|
|
||||||
import markdown_katex #pragma: no cover
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
#### misc. helpers ####
|
#### misc. helpers ####
|
||||||
|
|
||||||
def _makelist(l):
|
def _makelist(l):
|
||||||
|
|
@ -134,11 +132,12 @@ class SpoilerExtension(markdown.extensions.Extension):
|
||||||
|
|
||||||
#### DATABASE SCHEMA ####
|
#### DATABASE SCHEMA ####
|
||||||
|
|
||||||
database_url = _getconf('database', 'url')
|
database_url = os.getenv("DATABASE_URL") or _getconf('database', 'url')
|
||||||
if database_url:
|
if database_url:
|
||||||
database = dbconnect(database_url)
|
database = dbconnect(database_url)
|
||||||
else:
|
else:
|
||||||
database = SqliteDatabase(_getconf("database", "directory") + '/data.sqlite')
|
print("Database URL required.")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
class BaseModel(Model):
|
class BaseModel(Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -241,7 +240,7 @@ class Page(BaseModel):
|
||||||
flags = BitField()
|
flags = BitField()
|
||||||
is_redirect = flags.flag(1)
|
is_redirect = flags.flag(1)
|
||||||
is_sync = flags.flag(2)
|
is_sync = flags.flag(2)
|
||||||
is_math_enabled = flags.flag(4)
|
is_math_enabled = flags.flag(4) # legacy, math is no more supported
|
||||||
is_locked = flags.flag(8)
|
is_locked = flags.flag(8)
|
||||||
is_cw = flags.flag(16)
|
is_cw = flags.flag(16)
|
||||||
@property
|
@property
|
||||||
|
|
@ -254,7 +253,7 @@ class Page(BaseModel):
|
||||||
if self.is_cw:
|
if self.is_cw:
|
||||||
return '(Content Warning: we are not allowed to show a description.)'
|
return '(Content Warning: we are not allowed to show a description.)'
|
||||||
full_text = self.latest.text
|
full_text = self.latest.text
|
||||||
text = remove_tags(full_text, convert = not self.is_math_enabled and not _getconf('appearance', 'simple_remove_tags', False))
|
text = remove_tags(full_text, convert = not _getconf('appearance', 'simple_remove_tags', False))
|
||||||
return text[:200] + ('\u2026' if len(text) > 200 else '')
|
return text[:200] + ('\u2026' if len(text) > 200 else '')
|
||||||
def change_tags(self, new_tags):
|
def change_tags(self, new_tags):
|
||||||
old_tags = set(x.name for x in self.tags)
|
old_tags = set(x.name for x in self.tags)
|
||||||
|
|
@ -366,10 +365,10 @@ class PageRevision(BaseModel):
|
||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self):
|
||||||
return self.textref.get_content()
|
return self.textref.get_content()
|
||||||
def html(self, *, math=True):
|
def html(self):
|
||||||
return md(self.text, math=self.page.is_math_enabled and math)
|
return md(self.text)
|
||||||
def html_and_toc(self, *, math=True):
|
def html_and_toc(self):
|
||||||
return md_and_toc(self.text, math=self.page.is_math_enabled and math)
|
return md_and_toc(self.text)
|
||||||
def human_pub_date(self):
|
def human_pub_date(self):
|
||||||
delta = datetime.datetime.now() - self.pub_date
|
delta = datetime.datetime.now() - self.pub_date
|
||||||
T = partial(get_string, g.lang)
|
T = partial(get_string, g.lang)
|
||||||
|
|
@ -571,10 +570,7 @@ def has_perms(user, flags, page=None):
|
||||||
|
|
||||||
#### WIKI SYNTAX ####
|
#### WIKI SYNTAX ####
|
||||||
|
|
||||||
def md_and_toc(text, expand_magic=False, toc=True, math=True):
|
def md_and_toc(text, toc=True):
|
||||||
if expand_magic:
|
|
||||||
# DEPRECATED seeking for a better solution.
|
|
||||||
warnings.warn('Magic words are no more supported.', DeprecationWarning)
|
|
||||||
extensions = ['tables', 'footnotes', 'fenced_code', 'sane_lists']
|
extensions = ['tables', 'footnotes', 'fenced_code', 'sane_lists']
|
||||||
extension_configs = {}
|
extension_configs = {}
|
||||||
if not _getconf('markdown', 'disable_custom_extensions'):
|
if not _getconf('markdown', 'disable_custom_extensions'):
|
||||||
|
|
@ -582,12 +578,6 @@ def md_and_toc(text, expand_magic=False, toc=True, math=True):
|
||||||
extensions.append(SpoilerExtension())
|
extensions.append(SpoilerExtension())
|
||||||
if toc:
|
if toc:
|
||||||
extensions.append('toc')
|
extensions.append('toc')
|
||||||
if math and markdown_katex and ('$`' in text or '```math' in text):
|
|
||||||
extensions.append('markdown_katex')
|
|
||||||
extension_configs['markdown_katex'] = {
|
|
||||||
'no_inline_svg': True,
|
|
||||||
'insert_fonts_css': not _getconf('site', 'katex_url')
|
|
||||||
}
|
|
||||||
try:
|
try:
|
||||||
converter = markdown.Markdown(extensions=extensions, extension_configs=extension_configs)
|
converter = markdown.Markdown(extensions=extensions, extension_configs=extension_configs)
|
||||||
if toc:
|
if toc:
|
||||||
|
|
@ -597,14 +587,14 @@ def md_and_toc(text, expand_magic=False, toc=True, math=True):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return '<p class="error">There was an error during rendering: {e.__class__.__name__}: {e}</p>'.format(e=e), ''
|
return '<p class="error">There was an error during rendering: {e.__class__.__name__}: {e}</p>'.format(e=e), ''
|
||||||
|
|
||||||
def md(text, expand_magic=False, toc=True, math=True):
|
def md(text, toc=True):
|
||||||
return md_and_toc(text, expand_magic=expand_magic, toc=toc, math=math)[0]
|
return md_and_toc(text, toc=toc)[0]
|
||||||
|
|
||||||
def remove_tags(text, convert=True, headings=True):
|
def remove_tags(text, convert=True, headings=True):
|
||||||
if headings:
|
if headings:
|
||||||
text = re.sub(r'\#[^\n]*', '', text)
|
text = re.sub(r'\#[^\n]*', '', text)
|
||||||
if convert:
|
if convert:
|
||||||
text = md(text, toc=False, math=False)
|
text = md(text, toc=False)
|
||||||
return re.sub(r'<.*?>', '', text)
|
return re.sub(r'<.*?>', '', text)
|
||||||
|
|
||||||
def is_username(s):
|
def is_username(s):
|
||||||
|
|
@ -670,12 +660,10 @@ def _inject_variables():
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'T': partial(get_string, _get_lang()),
|
'T': partial(get_string, _get_lang()),
|
||||||
'app_name': _getconf('site', 'title'),
|
'app_name': os.getenv("APP_NAME") or _getconf('site', 'title'),
|
||||||
'strong': lambda x:Markup('<strong>{0}</strong>').format(x),
|
'strong': lambda x:Markup('<strong>{0}</strong>').format(x),
|
||||||
'app_version': __version__,
|
'app_version': __version__,
|
||||||
'math_version': markdown_katex.__version__ if markdown_katex else None,
|
'material_icons_url': _getconf('site', 'material_icons_url')
|
||||||
'material_icons_url': _getconf('site', 'material_icons_url'),
|
|
||||||
'katex_url': _getconf('site', 'katex_url')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
|
|
@ -731,7 +719,7 @@ def error_500(body):
|
||||||
# Middle point during page editing.
|
# Middle point during page editing.
|
||||||
def savepoint(form, is_preview=False, pageobj=None):
|
def savepoint(form, is_preview=False, pageobj=None):
|
||||||
if is_preview:
|
if is_preview:
|
||||||
preview = md(form['text'], math='enablemath' in form)
|
preview = md(form['text'])
|
||||||
else:
|
else:
|
||||||
preview = None
|
preview = None
|
||||||
pl_js_info = dict()
|
pl_js_info = dict()
|
||||||
|
|
@ -747,7 +735,6 @@ def savepoint(form, is_preview=False, pageobj=None):
|
||||||
pl_text=form['text'],
|
pl_text=form['text'],
|
||||||
pl_tags=form['tags'],
|
pl_tags=form['tags'],
|
||||||
pl_comment=form['comment'],
|
pl_comment=form['comment'],
|
||||||
pl_enablemath='enablemath' in form,
|
|
||||||
pl_is_locked='lockpage' in form,
|
pl_is_locked='lockpage' in form,
|
||||||
pl_owner_is_current_user=pageobj.is_owned_by(current_user) if pageobj else True,
|
pl_owner_is_current_user=pageobj.is_owned_by(current_user) if pageobj else True,
|
||||||
preview=preview,
|
preview=preview,
|
||||||
|
|
@ -787,7 +774,6 @@ def create():
|
||||||
touched=datetime.datetime.now(),
|
touched=datetime.datetime.now(),
|
||||||
owner_id=current_user.id,
|
owner_id=current_user.id,
|
||||||
calendar=datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None,
|
calendar=datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None,
|
||||||
is_math_enabled='enablemath' in request.form,
|
|
||||||
is_locked = 'lockpage' in request.form,
|
is_locked = 'lockpage' in request.form,
|
||||||
is_cw = 'cw' in request.form
|
is_cw = 'cw' in request.form
|
||||||
)
|
)
|
||||||
|
|
@ -842,7 +828,6 @@ def edit(id):
|
||||||
p.url = p_url
|
p.url = p_url
|
||||||
p.title = request.form['title']
|
p.title = request.form['title']
|
||||||
p.touched = datetime.datetime.now()
|
p.touched = datetime.datetime.now()
|
||||||
p.is_math_enabled = 'enablemath' in request.form
|
|
||||||
p.is_locked = 'lockpage' in request.form
|
p.is_locked = 'lockpage' in request.form
|
||||||
p.is_cw = 'cw' in request.form
|
p.is_cw = 'cw' in request.form
|
||||||
p.calendar = datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None
|
p.calendar = datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None
|
||||||
|
|
@ -867,8 +852,6 @@ def edit(id):
|
||||||
"tags": ','.join(x.name for x in p.tags),
|
"tags": ','.join(x.name for x in p.tags),
|
||||||
"comment": ""
|
"comment": ""
|
||||||
}
|
}
|
||||||
if p.is_math_enabled:
|
|
||||||
form["enablemath"] = "1"
|
|
||||||
if p.is_locked:
|
if p.is_locked:
|
||||||
form["lockpage"] = "1"
|
form["lockpage"] = "1"
|
||||||
if p.calendar:
|
if p.calendar:
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,6 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if math_version and katex_url %}
|
|
||||||
<link rel="stylesheet" href="{{ katex_url }}" />
|
|
||||||
<style type="text/css">
|
|
||||||
.katex img {
|
|
||||||
object-fit: fill;
|
|
||||||
padding: unset;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: inherit;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endif %}
|
|
||||||
{% block json_info %}{% endblock %}
|
{% block json_info %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body {% if request.cookies.get('dark') == '1' %}class="dark"{% endif %}>
|
<body {% if request.cookies.get('dark') == '1' %}class="dark"{% endif %}>
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,6 @@
|
||||||
{% if not pl_readonly %}
|
{% if not pl_readonly %}
|
||||||
<div class="pre-text-input">
|
<div class="pre-text-input">
|
||||||
<p>This editor is using Markdown for text formatting (e.g. bold, italic, headers and tables). <a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" rel="nofollow">More info on Markdown</a>.</p>
|
<p>This editor is using Markdown for text formatting (e.g. bold, italic, headers and tables). <a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" rel="nofollow">More info on Markdown</a>.</p>
|
||||||
{% if math_version and pl_enablemath %}
|
|
||||||
<p>Math with KaTeX is <mark>enabled</mark>. <a href="https://katex.org/docs/supported.html">KaTeX guide</a></p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="over-text-input"></div>
|
<div class="over-text-input"></div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
@ -68,10 +65,6 @@
|
||||||
<input type="text" name="comment" value="{{ pl_comment }}" placeholder="{{ T('write-a-comment') }}" />
|
<input type="text" name="comment" value="{{ pl_comment }}" placeholder="{{ T('write-a-comment') }}" />
|
||||||
</div>
|
</div>
|
||||||
<h3>Advanced options</h3>
|
<h3>Advanced options</h3>
|
||||||
<div>
|
|
||||||
<input type="checkbox" id="CB__enablemath" name="enablemath" {% if math_version and pl_enablemath %}checked=""{% elif not math_version %}disabled=""{% endif %}>
|
|
||||||
<label for="CB__enablemath">Enable math expressions parsing {% if not math_version %}(disabled for this instance){% endif %}</label>
|
|
||||||
</div>
|
|
||||||
{% if pl_owner_is_current_user %}
|
{% if pl_owner_is_current_user %}
|
||||||
<div>
|
<div>
|
||||||
<input type="checkbox" id="CB__lockpage" name="lockpage" {% if pl_is_locked %}checked=""{% endif %}>
|
<input type="checkbox" id="CB__lockpage" name="lockpage" {% if pl_is_locked %}checked=""{% endif %}>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
{% block json_info %}<script>window.page_info={{ p.js_info()|tojson|safe }};</script>{% endblock %}
|
{% block json_info %}<script>window.page_info={{ p.js_info()|tojson|safe }};</script>{% endblock %}
|
||||||
|
|
||||||
{% set html_and_toc = rev.html_and_toc(math = request.args.get('math') not in ['0', 'false', 'no', 'off']) %}
|
{% set html_and_toc = rev.html_and_toc() %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<article>
|
<article>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue