new data model: mark and review pages
This commit is contained in:
parent
86b13bf08f
commit
de2fde302a
16 changed files with 402 additions and 197 deletions
45
catalog/static/catalog.js
Normal file
45
catalog/static/catalog.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
function catalog_init(context) {
|
||||
// readonly star rating of detail display section
|
||||
let ratingLabels = $("#main .rating-star", context);
|
||||
$(ratingLabels).each( function(index, value) {
|
||||
let ratingScore = $(this).data("rating-score") / 2;
|
||||
$(this).starRating({
|
||||
initialRating: ratingScore,
|
||||
readOnly: true,
|
||||
});
|
||||
});
|
||||
// readonly star rating at aside section
|
||||
ratingLabels = $("#aside .rating-star"), context;
|
||||
$(ratingLabels).each( function(index, value) {
|
||||
let ratingScore = $(this).data("rating-score") / 2;
|
||||
$(this).starRating({
|
||||
initialRating: ratingScore,
|
||||
readOnly: true,
|
||||
starSize: 15,
|
||||
});
|
||||
});
|
||||
// hide long text
|
||||
$(".entity-desc__content", context).each(function() {
|
||||
let copy = $(this).clone()
|
||||
.addClass('entity-desc__content--folded')
|
||||
.css("visibility", "hidden");
|
||||
$(this).after(copy);
|
||||
if ($(this).height() > copy.height()) {
|
||||
$(this).addClass('entity-desc__content--folded');
|
||||
$(this).siblings(".entity-desc__unfold-button").removeClass("entity-desc__unfold-button--hidden");
|
||||
}
|
||||
copy.remove();
|
||||
});
|
||||
|
||||
// expand hidden long text
|
||||
$(".entity-desc__unfold-button a", context).on('click', function() {
|
||||
$(this).parent().siblings(".entity-desc__content").removeClass('entity-desc__content--folded');
|
||||
$(this).parent(".entity-desc__unfold-button").remove();
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
document.body.addEventListener('htmx:load', function(evt) {
|
||||
catalog_init(evt.detail.elt);
|
||||
});
|
||||
});
|
25
catalog/templates/common_libs.html
Normal file
25
catalog/templates/common_libs.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% load static %}
|
||||
{% if sentry_dsn %}
|
||||
<script src="https://browser.sentry-cdn.com/7.7.0/bundle.min.js"></script>
|
||||
<script>
|
||||
if (window.Sentry) Sentry.init({
|
||||
dsn: "{{ sentry_dsn }}",
|
||||
release: "{{ version_hash }}",
|
||||
environment: "{{ settings_module }}",
|
||||
tracesSampleRate: 1.0,
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% if jquery %}
|
||||
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
|
||||
{% else %}
|
||||
<script src="https://cdn.staticfile.org/cash/8.1.1/cash.min.js"></script>
|
||||
{% endif %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/htmx/1.8.4/htmx.min.js"></script>
|
||||
<script src="{% static 'lib/js/hyperscript-0.9.7.min.js' %}"></script>
|
||||
<script src="{% static 'lib/js/rating-star.js' %}"></script>
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'lib/css/collection.css' %}">
|
||||
<link rel="search"type="application/opensearchdescription+xml" title="{{ site_name }}" href="{% static 'opensearch.xml' %}">
|
||||
<script src="{% static 'catalog.js' %}"></script>
|
|
@ -10,12 +10,21 @@
|
|||
{% load strip_scheme %}
|
||||
{% load thumb %}
|
||||
|
||||
{% block opengraph %}
|
||||
{% if item.author %}
|
||||
<meta property="og:book:author" content="{% for author in item.author %}{{ author }}{% if not forloop.last %},{% endif %}{% endfor %}">
|
||||
{% endif %}
|
||||
{% if item.isbn %}
|
||||
<meta property="og:book:isbn" content="{{ item.isbn }}">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block details %}
|
||||
<div class="entity-detail__fields">
|
||||
<div class="entity-detail__rating">
|
||||
{% if item.rating %}
|
||||
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:"0" }}"></span>
|
||||
<span class="entity-detail__rating-score"> {{ item.rating }} </span>
|
||||
<span class="entity-detail__rating-star rating-star" data-rating-score="{{ item.rating | floatformat:0 }}"></span>
|
||||
<span class="entity-detail__rating-score"> {{ item.rating | floatformat:1 }} </span>
|
||||
<small>({{ item.rating_count }}人评分)</small>
|
||||
{% else %}
|
||||
<span> {% trans '评分:评分人数不足' %}</span>
|
||||
|
|
|
@ -16,23 +16,15 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta property="og:title" content="{{ site_name }}{% trans item.category.label %} - {{ item.title }}">
|
||||
<meta property="og:type" content="book">
|
||||
<meta property="og:type" content="{{ item.category }}">
|
||||
<meta property="og:url" content="{{ request.build_absolute_uri }}">
|
||||
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{{ item.cover.url }}">
|
||||
<meta property="og:site_name" content="{{ site_name }}">
|
||||
<meta property="og:description" content="{{ item.brief }}">
|
||||
{% if item.author %}
|
||||
<meta property="og:book:author" content="{% for author in item.author %}{{ author }}{% if not forloop.last %},{% endif %}{% endfor %}">
|
||||
{% endif %}
|
||||
{% if item.isbn %}
|
||||
<meta property="og:book:isbn" content="{{ item.isbn }}">
|
||||
{% endif %}
|
||||
|
||||
{% block opengraph %}
|
||||
{% endblock %}
|
||||
<title>{{ site_name }} - {% trans item.category.label %} | {{ item.title }}</title>
|
||||
|
||||
{% include "partial/_common_libs.html" with jquery=1 %}
|
||||
|
||||
<script src="{% static 'lib/js/rating-star.js' %}"></script>
|
||||
{% include "common_libs.html" with jquery=1 %}
|
||||
<script src="{% static 'lib/js/tag-input.js' %}"></script>
|
||||
<link href="{% static 'lib/css/tag-input.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
</head>
|
||||
|
@ -204,9 +196,10 @@
|
|||
{% endif %}
|
||||
<span class="mark-panel__actions">
|
||||
<a href="#" hx-get="{% url 'journal:mark' item.uuid %}" class="edit" hx-target="body" hx-swap="beforeend">{% trans '修改' %}</a>
|
||||
<form action="{% url 'books:delete_mark' mark.id %}" method="post">
|
||||
<form id="mark_delete" action="{% url 'journal:mark' item.uuid %}" method="post">
|
||||
{% csrf_token %}
|
||||
<a href="" class="delete">{% trans '删除' %}</a>
|
||||
<input type="hidden" name="delete" value="1">
|
||||
<a href="javascript:$('#mark_delete').submit()" class="delete">{% trans '删除' %}</a>
|
||||
</form>
|
||||
</span>
|
||||
<div class="mark-panel__clear"></div>
|
||||
|
@ -234,26 +227,24 @@
|
|||
<div class="action-panel" id="addMarkPanel">
|
||||
<div class="action-panel__label">{% trans '标记' %}{% trans item.demonstrative %}</div>
|
||||
<div class="action-panel__button-group">
|
||||
<button class="action-panel__button" data-status="{{ status_enum.WISH.value }}" id="wishButton">{% trans '想读' %}</button>
|
||||
<button class="action-panel__button" data-status="{{ status_enum.DO.value }}">{% trans '在读' %}</button>
|
||||
<button class="action-panel__button" data-status="{{ status_enum.COLLECT.value }}">{% trans '读过' %}</button>
|
||||
{% for k, v in shelf_types %}
|
||||
<button class="action-panel__button" data-status="{{ k }}" hx-get="{% url 'journal:mark' item.uuid %}?shelf_type={{ k }}" class="edit" hx-target="body" hx-swap="beforeend">{% trans v %}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="aside-section-wrapper">
|
||||
{% if review %}
|
||||
<div class="review-panel">
|
||||
|
||||
<div class="review-panel action-panel">
|
||||
{% if review %}
|
||||
<span class="review-panel__label">{% trans '我的评论' %}</span>
|
||||
{% if review.visibility > 0 %}
|
||||
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
|
||||
{% endif %}
|
||||
|
||||
<span class="review-panel__actions">
|
||||
<a href="{% url 'books:update_review' review.id %}">{% trans '编辑' %}</a>
|
||||
<a href="{% url 'books:delete_review' review.id %}">{% trans '删除' %}</a>
|
||||
<a href="{% url 'journal:review_edit' item.uuid review.uuid %}">{% trans '编辑' %}</a>
|
||||
<a href="{% url 'journal:review_delete' review.uuid %}">{% trans '删除' %}</a>
|
||||
</span>
|
||||
|
||||
<div class="review-panel__time">{{ review.edited_time }}</div>
|
||||
|
@ -261,20 +252,14 @@
|
|||
<a href="{% url 'journal:review_retrieve' review.uuid %}" class="review-panel__review-title">
|
||||
{{ review.title }}
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div class="action-panel">
|
||||
<div class="action-panel__label">{% trans '我的评论' %}</div>
|
||||
{% else %}
|
||||
<div class="action-panel__button-group action-panel__button-group--center">
|
||||
|
||||
<a href="{% url 'books:create_review' item.id %}">
|
||||
<button class="action-panel__button">{% trans '去写评论' %}</button>
|
||||
<a href="{% url 'journal:review_create' item.uuid %}">
|
||||
<button class="action-panel__button">{% trans '撰写评论' %}</button>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -308,44 +293,5 @@
|
|||
</div>
|
||||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
<script>
|
||||
// readonly star rating of detail display section
|
||||
let ratingLabels = $("#main .rating-star");
|
||||
$(ratingLabels).each( function(index, value) {
|
||||
let ratingScore = $(this).data("rating-score") / 2;
|
||||
$(this).starRating({
|
||||
initialRating: ratingScore,
|
||||
readOnly: true,
|
||||
});
|
||||
});
|
||||
// readonly star rating at aside section
|
||||
ratingLabels = $("#aside .rating-star");
|
||||
$(ratingLabels).each( function(index, value) {
|
||||
let ratingScore = $(this).data("rating-score") / 2;
|
||||
$(this).starRating({
|
||||
initialRating: ratingScore,
|
||||
readOnly: true,
|
||||
starSize: 15,
|
||||
});
|
||||
});
|
||||
// hide long text
|
||||
$(".entity-desc__content").each(function() {
|
||||
let copy = $(this).clone()
|
||||
.addClass('entity-desc__content--folded')
|
||||
.css("visibility", "hidden");
|
||||
$(this).after(copy);
|
||||
if ($(this).height() > copy.height()) {
|
||||
$(this).addClass('entity-desc__content--folded');
|
||||
$(this).siblings(".entity-desc__unfold-button").removeClass("entity-desc__unfold-button--hidden");
|
||||
}
|
||||
copy.remove();
|
||||
});
|
||||
|
||||
// expand hidden long text
|
||||
$(".entity-desc__unfold-button a").on('click', function() {
|
||||
$(this).parent().siblings(".entity-desc__content").removeClass('entity-desc__content--folded');
|
||||
$(this).parent(".entity-desc__unfold-button").remove();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -13,11 +13,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ site_name }} - {{ item.title }}{% trans '的标记' %}</title>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script src="{% static 'lib/js/rating-star.js' %}"></script>
|
||||
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
|
||||
{% include "common_libs.html" with jquery=1 %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -105,47 +101,5 @@
|
|||
</div>
|
||||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// readonly star rating of detail display section
|
||||
let ratingLabels = $("#main .rating-star");
|
||||
$(ratingLabels).each( function(index, value) {
|
||||
let ratingScore = $(this).data("rating-score") / 2;
|
||||
$(this).starRating({
|
||||
initialRating: ratingScore,
|
||||
readOnly: true,
|
||||
});
|
||||
});
|
||||
// readonly star rating at aside section
|
||||
ratingLabels = $("#aside .rating-star");
|
||||
$(ratingLabels).each( function(index, value) {
|
||||
let ratingScore = $(this).data("rating-score") / 2;
|
||||
$(this).starRating({
|
||||
initialRating: ratingScore,
|
||||
readOnly: true,
|
||||
starSize: 15,
|
||||
});
|
||||
});
|
||||
// hide long text
|
||||
$(".entity-desc__content").each(function() {
|
||||
let copy = $(this).clone()
|
||||
.addClass('entity-desc__content--folded')
|
||||
.css("visibility", "hidden");
|
||||
$(this).after(copy);
|
||||
if ($(this).height() > copy.height()) {
|
||||
$(this).addClass('entity-desc__content--folded');
|
||||
$(this).siblings(".entity-desc__unfold-button").removeClass("entity-desc__unfold-button--hidden");
|
||||
}
|
||||
copy.remove();
|
||||
});
|
||||
|
||||
// expand hidden long text
|
||||
$(".entity-desc__unfold-button a").on('click', function() {
|
||||
$(this).parent().siblings(".entity-desc__content").removeClass('entity-desc__content--folded');
|
||||
$(this).parent(".entity-desc__unfold-button").remove();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
||||
|
|
|
@ -13,11 +13,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ site_name }} - {{ item.title }}{% trans '的评论' %}</title>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script src="{% static 'lib/js/rating-star.js' %}"></script>
|
||||
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
|
||||
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
|
||||
{% include "common_libs.html" with jquery=1 %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -30,22 +26,18 @@
|
|||
<div class="main-section-wrapper">
|
||||
<div class="entity-reviews">
|
||||
<h5 class="entity-reviews__title entity-reviews__title--stand-alone">
|
||||
<a href="{% url 'books:retrieve' item.id %}">{{ item.title }}</a>{% trans ' 的评论' %}
|
||||
<a href="{{ item.url }}">{{ item.title }}</a>{% trans ' 的评论' %}
|
||||
</h5>
|
||||
<ul class="entity-reviews__review-list">
|
||||
|
||||
{% for review in reviews %}
|
||||
|
||||
<li class="entity-reviews__review entity-reviews__review--wider">
|
||||
|
||||
<a href="{% url 'users:home' review.owner.mastodon_username %}" class="entity-reviews__owner-link">{{ review.owner.username }}</a>
|
||||
{% if review.visibility > 0 %}
|
||||
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z"/></svg></span>
|
||||
{% endif %}
|
||||
<span class="entity-reviews__review-time">{{ review.edited_time }}</span>
|
||||
|
||||
<span href="{% url 'books:retrieve_review' review.id %}" class="entity-reviews__review-title"><a href="{% url 'books:retrieve_review' review.id %}">{{ review.title }}</a></span>
|
||||
|
||||
<span class="entity-reviews__review-title"><a href="{% url 'journal:review_retrieve' review.uuid %}">{{ review.title }}</a></span>
|
||||
</li>
|
||||
{% empty %}
|
||||
<div>{% trans '无结果' %}</div>
|
||||
|
|
|
@ -19,6 +19,7 @@ from journal.models import Mark, ShelfMember, Review
|
|||
from journal.models import query_visible, query_following
|
||||
from common.utils import PageLinksGenerator
|
||||
from common.views import PAGE_LINK_NUMBER
|
||||
from journal.models import ShelfTypeNames
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -43,6 +44,7 @@ def retrieve(request, item_path, item_uuid):
|
|||
mark_list = None
|
||||
review_list = None
|
||||
collection_list = []
|
||||
shelf_types = [(n[1], n[2]) for n in iter(ShelfTypeNames) if n[0] == item.category]
|
||||
if request.user.is_authenticated:
|
||||
visible = query_visible(request.user)
|
||||
mark = Mark(request.user, item)
|
||||
|
@ -60,6 +62,7 @@ def retrieve(request, item_path, item_uuid):
|
|||
'mark_list': mark_list,
|
||||
'review_list': review_list,
|
||||
'collection_list': collection_list,
|
||||
'shelf_types': shelf_types,
|
||||
}
|
||||
)
|
||||
else:
|
||||
|
|
35
journal/forms.py
Normal file
35
journal/forms.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from django import forms
|
||||
from markdownx.fields import MarkdownxFormField
|
||||
import django.contrib.postgres.forms as postgres
|
||||
from django.utils import formats
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import json
|
||||
from .models import *
|
||||
|
||||
|
||||
class ReviewForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Review
|
||||
fields = [
|
||||
'id',
|
||||
'item',
|
||||
'title',
|
||||
'body',
|
||||
'visibility'
|
||||
]
|
||||
widgets = {
|
||||
'item': forms.TextInput(attrs={"hidden": ""}),
|
||||
}
|
||||
title = forms.CharField(label=_("评论标题"))
|
||||
body = MarkdownxFormField(label=_("评论正文 (Markdown)"))
|
||||
share_to_mastodon = forms.BooleanField(
|
||||
label=_("分享到联邦网络"), initial=True, required=False)
|
||||
id = forms.IntegerField(required=False, widget=forms.HiddenInput())
|
||||
visibility = forms.TypedChoiceField(
|
||||
label=_("可见性"),
|
||||
initial=0,
|
||||
coerce=int,
|
||||
choices=VisibilityType.choices,
|
||||
widget=forms.RadioSelect
|
||||
)
|
|
@ -23,6 +23,12 @@ from django.db.models import Q
|
|||
import mistune
|
||||
|
||||
|
||||
class VisibilityType(models.IntegerChoices):
|
||||
Public = 0, _('公开')
|
||||
Follower_Only = 1, _('仅关注者')
|
||||
Private = 2, _('仅自己')
|
||||
|
||||
|
||||
def query_visible(user):
|
||||
return Q(visibility=0) | Q(owner_id__in=user.following, visibility__lt=2) | Q(owner_id=user.id)
|
||||
|
||||
|
@ -47,7 +53,7 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
|
||||
@property
|
||||
def url(self):
|
||||
return f'/{self.url_path}/{self.uuid}/' if self.url_path else None
|
||||
return f'/{self.url_path}/{self.uuid}' if self.url_path else None
|
||||
|
||||
@property
|
||||
def absolute_url(self):
|
||||
|
@ -62,7 +68,7 @@ class Content(Piece):
|
|||
item = models.ForeignKey(Item, on_delete=models.PROTECT)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.id}({self.item})"
|
||||
return f"{self.uuid}@{self.item}"
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -644,3 +650,6 @@ class Mark:
|
|||
if self.shelfmember.metadata.get('shared_link') != self.shared_link:
|
||||
self.shelfmember.metadata['shared_link'] = self.shared_link
|
||||
self.shelfmember.save()
|
||||
|
||||
def delete(self):
|
||||
self.update(None, None, None, 0)
|
||||
|
|
|
@ -31,17 +31,17 @@
|
|||
<div id="statusSelection" class="mark-modal__status-radio float-right">
|
||||
<ul id="id_status">
|
||||
{% for k, v in shelf_types %}
|
||||
<li><label for="id_status_{{ k }}"><input type="radio" name="status" value="{{ k }}" required="" id="id_status_{{ k }}" {% if mark.shelf_type == k %}checked=""{% endif %}> {{ v }}</label></li>
|
||||
<li><label for="id_status_{{ k }}"><input type="radio" name="status" value="{{ k }}" required="" id="id_status_{{ k }}" {% if shelf_type == k %}checked=""{% endif %}> {{ v }}</label></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mark-modal__rating-star rating-star-edit"></div>
|
||||
<input type="hidden" name="rating" id="id_rating" value="{{ mark.rating }}">
|
||||
<input type="hidden" name="rating" id="id_rating" value="{{ mark.rating|floatformat:0 }}">
|
||||
|
||||
<div class="mark-modal__clear"></div>
|
||||
|
||||
<textarea name="text" cols="40" rows="10" placeholder="超过360字部分实例可能无法显示" maxlength="360" id="id_text">{{ mark.text }}</textarea>
|
||||
<textarea name="text" cols="40" rows="10" placeholder="超过360字部分实例可能无法显示" maxlength="360" id="id_text">{% if mark.text %}{{ mark.text }}{% endif %}</textarea>
|
||||
|
||||
<div class="mark-modal__tag">
|
||||
<label>标签</label>
|
||||
|
@ -100,19 +100,19 @@
|
|||
// hide rating star when select wish
|
||||
const WISH_CODE = "wishlist";
|
||||
if ($("#statusSelection input[type='radio']:checked").val() == WISH_CODE) {
|
||||
$("#model .rating-star-edit").hide();
|
||||
$("#modal .rating-star-edit").hide();
|
||||
}
|
||||
$("#statusSelection input[type='radio']").on('click', function() {
|
||||
if ($(this).val() == WISH_CODE) {
|
||||
$("#model .rating-star-edit").hide();
|
||||
$("#modal .rating-star-edit").hide();
|
||||
} else {
|
||||
$("#model .rating-star-edit").show();
|
||||
$("#modal .rating-star-edit").show();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// show confirm modal
|
||||
$("#model a.delete").on('click', function(e) {
|
||||
$("#modal a.delete").on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$(".confirm-modal").show();
|
||||
$(".bg-mask").show();
|
||||
|
@ -121,7 +121,7 @@
|
|||
// confirm modal
|
||||
$(".confirm-modal input[type='submit']").on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$("#model form").submit();
|
||||
$("#modal form").submit();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
|
|
@ -11,18 +11,13 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta property="og:title" content="{{ site_name }}书评 - {{ review.title }}">
|
||||
<meta property="og:title" content="{{ site_name }} - {{ review.title }} ({{ review.item.title }} {% trans '评论' %})">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:article:author" content="{{ review.owner.username }}">
|
||||
<meta property="og:url" content="{{ request.build_absolute_uri }}">
|
||||
<meta property="og:image" content="{{ review.item.cover|thumb:'normal' }}">
|
||||
<title>{{ site_name }}{% trans '评论' %} - {{ review.title }}</title>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
|
||||
<script src="{% static 'lib/js/rating-star.js' %}"></script>
|
||||
<script src="{% static 'js/rating-star-readonly.js' %}"></script>
|
||||
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'lib/css/collection.css' %}">
|
||||
{% include "common_libs.html" with jquery=1 %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -62,8 +57,8 @@
|
|||
</div>
|
||||
<div class="review-head__actions">
|
||||
{% if request.user == review.owner %}
|
||||
<a class="review-head__action-link" href="{% url 'journal:update_review' review.id %}">{% trans '编辑' %}</a>
|
||||
<a class="review-head__action-link" href="{% url 'journal:delete_review' review.id %}">{% trans '删除' %}</a>
|
||||
<a class="review-head__action-link" href="{% url 'journal:review_edit' review.item.uuid review.uuid %}">{% trans '编辑' %}</a>
|
||||
<a class="review-head__action-link" href="{% url 'journal:review_delete' review.uuid %}">{% trans '删除' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
93
journal/templates/review_delete.html
Normal file
93
journal/templates/review_delete.html
Normal file
|
@ -0,0 +1,93 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load admin_url %}
|
||||
{% load mastodon %}
|
||||
{% load oauth_token %}
|
||||
{% load truncate %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ site_name }} - {% trans '删除评论' %}</title>
|
||||
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="{% static 'lib/css/rating-star.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-wrapper">
|
||||
<div id="content-wrapper">
|
||||
{% include "partial/_navbar.html" %}
|
||||
|
||||
<section id="content">
|
||||
<div class="grid">
|
||||
<div class="single-section-wrapper" id="main">
|
||||
<h5>{% trans '确认删除这篇评论吗?' %}</h5>
|
||||
|
||||
<div class="dividing-line"></div>
|
||||
|
||||
|
||||
<div class="review-head">
|
||||
|
||||
<h5 class="review-head__title">
|
||||
{{ review.title }}
|
||||
</h5>
|
||||
{% if review.visibility > 0 %}
|
||||
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M17,8.48h-.73V6.27a6.27,6.27,0,1,0-12.53,0V8.48H3a.67.67,0,0,0-.67.67V19.33A.67.67,0,0,0,3,20H17a.67.67,0,0,0,.67-.67V9.15A.67.67,0,0,0,17,8.48ZM6.42,6.27h0a3.57,3.57,0,0,1,7.14,0h0V8.48H6.42Z" />
|
||||
</svg></span>
|
||||
{% endif %}
|
||||
|
||||
<div class="review-head__body">
|
||||
<div class="review-head__info">
|
||||
|
||||
<a href="{% url 'users:home' review.owner.mastodon_username %}"
|
||||
class="review-head__owner-link">{{ review.owner.username }}</a>
|
||||
|
||||
<span class="review-head__time">{{ review.edited_time }}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div id="rawContent" class="delete-preview">
|
||||
{{ form.body }}
|
||||
</div>
|
||||
{{ form.media }}
|
||||
|
||||
<div class="dividing-line"></div>
|
||||
|
||||
<div class="clearfix">
|
||||
<form action="{% url 'journal:review_delete' review.uuid %}" method="post" class="float-right">
|
||||
{% csrf_token %}
|
||||
<input class="button" type="submit" value="{% trans '确认' %}">
|
||||
</form>
|
||||
<button onclick="history.back()"
|
||||
class="button button-clear float-right">{% trans '返回' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
$(".markdownx textarea").hide();
|
||||
$(".markdownx .markdownx-preview").show();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
73
journal/templates/review_edit.html
Normal file
73
journal/templates/review_edit.html
Normal file
|
@ -0,0 +1,73 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load admin_url %}
|
||||
{% load mastodon %}
|
||||
{% load oauth_token %}
|
||||
{% load truncate %}
|
||||
{% load thumb %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ site_name }} - {{ item.title }} - {% trans '评论' %}</title>
|
||||
{% include "common_libs.html" with jquery=1 %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page-wrapper">
|
||||
<div id="content-wrapper">
|
||||
{% include "partial/_navbar.html" %}
|
||||
|
||||
<section id="content">
|
||||
<div class="grid">
|
||||
<div class="grid__main" id="main">
|
||||
<div class="single-section-wrapper">
|
||||
|
||||
<form action="{{ submit_url }}" method="post" class="review-form">
|
||||
{% csrf_token %}
|
||||
{{ form.item }}
|
||||
<div>
|
||||
{{ form.title.label }}
|
||||
</div>
|
||||
{{ form.title }}
|
||||
<div class="clearfix">
|
||||
<span class="float-left">
|
||||
{{ form.body.label }}
|
||||
</span>
|
||||
<span class="float-right">
|
||||
<span class="review-form__preview-button">{% trans '预览' %}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="rawContent">
|
||||
{{ form.body }}
|
||||
</div>
|
||||
<div class="review-form__fyi">{% trans '不知道什么是Markdown?可以参考' %}<a target="_blank" href="https://www.markdownguide.org/">{% trans '这里' %}</a></div>
|
||||
<div class="review-form__option">
|
||||
<div class="review-form__visibility-radio">
|
||||
|
||||
{{ form.visibility.label }}{{ form.visibility }}
|
||||
</div>
|
||||
<div class="review-form__share-checkbox">
|
||||
{{ form.share_to_mastodon }}{{ form.share_to_mastodon.label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<input class="button float-right" type="submit" value="{% trans '提交' %}">
|
||||
</div>
|
||||
{{ form.media }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid__aside" id="aside">
|
||||
{% include "sidebar_item.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -9,6 +9,8 @@ urlpatterns = [
|
|||
path('mark/<str:item_uuid>', mark, name='mark'),
|
||||
path('add_to_collection/<str:item_uuid>', add_to_collection, name='add_to_collection'),
|
||||
|
||||
path('review/<str:piece_uuid>', review_retrieve, name='review_retrieve'),
|
||||
path('review/create', review_create, name='review_create'),
|
||||
path('review/<str:review_uuid>', review_retrieve, name='review_retrieve'),
|
||||
path('review/create/<str:item_uuid>/', review_edit, name='review_create'),
|
||||
path('review/edit/<str:item_uuid>/<str:review_uuid>', review_edit, name='review_edit'),
|
||||
path('review/delete/<str:review_uuid>', review_delete, name='review_delete'),
|
||||
]
|
||||
|
|
|
@ -17,6 +17,9 @@ from django.db.models import Q
|
|||
import time
|
||||
from management.models import Announcement
|
||||
from django.utils.baseconv import base62
|
||||
from .forms import *
|
||||
from mastodon.api import share_review
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
PAGE_SIZE = 10
|
||||
|
@ -82,43 +85,85 @@ def mark(request, item_uuid):
|
|||
if request.method == 'GET':
|
||||
tags = TagManager.get_item_tags_by_user(item, request.user)
|
||||
shelf_types = [(n[1], n[2]) for n in iter(ShelfTypeNames) if n[0] == item.category]
|
||||
shelf_type = request.GET.get('shelf_type', mark.shelf_type)
|
||||
return render(request, 'mark.html', {
|
||||
'item': item,
|
||||
'mark': mark,
|
||||
'shelf_type': shelf_type,
|
||||
'tags': ','.join(tags),
|
||||
'shelf_types': shelf_types,
|
||||
})
|
||||
elif request.method == 'POST':
|
||||
visibility = int(request.POST.get('visibility', default=0))
|
||||
rating = int(request.POST.get('rating', default=0))
|
||||
status = ShelfType(request.POST.get('status'))
|
||||
text = request.POST.get('text')
|
||||
tags = request.POST.get('tags')
|
||||
tags = tags.split(',') if tags else []
|
||||
share_to_mastodon = bool(request.POST.get('share_to_mastodon', default=False))
|
||||
TagManager.tag_item_by_user(item, request.user, tags, visibility)
|
||||
try:
|
||||
mark.update(status, text, rating, visibility, share_to_mastodon=share_to_mastodon)
|
||||
except Exception:
|
||||
go_relogin(request)
|
||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||
if request.POST.get('delete', default=False):
|
||||
mark.delete()
|
||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||
else:
|
||||
visibility = int(request.POST.get('visibility', default=0))
|
||||
rating = request.POST.get('rating', default=0)
|
||||
rating = int(rating) if rating else None
|
||||
status = ShelfType(request.POST.get('status'))
|
||||
text = request.POST.get('text')
|
||||
tags = request.POST.get('tags')
|
||||
tags = tags.split(',') if tags else []
|
||||
share_to_mastodon = bool(request.POST.get('share_to_mastodon', default=False))
|
||||
TagManager.tag_item_by_user(item, request.user, tags, visibility)
|
||||
try:
|
||||
mark.update(status, text, rating, visibility, share_to_mastodon=share_to_mastodon)
|
||||
except Exception:
|
||||
go_relogin(request)
|
||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||
|
||||
|
||||
def review_retrieve(request, piece_uuid):
|
||||
piece = get_object_or_404(Review, uid=base62.decode(piece_uuid))
|
||||
if not piece:
|
||||
return HttpResponseNotFound("piece not found")
|
||||
def review_retrieve(request, review_uuid):
|
||||
piece = get_object_or_404(Review, uid=base62.decode(review_uuid))
|
||||
if not piece.is_visible_to(request.user):
|
||||
raise PermissionDenied()
|
||||
return render(request, 'review.html', {'review': piece})
|
||||
|
||||
|
||||
def review_edit(request, piece_uuid):
|
||||
pass
|
||||
@login_required
|
||||
def review_edit(request, item_uuid, review_uuid=None):
|
||||
item = get_object_or_404(Item, uid=base62.decode(item_uuid))
|
||||
review = get_object_or_404(Review, uid=base62.decode(review_uuid)) if review_uuid else None
|
||||
if review and not review.is_editable_by(request.user):
|
||||
raise PermissionDenied()
|
||||
if request.method == 'GET':
|
||||
form = ReviewForm(instance=review) if review else ReviewForm(initial={'item': item.id})
|
||||
return render(request, 'review_edit.html', {'form': form, 'item': item})
|
||||
elif request.method == 'POST':
|
||||
form = ReviewForm(request.POST, instance=review) if review else ReviewForm(request.POST)
|
||||
if form.is_valid():
|
||||
if not review:
|
||||
form.instance.owner = request.user
|
||||
form.instance.edited_time = timezone.now()
|
||||
form.save()
|
||||
if form.cleaned_data['share_to_mastodon']:
|
||||
form.instance.save = lambda **args: None
|
||||
form.instance.shared_link = None
|
||||
if not share_review(form.instance):
|
||||
return go_relogin(request)
|
||||
return redirect(reverse("journal:review_retrieve", args=[form.instance.uuid]))
|
||||
else:
|
||||
return HttpResponseBadRequest(form.errors)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
def review_create(request):
|
||||
pass
|
||||
@login_required
|
||||
def review_delete(request, review_uuid):
|
||||
review = get_object_or_404(Review, uid=base62.decode(review_uuid))
|
||||
if not review.is_editable_by(request.user):
|
||||
raise PermissionDenied()
|
||||
if request.method == 'GET':
|
||||
review_form = ReviewForm(instance=review)
|
||||
return render(request, 'review_delete.html', {'form': review_form, 'review': review})
|
||||
elif request.method == 'POST':
|
||||
item = review.item
|
||||
print(review)
|
||||
review.delete()
|
||||
return redirect(item.url)
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
def mark_list(request, shelf_type, item_category):
|
||||
|
@ -133,5 +178,6 @@ def collection_list(request):
|
|||
pass
|
||||
|
||||
|
||||
@login_required
|
||||
def liked_list(request):
|
||||
pass
|
||||
|
|
|
@ -13,29 +13,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ site_name }}</title>
|
||||
|
||||
{% include "partial/_common_libs.html" with jquery=1 %}
|
||||
|
||||
<script src="{% static 'lib/js/rating-star.js' %}"></script>
|
||||
<script>
|
||||
$(document).ready( function() {
|
||||
let render = function() {
|
||||
let ratingLabels = $(".rating-star");
|
||||
$(ratingLabels).each( function(index, value) {
|
||||
let ratingScore = $(this).data("rating-score") / 2;
|
||||
$(this).starRating({
|
||||
initialRating: ratingScore,
|
||||
readOnly: true,
|
||||
starSize: 16,
|
||||
});
|
||||
});
|
||||
};
|
||||
document.body.addEventListener('htmx:load', function(evt) {
|
||||
render();
|
||||
});
|
||||
render();
|
||||
});
|
||||
</script>
|
||||
{% include "common_libs.html" with jquery=1 %}
|
||||
<script src="{% static 'js/mastodon.js' %}"></script>
|
||||
<script src="{% static 'js/home.js' %}"></script>
|
||||
</head>
|
||||
|
|
Loading…
Add table
Reference in a new issue