add progress to note

This commit is contained in:
Your Name 2024-06-15 18:26:20 -04:00 committed by Henri Dickson
parent 3eafcd3ec8
commit 7157f3b0b0
22 changed files with 779 additions and 192 deletions

View file

@ -90,18 +90,25 @@
{% include "action_open_post.html" with post=note.latest_post %}
{% endif %}
</span>
<h6>{{ note.title|default:'' }}</h6>
<p>{{ note.content|linebreaks }}</p>
<div>
{% for attachment in note.attachments %}
{% if attachment.type == 'image' %}
<img src="{{ attachment.url }}"
alt="image attachment"
style="max-height:6em;
max-width:50%">
{% endif %}
{% endfor %}
</div>
{% if note.title %}<h6>{{ note.title|default:'' }}</h6>{% endif %}
<p>
{% if note.progress_value %}<span class="tag-list"><span><a>{{ note.progress_display }}</a></span></span>{% endif %}
{{ note.content|linebreaksbr }}
<div class="attachments">
{% for attachment in note.attachments %}
{% if attachment.type == 'image' %}
<a href="#img_{{ note.uuid }}_{{ loop.index }}">
<img src="{{ attachment.preview_url }}"
alt="image attachment"
class="preview">
</a>
<a href="#" class="lightbox" id="img_{{ note.uuid }}_{{ loop.index }}">
<span style="background-image: url('{{ attachment.url }}')"></span>
</a>
{% endif %}
{% endfor %}
</div>
</p>
{% endfor %}
</section>
<section>

View file

@ -3,11 +3,23 @@ import json
import django.contrib.postgres.forms as postgres
from django import forms
from django.core.exceptions import ValidationError
from django.forms import ModelForm
from django.utils import formats
from django.utils.translation import gettext_lazy as _
from markdownx.fields import MarkdownxFormField
class NeoModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# if "uuid" in self.fields:
# if self.instance and self.instance.pk:
# self.fields["uuid"].initial = self.instance.uuid
for visible in self.visible_fields():
w = visible.field.widget
w.attrs["class"] = "widget " + w.__class__.__name__.lower()
class PreviewImageInput(forms.FileInput):
template_name = "widgets/image.html"

View file

@ -86,6 +86,16 @@ dialog {
article {
padding-bottom: 1em;
form {
overflow: hidden;
input[type="submit"] {
margin-top: 1em;
}
div.widget.radioselect {
display: flex;
grid-column-gap: 1em;
}
}
}
input[type="date"] {

View file

@ -0,0 +1,26 @@
.lightbox {
display: none;
position: fixed;
z-index: 999;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 1em;
background: rgba(0, 0, 0, 0.8);
-webkit-backdrop-filter: var(--pico-modal-overlay-backdrop-filter);
}
.lightbox:target {
display: block;
}
.lightbox span {
display: block;
width: 100%;
height: 100%;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}

View file

@ -27,3 +27,10 @@ section.replies {
}
}
}
.attachements {
img.preview {
max-height: 6em;
max-width: 50%;
}
}

View file

@ -20,3 +20,4 @@
@import '_form.scss';
@import '_post.scss';
@import '_l10n.scss';
@import '_lightbox.scss';

View file

@ -159,7 +159,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -205,7 +215,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -233,7 +253,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -265,7 +295,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -316,7 +356,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -343,7 +393,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -382,7 +442,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -415,7 +485,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -451,7 +531,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -596,7 +686,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),
@ -656,7 +756,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),

View file

@ -27,7 +27,17 @@ class Migration(migrations.Migration):
to="journal.piece",
),
),
("visibility", models.PositiveSmallIntegerField(default=0)),
(
"visibility",
models.PositiveSmallIntegerField(
choices=[
(0, "Public"),
(1, "Followers Only"),
(2, "Mentioned Only"),
],
default=0,
),
),
(
"created_time",
models.DateTimeField(default=django.utils.timezone.now),

View file

@ -0,0 +1,44 @@
# Generated by Django 4.2.13 on 2024-06-14 22:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("journal", "0002_note"),
]
operations = [
migrations.AddField(
model_name="note",
name="progress_type",
field=models.CharField(
blank=True,
choices=[
("page", "Page"),
("chapter", "Chapter"),
("part", "Part"),
("episode", "Episode"),
("track", "Track"),
("cycle", "Cycle"),
("timestamp", "Timestamp"),
("percentage", "Percentage"),
],
default=None,
max_length=50,
null=True,
),
),
migrations.AddField(
model_name="note",
name="progress_value",
field=models.CharField(blank=True, default=None, max_length=500, null=True),
),
migrations.AddIndex(
model_name="note",
index=models.Index(
fields=["owner", "item", "created_time"],
name="journal_not_owner_i_7f9460_idx",
),
),
]

View file

@ -434,8 +434,8 @@ class PieceInteraction(models.Model):
class Content(Piece):
owner = models.ForeignKey(APIdentity, on_delete=models.PROTECT)
visibility = models.PositiveSmallIntegerField(
default=0
) # 0: Public / 1: Follower only / 2: Self only # type:ignore
choices=VisibilityType.choices, default=0, null=False
) # type:ignore
created_time = models.DateTimeField(default=timezone.now)
edited_time = models.DateTimeField(auto_now=True)
metadata = models.JSONField(default=dict)

View file

@ -8,7 +8,7 @@ from django.utils import timezone
from catalog.models import Item, ItemCategory
from users.models import APIdentity
from .common import Piece
from .common import Piece, VisibilityType
list_add = django.dispatch.Signal()
list_remove = django.dispatch.Signal()
@ -25,8 +25,8 @@ class List(Piece):
items: "models.ManyToManyField[Item, List]"
owner = models.ForeignKey(APIdentity, on_delete=models.PROTECT)
visibility = models.PositiveSmallIntegerField(
default=0
) # 0: Public / 1: Follower only / 2: Self only # type:ignore
choices=VisibilityType.choices, default=0, null=False
) # type:ignore
created_time = models.DateTimeField(default=timezone.now)
edited_time = models.DateTimeField(auto_now=True)
metadata = models.JSONField(default=dict)
@ -151,8 +151,8 @@ class ListMember(Piece):
parent: models.ForeignKey["ListMember", "List"]
owner = models.ForeignKey(APIdentity, on_delete=models.PROTECT)
visibility = models.PositiveSmallIntegerField(
default=0
) # 0: Public / 1: Follower only / 2: Self only # type:ignore[reportAssignmentType]
choices=VisibilityType.choices, default=0, null=False
) # type:ignore
created_time = models.DateTimeField(default=timezone.now)
edited_time = models.DateTimeField(auto_now=True)
metadata = models.JSONField(default=dict)

View file

@ -5,14 +5,14 @@ from django.utils.translation import gettext_lazy as _
from users.models import APIdentity
from .common import Piece
from .common import Piece, VisibilityType
class Like(Piece): # TODO remove
owner = models.ForeignKey(APIdentity, on_delete=models.PROTECT)
visibility = models.PositiveSmallIntegerField(
default=0
) # 0: Public / 1: Follower only / 2: Self only # type: ignore
choices=VisibilityType.choices, default=0, null=False
) # type:ignore
created_time = models.DateTimeField(default=timezone.now)
edited_time = models.DateTimeField(auto_now=True)
target = models.ForeignKey(Piece, on_delete=models.CASCADE, related_name="likes")

View file

@ -108,7 +108,9 @@ class Mark:
@cached_property
def notes(self):
return Note.objects.filter(owner=self.owner, item=self.item)
return Note.objects.filter(owner=self.owner, item=self.item).order_by(
"-created_time"
)
# post_ids = PiecePost.objects.filter(
# piece__note__owner_id=self.owner.pk, piece__note__item_id=self.item.pk
# ).values_list("post_id", flat=True)

View file

@ -1,9 +1,12 @@
import re
from functools import cached_property
from typing import override
from django.db import models
from django.utils.translation import gettext_lazy as _
from loguru import logger
from catalog.models import Item
from mastodon.api import delete_toot_later
from takahe.utils import Takahe
@ -11,17 +14,74 @@ from .common import Content
from .renderers import render_text
from .shelf import ShelfMember
_progress = re.compile(
r"^\s*(?P<prefix>(p|pg|page|ch|chapter|pt|part|e|ep|episode|trk|track|cycle))?(\s|\.|#)*(?P<value>(\d[\d\:\.\-]*\d|\d))\s*(?P<postfix>(%))?\s*(\s|\n|\.|:)",
re.IGNORECASE,
)
_number = re.compile(r"^[\s\d\:\.]+$")
class Note(Content):
class ProgressType(models.TextChoices):
PAGE = "page", _("Page")
CHAPTER = "chapter", _("Chapter")
# SECTION = "section", _("Section")
# VOLUME = "volume", _("Volume")
PART = "part", _("Part")
EPISODE = "episode", _("Episode")
TRACK = "track", _("Track")
CYCLE = "cycle", _("Cycle")
TIMESTAMP = "timestamp", _("Timestamp")
PERCENTAGE = "percentage", _("Percentage")
title = models.TextField(blank=True, null=True, default=None)
content = models.TextField(blank=False, null=False)
sensitive = models.BooleanField(default=False, null=False)
attachments = models.JSONField(default=list)
progress_type = models.CharField(
max_length=50,
choices=ProgressType.choices,
blank=True,
null=True,
default=None,
)
progress_value = models.CharField(
max_length=500, blank=True, null=True, default=None
)
_progress_display_template = {
ProgressType.PAGE: _("Page {value}"),
ProgressType.CHAPTER: _("Chapter {value}"),
# ProgressType.SECTION: _("Section {value}"),
# ProgressType.VOLUME: _("Volume {value}"),
ProgressType.PART: _("Part {value}"),
ProgressType.EPISODE: _("Episode {value}"),
ProgressType.TRACK: _("Track {value}"),
ProgressType.CYCLE: _("Cycle {value}"),
ProgressType.PERCENTAGE: "{value}%",
ProgressType.TIMESTAMP: "{value}",
}
class Meta:
indexes = [models.Index(fields=["owner", "item", "created_time"])]
@property
def html(self):
return render_text(self.content)
@property
def progress_display(self) -> str:
if not self.progress_value:
return ""
if not self.progress_type:
return str(self.progress_value)
tpl = Note._progress_display_template.get(self.progress_type, None)
if not tpl:
return str(self.progress_value)
if _number.match(self.progress_value):
return tpl.format(value=self.progress_value)
return self.progress_type.display + ": " + self.progress_value
@property
def ap_object(self):
d = {
@ -36,6 +96,11 @@ class Note(Content):
"withRegardTo": self.item.absolute_url,
"href": self.absolute_url,
}
if self.progress_value:
d["progress"] = {
"type": self.progress_type,
"value": self.progress_value,
}
return d
@override
@ -47,6 +112,17 @@ class Note(Content):
"sensitive": obj.get("sensitive", post.sensitive),
"attachments": [],
}
progress = obj.get("progress", {})
if progress.get("type"):
params["progress_type"] = progress.get("type")
if progress.get("value"):
params["progress_value"] = progress.get("value")
if post.local:
progress_type, progress_value = cls.extract_progress(params["content"])
print(progress_type, progress_value)
if progress_value:
params["progress_type"] = progress_type
params["progress_value"] = progress_value
if post:
for atta in post.attachments.all():
params["attachments"].append(
@ -103,3 +179,79 @@ class Note(Content):
),
# not passing "attachments" so it won't change
}
@classmethod
def extract_progress(cls, content):
m = _progress.match(content)
if m and m["value"]:
typ_ = "percentage" if m["postfix"] == "%" else m["prefix"]
match typ_:
case "p" | "pg" | "page":
typ = Note.ProgressType.PAGE
case "ch" | "chapter":
typ = Note.ProgressType.CHAPTER
# case "vol" | "volume":
# typ = ProgressType.VOLUME
# case "section":
# typ = ProgressType.SECTION
case "pt" | "part":
typ = Note.ProgressType.PART
case "e" | "ep" | "episode":
typ = Note.ProgressType.EPISODE
case "trk" | "track":
typ = Note.ProgressType.TRACK
case "cycle":
typ = Note.ProgressType.CYCLE
case "percentage":
typ = Note.ProgressType.PERCENTAGE
case _:
typ = "timestamp" if ":" in m["value"] else None
return typ, m["value"]
return None, None
@classmethod
def get_progress_types_by_item(cls, item: Item):
match item.__class__.__name__:
case "Edition":
v = [
Note.ProgressType.PAGE,
Note.ProgressType.CHAPTER,
Note.ProgressType.PERCENTAGE,
]
case "TVShow" | "TVSeason":
v = [
Note.ProgressType.PART,
Note.ProgressType.EPISODE,
Note.ProgressType.PERCENTAGE,
]
case "Movie":
v = [
Note.ProgressType.PART,
Note.ProgressType.TIMESTAMP,
Note.ProgressType.PERCENTAGE,
]
case "Podcast":
v = [
Note.ProgressType.EPISODE,
]
case "TVEpisode" | "PodcastEpisode":
v = []
case "Album":
v = [
Note.ProgressType.TRACK,
Note.ProgressType.TIMESTAMP,
Note.ProgressType.PERCENTAGE,
]
case "Game":
v = [
Note.ProgressType.CYCLE,
]
case "Performance" | "PerformanceProduction":
v = [
Note.ProgressType.PART,
Note.ProgressType.TIMESTAMP,
Note.ProgressType.PERCENTAGE,
]
case _:
v = []
return v

View file

@ -16,50 +16,23 @@
<strong>{% trans 'Note' %} - {{ item.display_title }}</strong>
</header>
<div>
<form action="{% url 'journal:note' item.uuid %}" method="post">
<form action="{% url 'journal:note' item.uuid %}"
method="post"
onsubmit="return !!$('#{{ form.content.id_for_label }}').val() || confirm('{% trans "Note with empty content will be deleted, sure to continue?" %}');;">
{% csrf_token %}
<input type="hidden" name="uuid" value="{{ note.uuid|default:'' }}">
<textarea name="content" cols="40" rows="10" placeholder="" id="id_content">{{ note.content|default:'' }}</textarea>
{{ form.uuid }}
<div class="grid">
<div>{{ form.progress_type }}</div>
<div>{{ form.progress_value }}</div>
</div>
{{ form.content }}
{{ form.title }}
<div class="grid">
<div>{{ form.visibility }}</div>
<div>
<fieldset>
<input type="radio"
name="visibility"
value="0"
required=""
id="id_visibility_0"
{% if note.visibility == 0 or not note %}checked{% endif %}>
<label for="id_visibility_0">{% trans "Public" %}</label>
<input type="radio"
name="visibility"
value="1"
required=""
id="id_visibility_1"
{% if note.visibility == 1 %}checked{% endif %}>
<label for="id_visibility_1">{% trans "Followers Only" %}</label>
<input type="radio"
name="visibility"
value="2"
required=""
id="id_visibility_2"
{% if note.visibility == 2 %}checked{% endif %}>
<label for="id_visibility_2">{% trans "Mentioned Only" %}</label>
</fieldset>
</div>
<div>
<fieldset>
{% if request.user.mastodon_acct %}
<label for="id_share_to_mastodon">
<input role="switch"
type="checkbox"
name="share_to_mastodon"
id="id_share_to_mastodon"
value="1"
{% if request.user.preference.mastodon_default_repost %}checked{% endif %}>
{% trans "Repost to timeline" %}
</label>
{% endif %}
</fieldset>
<label for="{{ form.share_to_mastodon.id_for_label }}">
{{ form.share_to_mastodon }} {{ form.share_to_mastodon.label }}
</label>
</div>
</div>
<div>

View file

@ -25,13 +25,17 @@
</span>
<div>{{ post.summary|default:'' }}</div>
<div {% if post.summary or post.sensitive %}class="spoiler" _="on click toggle .revealed on me"{% endif %}>
<div>
<div class="attachments">
{% for attachment in post.attachments.all %}
{% if attachment.is_image %}
<img src="{{ attachment.full_url.relative }}"
alt="attachment.file_display_name"
style="max-height:6em;
max-width:50%">
<a href="#img_{{ post.pk }}_{{ loop.index }}">
<img src="{{ attachment.thumbnail_url.relative }}"
alt="image attachment"
class="preview">
</a>
<a href="#" class="lightbox" id="img_{{ post.pk }}_{{ loop.index }}">
<span style="background-image: url('{{ attachment.full_url.relative }}')"></span>
</a>
{% endif %}
{% endfor %}
</div>

View file

@ -21,8 +21,8 @@ urlpatterns = [
path("wish/<str:item_uuid>", wish, name="wish"),
path("mark/<str:item_uuid>", mark, name="mark"),
path("comment/<str:item_uuid>", comment, name="comment"),
path("note/<str:item_uuid>", note, name="note"),
path("note/<str:item_uuid>/<str:note_uuid>", note, name="note"),
path("item/<str:item_uuid>/note", note_edit, name="note"),
path("item/<str:item_uuid>/note/<str:note_uuid>", note_edit, name="note"),
path("piece/<str:piece_uuid>/replies", piece_replies, name="piece_replies"),
path("post/<int:post_id>/replies", post_replies, name="post_replies"),
path("post/<int:post_id>/reply", post_reply, name="post_reply"),

View file

@ -16,7 +16,8 @@ from .collection import (
user_liked_collection_list,
)
from .common import piece_delete
from .mark import comment, mark, mark_log, note, user_mark_list, wish
from .mark import comment, mark, mark_log, user_mark_list, wish
from .note import note_edit
from .post import (
piece_replies,
post_boost,

View file

@ -1,5 +1,6 @@
from datetime import datetime
from django import forms
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied
@ -13,10 +14,8 @@ from loguru import logger
from catalog.models import *
from common.utils import AuthedHttpRequest, get_uuid_or_404
from mastodon.api import boost_toot_later
from takahe.utils import Takahe
from ..models import Comment, Mark, Note, ShelfManager, ShelfType, TagManager
from ..models import Comment, Mark, ShelfManager, ShelfType, TagManager
from .common import render_list, render_relogin, target_identity_required
PAGE_SIZE = 10
@ -190,53 +189,6 @@ def comment(request: AuthedHttpRequest, item_uuid):
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
@login_required
@require_http_methods(["GET", "POST"])
def note(request: AuthedHttpRequest, item_uuid: str, note_uuid: str = ""):
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
note_uuid = request.POST.get("uuid", note_uuid)
note = None
content = request.POST.get("content")
if note_uuid:
note = get_object_or_404(
Note, owner=request.user.identity, item=item, uid=get_uuid_or_404(note_uuid)
)
if request.method == "GET":
return render(
request,
"note.html",
{
"item": item,
"note": note,
},
)
else:
if request.POST.get("delete", default=False) or not content:
if not note:
raise Http404(_("Content not found"))
note.delete()
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
share_to_mastodon = bool(request.POST.get("share_to_mastodon", default=False))
visibility = int(request.POST.get("visibility", default=0))
delete_existing_post = False
if note:
delete_existing_post = visibility != note.visibility
note.content = content
note.visibility = visibility
note.save()
else:
note = Note.objects.create(
owner=request.user.identity,
item=item,
content=content,
visibility=visibility,
)
note.sync_to_timeline(delete_existing=delete_existing_post)
if share_to_mastodon:
note.sync_to_mastodon(delete_existing=delete_existing_post)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
def user_mark_list(request: AuthedHttpRequest, user_name, shelf_type, item_category):
return render_list(
request, user_name, "mark", shelf_type=shelf_type, item_category=item_category

110
journal/views/note.py Normal file
View file

@ -0,0 +1,110 @@
from django import forms
from django.contrib.auth.decorators import login_required
from django.core.exceptions import BadRequest, ObjectDoesNotExist, PermissionDenied
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.translation import gettext as _
from django.views.decorators.http import require_http_methods
from catalog.models import Item
from common.forms import NeoModelForm
from common.utils import AuthedHttpRequest, get_uuid_or_404
from ..models import Note
from ..models.common import VisibilityType
class NoteForm(NeoModelForm):
# _progress_choices = [
# ("", _("Progress Type (optional)"))
# ] + Note.ProgressType.choices
# progress_type = forms.ChoiceField(choices=_progress_choices, required=False)
visibility = forms.ChoiceField(
widget=forms.RadioSelect(), choices=VisibilityType.choices, initial=0
)
share_to_mastodon = forms.BooleanField(
label=_("Post to Fediverse"), initial=True, required=False
)
uuid = forms.CharField(widget=forms.HiddenInput(), required=False)
# content = forms.CharField(required=False, widget=forms.Textarea)
class Meta:
model = Note
fields = [
"id",
"title",
"content",
"visibility",
"progress_type",
"progress_value",
"sensitive",
]
widgets = {
"progress_value": forms.TextInput(
attrs={"placeholder": _("Progress (optional)")}
),
"content": forms.Textarea(attrs={"placeholder": _("Note Content")}),
"title": forms.TextInput(
attrs={"placeholder": _("Content Warning (optional)")}
),
}
def __init__(self, *args, **kwargs):
item = kwargs.pop("item")
super().__init__(*args, **kwargs)
# allow submit empty content for existing note, and we'll delete it
if self.instance.id:
self.fields["content"].required = False
# get the corresponding progress types for the item
types = Note.get_progress_types_by_item(item)
if self.instance.progress_type and self.instance.progress_type not in types:
types.append(self.instance.progress_type)
choices = [("", _("Progress Type (optional)"))] + [(x, x.label) for x in types]
self.fields["progress_type"].choices = choices # type: ignore
@login_required
@require_http_methods(["GET", "POST"])
def note_edit(request: AuthedHttpRequest, item_uuid: str, note_uuid: str = ""):
item = get_object_or_404(Item, uid=get_uuid_or_404(item_uuid))
owner = request.user.identity
note_uuid = request.POST.get("uuid", note_uuid)
note = None
if note_uuid:
note = get_object_or_404(
Note, owner=owner, item=item, uid=get_uuid_or_404(note_uuid)
)
form = NoteForm(
request.POST or None, item=item, instance=note, initial={"uuid": note_uuid}
)
form.instance.owner = owner
form.instance.item = item
if request.method == "GET":
return render(
request,
"note.html",
{
"item": item,
"note": note,
"form": form,
},
)
if not form.data["content"]:
if not note:
raise Http404(_("Content not found"))
note.delete()
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))
if note:
orig_visibility = note.visibility
else:
orig_visibility = None
if not form.is_valid():
raise BadRequest(_("Invalid form data"))
note = form.save()
delete_existing_post = (
orig_visibility is not None and orig_visibility != note.visibility
)
note.sync_to_timeline(delete_existing=delete_existing_post)
if form.cleaned_data["share_to_mastodon"]:
note.sync_to_mastodon(delete_existing=delete_existing_post)
return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))

View file

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-13 20:50-0400\n"
"POT-Creation-Date: 2024-06-15 18:22-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -715,15 +715,15 @@ msgstr "我的短评和标签"
msgid "my notes"
msgstr "我的笔记"
#: catalog/templates/_item_user_pieces.html:101
#: catalog/templates/_item_user_pieces.html:118
msgid "my review"
msgstr "我的评论"
#: catalog/templates/_item_user_pieces.html:143
#: catalog/templates/_item_user_pieces.html:160
msgid "my collection"
msgstr "我的收藏单"
#: catalog/templates/_item_user_pieces.html:173
#: catalog/templates/_item_user_pieces.html:190
msgid "mark history"
msgstr "标记历史"
@ -1029,7 +1029,7 @@ msgstr "创建"
#: catalog/templates/catalog_edit.html:50
#: journal/templates/add_to_collection.html:35
#: journal/templates/collection_edit.html:38 journal/templates/comment.html:69
#: journal/templates/mark.html:147 journal/templates/note.html:66
#: journal/templates/mark.html:147 journal/templates/note.html:39
#: journal/templates/review_edit.html:39 journal/templates/tag_edit.html:51
#: users/templates/users/account.html:43 users/templates/users/account.html:104
#: users/templates/users/preferences.html:168
@ -1114,7 +1114,7 @@ msgstr "热门标签"
#: catalog/templates/item_review_list.html:50 common/templates/_sidebar.html:99
#: common/templates/_sidebar_anonymous.html:43
#: common/templates/_sidebar_anonymous.html:58
#: journal/templates/collection_items.html:8 journal/templates/posts.html:45
#: journal/templates/collection_items.html:8 journal/templates/posts.html:49
#: journal/templates/profile.html:109 journal/templates/profile.html:151
#: journal/templates/profile.html:187
#: journal/templates/user_collection_list.html:51
@ -1393,7 +1393,7 @@ msgstr "权限不足"
#: catalog/views_edit.py:200 journal/views/collection.py:229
#: journal/views/collection.py:296 journal/views/common.py:81
#: journal/views/mark.py:142 journal/views/post.py:41 journal/views/post.py:55
#: journal/views/mark.py:141 journal/views/post.py:41 journal/views/post.py:55
#: journal/views/review.py:93 journal/views/review.py:96 users/views.py:169
msgid "Invalid parameter"
msgstr "无效参数"
@ -1678,7 +1678,7 @@ msgstr "标题"
msgid "Content (Markdown)"
msgstr "内容 Markdown格式"
#: journal/forms.py:21
#: journal/forms.py:21 journal/views/note.py:26
msgid "Post to Fediverse"
msgstr "发布到联邦宇宙"
@ -1722,31 +1722,96 @@ msgstr "备注"
#: journal/templates/action_open_post.html:14
#: journal/templates/action_open_post.html:16
#: journal/templates/collection_share.html:35 journal/templates/comment.html:35
#: journal/templates/mark.html:93 journal/templates/note.html:32
#: journal/templates/tag_edit.html:42 journal/templates/wrapped_share.html:43
#: users/templates/users/data.html:47 users/templates/users/data.html:139
#: journal/templates/mark.html:93 journal/templates/tag_edit.html:42
#: journal/templates/wrapped_share.html:43 users/templates/users/data.html:47
#: users/templates/users/data.html:139
#: users/templates/users/preferences.html:54
msgid "Public"
msgstr "公开"
#: journal/models/common.py:34 journal/templates/action_open_post.html:10
#: journal/templates/collection_share.html:46 journal/templates/comment.html:42
#: journal/templates/mark.html:100 journal/templates/note.html:39
#: journal/templates/wrapped_share.html:49 users/templates/users/data.html:55
#: users/templates/users/data.html:147
#: journal/templates/mark.html:100 journal/templates/wrapped_share.html:49
#: users/templates/users/data.html:55 users/templates/users/data.html:147
#: users/templates/users/preferences.html:61
msgid "Followers Only"
msgstr "仅关注者"
#: journal/models/common.py:35 journal/templates/action_open_post.html:12
#: journal/templates/collection_share.html:57 journal/templates/comment.html:49
#: journal/templates/mark.html:107 journal/templates/note.html:46
#: journal/templates/wrapped_share.html:55 users/templates/users/data.html:63
#: users/templates/users/data.html:155
#: journal/templates/mark.html:107 journal/templates/wrapped_share.html:55
#: users/templates/users/data.html:63 users/templates/users/data.html:155
#: users/templates/users/preferences.html:68
msgid "Mentioned Only"
msgstr "自己和提到的人"
#: journal/models/note.py:27
msgid "Page"
msgstr "页码"
#: journal/models/note.py:28
msgid "Chapter"
msgstr "章节"
#: journal/models/note.py:31
msgid "Part"
msgstr "分部"
#: journal/models/note.py:32
msgid "Episode"
msgstr "单集"
#: journal/models/note.py:33
msgid "Track"
msgstr "曲目"
#: journal/models/note.py:34
msgid "Cycle"
msgstr "周目"
#: journal/models/note.py:35
msgid "Timestamp"
msgstr "时间戳"
#: journal/models/note.py:36
msgid "Percentage"
msgstr "百分比"
#: journal/models/note.py:53
#, python-brace-format
msgid "Page {value}"
msgstr "第{value}页"
#: journal/models/note.py:54
#, python-brace-format
msgid "Chapter {value}"
msgstr "第{value}章"
#: journal/models/note.py:57
#, python-brace-format
msgid "Part {value}"
msgstr "第{value}部"
#: journal/models/note.py:58
#, python-brace-format
msgid "Episode {value}"
msgstr "第{value}集"
#: journal/models/note.py:59
#, python-brace-format
msgid "Track {value}"
msgstr "第{value}首"
#: journal/models/note.py:60
#, python-brace-format
msgid "Cycle {value}"
msgstr "{value}周目"
#: journal/models/renderers.py:94 mastodon/api.py:619 takahe/utils.py:540
#, python-brace-format
msgid "regarding {item_title}, may contain spoiler or triggering content"
msgstr "关于 {item_title},可能包含剧透或敏感内容"
#: journal/models/shelf.py:24
msgid "WISHLIST"
msgstr ""
@ -2323,7 +2388,6 @@ msgid "Tips: use &gt;!text!&lt; for spoilers; some instances may not be able to
msgstr "提示: 善用 &gt;!文字!&lt; 标记可隐藏剧透; 超过360字可能无法分享到联邦宇宙实例时间轴。"
#: journal/templates/comment.html:62 journal/templates/mark.html:120
#: journal/templates/note.html:59
msgid "Repost to timeline"
msgstr "转发到时间轴"
@ -2450,6 +2514,10 @@ msgstr ""
msgid "Note"
msgstr "笔记"
#: journal/templates/note.html:21
msgid "Note with empty content will be deleted, sure to continue?"
msgstr ""
#: journal/templates/profile.html:55
msgid "calendar"
msgstr "日历"
@ -2458,7 +2526,7 @@ msgstr "日历"
msgid "annual summary"
msgstr "年度小结"
#: journal/templates/profile.html:131 mastodon/api.py:747
#: journal/templates/profile.html:131 mastodon/api.py:678
msgid "collection"
msgstr "收藏单"
@ -2590,7 +2658,7 @@ msgstr "找不到条目,请使用本站条目网址。"
msgid "Login required"
msgstr "登录后访问"
#: journal/views/common.py:33 journal/views/mark.py:119
#: journal/views/common.py:33 journal/views/mark.py:118
msgid "Data saved but unable to repost to Fediverse instance."
msgstr "数据已保存但未能转发到联邦实例。"
@ -2602,15 +2670,35 @@ msgstr "正在重定向到你的联邦实例以重新认证。"
msgid "List not found."
msgstr "列表未找到"
#: journal/views/mark.py:110
#: journal/views/mark.py:109
msgid "Content too long for your Fediverse instance."
msgstr "内容过长,超出了你的联邦实例的限制。"
#: journal/views/mark.py:164 journal/views/mark.py:218
#: journal/views/mark.py:163 journal/views/note.py:94
#: journal/views/review.py:30
msgid "Content not found"
msgstr "内容未找到"
#: journal/views/note.py:44
msgid "Progress (optional)"
msgstr "进度(选填)"
#: journal/views/note.py:46
msgid "Note Content"
msgstr "笔记内容"
#: journal/views/note.py:48
msgid "Content Warning (optional)"
msgstr "剧透或敏感内容提示(选填)"
#: journal/views/note.py:62
msgid "Progress Type (optional)"
msgstr "进度类型(选填)"
#: journal/views/note.py:102
msgid "Invalid form data"
msgstr "无效表单信息。"
#: journal/views/review.py:112 journal/views/review.py:126
#, python-brace-format
msgid "Reviews by {0}"
@ -2649,16 +2737,11 @@ msgstr "标签已更新"
msgid "Summary posted to timeline."
msgstr "总结已发布到时间轴"
#: mastodon/api.py:600 takahe/utils.py:540
#, python-brace-format
msgid "regarding {item_title}, may contain spoiler or triggering content"
msgstr "关于 {item_title},可能包含剧透或敏感内容"
#: mastodon/api.py:752
#: mastodon/api.py:683
msgid "shared my collection"
msgstr "分享我的收藏单"
#: mastodon/api.py:755
#: mastodon/api.py:686
#, python-brace-format
msgid "shared {username}'s collection"
msgstr "分享 {username} 的收藏单"

View file

@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-13 20:50-0400\n"
"POT-Creation-Date: 2024-06-15 18:22-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -715,15 +715,15 @@ msgstr "我的短評和標籤"
msgid "my notes"
msgstr "我的筆記"
#: catalog/templates/_item_user_pieces.html:101
#: catalog/templates/_item_user_pieces.html:118
msgid "my review"
msgstr "我的評論"
#: catalog/templates/_item_user_pieces.html:143
#: catalog/templates/_item_user_pieces.html:160
msgid "my collection"
msgstr "我的收藏單"
#: catalog/templates/_item_user_pieces.html:173
#: catalog/templates/_item_user_pieces.html:190
msgid "mark history"
msgstr "標記歷史"
@ -1029,7 +1029,7 @@ msgstr "創建"
#: catalog/templates/catalog_edit.html:50
#: journal/templates/add_to_collection.html:35
#: journal/templates/collection_edit.html:38 journal/templates/comment.html:69
#: journal/templates/mark.html:147 journal/templates/note.html:66
#: journal/templates/mark.html:147 journal/templates/note.html:39
#: journal/templates/review_edit.html:39 journal/templates/tag_edit.html:51
#: users/templates/users/account.html:43 users/templates/users/account.html:104
#: users/templates/users/preferences.html:168
@ -1114,7 +1114,7 @@ msgstr "熱門標籤"
#: catalog/templates/item_review_list.html:50 common/templates/_sidebar.html:99
#: common/templates/_sidebar_anonymous.html:43
#: common/templates/_sidebar_anonymous.html:58
#: journal/templates/collection_items.html:8 journal/templates/posts.html:45
#: journal/templates/collection_items.html:8 journal/templates/posts.html:49
#: journal/templates/profile.html:109 journal/templates/profile.html:151
#: journal/templates/profile.html:187
#: journal/templates/user_collection_list.html:51
@ -1393,7 +1393,7 @@ msgstr "權限不足"
#: catalog/views_edit.py:200 journal/views/collection.py:229
#: journal/views/collection.py:296 journal/views/common.py:81
#: journal/views/mark.py:142 journal/views/post.py:41 journal/views/post.py:55
#: journal/views/mark.py:141 journal/views/post.py:41 journal/views/post.py:55
#: journal/views/review.py:93 journal/views/review.py:96 users/views.py:169
msgid "Invalid parameter"
msgstr "無效參數"
@ -1678,7 +1678,7 @@ msgstr "標題"
msgid "Content (Markdown)"
msgstr "內容 Markdown格式"
#: journal/forms.py:21
#: journal/forms.py:21 journal/views/note.py:26
msgid "Post to Fediverse"
msgstr "發佈到聯邦宇宙"
@ -1722,31 +1722,96 @@ msgstr "備註"
#: journal/templates/action_open_post.html:14
#: journal/templates/action_open_post.html:16
#: journal/templates/collection_share.html:35 journal/templates/comment.html:35
#: journal/templates/mark.html:93 journal/templates/note.html:32
#: journal/templates/tag_edit.html:42 journal/templates/wrapped_share.html:43
#: users/templates/users/data.html:47 users/templates/users/data.html:139
#: journal/templates/mark.html:93 journal/templates/tag_edit.html:42
#: journal/templates/wrapped_share.html:43 users/templates/users/data.html:47
#: users/templates/users/data.html:139
#: users/templates/users/preferences.html:54
msgid "Public"
msgstr "公開"
#: journal/models/common.py:34 journal/templates/action_open_post.html:10
#: journal/templates/collection_share.html:46 journal/templates/comment.html:42
#: journal/templates/mark.html:100 journal/templates/note.html:39
#: journal/templates/wrapped_share.html:49 users/templates/users/data.html:55
#: users/templates/users/data.html:147
#: journal/templates/mark.html:100 journal/templates/wrapped_share.html:49
#: users/templates/users/data.html:55 users/templates/users/data.html:147
#: users/templates/users/preferences.html:61
msgid "Followers Only"
msgstr "僅關注者"
#: journal/models/common.py:35 journal/templates/action_open_post.html:12
#: journal/templates/collection_share.html:57 journal/templates/comment.html:49
#: journal/templates/mark.html:107 journal/templates/note.html:46
#: journal/templates/wrapped_share.html:55 users/templates/users/data.html:63
#: users/templates/users/data.html:155
#: journal/templates/mark.html:107 journal/templates/wrapped_share.html:55
#: users/templates/users/data.html:63 users/templates/users/data.html:155
#: users/templates/users/preferences.html:68
msgid "Mentioned Only"
msgstr "自己和提到的人"
#: journal/models/note.py:27
msgid "Page"
msgstr "頁碼"
#: journal/models/note.py:28
msgid "Chapter"
msgstr "章節"
#: journal/models/note.py:31
msgid "Part"
msgstr "分部"
#: journal/models/note.py:32
msgid "Episode"
msgstr "單集"
#: journal/models/note.py:33
msgid "Track"
msgstr "曲目"
#: journal/models/note.py:34
msgid "Cycle"
msgstr "周目"
#: journal/models/note.py:35
msgid "Timestamp"
msgstr "時間戳"
#: journal/models/note.py:36
msgid "Percentage"
msgstr "百分比"
#: journal/models/note.py:53
#, python-brace-format
msgid "Page {value}"
msgstr "第{value}頁"
#: journal/models/note.py:54
#, python-brace-format
msgid "Chapter {value}"
msgstr "第{value}章"
#: journal/models/note.py:57
#, python-brace-format
msgid "Part {value}"
msgstr "第{value}部"
#: journal/models/note.py:58
#, python-brace-format
msgid "Episode {value}"
msgstr "第{value}集"
#: journal/models/note.py:59
#, python-brace-format
msgid "Track {value}"
msgstr "第{value}首"
#: journal/models/note.py:60
#, python-brace-format
msgid "Cycle {value}"
msgstr "{value}周目"
#: journal/models/renderers.py:94 mastodon/api.py:619 takahe/utils.py:540
#, python-brace-format
msgid "regarding {item_title}, may contain spoiler or triggering content"
msgstr "關於 {item_title},可能包含劇透或敏感內容"
#: journal/models/shelf.py:24
msgid "WISHLIST"
msgstr ""
@ -2323,7 +2388,6 @@ msgid "Tips: use &gt;!text!&lt; for spoilers; some instances may not be able to
msgstr "提示: 善用 &gt;!文字!&lt; 標記可隱藏劇透; 超過360字可能無法分享到聯邦宇宙實例時間軸。"
#: journal/templates/comment.html:62 journal/templates/mark.html:120
#: journal/templates/note.html:59
msgid "Repost to timeline"
msgstr "轉發到時間軸"
@ -2450,6 +2514,10 @@ msgstr ""
msgid "Note"
msgstr "筆記"
#: journal/templates/note.html:21
msgid "Note with empty content will be deleted, sure to continue?"
msgstr ""
#: journal/templates/profile.html:55
msgid "calendar"
msgstr "日曆"
@ -2458,7 +2526,7 @@ msgstr "日曆"
msgid "annual summary"
msgstr "年度小結"
#: journal/templates/profile.html:131 mastodon/api.py:747
#: journal/templates/profile.html:131 mastodon/api.py:678
msgid "collection"
msgstr "收藏單"
@ -2590,7 +2658,7 @@ msgstr "找不到條目,請使用本站條目網址。"
msgid "Login required"
msgstr "登錄後訪問"
#: journal/views/common.py:33 journal/views/mark.py:119
#: journal/views/common.py:33 journal/views/mark.py:118
msgid "Data saved but unable to repost to Fediverse instance."
msgstr "數據已保存但未能轉發到聯邦實例。"
@ -2602,15 +2670,35 @@ msgstr "正在重定向到你的聯邦實例以重新認證。"
msgid "List not found."
msgstr "列表未找到"
#: journal/views/mark.py:110
#: journal/views/mark.py:109
msgid "Content too long for your Fediverse instance."
msgstr "內容過長,超出了你的聯邦實例的限制。"
#: journal/views/mark.py:164 journal/views/mark.py:218
#: journal/views/mark.py:163 journal/views/note.py:94
#: journal/views/review.py:30
msgid "Content not found"
msgstr "內容未找到"
#: journal/views/note.py:44
msgid "Progress (optional)"
msgstr "進度(選填)"
#: journal/views/note.py:46
msgid "Note Content"
msgstr "筆記內容"
#: journal/views/note.py:48
msgid "Content Warning (optional)"
msgstr "劇透或敏感內容提示(選填)"
#: journal/views/note.py:62
msgid "Progress Type (optional)"
msgstr "進度類型(選填)"
#: journal/views/note.py:102
msgid "Invalid form data"
msgstr "無效表單信息。"
#: journal/views/review.py:112 journal/views/review.py:126
#, python-brace-format
msgid "Reviews by {0}"
@ -2649,16 +2737,11 @@ msgstr "標籤已更新"
msgid "Summary posted to timeline."
msgstr "總結已發佈到時間軸"
#: mastodon/api.py:600 takahe/utils.py:540
#, python-brace-format
msgid "regarding {item_title}, may contain spoiler or triggering content"
msgstr "關於 {item_title},可能包含劇透或敏感內容"
#: mastodon/api.py:752
#: mastodon/api.py:683
msgid "shared my collection"
msgstr "分享我的收藏單"
#: mastodon/api.py:755
#: mastodon/api.py:686
#, python-brace-format
msgid "shared {username}'s collection"
msgstr "分享 {username} 的收藏單"