salvi/salvi/routes/edit.py
2025-09-05 21:42:18 +02:00

203 lines
7.2 KiB
Python

import datetime
import re
import sys
from typing import Mapping
from flask import Blueprint, abort, flash, g, jsonify, redirect, render_template, request
from flask_login import current_user, login_required
from sqlalchemy import select, insert
from suou import want_isodate
from salvi.i18n import get_string
from ..utils import parse_tag_list
from ..renderer import md_and_toc
from ..models import PERM_CREATE, Page, PageLink, PageRevision, PageText, User, db, is_url_available, is_valid_url, perms_required
current_user: User
bp = Blueprint('edit', __name__)
def savepoint(form: Mapping, is_preview: bool = False, pageobj: Page | None = None):
'''Middle point during page editing.'''
if is_preview:
preview = md_and_toc(form['text'])[0]
else:
preview = None
pl_js_info = dict()
pl_js_info['editing'] = dict(
original_text = pageobj.current_text() if pageobj else None,
preview_text = form['text'],
page_id = pageobj.id if pageobj else None
)
return render_template(
'edit.html',
pl_url=form['url'],
pl_title=form['title'],
pl_text=form['text'],
pl_tags=form['tags'],
pl_comment=form['comment'],
pl_is_locked='lockpage' in form,
pl_owner_is_current_user=current_user.owns(pageobj) if pageobj else True,
preview=preview,
pl_js_info=pl_js_info,
pl_calendar=('usecalendar' in form) and form['calendar'],
pl_readonly=not pageobj.is_editable_by(current_user) if pageobj else False,
pl_cw=('cw' in form) and form['cw']
)
@bp.route('/create/', methods=['GET', 'POST'])
@perms_required(PERM_CREATE, message='You are not authorized to create pages.')
def create():
if request.method == 'POST':
if request.form.get('preview'):
return savepoint(request.form, is_preview=True)
p_url = request.form['url'] or None
p_title = request.form['title']
if p_url:
if not is_valid_url(p_url):
flash('Invalid URL. Valid URLs contain only letters, numbers and hyphens.')
return savepoint(request.form)
elif not is_url_available(p_url):
flash('This URL is not available.')
return savepoint(request.form)
try:
p_tags = parse_tag_list(request.form.get('tags', ''))
except ValueError:
flash('Invalid tags text. Tags contain only letters, numbers and hyphens, and are separated by comma.')
return savepoint(request.form)
p_calendar = datetime.date.fromisoformat(request.form["calendar"]) if 'usecalendar' in request.form else None
try:
p = db.session.execute(insert(Page).values(
url = p_url,
title = p_title,
is_redirect = False,
touched = datetime.datetime.now(),
owner_id = current_user.id,
calendar = p_calendar,
is_locked = 'lockpage' in request.form,
is_cw = 'cw' in request.form
).returning(Page)).scalar()
p.change_tags(p_tags)
pt = PageText.create_content(request.form['text'])
db.session.execute(insert(PageRevision).values(
page_id = p.id,
user_id = p.owner_id,
comment = request.form.get('comment', ''),
textref_id = pt.id,
pub_date = datetime.datetime.now(),
length = len(request.form['text'])
))
PageLink.parse_links(p, request.form['text'])
db.session.commit()
except Exception as e:
flash(f'An error occurred while saving this revision. Please be patient.')
sys.excepthook(*sys.exc_info())
return savepoint(request.form)
return redirect(p.get_url())
return savepoint(dict(url=request.args.get('url'), title='', text='', tags='', comment=get_string(g.lang, 'page_created')))
@bp.route('/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def edit(id: int):
p = db.session.execute(select(Page).where(Page.id == id)).scalar()
if p is None:
abort(404)
p_latest = p.latest()
if request.method == 'POST':
if not p.can_edit(current_user):
flash('You are trying to edit a locked page!')
abort(403)
if request.form.get('preview'):
return savepoint(request.form, is_preview=True, pageobj=p)
p_url = request.form['url'] or None
if p_url:
if not is_valid_url(p_url):
flash('Invalid URL. Valid URLs contain only letters, numbers and hyphens.')
return savepoint(request.form, pageobj=p)
elif not is_url_available(p_url) and p_url != p.url:
flash('This URL is not available.')
return savepoint(request.form, pageobj=p)
p_tags = [x.strip().lower().replace(' ', '-').replace('_', '-').lstrip('#')
for x in request.form.get('tags', '').split(',')]
p_tags = [x for x in p_tags if x]
if any(not re.fullmatch(SLUG_RE, x) for x in p_tags):
flash('Invalid tags text. Tags contain only letters, numbers and hyphens, and are separated by comma.')
return savepoint(request.form, pageobj=p)
p.url = p_url
p.title = request.form['title']
p.touched = datetime.datetime.now()
p.is_locked = 'lockpage' 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.change_tags(p_tags)
if request.form['text'] != p_latest.text():
pt = PageText.create_content(request.form['text'])
db.session.execute(insert(PageRevision).values(
page_id=p.id,
user_id=current_user.id,
comment=request.form["comment"],
textref_id=pt.id,
pub_date=datetime.datetime.now(),
length=len(request.form['text'])
))
db.session.add(p)
db.session.commit()
return redirect(p.get_url())
form = dict(
url=p.url,
title=p.title,
text=p_latest.text(),
tags=','.join(x.name for x in p.tags),
comment=''
)
if p.is_locked:
form['lockpage'] = '1'
if p.calendar:
form['usecalendar'] = '1'
form['calendar'] = want_isodate(p.calendar).split('T')[0]
return savepoint(form, pageobj=p)
@bp.route('/_jsoninfo/<int:id>', methods=['GET', 'POST'])
@bp.route('/p/<int:id>.json', methods=['GET'])
def page_jsoninfo(id):
p = db.session.execute(select(Page).where(Page.id == id)).scalar()
if p is None:
return jsonify({'status':'fail'}), 404
j = p.js_info()
j["status"] = "ok"
if request.method == "POST":
j["text"] = p.latest().text()
return jsonify(j)
@bp.route("/_jsoninfo/changed/<float:ts>")
def jsoninfo_changed(ts):
tse = str(datetime.datetime.fromtimestamp(ts).isoformat(" "))
ps = db.session.execute(select(Page).where(Page.touched >= tse)).scalars()
return jsonify({
"ids": [i.id for i in ps],
"status": "ok"
})