2025.10.06-03:08:05

This commit is contained in:
2025-10-06 03:08:05 +02:00
parent 38a85cb9d5
commit e08b03bb42
57 changed files with 3222 additions and 157 deletions

View File

@@ -0,0 +1,91 @@
{% extends "account/base_manage_email.html" %}
{% load static allauth i18n widget_tweaks %}
{% block head_title %}
{% trans "Email Addresses" %}
{% endblock head_title %}
{% block content %}
{% element h1 %}
{% trans "Email Addresses" %}
{% endelement %}
{% if emailaddresses %}
{% element p %}
{% trans 'The following email addresses are associated with your account:' %}
{% endelement %}
{% url 'account_email' as email_url %}
{% element form form=form action=email_url method="post" tags="email,list" %}
{% slot body %}
{% csrf_token %}
{% for radio in emailaddress_radios %}
{% with emailaddress=radio.emailaddress %}
{% element field type="radio" checked=radio.checked name="email" value=emailaddress.email id=radio.id %}
{% slot label %}
{{ emailaddress.email }}
{% if emailaddress.verified %}
{% element badge tags="success,email,verified" %}
{% translate "Verified" %}
{% endelement %}
{% else %}
{% element badge tags="warning,email,unverified" %}
{% translate "Unverified" %}
{% endelement %}
{% endif %}
{% if emailaddress.primary %}
{% element badge tags="email,primary" %}
{% translate "Primary" %}
{% endelement %}
{% endif %}
{% endslot %}
{% endelement %}
{% endwith %}
{% endfor %}
{% endslot %}
{% slot actions %}
<div class="text-end">
{% element button type="submit" class="btn btn-primary mb-3" name="action_primary" %}
{% trans 'Make Primary' %}
{% endelement %}
{% element button tags="secondary" class="btn btn-secondary mb-3" type="submit" name="action_send" %}
{% trans 'Re-send Verification' %}
{% endelement %}
{% element button class="btn btn-danger mb-3" tags="danger,delete" type="submit" name="action_remove" %}
{% trans 'Remove' %}
{% endelement %}
</div>
{% endslot %}
{% endelement %}
{% else %}
{% include "account/snippets/warn_no_email.html" %}
{% endif %}
{% if can_add_email %}
{% element h2 %}
{% trans "Add Email Address" %}
{% endelement %}
{% url 'account_email' as action_url %}
{% element form form=form method="post" action=action_url tags="email,add" %}
{% slot body %}
{% csrf_token %}
<div class="form-floating form-floating-sm mb-3">
{% render_field form.email class="form-control" placeholder="Neue Email-Adresse" %}
<label for="id_email">Neue Email-Adresse</label>
</div>
{% endslot %}
{% slot actions %}
<div class="text-end">
{% element button name="action_add" class="btn btn-success" type="submit" %}
{% trans "Add Email" %}
{% endelement %}
</div>
{% endslot %}
{% endelement %}
{% endif %}
{% endblock content %}
{% block extra_body %}
<script src="{% static 'account/js/account.js' %}"></script>
<script src="{% static 'account/js/onload.js' %}"></script>
<script data-allauth-onload="allauth.account.forms.manageEmailForm" type="application/json">{
"i18n": {"confirmDelete": "{% trans 'Do you really want to remove the selected email address?' %}"}
}
</script>
{% endblock extra_body %}

View File

@@ -0,0 +1,68 @@
{% extends "account/base_manage_email.html" %}
{% load i18n %}
{% load allauth %}
{% block head_title %}
{% trans "Email Address" %}
{% endblock head_title %}
{% block content %}
{% element h1 %}
{% trans "Email Address" %}
{% endelement %}
{% if not emailaddresses %}
{% include "account/snippets/warn_no_email.html" %}
{% endif %}
{% url 'account_email' as action_url %}
{% element form method="post" action=action_url %}
{% slot body %}
{% csrf_token %}
{% if current_emailaddress %}
{% element field id="current_email" disabled=True type="email" value=current_emailaddress.email %}
{% slot label %}
{% translate "Current email" %}:
{% endslot %}
{% endelement %}
{% endif %}
{% if new_emailaddress %}
{% element field id="new_email" value=new_emailaddress.email disabled=True type="email" %}
{% slot label %}
{% if not current_emailaddress %}
{% translate "Current email" %}:
{% else %}
{% translate "Changing to" %}:
{% endif %}
{% endslot %}
{% slot help_text %}
{% blocktranslate %}Your email address is still pending verification.{% endblocktranslate %}
{% element button class="btn btn-secondary" form="pending-email" type="submit" name="action_send" tags="minor,secondary" %}
{% trans 'Re-send Verification' %}
{% endelement %}
{% if current_emailaddress %}
{% element button class="btn btn-danger" form="pending-email" type="submit" name="action_remove" tags="danger,minor" %}
{% trans 'Cancel Change' %}
{% endelement %}
{% endif %}
{% endslot %}
{% endelement %}
{% endif %}
{% element field id=form.email.auto_id name="email" value=form.email.value errors=form.email.errors type="email" %}
{% slot label %}
{% translate "Change to" %}:
{% endslot %}
{% endelement %}
{% endslot %}
{% slot actions %}
{% element button class="btn btn-primary" name="action_add" type="submit" %}
{% trans "Change Email" %}
{% endelement %}
{% endslot %}
{% endelement %}
{% if new_emailaddress %}
<form style="display: none"
id="pending-email"
method="post"
action="{% url 'account_email' %}">
{% csrf_token %}
<input type="hidden" name="email" value="{{ new_emailaddress.email }}">
</form>
{% endif %}
{% endblock content %}

View File

@@ -0,0 +1,41 @@
{% extends "account/base_entrance.html" %}
{% load i18n %}
{% load account %}
{% load allauth %}
{% block head_title %}
{% trans "Confirm Email Address" %}
{% endblock head_title %}
{% block content %}
{% element h1 %}
{% trans "Confirm Email Address" %}
{% endelement %}
{% if confirmation %}
{% user_display confirmation.email_address.user as user_display %}
{% if can_confirm %}
{% element p %}
{% blocktrans with confirmation.email_address.email as email %}Please confirm that <a href="mailto:{{ email }}">{{ email }}</a> is an email address for user {{ user_display }}.{% endblocktrans %}
{% endelement %}
{% url 'account_confirm_email' confirmation.key as action_url %}
{% element form method="post" action=action_url %}
{% slot actions %}
{% csrf_token %}
{{ redirect_field }}
<div class="text-end">
{% element button class="btn btn-success" type="submit" %}
{% trans 'Confirm' %}
{% endelement %}
</div>
{% endslot %}
{% endelement %}
{% else %}
{% element p %}
{% blocktrans %}Unable to confirm {{ email }} because it is already confirmed by a different account.{% endblocktrans %}
{% endelement %}
{% endif %}
{% else %}
{% url 'account_email' as email_url %}
{% element p %}
{% blocktrans %}This email confirmation link expired or is invalid. Please <a href="{{ email_url }}">issue a new email confirmation request</a>.{% endblocktrans %}
{% endelement %}
{% endif %}
{% endblock content %}

View File

@@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% load i18n widget_tweaks allauth account socialaccount %}
{% block head_title %}cmoser.eu - {% translate 'Log in' %}{% endblock head_title %}
{% block content %}
<main class="card rounded mx-auto p-4" style="width:30rem;">
<div class="card-body">
<div class="d-flex flex-column">
<div class="flex flex-col text-center mb-4">
<h1 class="h3">Melde dich bei deinem Account an</h1>
<form method="POST" class="mb-4">
{% csrf_token %}
{% if form.errors %}
{% for field, errors in form.errors.items %}
{% for error in errors %}
<div class="p-2 my-2 text-sm text-red-700 bg-red-50 rounded-md border-red-300 border">
{{ error }}
</div>
{% endfor %}
{% endfor %}
{% endif %}
<div class="form-floating mb-3">
{% render_field form.login class="form-control" placeholder="Emailadresse" %}
<label for="{{ form.login.id_for_label }}">Emailaddresse</label>
</div>
<div class="form-floating mb-3">
{% render_field form.password class="form-control" %}
<label for="{{ form.password.id_for_label }}">Passwort</label>
</div>
<div>
{% render_field form.remember class="input-checked" %}
<label class="input-checked-label" for="{{ form.remember.id_for_label }}">{{ form.remember.label }}</label>
</div>
<div class="text-end">
<button type="submit" class="btn btn-success">Sign in</button>
</div>
</form>
</div>
<hr>
<h2 class="h4 text-center">Oder nutze deinen Socialaccount</h3>
{% if SOCIALACCOUNT_ENABLED %}
{% include 'socialaccount/snippets/login.html' with page_layout="entrance" %}
{% endif %}
</div>
</main>
{% endblock content %}

View File

@@ -0,0 +1,39 @@
{% extends "account/base_manage_password.html" %}
{% load allauth i18n widget_tweaks %}
{% block head_title %}
{% trans "Change Password" %}
{% endblock head_title %}
{% block content %}
{% element h1 %}
{% trans "Change Password" %}
{% endelement %}
{% url 'account_change_password' as action_url %}
{% element form form=form method="post" action=action_url %}
{% slot body %}
{% csrf_token %}
{#{{ redirect_field }}#}
{# {% element fields form=form %}{% endelement %} #}
<div class="form-floating form-floating-sm mb-3">
{% render_field form.oldpassword placeholder="Aktuelles Passwort" class="form-control" %}
<label for="{{ id_oldpassword }}">Aktuelles Passwort</label>
</div>
<div class="form-floating form-floating-sm mb-3">
{% render_field form.password1 class="form-control" placehoder="Neues Passwort" autocomplete="new-password" required="" %}
<label for="{{ id_password1 }}">Neues Passwort</label>
</div>
<div class="form-floating form-flaoting-sm mb-3">
{% render_field form.password2 class="form-control" placehoder="Neues Passwort" autocomplete="new-password" required="" %}
<label for="{{ id_password2 }}">Neues Passwort</label>
</div>
{% endslot %}
{% slot actions %}
<div class="text-end">
<a class="me-2" href="{% url 'account_reset_password' %}">{% trans "Forgot Password?" %}</a>
{% element button class="btn btn-primary" type="submit" %}
{% trans "Change Password" %}
{% endelement %}
</div>
{% endslot %}
{% endelement %}
{% endblock content %}

View File

@@ -0,0 +1,38 @@
{% extends "account/base_entrance.html" %}
{% load i18n allauth account widget_tweaks %}
{% block head_title %}
{% trans "Password Reset" %}
{% endblock head_title %}
{% block content %}
{% element h1 %}
{% trans "Password Reset" %}
{% endelement %}
{% if user.is_authenticated %}
{% include "account/snippets/already_logged_in.html" %}
{% endif %}
{% element p %}
{% trans "Forgotten your password? Enter your email address below, and we'll send you an email allowing you to reset it." %}
{% endelement %}
{% url 'account_reset_password' as reset_url %}
{% element form form=form method="post" action=reset_url %}
{% slot body %}
{% csrf_token %}
{{ redirect_field }}
<div class="form-floating form-floating-sm">
{% render_field form.email name="email" autocomplete="email" placeholder="E-Mail-Adresse" max-length="320" required="" class="form-control mb-3" %}
<label for="id_email">E-Mail-Adresse</label>
</div>
{% endslot %}
{% slot actions %}
<div class="text-end mb-3">
{% element button type="submit" class="btn btn-primary" %}
{% trans 'Reset My Password' %}
{% endelement %}
</div>
{% endslot %}
{% endelement %}
{% element p %}
{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}
{% endelement %}
{% endblock content %}

View File

@@ -0,0 +1,13 @@
{% load allauth %}
{% comment %} djlint:off {% endcomment %}
<{% if attrs.href %}a href="{{ attrs.href }}"{% else %}button{% endif %}
{% if attrs.form %}form="{{ attrs.form }}"{% endif %}
{% if attrs.id %}id="{{ attrs.id }}"{% endif %}
{% if attrs.name %}name="{{ attrs.name }}"{% endif %}
{% if attrs.value %}value="{{ attrs.value }}"{% endif %}
{% if attrs.type %}type="{{ attrs.type }}"{% endif %}
{% if attrs.class %}class="{{ attrs.class }}"{% endif %}
>
{% slot %}
{% endslot %}
</{% if attrs.href %}a{% else %}button{% endif %}>

View File

@@ -0,0 +1,21 @@
{% load static %}
{% if not attrs.name|lower == "openid" %}
<li class="list-group-item">
<a title="{{ attrs.name }}" href="{{ attrs.href }}" class="icon-link">
{% if attrs.name|lower == "steam" %}
<svg class="bi">
<use xlink:href="{% static "img/bootstrap-icons.svg" %}#steam"></use>
</svg>
{% elif attrs.name|lower == "github" %}
<svg class="bi">
<use xlink:href="{% static "img/bootstrap-icons.svg" %}#github"></use>
</svg>
{% elif attrs.name|lower == "google" %}
<svg class="bi">
<use xlink:href="{% static "img/bootstrap-icons.svg" %}#google"></use>
</svg>
{% endif %}
{{ attrs.name }}
</a>
</li>
{% endif %}

View File

@@ -0,0 +1,5 @@
{% load allauth %}
<ul class="list-group">
{% slot %}
{% endslot %}
</ul>

View File

@@ -0,0 +1,2 @@
{% extends 'base.html' %}

View File

@@ -7,6 +7,9 @@
<title>TinyWiki</title> <title>TinyWiki</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
<style>
{% block style %}{% endblock %}
</style>
<script> <script>
document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')); document.documentElement.setAttribute('data-bs-theme', (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'));
</script> </script>
@@ -15,7 +18,7 @@
<body class="min-vh-100 d-flex flex-column"> <body class="min-vh-100 d-flex flex-column">
<div class="container-fluid"> <div class="container-fluid">
<div class="row bg-info px-2"> <div class="row bg-primary pt-2">
<div clas="col-md-12 text-white "> <div clas="col-md-12 text-white ">
{% if brand_logo %} {% if brand_logo %}
<img class="mb2 me-2" width="46" height="46" src="{{ brand_logo }}"> <img class="mb2 me-2" width="46" height="46" src="{{ brand_logo }}">
@@ -26,7 +29,7 @@
{% endif %} {% endif %}
<span class="display-6 font-weight-bold text-white me-2">{{ brand_name }}</span> <span class="display-6 font-weight-bold text-white me-2">{{ brand_name }}</span>
{% if subtitle %} {% if subtitle %}
<span class="h2">{{ subtitle }}</h2> <span class="h2 text-truncate-sm">{{ subtitle }}</h2>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -61,8 +64,25 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="row h-100"> <div class="row h-100 mb-4">
<div class="col-lg-3"> <div class="col-lg-3 pt-6 px-4 d-none d-lg-block" >
<ul class="list-group mt-2">
<li class="list-group-item bg-primary text-white">
<span class="list-group-badge">TinyWiki Pages</span>
</li>
<li class="list-group-item">
<a href="{% url "tinywiki:page" slug="tw-bbcode" %}">BBCode Guide</a>
</li>
<li class="list-group-item">
<a href="{% url "tinywiki:page" slug="tw-markdown" %}">Markdown Guide</a>
</li>
<li class="list-group-item">
<a href="{% url "tinywiki:page" slug="tw-license" %}">TinyWiki License</a>
</li>
<li class="list-group-item">
<a href="{% url "tinywiki:page" slug="tw-bootstrap-license" %}">Bootstrap License</a>
</li>
</ul>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<main> <main>
@@ -83,6 +103,7 @@
<a class="text-white" href="{% url 'tinywiki:page' 'tw-license' %}"> &copy; 2025</a> <a class="text-white" href="{% url 'tinywiki:page' 'tw-license' %}"> &copy; 2025</a>
</span> </span>
</footer> </footer>
{% block extra_body %}{% endblock extra_body %}
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.min.js"></script>
{% block extra_scripts %}{% endblock extra_scripts %} {% block extra_scripts %}{% endblock extra_scripts %}

View File

@@ -0,0 +1,55 @@
{% extends 'base.html' %}
{# {% extends "socialaccount/base_manage.html" %} #}
{% load i18n %}
{% load allauth %}
{% block head_title %}
{% trans "Account Connections" %}
{% endblock head_title %}
{% block content %}
{% element h1 %}
{% trans "Account Connections" %}
{% endelement %}
{% if form.accounts %}
{% element p %}
{% blocktrans %}You can sign in to your account using any of the following third-party accounts:{% endblocktrans %}
{% endelement %}
{% url 'socialaccount_connections' as action_url %}
{% element form form=form method="post" action=action_url %}
{% slot body %}
{% csrf_token %}
{% for acc in form.fields.account.choices %}
{% with account=acc.0.instance.get_provider_account %}
{% setvar radio_id %}
id_account_{{ account.account.pk }}
{% endsetvar %}
{% setvar tags %}
socialaccount,{{ account.account.provider }}
{% endsetvar %}
{% element field id=radio_id type="radio" name="account" value=account.account.pk %}
{% slot label %}
{{ account }}
{% element badge tags=tags %}
{{ account.get_brand.name }}
{% endelement %}
{% endslot %}
{% endelement %}
{% endwith %}
{% endfor %}
{% endslot %}
{% slot actions %}
{% element button tags="delete,danger" class="btn btn-danger" type="submit" %}
{% trans 'Remove' %}
{% endelement %}
{% endslot %}
{% endelement %}
{% else %}
{% element p %}
{% trans 'You currently have no third-party accounts connected to this account.' %}
{% endelement %}
{% endif %}
{% element h2 %}
{% trans 'Add a Third-Party Account' %}
{% endelement %}
{% include "socialaccount/snippets/provider_list.html" with process="connect" %}
{% include "socialaccount/snippets/login_extra.html" %}
{% endblock content %}

View File

@@ -0,0 +1,18 @@
{% load allauth socialaccount %}
{% get_providers as socialaccount_providers %}
{% if socialaccount_providers %}
{% element provider_list %}
{% for provider in socialaccount_providers %}
{% if not provider.name == "OpenID" %}
{% for brand in provider.get_brands %}
{% provider_login_url provider openid=brand.openid_url process=process as href %}
{% element provider name=brand.name provider_id=provider.id href=href %}
{% endelement %}
{% endfor %}
{% endif %}
{% provider_login_url provider process=process scope=scope auth_params=auth_params as href %}
{% element provider name=provider.name provider_id=provider.id href=href %}
{% endelement %}
{% endfor %}
{% endelement %}
{% endif %}

View File

@@ -1,5 +1,6 @@
from django import forms from django import forms
from .models import Page,Image from .models import Page,Image
from django.utils.translation import gettext_lazy as _
class PageForm(forms.ModelForm): class PageForm(forms.ModelForm):
class Meta: class Meta:
@@ -12,6 +13,11 @@ class PageForm(forms.ModelForm):
'content', 'content',
] ]
class PageDeleteForm(forms.Form):
slug = forms.SlugField(allow_unicode=False,
required=False,
label=_("Slug"))
class PageAdminForm(forms.ModelForm): class PageAdminForm(forms.ModelForm):
class Meta: class Meta:
model = Page model = Page
@@ -24,3 +30,25 @@ class PageAdminForm(forms.ModelForm):
'content', 'content',
] ]
class ImageUploadView(forms.ModelForm):
class Meta:
model = Image
fields = [
"image",
"alt",
"description",
]
class ImageEditView(forms.ModelForm):
class Meta:
model = Image
fields = [
"alt",
"description",
]
class ImageDeleteView(forms.Form):
slug = forms.SlugField(allow_unicode=False,
required=False)

View File

@@ -59,7 +59,7 @@ class Migration(migrations.Migration):
group.permissions.add(perm_mapping[perm]) group.permissions.add(perm_mapping[perm])
def init_default_pages(apps,schema_editor)->None: def init_builtin_pages(apps,schema_editor)->None:
from ..models import Page,Image from ..models import Page,Image
from ..enums import WikiContentType,WikiPageStatus from ..enums import WikiContentType,WikiPageStatus
from pathlib import Path from pathlib import Path
@@ -85,14 +85,47 @@ class Migration(migrations.Migration):
content=content) content=content)
def init_builtin_images(apps,schema_edit):
from pathlib import Path
from ..models import Image
from django.core.files.images import ImageFile
import json
image_dir = Path(__file__).resolve().parent / "images"
images_json_file = image_dir/ "images.json"
if not images_json_file.is_file():
return
with open(images_json_file,"rt",encoding="utf-8") as ifile:
images_data = json.loads(ifile.read())
for slug,_spec in images_data.items():
spec = dict(_spec)
image_basename = spec['image']
image_filename = image_dir / spec["image"]
spec['slug'] = slug
del spec['image']
img = Image(**spec)
with open(image_filename,"rb") as ifile:
img_file = ImageFile(ifile,image_basename)
img.image.save(image_basename,img_file)
img.save()
def init_user_imges(apps,schema_edit)->None:
#TODO
pass
def init_user_pages(apps,schema_edit)->None: def init_user_pages(apps,schema_edit)->None:
from ..models import Page,Image
#TODO #TODO
pass
operations = [ operations = [
migrations.RunPython(init_tinywiki_groups_and_permissions), migrations.RunPython(init_tinywiki_groups_and_permissions),
migrations.RunPython(init_tinywiki_user), migrations.RunPython(init_tinywiki_user),
migrations.RunPython(init_default_pages), migrations.RunPython(init_builtin_images),
migrations.RunPython(init_builtin_pages),
] ]

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

View File

@@ -0,0 +1,7 @@
{
"tw-image-01": {
"alt": "Foggy Mountain-tops in the dawn",
"image": "fabian-bachli-jQAe44MEIXU-unsplash.jpg",
"description": "Foto from [url=\"https://unsplash.com/de/@fabianbaechli?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash\"]Fabian Bächli[/url] on [url=\"https://unsplash.com/de/fotos/nebelige-berggipfel-in-der-morgendammerung-mit-sanften-wolken-jQAe44MEIXU?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash\"]Unsplash[/url]"
}
}

View File

@@ -0,0 +1,582 @@
[h1]BBCode used by TinyWiki[/h1]
[p]
BBCode uses tags to lay out your page. Tags written as opened by [code][tag][/code] and closed by [code][/tag][/code]. Standalone tags close automatically.
[/p]
[h2]Headers[/h2]
[p]
Headers are created by the [i]h[/i] tags. They range from [code][h1][/code] to [code][h6][/code]. You should start your page with a [code][h1][/code]-header. You shall only define one [code][h1][/code]-header in your page. It serves as the title displayed.
[/p]
[p]
You define headers as follows:
[/p]
[codeblock=bbcode][h1]Title header[/h1]
[h2]Header 2[/h2]
[h3]Header 3[/h3]
[h4]Header 4[/h4]
[h5]Header 5[/h5]
[h6]Header 6[/h6][/codeblock]
Which render as:
[/p]
[h1]Title header[/h1]
[h2]Header 2[/h2]
[h3]Header 3[/h3]
[h4]Header 4[/h4]
[h5]Header 5[/h5]
[h6]Header 6[/h6]
[hr]
[h2]Paragraphs[/h2]
[p]Paragraphs are created by the [code][p][/code] tag.[/p]
See the following code:
[codeblock=bbcode]
[hr]
[p]This is a paragrpah.[/p]
[p]And this is another one with a [b]encapsulated bold tag[/b].[/p]
[hr][/codeblock]
This renders as:[hr]
[p]This is a paragrpah.[/p]
[p]And this is another one with a [b]encapsulated bold tag[/b].[/p]
[hr]
[h2]Typography[/h2]
[p]
You can create [b]bold[/b] text with the [code][b][/code]-tag, [i]italic[/i] text with the [code][i][/code]-tag. To create text with [strong]strong emphasis[/strong], use the [code][strong][/code]-tag. [em]Emphasized text[/em] is created using the [code][em][/code]-tag. [u]Underlining text[/u] is done with the [code][u][/code]-tag, [s]Striked through[/s] text with the [code][s][/code]-tag. And to [mark]mark text[/mark] you can use the [code][mark][/code]-tag.
[/p]
[p]
The tags described in the previous paragraph are rendered as semantic-HTML and handled by screen-readers accordingly.
[/p]
[p]
If you want to show inline code, use the [code][code][/code]-tag. Code blocks can be can be created wtih the [code][codeblock][/code]-tag. You can also specify a language for codeblocks fields. this is done by using the [code][code=language][/code] option set to the codeblock-tag ([code][codeblock=bbcode][/code] is used to render the codeblock fields in this text).
[/p]
[p]
Line breaks are created with the [code][br][/code]-tag and horizontal rulers with the [code][hr][/code]-tag.
[/p]
[p]
An example:
[codeblock=bbcode][b]This text is bold.[/b][br]
[i]This text is italic.[/i][br]
[u]This is some underlined text.[/u][br]
[s]We can also strike through some text.[/s][br]
[strong]Here some text with strong emphasis.[/strong][br]
[em]Emphasized text can also be created.[/em][br]
[mark]And finally we mark some text.[/mark][br]
[code]finally_some_code()[/code]
[hr][/codeblock]
This renders as follows:
[/p]
[p]
[b]This text is bold.[/b][br]
[i]This text is italic.[/i][br]
[u]This is some underlined text.[/u][br]
[s]We can also strike through some text.[/s][br]
[strong]Here some text with strong emphasis.[/strong][br]
[em]Emphasized text can also be created.[/em][br]
[mark]And we mark some text.[/mark][br]
[code]finally_some_code()[/code]
[hr]
[/p]
[h2]Links[/h2]
[p]
Links are created using the [code][url][/code]-tag. You can create a link by using [code][url]www.example.com[/url[/code] to create a simple link with the same link description as the URL itself or you use the [code][url=https://www.example.com]Link description[/url][/code] style if you want the link description differ from the link itself.
[/p]
[p]
To link against a wiki-page, you can use the [code][wiki-url=page_slug][/code]-code or the [code][wiki][/code]-tag. The [code][wiki-url][/code] works nearly the same, is used to link against a page with a user-defined description. Whereas the [code][wiki=slug][/code]-tag, which is self-closing, is used to link against a wiki page and display the page's title.
[/p]
[p]Here an example:[/p]
[codeblock=bbcode][url]www.google.com[/url][br]
[url=https://duckduckgo.com]Another search engine[/url][br]
[wiki-url=tw-license]The license of Tinywiki[/wiki-url][br]
[wiki=tw-bbcode][br]
And a link to the wiki-homepage: [wiki][/codeblock]
[p]
[url]www.google.com[/url][br]
[url=https://duckduckgo.com]Another search engine[/url][br]
[wiki-url=tw-license]The license of Tinywiki[/wiki-url][br]
[wiki=tw-bbcode][br]
And a link to the wiki-homepage [wiki]
[/p]
[h2]Lists[/h2]
[p]
Unordered lists can be created using the [code][list][*] Item1 [*] Item2[/list][/code] tags. Alternatively, you can use the [code][ul][li]Item1[/li][li]Item2[/li][/ul][/code] notation.
[/p]
[p]
Ordered lists are created with the [code][ol][/code] tag. The notation is the same as the [code][ul][/code] tag.
[/p]
[p]
Here an example:
[codeblock=bbcode]
[hr]
An inline list:
[list]
[*] Item1
[*] Item2
[*] Item 3
[/list]
[hr]
An unordered list:
[ul]
[li] Item 1[/li]
[li] Item 2[/li]
[li]
[ul]
[li]Embedded 1[/li]
[li]Embedded 2[/li]
[/ul]
[/li]
[/ul]
[hr]
An ordered list:
[ol]
[li] Item 1[/li]
[li] Item 2[/li]
[li]
[ol]
[li] Embedded 1[/li]
[li]Embedded 2[/li]
[/ol]
[/li]
[/ol]
[/codeblock]
[p]
A list:
[list]
[*] Item1
[*] Item2
[*] Item 3
[/list]
[hr]
An unordered list:
[ul]
[li] Item 1[/li]
[li] Item 2[/li]
[li]
[ul]
[li]Embedded 1[/li]
[li]Embedded 2[/li]
[/ul]
[/li]
[/ul]
[hr]
An ordered list:
[ol]
[li] Item 1[/li]
[li] Item 2[/li]
[li]
[ol]
[li]Embedded 1[/li]
[li]Embedded 2[/li]
[/ol]
[/li]
[/ol]
[/p]
[h2]Tables[/h2]
[p]
Tables are created with the [code]table[/code] tag. They contain rows, created with the [code]table-row[/code] or [code][tr][/code] tag. Each row contain table headers or table data. Table headers are created with [code][table-header][/code] or [code][th][/code] tag, table data with the [code][table-data][/code] or [code][td][/code] tag.
[/p]
[codeblock=bbcode]
[table]
[table-row]
[table-header]Name[/table-header]
[table-header]Email[/table-header]
[table-header]Phone number[/table-header]
[table-row]
[table-row]
[table-data]John Doe[/table-data]
[table-data]johndoe@example.com[/table-data]
[table-data]345693887[/table-data]
[/table-row]
[table-row]
[table-data]Jane Doe[/table-data]
[table-data]janedoe@example.com[/table-data]
[table-data]685528874[/table-data]
[/table-row]
[table-row]
[table-data]Patrick Smith[/table-data]
[table-data]patricksmith@example.com[/table-data]
[table-data]5786255143[/table-data]
[/table-row]
[/table]
[/codeblock]
[table]
[table-row]
[table-header]Name[/table-header]
[table-header]Email[/table-header]
[table-header]Phone number[/table-header]
[table-row]
[table-row]
[table-data]John Doe[/table-data]
[table-data]johndoe@example.com[/table-data]
[table-data]345693887[/table-data]
[/table-row]
[table-row]
[table-data]Jane Doe[/table-data]
[table-data]janedoe@example.com[/table-data]
[table-data]685528874[/table-data]
[/table-row]
[table-row]
[table-data]Patrick Smith[/table-data]
[table-data]patricksmith@example.com[/table-data]
[table-data]5786255143[/table-data]
[/table-row]
[/table]
[h3]Bootstrap extensions[/h3]
[p][b]NOTE: The following tables might not render right when not using [url=https://getbootstrap.com]Bootstrap[/url]![/b][/p]
[p]
[p]
When using [i]Bootstrap[/i] you can style your tables. You can use [code]primary, secondary, info, warning, danger, success, light[/code] and [code]dark[/code] as an argument for the [code][table][/code] tag to give the table some color.
[/p]
[codeblock]
[table=primary]
[tr]
[th]Name[/th]
[th]Email[/th]
[th]Phone number[/th]
[/tr]
[tr]
[td]John Doe[/td]
[td]johndoe@example.com[/td]
[td]345693887[/td]
[/tr]
[tr]
[td]Jane Doe[/td]
[td]janedoe@example.com[/td]
[td]685528874[/td]
[/tr]
[tr]
[td]Patrick Smith[/td]
[td]patricksmith@example.com[/td]
[td]5786255143[/td]
[/tr]
[/table]
[/codeblock]
[table=primary]
[tr]
[th]Name[/th]
[th]Email[/th]
[th]Phone number[/th]
[/tr]
[tr]
[td]John Doe[/td]
[td]johndoe@example.com[/td]
[td]345693887[/td]
[/tr]
[tr]
[td]Jane Doe[/td]
[td]janedoe@example.com[/td]
[td]685528874[/td]
[/tr]
[tr]
[td]Patrick Smith[/td]
[td]patricksmith@example.com[/td]
[td]5786255143[/td]
[/tr]
[/table]
[p]When your wiki is powered by [i]Bootstrap[/i], you can also create bordered tables. If the bordered attribute is not one of [code]0, false, n, no[/code] or [code]off[/code], a bordered table is rendered. If the attribute is one of [code]primary, secondary, info, warning, danger, success, light[/code] or [code]dark[/code], the border colors are set accordingly.
[/p]
[codeblock]
[table bordered=1]
[tr]
[th]Name[/th]
[th]Email[/th]
[th]Phone number[/th]
[/tr]
[tr]
[td]John Doe[/td]
[td]johndoe@example.com[/td]
[td]345693887[/td]
[/tr]
[tr]
[td]Jane Doe[/td]
[td]janedoe@example.com[/td]
[td]685528874[/td]
[/tr]
[tr]
[td]Patrick Smith[/td]
[td]patricksmith@example.com[/td]
[td]5786255143[/td]
[/tr]
[/table]
[table bordered=success]
[tr]
[th]Name[/th]
[th]Email[/th]
[th]Phone number[/th]
[/tr]
[tr]
[td]John Doe[/td]
[td]johndoe@example.com[/td]
[td]345693887[/td]
[/tr]
[tr]
[td]Jane Doe[/td]
[td]janedoe@example.com[/td]
[td]685528874[/td]
[/tr]
[tr]
[td]Patrick Smith[/td]
[td]patricksmith@example.com[/td]
[td]5786255143[/td]
[/tr]
[/table]
[table=info bordered=danger]
[tr]
[th]Name[/th]
[th]Email[/th]
[th]Phone number[/th]
[/tr]
[tr]
[td]John Doe[/td]
[td]johndoe@example.com[/td]
[td]345693887[/td]
[/tr]
[tr]
[td]Jane Doe[/td]
[td]janedoe@example.com[/td]
[td]685528874[/td]
[/tr]
[tr]
[td]Patrick Smith[/td]
[td]patricksmith@example.com[/td]
[td]5786255143[/td]
[/tr]
[/table]
[/codeblock]
[table bordered=1]
[tr]
[th]Name[/th]
[th]Email[/th]
[th]Phone number[/th]
[/tr]
[tr]
[td]John Doe[/td]
[td]johndoe@example.com[/td]
[td]345693887[/td]
[/tr]
[tr]
[td]Jane Doe[/td]
[td]janedoe@example.com[/td]
[td]685528874[/td]
[/tr]
[tr]
[td]Patrick Smith[/td]
[td]patricksmith@example.com[/td]
[td]5786255143[/td]
[/tr]
[/table]
[table bordered=success]
[tr]
[th]Name[/th]
[th]Email[/th]
[th]Phone number[/th]
[/tr]
[tr]
[td]John Doe[/td]
[td]johndoe@example.com[/td]
[td]345693887[/td]
[/tr]
[tr]
[td]Jane Doe[/td]
[td]janedoe@example.com[/td]
[td]685528874[/td]
[/tr]
[tr]
[td]Patrick Smith[/td]
[td]patricksmith@example.com[/td]
[td]5786255143[/td]
[/tr]
[/table]
[table=info bordered=danger]
[tr]
[th]Name[/th]
[th]Email[/th]
[th]Phone number[/th]
[/tr]
[tr]
[td]John Doe[/td]
[td]johndoe@example.com[/td]
[td]345693887[/td]
[/tr]
[tr]
[td]Jane Doe[/td]
[td]janedoe@example.com[/td]
[td]685528874[/td]
[/tr]
[tr]
[td]Patrick Smith[/td]
[td]patricksmith@example.com[/td]
[td]5786255143[/td]
[/tr]
[/table]
[p]
With [i]Bootstrap[/i] you can also style individual rows or even cells with [code]primary, secondary, info, warning, danger, success, light[/code] or [code]dark[/code] applying to the tag.
[/p]
[codeblock]
[table]
[tr]
[th]Styling[/th]
[th]Cell[/th]
[th]Cell[/th]
[/tr]
[tr=primary]
[td]primary[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=secondary]
[td]secondary[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=info]
[td]info[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=success]
[td]success[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=warning]
[td]warning[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=danger]
[td]danger[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=light]
[td]light[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=dark]
[td]dark[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[/table]
[/codeblock]
[table]
[tr]
[th]Styling[/th]
[th]Cell[/th]
[th]Cell[/th]
[/tr]
[tr=primary]
[td]primary[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=secondary]
[td]secondary[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=info]
[td]info[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=success]
[td]success[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=warning]
[td]warning[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=danger]
[td]danger[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=light]
[td]light[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[tr=dark]
[td]dark[/td]
[td]cell[/td]
[td]cell[/td]
[/tr]
[/table]
[h3]Rowspan and Colspan[/h3]
[p]
You can also create tables that span over mutliple rows or columns. Add the [code]rowspan=n[/code] or [code]colspan=n[/code] to the table header or table data ([i]n[/i] is the number of rows or columns that should be spanned).
[/p]
[codeblock]
[table bordered=1]
[tr]
[th]Header 1[/th]
[th]Header 2[/th]
[th]Header 3[/th]
[/tr]
[tr]
[td]Row1-Column1[/td]
[td]Row1-Column2[/td]
[td rowspan=2]Row1-Column3[/td]
[/tr]
[tr]
[td]Row2-Column1[/td]
[td]Row2-Column2[/td]
[/tr]
[tr]
[td]Row3-Column1[/td]
[td colspan=2]Row3-Column2[/td]
[/tr]
[/table]
[/codeblock]
[table bordered=1]
[tr]
[th]Header 1[/th]
[th]Header 2[/th]
[th]Header 3[/th]
[/tr]
[tr]
[td]Row1-Column1[/td]
[td]Row1-Column2[/td]
[td rowspan=2]Row1-Column3[/td]
[/tr]
[tr]
[td]Row2-Column1[/td]
[td]Row2-Column2[/td]
[/tr]
[tr]
[td]Row3-Column1[/td]
[td colspan=2]Row3-Column2[/td]
[/tr]
[/table]
[h2]Images[/h2]

View File

@@ -10,5 +10,11 @@
"content_type": "bbcode", "content_type": "bbcode",
"status":"published", "status":"published",
"file":"bs-license.bbcode" "file":"bs-license.bbcode"
},
"tw-bbcode": {
"title": "BBCode used by TinyWiki",
"content_type": "bbcode",
"status":"published",
"file":"bbcode.bbcode"
} }
} }

View File

@@ -8,6 +8,10 @@ from django.contrib.auth import get_user_model
from tinywiki.enums import WIKI_CONTENT_TYPES, WIKI_PAGE_STATUS, WikiContentType, WikiPageStatus from tinywiki.enums import WIKI_CONTENT_TYPES, WIKI_PAGE_STATUS, WikiContentType, WikiPageStatus
import markdown import markdown
import bbcode as _bbcode
from tinywiki.parser import bbcode
from .parser import parse_bbcode from .parser import parse_bbcode
from . import settings from . import settings
import tinywiki import tinywiki
@@ -122,3 +126,11 @@ class Image(models.Model):
uploaded_at = models.DateTimeField(_("uploaded at"), uploaded_at = models.DateTimeField(_("uploaded at"),
auto_now_add=True) auto_now_add=True)
@property
def description_html(self)->str:
return _bbcode.render_html(self.description)
@property
def description_html_safe(self)->SafeText:
return mark_safe(self.description_html)

View File

@@ -1,6 +1,6 @@
import bbcode import bbcode
from . import formatters from . import formatters
PARSER = bbcode.Parser(newline="\n",escape_html=True) PARSER = bbcode.Parser(newline="\n",escape_html=True,replace_links=False)
def _(): def _():
for i in formatters.SIMPLE_FORMATTERS: for i in formatters.SIMPLE_FORMATTERS:

View File

@@ -10,26 +10,36 @@ from .text_formatters import (
render_wiki_image, render_wiki_image,
render_wiki_link, render_wiki_link,
render_wiki_url, render_wiki_url,
) render_table,
from .simple_formatters import ( render_table_header,
SIMPLE_HEADER_FORMATTERS, render_table_data,
render_table_row,
render_youtube_video,
) )
from .simple_formatters import SIMPLE_FORMATTERS
# a list of tuples containig an tuple args and a dict of kwargs # a list of tuples containig an tuple args and a dict of kwargs
SIMPLE_FORMATTERS=[
*SIMPLE_HEADER_FORMATTERS,
]
#a list of tuples containing an tuple of args and a dict of kwargs #a list of tuples containing an tuple of args and a dict of kwargs
FORMATTERS=[ FORMATTERS=[
(('url',render_url),{'strip':True,'swallow_trailing_newline':True,'same_tag_closes':True}), (('url',render_url),{'strip':True,'swallow_trailing_newline':True,'same_tag_closes':True}),
(('wiki-url',render_wiki_url),{'strip':True,'swallow_trailing_newline':True,'same_tag_closes':True}), (('wiki-url',render_wiki_url),{'strip':True,'swallow_trailing_newline':True,'same_tag_closes':True}),
(('wiki',render_wiki_link),{'strip':True,'swallow_tailin_newline':True,'standalone':True}), (('wiki',render_wiki_link),{'strip':True,'swallow_tailin_newline':True,'standalone':True}),
(('codeblock',render_codeblock),{'strip':False,'swallow_trailing_newline':False,'same_tag_closes':True}), (('codeblock',render_codeblock),{'strip':True,'swallow_trailing_newline':False,'same_tag_closes':False,"render_embedded":False}),
(('ol',render_ordered_list),{}), (('ol',render_ordered_list),{}),
(('ul',render_unordered_list),{}), (('ul',render_unordered_list),{}),
(('li',render_list_item),{}), (('li',render_list_item),{}),
(('p',render_paragraph),{}), (('p',render_paragraph),{'same_tag_closes':False}),
(('image',render_image),{}), (('image',render_image),{'same_tag_closes':True}),
(('wiki-image',render_wiki_image),{'standalone':True}) (('img',render_image),{'same_tag_closes':True}),
(('wiki-image',render_wiki_image),{'standalone':True}),
(('wimg',render_wiki_image),{'standalone':True}),
(('table',render_table),{}),
(('table-row',render_table_row),{}),
(('tr',render_table_row),{}),
(('table-header',render_table_header),{}),
(('th',render_table_header),{}),
(('table-data',render_table_data),{}),
(('td',render_table_data),{}),
(('youtube',render_youtube_video),{'same_tag_closes':True}),
] ]

View File

@@ -1,11 +1,15 @@
SIMPLE_HEADER_FORMATTERS = [ SIMPLE_FORMATTERS = [
(('h1',"<h1>%(value)s</h1>"),{}), (('h1',"<h1>%(value)s</h1>"),{}),
(('h2',"<h2>%(value)s</h2>"),{}), (('h2',"<h2>%(value)s</h2>"),{}),
(('h3',"<h3>%(value)s</h3>"),{}), (('h3',"<h3>%(value)s</h3>"),{}),
(('h4',"<h4>%(value)s</h4>"),{}), (('h4',"<h4>%(value)s</h4>"),{}),
(('h5',"<h5>%(value)s</h5>"),{}), (('h5',"<h5>%(value)s</h5>"),{}),
(('h6',"<h6>%(value)s</h6>"),{}), (('h6',"<h6>%(value)s</h6>"),{}),
(('mark',"<mark>%(value)s</mark>"),{}),
(("strong","<strong>%(value)s</strong>"),{}),
(("em","<em>%(value)s</em>"),{}),
(("br","<br>"),{"standalone":True}),
(('copy',"&copy;"),{'standalone':True}), (('copy',"&copy;"),{'standalone':True}),
(('reg',"&reg;"),{'standalone':True}), (('reg',"&reg;"),{'standalone':True}),
(('trade',"&trade;"),{'standalone':True}), (('trade',"&trade;"),{'standalone':True}),

View File

@@ -2,9 +2,10 @@ from django_project.settings import STATIC_URL
from django.urls import reverse from django.urls import reverse
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from ... import settings from ... import settings
from ... import models from ... import models
import bbcode
def render_url(tag_name:str,value,options,parent,context): def render_url(tag_name:str,value,options,parent,context):
try: try:
@@ -12,17 +13,19 @@ def render_url(tag_name:str,value,options,parent,context):
except KeyError: except KeyError:
url = value url = value
if '://' not in url: if not url.startswith('/') and '://' not in url:
url = "http://" + url url = "http://" + url
if settings.USE_BOOTSTRAP: if settings.USE_BOOTSTRAP:
return f"<a href=\"{url}\" class=\"icon-link icon-link-hover\" referrer-policy=\"no-referrer\" rel=\"noreferrer noopener\">{value}{render_to_string('tinywiki/icons/box-arrow-up-right.svg')}</a>" if ['noicon in options']:
return f"<a href=\"{url}\" referrer-policy=\"no-referrer\" rel=\"noreferrer noopener\">{value}</a>"
return f"<a href=\"{url}\" class=\"icon-link icon-link-hover\" referrer-policy=\"no-referrer\" rel=\"noreferrer noopener\">{value}<svg class=\"bi\"><use xlink:href=\"{settings.settings.STATIC_URL + "tinywiki/icons/bootstrap-icons.svg"}#box-arrow-up-right\"></use></svg></a>"
return f"<a href=\"{url}\" referrer-policy=\"no-referrer\" rel=\"noreferrer noopener\">{value}</a>" return f"<a href=\"{url}\" referrer-policy=\"no-referrer\" rel=\"noreferrer noopener\">{value}</a>"
def render_wiki_url(tag_name,value,options,parent,context): def render_wiki_url(tag_name,value,options,parent,context):
if tag_name in options: if tag_name in options:
url = reverse("tinywiki:page",kwargs={'slug':options[tag_name]}) url = reverse("tinywiki:page",kwargs={'slug':options[tag_name]})
slug=options['tag_name'] slug=options[tag_name]
try: try:
page = models.Page.objects.get(slug=slug) page = models.Page.objects.get(slug=slug)
except models.Page.DoesNotExist: except models.Page.DoesNotExist:
@@ -33,47 +36,49 @@ def render_wiki_url(tag_name,value,options,parent,context):
slug=None slug=None
if settings.USE_BOOTSTRAP: if settings.USE_BOOTSTRAP:
href = settings.settings.STATIC_URL+"tinywiki/icons/bootstrap-icons.svg"
if page: if page:
if page.slug.startswith('tw-'): if page.slug.startswith('tw-'):
svg=render_to_string('tinywiki/icons/journal.svg') svg = "journal"
elif page.slug: elif page.slug:
svg=render_to_string('tinywiki/icons/book.svg') svg = "book"
else: else:
svg=render_to_string('tinywiki/icons/file-earmark-x') svg=href + "file-earmark-x"
return f"<a href=\"{url}\" class=\"icon-link icon-link-hover\">{value}<svg class=\"bi\"><use xlink:href=\"{href}#{svg}\"></use></svg></a>"
return f"<a href=\"{url}\" class=\"icon-link icon-link-hover\">{value}{svg}</a>"
return f"<a href=\"{url}\">{value}</a>" return f"<a href=\"{url}\">{value}</a>"
def render_wiki_link(tag_name,value,options,parent,context): def render_wiki_link(tag_name,value,options,parent,context):
if tag_name in options: if tag_name in options:
slug = options['tag_name'] slug = options[tag_name]
print("slug",slug)
try: try:
page = models.Page.objects.get(slug=slug) page = models.Page.objects.get(slug=slug)
title = page.title title = page.title
if slug.starts_with('tw-'): if slug.startswith('tw-'):
svg = "tinywiki/icons/journal.svg" svg = "journal"
else: else:
svg = "tinywiki/icons/book.svg" svg = "book"
except: except models.Page.DoesNotExist:
page = None page = None
title = _("Page not found") title = _("Page not found")
svg_template = "tinywiki/icons/file-earmark-x.svg" svg = "file-earmark-x"
url = reverse("tinywiki:page",kwargs={'slug':slug}) url = reverse("tinywiki:page",kwargs={'slug':slug})
else: else:
slug = None slug = None
title = _("Home") title = _("Home")
url = reverse("tinywiki:home") url = reverse("tinywiki:home")
svg_template = "tinywiki/icons/house.svg" svg = "house"
if settings.USE_BOOTSTRAP: if settings.USE_BOOTSTRAP:
return f"<a href=\"{url}\" class=\"icon-link icon-link-hover\">{value}{render_to_string(svg_template)}</a>" href = settings.settings.STATIC_URL + "tinywiki/icons/bootstrap-icons.svg"
return f"<a href=\"{url}\" class=\"icon-link icon-link-hover\">{title}<svg class=\"bi\"><use xlink:href=\"{href}#{svg}\"></use></svg></a>"
return f"<a href=\"{url}\">{value}</a>" return f"<a href=\"{url}\">{value}</a>"
def render_codeblock(tag_name:str,value,options,parent,context)->str: def render_codeblock(tag_name:str,value,options,parent,context)->str:
if 'codeblock' in options: if tag_name in options:
return f"<pre><code class=\"language-{options['codeblock']}\">{value}</pre></code>" return f"<pre style=\"overflow-x:auto;\"><code class=\"language-{options[tag_name]}\">{value}</pre></code>"
return f"<pre><code>{value}</pre></code>" return f"<pre style=\"overflow-x:auto;\"><code>{value}</pre></code>"
def render_ordered_list(tag_name:str,value,options,parent,context)->str: def render_ordered_list(tag_name:str,value,options,parent,context)->str:
return f"<ol>{value}</ol>" return f"<ol>{value}</ol>"
@@ -112,7 +117,7 @@ def render_image(tag_name:str,value,options,parent,context):
if 'width' in options: if 'width' in options:
_w = options['width'] _w = options['width']
if _w.endswith('px'): if _w.endswith('px') or _w.endswith('em') or _w.endswith('rem'):
fig_styles.append(f"width:{_w};") fig_styles.append(f"width:{_w};")
else: else:
if _w.endswith('%'): if _w.endswith('%'):
@@ -129,16 +134,41 @@ def render_image(tag_name:str,value,options,parent,context):
fig_classes.append(f'w-{width}') fig_classes.append(f'w-{width}')
else: else:
fig_styles.append(f"width:{_w}%;") fig_styles.append(f"width:{_w}%;")
if 'height' in options:
_h = options['width']
if _h.endswith('px') or _h.endswith('em') or _h.endswith('rem'):
fig_styles.append(f"height:{_h};")
else:
if _h.endswith('%'):
_h= _h[:-1]
if _h.isdigit():
_h=int(_w)
if _h > 100:
_h = 100
if settings.USE_BOOTSTRAP:
if 1 < int(_h) <= 25:
height = 25
else:
height = ((_h // 25) * 25)
if height > 100:
height = 100
fig_classes.append(f'h-{height}')
else:
fig_styles.append(f"height:{_h}%;")
if "position" in options: if "position" in options:
pos = options['position'] pos = options['position']
if settings.USE_BOOTSTRAP: if settings.USE_BOOTSTRAP:
if pos == "left" or pos=="start": if pos == "left" or pos=="start":
fig_classes += ["float-start","me-2"] fig_classes += ["float-start","me-2"]
classes += ["float-start","me-2"]
elif pos == "right" or pos == "end": elif pos == "right" or pos == "end":
fig_classes += ["float-end","ms-2"] fig_classes += ["float-end","ms-2"]
classes += ["float-end","ms-2"]
elif pos == "center": elif pos == "center":
fig_classes += ["mx-auto","d-block"] fig_classes += ["mx-auto","d-block"]
classes += ["mx-auto","d-block"]
if styles: if styles:
style=f"style=\"{"".join(styles)}\"" style=f"style=\"{"".join(styles)}\""
@@ -176,7 +206,7 @@ def render_wiki_image(tag_name:str,value,options,parent,context):
if 'width' in options: if 'width' in options:
_w = options['width'] _w = options['width']
if _w.endswith('px'): if _w.endswith('px') or _w.endswith('em') or _w.endswith('rem'):
fig_styles.append(f"width:{_w};") fig_styles.append(f"width:{_w};")
else: else:
if _w.endswith('%'): if _w.endswith('%'):
@@ -193,6 +223,28 @@ def render_wiki_image(tag_name:str,value,options,parent,context):
fig_classes.append(f'w-{width}') fig_classes.append(f'w-{width}')
else: else:
fig_styles.append(f"width:{_w}%;") fig_styles.append(f"width:{_w}%;")
if 'height' in options:
_h = options['width']
if _h.endswith('px') or _h.endswith('em') or _h.endswith('rem'):
fig_styles.append(f"height:{_h};")
else:
if _h.endswith('%'):
_h= _h[:-1]
if _h.isdigit():
_h=int(_w)
if _h > 100:
_h = 100
if settings.USE_BOOTSTRAP:
if 1 < int(_h) <= 25:
height = 25
else:
height = ((_h // 25) * 25)
if height > 100:
height = 100
fig_classes.append(f'h-{height}')
else:
fig_styles.append(f"height:{_h}%;")
if "position" in options: if "position" in options:
pos = options['position'] pos = options['position']
@@ -215,7 +267,155 @@ def render_wiki_image(tag_name:str,value,options,parent,context):
fig_style="" fig_style=""
if settings.USE_BOOTSTRAP: if settings.USE_BOOTSTRAP:
return f'<figure class="{" ".join(fig_classes)}" {fig_style}><img src="{image.image.url}" alt="{image.alt}" class="{' '.join(classes)}" {style}><figcaption class="figure-caption text-end">{image.description}</figcaption></figure>' return f'<figure class="{" ".join(fig_classes)}" {fig_style}><img src="{image.image.url}" alt="{image.alt}" class="{' '.join(classes)}" {style}><figcaption class="figure-caption text-end">{image.description_html}</figcaption></figure>'
else: else:
return f'<figure {fig_style}><img src="{image.image.url}" alt="{image.alt}" {style}><figcaption>{image.description}</figcaption></figure>' return f'<figure {fig_style}><img src="{image.image.url}" alt="{image.alt}" {style}><figcaption>{image.description}</figcaption></figure>'
def render_table(tag_name:str,value,options,parent,context):
if settings.USE_BOOTSTRAP:
classes=["table"]
if "bordered" in options:
if options["bordered"] not in ("0","n","no","false","off"):
classes.append("table-bordered")
if options["bordered"] in ("primary","secondary","info","warning","danger","success","light","dark"):
classes.append(f"border-{options['bordered']}")
if tag_name in options:
if options[tag_name] in ("primary","secondary","info","warning","danger","success","light","dark"):
classes.append(f"table-{options[tag_name]}")
return f"<table class=\"{" ".join(classes)}\">{value}</table>"
return f"<table>{value}</table>"
def render_table_row(tag_name:str,value,options,parent,context):
classes=[]
if settings.USE_BOOTSTRAP:
if tag_name in options:
if options[tag_name] in ("primary","secondary","info","warning","danger","success","light","dark"):
classes.append(f"table-{options[tag_name]}")
class_attr=f"class=\"{" ".join(classes)}\"" if classes else ""
return f"<tr {class_attr}>{value}</tr>"
def render_table_header(tag_name:str,value,options,parent,context):
extra_attributes=[]
classes=[]
if "colspan" in options:
extra_attributes.append(f"colspan=\"{options['colspan']}\"")
if "rowspan" in options:
extra_attributes.append(f"rowspan=\"{options['rowspan']}\"")
if settings.USE_BOOTSTRAP:
if tag_name in options:
if options[tag_name] in ("primary","secondary","info","warning","danger","success","light","dark"):
classes.append(f"table-{options[tag_name]}")
class_attr=f"class=\"{" ".join(classes)}\"" if classes else ""
return f"<th {class_attr} {" ".join(extra_attributes)}>{value}</th>"
def render_table_data(tag_name:str,value,options,parent,context):
extra_attributes=[]
classes=[]
if "colspan" in options:
extra_attributes.append(f"colspan=\"{options['colspan']}\"")
if "rowspan" in options:
extra_attributes.append(f"rowspan=\"{options['rowspan']}\"")
if settings.USE_BOOTSTRAP:
if tag_name in options:
if options[tag_name] in ("primary","secondary","info","warning","danger","success","light","dark"):
classes.append(f"table-{options[tag_name]}")
class_attr=f"class=\"{" ".join(classes)}\"" if classes else ""
return f"<td {class_attr} {" ".join(extra_attributes)}>{value}</td>"
def render_youtube_video(tag_name:str,value,options,parent,context):
if tag_name not in options:
return ""
if 'alt' in options:
alt=options['alt']
else:
alt=""
if settings.USE_BOOTSTRAP:
styles=[]
classes=["w-100"]
div_classes=["my-1"]
div_styles=[]
else:
styles=["max-width:100%;"]
classes=[]
div_classes=[]
div_styles=[]
if 'width' in options:
_w = options['width']
if _w.endswith('px') or _w.endswith('em') or _w.endswith('rem'):
if settings.USE_BOOTSTRAP:
div_styles.append(f"width:{_w};")
styles.append(f"width:{_w};")
else:
if _w.endswith('%'):
_w = _w[:-1]
if _w.isdigit():
_w=int(_w)
if _w > 100:
_w = 100
if settings.USE_BOOTSTRAP:
if 1 < int(_w) <= 25:
width = 25
else:
width = ((_w // 25) * 25)
div_classes.append(f'w-{width}')
else:
styles.append(f"width:{_w}%;")
if 'height' in options:
_h = options['width']
if _h.endswith('px') or _h.endswith('em') or _h.endswith('rem'):
if settings.USE_BOOTSTRAP:
div_styles.append(f"height:{_h};")
else:
styles.append(f"height:{_h};")
else:
if _h.endswith('%'):
_h= _h[:-1]
if _h.isdigit():
_h=int(_w)
if _h > 100:
_h = 100
if settings.USE_BOOTSTRAP:
if 1 < int(_h) <= 25:
height = 25
else:
height = ((_h // 25) * 25)
if height > 100:
height = 100
div_classes.append(f'h-{height}')
else:
styles.append(f"height:{_h}%;")
if "position" in options:
pos = options['position']
if settings.USE_BOOTSTRAP:
if pos == "left" or pos=="start":
div_classes += ["float-start","me-2"]
#classes += ["float-start","me-2"]
elif pos == "right" or pos == "end":
div_classes += ["float-end","ms-2"]
#classes += ["float-end","ms-2"]
elif pos == "center":
div_classes += ["mx-auto","d-block"]
#classes += ["mx-auto","d-block"]
if styles:
style=f"style=\"{"".join(styles)}\""
else:
style=""
if div_styles:
div_style=f'style="{"".join(div_styles)}"'
else:
div_style=""
if settings.USE_BOOTSTRAP:
return f'<div class=""{' '.join(div_classes)}" {div_style}><iframe class={" ".join(classes)}" src="https://www.youtube.com/embed/{options[tag_name]}" allowfullscreen></iframe></div>'
else:
return f'<div {div_style}><iframe src="https://www.youtube.com/embed/{options[tag_name]}?rel=0" allowfullscreen></iframe></div>'

View File

@@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}

View File

@@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#a626a4}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#50a14f}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#986801}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}

View File

@@ -0,0 +1,38 @@
/*
Language: bbcode
Author: Paul Reid <paul@reid-family.org>
Description: highlightjs language definition for bbcode files
Category: config
*/
hljs.registerLanguage('bbcode', function (hljs) {
return {
case_insensitive: true,
contains: [
{
className: 'name',
begin: /\[[^=\s\]]*/
},
{
className: 'name',
begin: ']'
},
{
className: 'attribute',
begin: /(?<==)[^\]\s]*/
},
{
className: 'attr',
begin: /(?<=\[[^\]]* )[^\s=\]]*/
},
{
className: 'string',
begin: /[=;:8]'?\-?[\)\(3SPDO>@$|/]/
},
{
className: 'string',
begin: /:[\w]*:/
}
]
};
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>TinyWiki</title> <title>{% block head_title %}TinyWiki{% endblock %}</title>
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</head> </head>

View File

@@ -12,12 +12,15 @@
{% else %} {% else %}
<h1>Welcome to TinyWiki</h1> <h1>Welcome to TinyWiki</h1>
<p>{% blocktranslate %}You are seeing this welcome page because there is no Welcome page
configured for your Wiki. To configure a welcome page create a new page with the <p style="text-align:justify">{% blocktranslate %}You are seeing this welcome page because there is no Welcome page
slug <i>tw-home</i> and put the content for your welcome-page there.{% endblocktranslate %}</p> configured for your Wiki. To configure a welcome page {{ create_tw_home }} and put the content for your welcome-page there.{% endblocktranslate %}</p>
<p>{% blocktranslate %}You can use Markdown or BBCode to write your pages. If you don't know <p style="text-align:justify">{% blocktranslate %}You can use BBCode or Markdown to write your pages. If you don't know
Markdown read the <a href="#">Guide for Markdown used by TinyWiki</a>. Or if you want to use Markdown or BBCode there are two guides you can consult before you start editing your pages.{% endblocktranslate %}
BBCode there is a <a href="#">Guide for BBCode used by TinyWiki</a> too.{% endblocktranslate %} <ol>
<li>{{ bbcode_guide }}</li>
<li>{{ markdown_guide }}</li>
</ol>
</p> </p>
{% endif %} {% endif %}
{% endblock content %} {% endblock content %}

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-book" viewBox="0 0 16 16">
<path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783"/>
</svg>

Before

Width:  |  Height:  |  Size: 772 B

View File

@@ -1,4 +0,0 @@
<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-box-arrow-up-right\" viewBox=\"0 0 16 16\">
<path fill-rule=\"evenodd\" d=\"M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5\"/>
<path fill-rule=\"evenodd\" d=\"M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z\"/>
</svg>

Before

Width:  |  Height:  |  Size: 552 B

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-x" viewBox="0 0 16 16">
<path d="M6.854 7.146a.5.5 0 1 0-.708.708L7.293 9l-1.147 1.146a.5.5 0 0 0 .708.708L8 9.707l1.146 1.147a.5.5 0 0 0 .708-.708L8.707 9l1.147-1.146a.5.5 0 0 0-.708-.708L8 8.293z"/>
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 485 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-house" viewBox="0 0 16 16">
<path d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.708L2 8.207V13.5A1.5 1.5 0 0 0 3.5 15h9a1.5 1.5 0 0 0 1.5-1.5V8.207l.646.647a.5.5 0 0 0 .708-.708L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293zM13 7.207V13.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V7.207l5-5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 415 B

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-journal" viewBox="0 0 16 16">
<path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2"/>
<path d="M1 5v -.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 498 B

View File

@@ -0,0 +1,35 @@
{% extends "tinywiki/page/bs-model-base.html" %}
{% load i18n %}
{% block head_title %}{{ site_title }} - {% translate "Create a new Wiki page" %}{% endblock %}
{% block title %}{% translate "Create a new Wiki page" %}{% endblock title%}
{% block scripts %}
<script>
function save_and_continue() {
form = document.getElementById("wiki-page-form");
form.action = form.action + "?save=1";
form.submit();
}
</script>
{% endblock scripts %}
{% block form_buttons %}
<button class="btn btn-primary" type="button" onclick="save_and_continue();">{% translate "Create page and continue" %}<button>
<button class="btn btn-success">{% translate "Create Page" %}</button>
<a class="btn btn-secondary" href="{% url 'tinywiki:home' %}">{% translate "Cancel" %}</a>
{% endblock form_buttons %}
{% block extended_extra_scripts %}
<script>
document.addEventListener('keydown',function(e) {
if (e.ctrlKey && e.key == "s") {
e.preventDefault();
var form = document.getElementById('wiki-page-form');
form.action = form.action + "?save=1";
form.submit();
}
});
</script>
{% endblock extended_extra_scripts%}

View File

@@ -0,0 +1,45 @@
{% extends "tinywiki/page/bs-model-base.html" %}
{% load i18n static %}
{% block head_title %}{{ site_title }} - {% translate "Edit Wiki Page" %}{% endblock head_title %}
{% block title %}{% translate "Edit Wiki Page" %}{% endblock title%}
{% block scripts %}
<script src="{% static "tinywiki/js/htmx.min.js" %}"></script>
{% endblock %}
{% block form_buttons %}
<span id="popover-btn-save-and-continue">
<button id="btn-save-and-continue"
type="button"
class="btn btn-primary"
id="btn-save-and-continue"
data-bs-toggle="popopver"
data-bs-placement="top"
hx-target="#popover-btn-save-and-continue"
hx-post="{% url "tinywiki:hx-page-edit" pk=page.pk %}"
>
{% translate "Save and continue" %}
</button>
</span>
<button type="submit" class="btn btn-success">{% translate "Save and show" %}</button>
<a class="btn btn-secondary" href="{% url "tinywiki:page" form.instance.slug %}">{% translate "Cancel" %}</a>
{% endblock form_buttons %}
{% block extended_extra_scripts%}
<script>
document.addEventListener('keydown',function(e) {
if (e.ctrlKey && e.key == "s") {
e.preventDefault();
btn = document.getElementById("btn-save-and-continue")
btn.click()
}
});
</script>
<script>
btn_save_popover = new bootstrap.Popover(document.getElementById("btn-save-and-continue"),trigger="manual");
btn_save_popover.show();
setTimeout(()=>{btn_save_popover.hide();},3000);
</script>
{% endblock %}

View File

@@ -0,0 +1,66 @@
{% extends base_template %}
{% load i18n widget_tweaks %}
{% block style %}
{% endblock %}
{% block content %}
<h1>{% block title %}{% endblock title%}</h1>
<form method="POST"
class="mb-4"
id="wiki-page-form"
action="{% if create %}{% url "tinywiki:page-create" %}{% else %}{% url "tinywiki:page-edit" slug=form.instance.slug|default:"1" %}{% endif %}">
{% csrf_token %}
<div class="form-floating form-floating-lg mb-3">
{% render_field form.title class="form-control form-control-lg" %}
{{ form.title.label_tag }}
</div>
<div class="form-floating form-floating-sm mb-3">
{% if create and slug %}
{% render_field form.slug class="form-control form-control-sm" value=slug placeholder="slug" %}
{% else %}
{% render_field form.slug class="form-control form-control-sm" palceholder="slug" %}
{% endif %}
{{ form.slug.label_tag }}
</div>
<div class="row">
<div class="col-lg-6">
<div class="form-floating form-floating-sm mb-3">
{% render_field form.content_type_data class="form-select form-select-sm" %}
{{ form.content_type_data.label_tag }}
</div>
</div>
<div class="col-lg-6">
<div class="form-floating form-floating-sm mb-3">
{% render_field form.status_data class="form-select form-select-sm" %}
{{ form.status_data.label_tag }}
</div>
</div>
</div>
<div class="form-floating mb-3">
{% render_field form.content class="form-control h-100" style="font-family:monospace;" rows=25 %}
{{ form.content.label_tag }}
</div>
<div class="text-end">
{% block form_buttons %}{% endblock form_buttons %}
</div>
</form>
{% endblock content%}
{% block extra_scripts %}
<script>
document.getElementById("id_content").addEventListener('keydown',function(e) {
if (e.key == "Tab") {
e.preventDefault();
var start = this.selectionStart;
var end = this.selectionEnd;
console.log(this.value);
// set textarea value to: text before caret + 4 spaces + text after caret
this.value = this.value.substring(0,start) + " " + this.value.substring(end);
this.selectionStart = this.selectionEnd = start + 4;
}
});
</script>
{% block extended_extra_scripts %}{% endblock %}
{% endblock extra_scripts %}

View File

@@ -1,11 +1,19 @@
{% extends base_template %} {% extends base_template %}
{% load i18n %} {% load i18n static %}
{% block extra_css %} {% block extra_css %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/default.min.css"> <link rel="stylesheet" id="hljs-theme" href="{% static 'tinywiki/css/atom-one-light.min.css' %}">
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js"></script> <script>
const theme=(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
if (theme==="dark") {
let hljs_theme = document.getElementById('hljs-theme');
hljs_theme.setAttribute('href',href="{% static 'tinywiki/css/atom-one-dark.min.css' %}");
}
</script>
<script src="{% static 'tinywiki/js/highlight.min.js' %}"></script>
<script src="{% static 'tinywiki/js/bbcode.js' %}"></script>
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
@@ -18,9 +26,24 @@
<a class="btn btn-primary" href="{% url 'tinywiki:page-edit' slug=page.slug %}">{% translate "Edit Page" %}</a> <a class="btn btn-primary" href="{% url 'tinywiki:page-edit' slug=page.slug %}">{% translate "Edit Page" %}</a>
{% endif %} {% endif %}
{% if user_can_delete_wiki_page %} {% if user_can_delete_wiki_page %}
<button class="btn btn-danger" type="button">Delete Page</button> <button class="btn btn-danger"
hx-get="{% url 'tinywiki:hx-page-delete' pk=page.pk %}"
hx-target="#modal-here"
hx-swap="innerHTML"
hx-trigger="clicked"
data-bs-toggle="modal"
data-bs-target="#modal-here"
>{% translate "Delete Page" %}</button>
{% endif %} {% endif %}
</div> </div>
<h1>{{ page.title }}</h1>
{{ page.html_content }} {{ page.html_content }}
{% endblock content %} {% endblock content %}
{% block extra_body %}
<div id="modal-here" class="modal modal-blur fade" style="display:none;" aria-hidden="false" tab-index="-1">
<div class="modal-dialog modal-md modal-dialog-centered" role="document">
<div class="modal-content"></div>
</div>
</div>
{% endblock extra_body%}

View File

@@ -4,13 +4,6 @@
{% block title %}{% translate "Create a new Wiki page" %}{% endblock title%} {% block title %}{% translate "Create a new Wiki page" %}{% endblock title%}
{% block form_buttons %} {% block form_buttons %}
{% if use_bootstrap %}
<div class="text-end">
<button class="btn btn-primary">{% translate "Create Page" %}</button>
<a class="btn btn-secondary" href="{% url 'tinywiki:home' %}">{% translate "Cancel" %}</a>
</div>
{% else %}
<button type="submit">{% translate "Create Page" %}</button> <button type="submit">{% translate "Create Page" %}</button>
<a class="button button-secondary" href="{% url 'tinywiki:home' %}">{% translate "Cancel" %}</a> <a class="button button-secondary" href="{% url 'tinywiki:home' %}">{% translate "Cancel" %}</a>
{% endif %}
{% endblock form_buttons %} {% endblock form_buttons %}

View File

@@ -0,0 +1,12 @@
{% extends base_template %}
{% load i18n %}
{% block content %}
<form method="POST">
{% csrf_token %}
<p>Type <b>{{ page.slug }}</b> in the field to delete the page.</p>
{{ form.as_p }}
<button class="button button-danger" type="submit">{% translate "Delete Page" %}</button>
<a class="button button-secondary" href="{% url 'tinywiki:page' page.slug %}">Cancel</a>
</form>
{% endblock content %}

View File

@@ -4,13 +4,6 @@
{% block title %}{% translate "Edit Wiki Page" %}{% endblock title%} {% block title %}{% translate "Edit Wiki Page" %}{% endblock title%}
{% block form_buttons %} {% block form_buttons %}
{% if use_bootstrap %}
<div class="text-end">
<button type="submit" class="btn btn-primary">{% translate "Save Changes" %}</button>
<a class="btn btn-secondary" href="{% url "tinywiki:page" form.instance.slug %}">{% translate "Cancel" %}</a>
</div>
{% else %}
<button type="submit">{% translate "Save Changes" %}</button> <button type="submit">{% translate "Save Changes" %}</button>
<a class="button button-secondary" href="{% url "tinywiki:page" form.instance.slug %}">{% translate "Cancel" %}</a> <a class="button button-secondary" href="{% url "tinywiki:page" form.instance.slug %}">{% translate "Cancel" %}</a>
{% endif %}
{% endblock form_buttons %} {% endblock form_buttons %}

View File

@@ -0,0 +1,27 @@
{% load i18n widget_tweaks %}
<div class="modal-dialog modal-md modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">{% translate "Delete Wiki-Page" %}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="POST">
{% csrf_token %}
<p>
{% with page.slug as page_slug %}
{% blocktranslate %}Type <b>{{ page_slug }}</b> in the field below to delete the Wiki-Page.{% endblocktranslate %}
{% endwith %}
<div class="form-floating form-floating-sm">
{% render_field form.slug class="form-control form-cotrol-sm" placeholder=page.slug %}
<label for="{{ form.slug.auto_id }}">{{ form.slug }}</label>
</div>
<div class="text-end">
<button class="btn btn-danger" type="submit">{% translate "Delete the page" %}</button>
<button clasS="btn btn-secondary" type="button" data-bs-dismiss="modal">{% translate "Cancel" %}</button>
</div>
</p>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
{% load i18n %}
<span id="popover-btn-save-and-continue">
<button type="button"
class="btn btn-primary"
id="btn-save-and-continue"
data-bs-toggle="popopver"
data-bs-placement="top"
hx-target="#popover-btn-save-and-continue"
hx-post="{% url "tinywiki:hx-page-edit" pk=page.pk %}"
{% if save_success %}
data-bs-title="Successfully saved"
data-bs-content="Your Wiki page has been successfully saved!"
{% else %}
data-bs-title="Saving failed"
data-bs-content="Your Wiki page could not be saved!"
{% endif %}
>
Save and continue
</button>
<script>
btn_save_popover = new bootstrap.Popover(document.getElementById("btn-save-and-continue"),trigger="manual");
btn_save_popover.show();
setTimeout(() => {btn_save_popover.hide();},3000)
</script>
</span>

View File

@@ -4,58 +4,24 @@
{% block content %} {% block content %}
<h1>{% block title %}{% endblock title%}</h1> <h1>{% block title %}{% endblock title%}</h1>
<form method="POST" <form method="POST"
class="mb-4"
id="wiki-page-form" id="wiki-page-form"
action="{% if create %}{% url "tinywiki:page-create" %}{% else %}{% url "tinywiki:page-edit" slug=form.instance.slug|default:"1" %}{% endif %}"> action="{% if create %}{% url "tinywiki:page-create" %}{% else %}{% url "tinywiki:page-edit" slug=form.instance.slug|default:"1" %}{% endif %}">
{% csrf_token %} {% csrf_token %}
{% if use_bootstrap %}
<div class="form-floating mb-2">
{% with "form-control form-control-lg "|add:title_extra as title_class %}
{% render_field form.title class=title_class placeholder="{{ form.title.label }}" %}
{% endwith %}
<label for="{{ form.title.auto_id }}">{{ form.title.label }}</label>
</div>
<div class="form-floating mb-2">
{% with "form-control form-control-sm "|add:slug_extra as slug_class%}
{% render_field form.slug class=slug_class placeholder="{{ form.slug.label }}" %}
{% endwith %}
<label for="{{ form.slug.auto_id }}">{{ form.slug.label }}</label>
</div>
<div class="row">
<div class="col-md-6 mb-2">
<div class="form-floating mb-2">
{% with "form-select form-select-sm "|add:content_type_extra as content_type_class %}
{% render_field form.content_type_data class=content_type_class %}
{% endwith %}
<label for="{{ for.content_type_data.auto_id }}">{{ form.content_type_data.label }}</label>
</div>
</div>
<div class="col-md-6">
<div class="form-floating mb-2">
{% with "form-select form-select-sm "|add:status_extra as status_class %}
{% render_field form.status_data class=status_class %}
{% endwith %}
<label for="{{ for.status_data.auto_id }}">{{ form.status_data.label }}</label>
</div>
</div>
</div>
<div class="form-floating mb-3">
{% with "form-control h-100 "|add:content_extra as content_class %}
{% render_field form.content class=content_class rows="25"%}
{% endwith %}
<label for="{{ form.content.auto_id }}">{{ form.content.label }}</label>
</div>
<div class="text-end">
</div>
{% else %}
<p> <p>
{% with title_extra|default:"" as title_class %} {% with title_extra|default:"" as title_class %}
{{ form.title.label_tag }}{% render_field form.title class=Title_class %} {{ form.title.label_tag }} {% render_field form.title class="{{ title_class }}" %}
{% endwith %} {% endwith %}
</p> </p>
<p> <p>
{{ form.slug.label_tag }}
{% with slug_extra|default:"" as slug_class%} {% with slug_extra|default:"" as slug_class%}
{{ form.slug.label_tag }}{% render_field form.slug class=slug_class %} {% if create and slug %}
{% render_field form.slug class=slug_class value=slug placeholder="slug" %}
{% else %}
{% render_field form.slug class=slug_class palceholder="slug" %}
{% endif %}
{% endwith %} {% endwith %}
</p> </p>
<p> <p>
@@ -73,8 +39,9 @@
{{ form.content.label_tag }} {% render_field form.content class=content_class cols=80 rows=30 %} {{ form.content.label_tag }} {% render_field form.content class=content_class cols=80 rows=30 %}
{% endwith %} {% endwith %}
</p> </p>
{% endif %} <div class="text-end">
{% block form_buttons %}{% endblock form_buttons %} {% block form_buttons %}{% endblock form_buttons %}
</div>
</form> </form>
{% endblock content%} {% endblock content%}

View File

@@ -1,11 +1,13 @@
{% extends base_template %} {% extends base_template %}
{% load i18n %} {% load i18n static %}
{% block extra_css %} {% block extra_css %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/default.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/default.min.css">
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js"></script> <script src="{% static 'tinywiki/js/htmx.min.js' %}"></script>
<script src="{% static 'tinywiki/js/highlight.min.js' %}"></script>
<script src="{% static 'tinywiki/js/bbcode.js' %}"></script>
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
@@ -17,8 +19,11 @@
<a href="{% url 'tinywiki:page-edit' slug=page.slug %}">{% translate "Edit Page" %}</a> <a href="{% url 'tinywiki:page-edit' slug=page.slug %}">{% translate "Edit Page" %}</a>
{% endif %} {% endif %}
{% if user_can_delete_wiki_page %} {% if user_can_delete_wiki_page %}
<a href="#">Delete Page</a> <a href="{% url 'tinywiki:page-delete' slug=page.slug %}">{% translate "Delete Page" %}</a>
{% endif %} {% endif %}
<h1>{{ page.title }}</h1> <h1>{{ page.title }}</h1>
{{ page.html_content }} {{ page.html_content }}
{% endblock content %} {% endblock content %}
{% block extra_body %}
{% block %}

View File

@@ -10,4 +10,7 @@ urlpatterns = [
path("page/<slug:slug>/",PageView.as_view(),name="page"), path("page/<slug:slug>/",PageView.as_view(),name="page"),
path("page-create/",PageCreateView.as_view(),name="page-create"), path("page-create/",PageCreateView.as_view(),name="page-create"),
path("page/<slug:slug>/edit/",PageEditView.as_view(),name='page-edit'), path("page/<slug:slug>/edit/",PageEditView.as_view(),name='page-edit'),
path("page/<slug:slug>/dekete/",PageDeleteView.as_view(),name='page-delete'),
path("__hx__/page/<int:pk>/edit/",HxPageEditView.as_view(),name='hx-page-edit'),
path("__hx__/page/<int:pk>/delete/",HxPageDeleteView.as_view(),name='hx-page-delete'),
] ]

View File

@@ -6,6 +6,57 @@ from typing import Any
class Base: class Base:
base_template_name = settings.TINYWIKI_BASE_TEMPLATE base_template_name = settings.TINYWIKI_BASE_TEMPLATE
@property
def user_can_edit_pages(self)->bool:
if (self.request.user.is_staff
or self.request.user.has_perm('page.tiynwiki-edit')
or self.request.user.has_perm('page.tinywiki-edit-all')):
return True
return False
@property
def user_can_edit_all_pages(self)->bool:
if (self.request.user.is_staff
or self.request.user.has_perm('page.tinywiki-edit-all')):
return True
return False
@property
def user_can_edit_system_pages(self)->bool:
if (self.request.user.is_staff
or self.request.user.has_perm('page.tiynwiki-edit-system')):
return True
return False
@property
def user_can_create_pages(self)->bool:
if (self.request.user.is_staff
or self.request.user.has_perm('page.tiynwiki-create')):
return True
return False
@property
def user_can_create_system_pages(self)->bool:
if (self.request.user.is_staff
or self.request.user.has_perm('page.tiynwiki-create-system')):
return True
return False
def page_is_creatable(self,slug:str)->bool:
if not slug:
return False
if slug.startswith('tw-'):
return self.user_can_create_system_pages
return self.user_can_create_pages
def page_is_editable(self,page)->bool:
if page.slug.startswith('tw-'):
return self.user_can_edit_system_pages
elif (self.user_can_edit_all_pages
or (self.request.user.pk == page.author.pk and self.user_can_edit_pages)):
return True
return False
@classmethod @classmethod
def get_base_template_name(cls)->str: def get_base_template_name(cls)->str:
return cls.base_template_name return cls.base_template_name

View File

@@ -1,6 +1,9 @@
from curses.ascii import isalpha from curses.ascii import isalpha
from django.shortcuts import render from django.shortcuts import render
from django.utils.safestring import mark_safe
from django.urls import reverse
from django_project.settings import STATIC_URL
from tinywiki import settings from tinywiki import settings
from .base import View from .base import View
from django.http import HttpRequest,HttpResponse from django.http import HttpRequest,HttpResponse
@@ -18,12 +21,34 @@ class HomeView(View):
def get(self,request): def get(self,request):
try: try:
page = Page.objects.get(slug='tw-home') page = Page.objects.get(slug='tw-home')
except Page.DoesNotExist: if (not Page.status == WikiPageStatus.PUBLISHED
and not request.user.is_staff
and not request.user.has_perm('page.view-all')):
page = None page = None
except Page.DoesNotExist:
page = None
if self.user_can_create_system_pages:
if settings.USE_BOOTSTRAP:
create_tw_home = f"<a class=\"icon-link icon-link-hover\" href={reverse("tinywiki:page",kwargs={'slug':'tw-home'})}>{_('create a new page with the slug <i>tw-home</i>')}<svg class=\"bi\"><use xlink:href=\"{settings.settings.STATIC_URL + 'tinywiki/icons/bootstrap-icons.svg' }#house-add\" ></use></svg></a>"
else:
create_tw_home = f"<a href={reverse("tinywiki:page",kwargs={'slug':'tw-home'})}>{_('create a new page with the slug <i>tw-home</i>')}</a>"
else:
create_tw_home = "create a new page with the slug <i>tw-home</i>"
if settings.USE_BOOTSTRAP:
markdown_guide = f"<a class=\"icon-link icon-link-hover\" href={reverse('tinywiki:page',kwargs={'slug':'tw-markdown'})}>{_('Guide for markdown used by TinyWiki')}<svg class=\"bi\"><use xlink:href=\"{settings.settings.STATIC_URL + 'tinywiki/icons/bootstrap-icons.svg' }#journal\"></use></svg></a>"
bbcode_guide = f"<a class=\"icon-link icon-link-hover\" href={reverse('tinywiki:page',kwargs={'slug':'tw-bbcode'})}>{_('Guide for BBCode used by TinyWiki')}<svg class=\"bi\"><use xlink:href=\"{settings.settings.STATIC_URL + 'tinywiki/icons/bootstrap-icons.svg' }#journal\"></use></svg></a>"
else:
markdown_guide = f"<a class=\"icon-link icon-link-hover\" href={reverse('tinywiki:page',kwargs={'slug':'tw-markdown'})}>{_('Guide for markdown used by TinyWiki')}</a>"
bbcode_guide = f"<a class=\"icon-link icon-link-hover\" href={reverse('tinywiki:page',kwargs={'slug':'tw-bbcode'})}>{_('Guide for BBCode used by TinyWiki')}</a>"
return render(request, return render(request,
self.get_template_name(), self.get_template_name(),
self.get_context_data(page=page)) self.get_context_data(page=page,
user_can_create_system_pages=self.user_can_create_system_pages,
create_tw_home=mark_safe(create_tw_home),
markdown_guide=mark_safe(markdown_guide),
bbcode_guide=mark_safe(bbcode_guide)))
class TocView(View): class TocView(View):
template_name = "tinywiki/home/wiki-content.html" template_name = "tinywiki/home/wiki-content.html"

19
tinywiki/views/image.py Normal file
View File

@@ -0,0 +1,19 @@
from .base import FormView
class ImageUplodadView(FormView):
pass
class ImageEditView(FormView):
pass
class ImageDeleteFiew(FormView):
pass
class HxImageUplaodView(FormView):
pass
class HxImageEditView(FormView):
pass
class HxImageDeleteView(FormView):
pass

View File

@@ -1,15 +1,16 @@
from django.shortcuts import render,get_object_or_404,redirect from django.shortcuts import render,get_object_or_404,redirect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.http import HttpRequest,HttpResponse from django.http import HttpRequest,HttpResponse,Http404
from django.contrib.auth.mixins import LoginRequiredMixin,UserPassesTestMixin from django.contrib.auth.mixins import LoginRequiredMixin,UserPassesTestMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from .. import settings from .. import settings
from ..models import Page from ..models import Page
from .base import View,FormView from .base import View,FormView
from ..forms import PageForm,PageAdminForm from ..forms import PageForm,PageAdminForm,PageDeleteForm
class PageView(View): class PageView(View):
template_name = "tinywiki/page/view.html" template_name = "tinywiki/page/view.html"
@@ -49,13 +50,19 @@ class PageView(View):
def get(self,request:HttpRequest,slug:str)->HttpResponse: def get(self,request:HttpRequest,slug:str)->HttpResponse:
page = get_object_or_404(Page,slug=slug) try:
page = Page.objects.get(slug=slug)
except Page.DoesNotExist:
if self.page_is_creatable(slug):
return redirect(reverse('tinywiki:page-create') + f"?slug={slug}")
raise Http404()
return render(request, return render(request,
self.get_template_name(), self.get_template_name(),
self.get_context_data(page=page)) self.get_context_data(page=page))
class PageCreateView(LoginRequiredMixin,UserPassesTestMixin,FormView): class PageCreateView(LoginRequiredMixin,UserPassesTestMixin,FormView):
template_name = "tinywiki/page/create.html" template_name = "tinywiki/page/create.html"
bs_template_name = "tinywiki/page/bs-create.html"
form_class = PageForm form_class = PageForm
def test_func(self) -> bool: def test_func(self) -> bool:
@@ -65,14 +72,26 @@ class PageCreateView(LoginRequiredMixin,UserPassesTestMixin,FormView):
return self.request.user.has_perm('tinywiki.tinywiki-create') return self.request.user.has_perm('tinywiki.tinywiki-create')
return False return False
def get_template_name(self) -> str:
if settings.USE_BOOTSTRAP:
return self.bs_template_name
return self.template_name
def get(self,request):
return render(request,
self.get_template_name(),
self.get_context_data())
def get_context_data(self,**kwargs): def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['create']=True context['create']=True
context['slug'] = self.request.GET.get('slug',None)
context.setdefault("title_extra","") context.setdefault("title_extra","")
context.setdefault("slug_extra","") context.setdefault("slug_extra","")
context.setdefault("content_type_extra","") context.setdefault("content_type_extra","")
context.setdefault("status_extra","") context.setdefault("status_extra","")
context.setdefault("content_extra","") context.setdefault("content_extra","")
return context return context
def form_invalid(self, form): def form_invalid(self, form):
@@ -149,6 +168,7 @@ class PageCreateView(LoginRequiredMixin,UserPassesTestMixin,FormView):
class PageEditView(LoginRequiredMixin,UserPassesTestMixin,FormView): class PageEditView(LoginRequiredMixin,UserPassesTestMixin,FormView):
template_name = "tinywiki/page/edit.html" template_name = "tinywiki/page/edit.html"
bs_template_name = "tinywiki/page/bs-edit.html"
form_class = PageForm form_class = PageForm
def test_func(self) -> bool: def test_func(self) -> bool:
@@ -156,9 +176,16 @@ class PageEditView(LoginRequiredMixin,UserPassesTestMixin,FormView):
if self.request.user.is_staff: if self.request.user.is_staff:
return True return True
return False return False
def get_context_data(self,**kwargs):
def get_template_name(self) -> str:
if settings.USE_BOOTSTRAP:
return self.bs_template_name
return self.template_name
def get_context_data(self,page,**kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['create']=False context['create']=False
context['page']=page
context.setdefault("title_extra","") context.setdefault("title_extra","")
context.setdefault("slug_extra","") context.setdefault("slug_extra","")
context.setdefault("content_type_extra","") context.setdefault("content_type_extra","")
@@ -195,6 +222,7 @@ class PageEditView(LoginRequiredMixin,UserPassesTestMixin,FormView):
return render(self.request, return render(self.request,
self.get_template_name(), self.get_template_name(),
self.get_context_data( self.get_context_data(
page=self.instance,
slug_extra=slug_extra, slug_extra=slug_extra,
title_extra=title_extra, title_extra=title_extra,
status_extra=status_extra, status_extra=status_extra,
@@ -218,7 +246,10 @@ class PageEditView(LoginRequiredMixin,UserPassesTestMixin,FormView):
raise PermissionDenied() raise PermissionDenied()
self.instance = instance self.instance = instance
return super().get(request)
return render(request,
self.get_template_name(),
self.get_context_data(page=instance))
def post(self,request,slug:str): def post(self,request,slug:str):
instance = get_object_or_404(Page,slug=slug) instance = get_object_or_404(Page,slug=slug)
@@ -255,7 +286,138 @@ class PageEditView(LoginRequiredMixin,UserPassesTestMixin,FormView):
self.get_template_name(), self.get_template_name(),
self.get_context_data(slug_invalid=True)) self.get_context_data(slug_invalid=True))
class PageDeleteView(View): class PageDeleteView(LoginRequiredMixin,UserPassesTestMixin,FormView):
template_name = "tinywiki/page/delete" template_name = "tinywiki/page/delete.html"
def get(self,request,slug): form_class = PageDeleteForm
return render() def test_func(self) -> bool:
if self.request.user.is_staff:
return True
if self.request.user.has_perm('tinyiwki-delete-all') or self.request.user.has_perm('tinywiki-delete'):
return True
return False
def get(self,request:HttpRequest,slug:str)->HttpResponse:
instance = get_object_or_404(Page,slug=slug)
if not request.user.is_staff and not instance.author == request.user:
raise PermissionDenied()
return render(request,
self.template_name,
self.get_context_data(form=self.get_form_class()(),
page=instance))
def form_invalid(self,form:PageDeleteForm)->HttpResponse:
return redirect(reverse('tinywiki:page',kwargs={'slug':self.instance.slug}))
def form_valid(self,form:PageDeleteForm)->HttpResponse:
if self.instance.slug == form.cleaned_data['slug']:
try:
self.instance.delete()
return redirect(reverse("tinywiki:home"))
except: pass
return redirect(reverse('tinywiki:page',kwargs={'slug':self.instance.slug}))
def post(self,request:HttpRequest,slug:str)->HttpResponse:
self.instance = get_object_or_404(Page,slug=slug)
if not request.user.is_staff and not instance.author == request.user:
raise PermissionDenied()
return super().post(request)
class HxPageEditView(LoginRequiredMixin,UserPassesTestMixin,FormView):
template_name = "tinywiki/page/hx-edit.html"
bs_template_name = "tinywiki/page/hx-bs-edit.html"
form_class = PageForm
def test_func(self) -> bool:
if self.request.user.is_authenticated:
if self.request.user.is_staff:
return True
if self.request.user.has_perm('tinywiki-edit-page') or self.request.user.has_perm('tinywiki-edit-all-pages'):
return True
return False
def get_template_name(self) -> str:
if settings.USE_BOOTSTRAP:
return self.bs_template_name
return self.template_name
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['create']=False
context['page']=self.instance
context.setdefault("title_extra","")
context.setdefault("slug_extra","")
context.setdefault("content_type_extra","")
context.setdefault("status_extra","")
context.setdefault("content_extra","")
return context
def form_invalid(self, form):
if 'slug' in form.errors:
slug_extra = "is-invalid"
else:
slug_extra = "is-valid"
if 'title' in form.errors:
title_extra = 'is-invalid'
else:
title_extra = 'is-valid'
if 'status_data' in form.errors:
status_extra = 'is-invalid'
else:
status_extra = 'is-valid'
if 'content_type_data' in form.errors:
content_type_extra = 'is-invalid'
else:
content_type_extra = 'is-valid'
if 'content' in form.errors:
content_extra = 'is-invalid'
else:
content_extra = 'is-valid'
return render(self.request,
self.get_template_name(),
self.get_context_data(
page=self.instance,
))
def post(self,request,pk:int):
instance = get_object_or_404(Page,pk=pk)
user = request.user
if (instance.slug.startswith('tw-') and not user.is_staff):
raise PermissionDenied(_("Only staff users are allowed to edit TinyWiki system pages!"))
if user.pk != instance.author.pk:
if not user.is_staff and not user.has_perm("page.tinywiki-edit-all"):
raise PermissionDenied()
else:
if not user.has_perm('page.tinywiki-edit-all') or not user.has_perm('page.tinywiki-edit'):
raise PermissionDenied()
self.instance = instance
return super().post(request)
def get_form(self):
return self.get_form_class()(instance=self.instance,**self.get_form_kwargs())
def form_valid(self,form):
user = self.request.user
instance = form.save(commit=False)
instance.created_by = user
instance.last_edited_by = user
try:
form.save(commit=True)
return render(self.request,self.get_template_name(),self.get_context_data(save_success=True))
except:
return render(self.request,
self.get_template_name(),
self.get_context_data(save_success=False))
class HxPageDeleteView(View):
pass