add calendar grid view
This commit is contained in:
parent
c796c462a1
commit
cbb36c3ea4
17 changed files with 431 additions and 107 deletions
|
@ -116,7 +116,7 @@
|
|||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
|
||||
{% if unread_announcements %}
|
||||
{% if request.user.unread_announcements %}
|
||||
{% include "partial/_announcement.html" %}
|
||||
{% endif %}
|
||||
</body>
|
||||
|
|
|
@ -306,19 +306,9 @@ def discover(request):
|
|||
if user.is_authenticated:
|
||||
layout = user.get_preference().discover_layout
|
||||
top_tags = user.tag_manager.all_tags[:10]
|
||||
unread_announcements = Announcement.objects.filter(
|
||||
pk__gt=request.user.read_announcement_index
|
||||
).order_by("-pk")
|
||||
try:
|
||||
user.read_announcement_index = Announcement.objects.latest("pk").pk
|
||||
user.save(update_fields=["read_announcement_index"])
|
||||
except ObjectDoesNotExist:
|
||||
# when there is no annoucenment
|
||||
pass
|
||||
else:
|
||||
layout = []
|
||||
top_tags = []
|
||||
unread_announcements = []
|
||||
|
||||
cache_key = "public_gallery_list"
|
||||
gallery_list = cache.get(cache_key, [])
|
||||
|
@ -374,6 +364,5 @@ def discover(request):
|
|||
"top_tags": top_tags,
|
||||
"gallery_list": gallery_list,
|
||||
"layout": layout,
|
||||
"unread_announcements": unread_announcements,
|
||||
},
|
||||
)
|
||||
|
|
31
common/static/lib/css/calendar_yearview_blocks.css
Normal file
31
common/static/lib/css/calendar_yearview_blocks.css
Normal file
|
@ -0,0 +1,31 @@
|
|||
.svg-tip {
|
||||
padding: 10px;
|
||||
background: #191919;
|
||||
opacity: 0.8;
|
||||
color: #eee;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
z-index: 99999;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.svg-tip:after {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
height: 5px;
|
||||
width: 5px;
|
||||
bottom: -10px;
|
||||
margin: 0 0 0 -5px;
|
||||
content: " ";
|
||||
border: 5px solid transparent;
|
||||
border-top-color: rgba(0,0,0,0.8);
|
||||
}
|
||||
|
||||
.wday, .month {
|
||||
font-variant: small-caps;
|
||||
color: #222222;
|
||||
font-size: 12px;
|
||||
}
|
246
common/static/lib/js/calendar_yearview_blocks.js
Normal file
246
common/static/lib/js/calendar_yearview_blocks.js
Normal file
|
@ -0,0 +1,246 @@
|
|||
(function ($) {
|
||||
|
||||
$.fn.calendar_yearview_blocks = function (options) {
|
||||
|
||||
// Format string
|
||||
if (!String.prototype.formatString) {
|
||||
String.prototype.formatString = function () {
|
||||
var args = arguments;
|
||||
return this.replace(/{(\d+)}/g, function (match, number) {
|
||||
return typeof args[number] !== 'undefined'
|
||||
? args[number]
|
||||
: match
|
||||
;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// If the number less than 10, add a zero before it
|
||||
var prettyNumber = function (number) {
|
||||
return number < 10 ? '0' + number.toString() : number = number.toString();
|
||||
};
|
||||
|
||||
var getDisplayDate = function (date_obj) {
|
||||
var pretty_month = prettyNumber(date_obj.getMonth() + 1);
|
||||
var pretty_date = prettyNumber(date_obj.getDate());
|
||||
return "{0}-{1}-{2}".formatString(date_obj.getFullYear(), pretty_month, pretty_date);
|
||||
};
|
||||
|
||||
var start = function () {
|
||||
obj_timestamp = JSON.parse(settings.data);
|
||||
|
||||
var wrap_chart = _this;
|
||||
|
||||
var end_date = new Date(settings.final_date);
|
||||
end_date.setDate(end_date.getDate()+1);
|
||||
var current_date = new Date();
|
||||
var start_date = new Date();
|
||||
start_date.setMonth(end_date.getMonth() - 12);
|
||||
|
||||
var start_weekday = settings.start_monday === true?1:0;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var day = start_date.getDay();
|
||||
if (day === start_weekday) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// Loop until start_weekday
|
||||
start_date.setDate(start_date.getDate() + 1);
|
||||
}
|
||||
}
|
||||
var loop_html = "";
|
||||
|
||||
// One year has 52 weeks
|
||||
var step = 13; // Amount of pixels to move
|
||||
|
||||
var month_position = [];
|
||||
month_position.push({month_index: start_date.getMonth(), x: 0});
|
||||
var using_month = start_date.getMonth();
|
||||
for (var i = 0; i <= 52; i++) { // For each week, generate a column
|
||||
var g_x = i * step;
|
||||
var item_html = '<g transform="translate(' + g_x.toString() + ',0)">';
|
||||
|
||||
for (var j = 0; j < 7; j++) { // For each weekday, generate a row
|
||||
|
||||
if (start_date > end_date) {
|
||||
// Break the loop when today's date is found
|
||||
break;
|
||||
}
|
||||
var y = j * step;
|
||||
|
||||
var month_in_day = start_date.getMonth();
|
||||
var data_date = getDisplayDate(start_date);
|
||||
|
||||
// Check first day in week
|
||||
if (j === start_weekday && month_in_day !== using_month) {
|
||||
using_month = month_in_day;
|
||||
month_position.push({month_index: using_month, x: g_x});
|
||||
}
|
||||
|
||||
// Put a box around today's date
|
||||
if (settings.stylize_today) {
|
||||
var match_today = current_date.getTime() === start_date.getTime() ? '" style="stroke:black;stroke-width:2;opacity:0.5"' : '';
|
||||
} else {
|
||||
var match_today = "";
|
||||
}
|
||||
|
||||
var items = [];
|
||||
var legend = '', items_str = '';
|
||||
if (obj_timestamp[data_date]) {
|
||||
if (obj_timestamp[data_date].items) {
|
||||
items = obj_timestamp[data_date].items;
|
||||
items_str = items.join(", ")
|
||||
items_str = items_str.replaceAll('&', '&');
|
||||
items_str = items_str.replaceAll('"', '"');
|
||||
}
|
||||
if (obj_timestamp[data_date].legend) {
|
||||
legend = obj_timestamp[data_date].legend;
|
||||
legend = legend.replaceAll('&', '&');
|
||||
legend = legend.replaceAll('"', '"');
|
||||
}
|
||||
}
|
||||
|
||||
var item_name = items[0]?items[0]:false;
|
||||
var color = settings.colors[item_name]?settings.colors[item_name]:settings.colors['default'];
|
||||
|
||||
// Fill a square for the 1st item
|
||||
item_html += '<rect class="day" width="11" height="11" y="' + y + '" fill="' + color + match_today + '" data-items="' + items_str + '" data-legend="' + legend + '" data-date="' + data_date + '"/>';
|
||||
if (items.length === 2) { // Fill a trangle for the 2nd
|
||||
var item_name_1 = items[1]?items[1]:false;
|
||||
var color_1 = settings.colors[item_name_1]?settings.colors[item_name_1]:settings.colors['default'];
|
||||
item_html += '<polygon points="' + 0 + ',' + (y+11) + ' ' + 0 + ',' + y + ' ' + 11 + ',' + y + '" fill="' + color_1 + '" data-items="' + items_str + '" data-legend="' + legend + '" data-date="' + data_date + '"/>';
|
||||
} else if (items.length === 3) { // Fill 2 rectangles for 2nd and 3rd
|
||||
var item_name_1 = items[1]?items[1]:false;
|
||||
var color_1 = settings.colors[item_name_1]?settings.colors[item_name_1]:settings.colors['default'];
|
||||
var item_name_2 = items[2]?items[2]:false;
|
||||
var color_2 = settings.colors[item_name_2]?settings.colors[item_name_2]:settings.colors['default'];
|
||||
item_html += '<polygon points="' + 0 + ',' + (y+8) + ' ' + 0 + ',' + y + ' ' + 11 + ',' + y + ' ' + 11 + ',' + (y+8) + '" fill="' + color_1 + '" data-items="' + items_str + '" data-legend="' + legend + '" data-date="' + data_date + '"/>';
|
||||
item_html += '<polygon points="' + 0 + ',' + (y+4) + ' ' + 0 + ',' + y + ' ' + 11 + ',' + y + ' ' + 11 + ',' + (y+4) + '" fill="' + color_2 + '" data-items="' + items_str + '" data-legend="' + legend + '" data-date="' + data_date + '"/>';
|
||||
} else if (items.length === 4) { // Fill 3 cubes for 2nd, 3rd and 4th
|
||||
var item_name_1 = items[1]?items[1]:false;
|
||||
var color_1 = settings.colors[item_name_1]?settings.colors[item_name_1]:settings.colors['default'];
|
||||
var item_name_2 = items[2]?items[2]:false;
|
||||
var color_2 = settings.colors[item_name_2]?settings.colors[item_name_2]:settings.colors['default'];
|
||||
var item_name_3 = items[3]?items[3]:false;
|
||||
var color_3 = settings.colors[item_name_3]?settings.colors[item_name_3]:settings.colors['default'];
|
||||
item_html += '<polygon points="' + 0 + ',' + (y+11) + ' ' + 0 + ',' + (y+6) + ' ' + 6 + ',' + (y+6) + ' ' + 6 + ',' + (y+11) + '" fill="' + color_1 + '" data-items="' + items_str + '" data-legend="' + legend + '" data-date="' + data_date + '"/>';
|
||||
item_html += '<polygon points="' + 0 + ',' + (y+6) + ' ' + 0 + ',' + y + ' ' + 6 + ',' + y + ' ' + 6 + ',' + (y+6) + '" fill="' + color_2 + '" data-items="' + items_str + '" data-legend="' + legend + '" data-date="' + data_date + '"/>';
|
||||
item_html += '<polygon points="' + 6 + ',' + (y+6) + ' ' + 6 + ',' + y + ' ' + 11 + ',' + y + ' ' + 11 + ',' + (y+6) + '" fill="' + color_3 + '" data-items="' + items_str + '" data-legend="' + legend + '" data-date="' + data_date + '"/>';
|
||||
}
|
||||
|
||||
// Move on to the next day
|
||||
start_date.setDate(start_date.getDate() + 1);
|
||||
|
||||
}
|
||||
|
||||
item_html += "</g>";
|
||||
|
||||
loop_html += item_html;
|
||||
|
||||
}
|
||||
|
||||
// Trick
|
||||
if (month_position[1].x - month_position[0].x < 40) {
|
||||
// Fix ugly graph by removing the first item
|
||||
month_position.shift(0);
|
||||
}
|
||||
|
||||
// Add labels for Months
|
||||
for (var i = 0; i < month_position.length; i++) {
|
||||
var item = month_position[i];
|
||||
var month_name = settings.month_names[item.month_index];
|
||||
loop_html += '<text x="' + item.x + '" y="-5" class="month">' + month_name + '</text>';
|
||||
}
|
||||
|
||||
// Add labels for Weekdays
|
||||
if (settings.start_monday === true) {
|
||||
loop_html += '<text text-anchor="middle" class="wday" dx="-12" dy="11">{0}</text>'.formatString(settings.day_names[0]) +
|
||||
'<text text-anchor="middle" class="wday" dx="-12" dy="36">{0}</text>'.formatString(settings.day_names[1]) +
|
||||
'<text text-anchor="middle" class="wday" dx="-12" dy="61">{0}</text>'.formatString(settings.day_names[2]) +
|
||||
'<text text-anchor="middle" class="wday" dx="-12" dy="86">{0}</text>'.formatString(settings.day_names[3]);
|
||||
} else {
|
||||
loop_html += '<text text-anchor="middle" class="wday" dx="-10" dy="22">{0}</text>'.formatString(settings.day_names[0]) +
|
||||
'<text text-anchor="middle" class="wday" dx="-10" dy="48">{0}</text>'.formatString(settings.day_names[1]) +
|
||||
'<text text-anchor="middle" class="wday" dx="-10" dy="74">{0}</text>'.formatString(settings.day_names[2]);
|
||||
}
|
||||
|
||||
// Fixed size with width= 721 and height = 110
|
||||
var wire_html =
|
||||
'<svg width="721" height="110">' +
|
||||
'<g transform="translate(25, 20)">' +
|
||||
loop_html +
|
||||
'</g>' + '"Your browser does not support inline SVG."' +
|
||||
'</svg>';
|
||||
|
||||
wrap_chart.html(wire_html);
|
||||
|
||||
_this.find('rect, polygon').on("mouseenter", mouseEnter);
|
||||
_this.find('rect, polygon').on("mouseleave", mouseLeave);
|
||||
_this.find('rect, polygon').on("click", mouseClick);
|
||||
appendTooltip();
|
||||
|
||||
};
|
||||
|
||||
var mouseLeave = function (evt) {
|
||||
$('.svg-tip').hide();
|
||||
};
|
||||
|
||||
var mouseClick = function (evt) {
|
||||
var items = $(evt.target).attr('data-items');
|
||||
var date = $(evt.target).attr('data-date');
|
||||
$("#moreinfo").text("On {0}, following items are seen: {1}".formatString(date, items))
|
||||
};
|
||||
|
||||
// Handle mouseEnter event when entering into rect element
|
||||
var mouseEnter = function (evt) {
|
||||
|
||||
var target_offset = $(evt.target).offset();
|
||||
var items = $(evt.target).attr('data-items');
|
||||
var legend = $(evt.target).attr('data-legend');
|
||||
var date = $(evt.target).attr('data-date');
|
||||
|
||||
var text = settings.tooltip_style === 'default' ? "{0}: <br />{1}".formatString(date, legend ? legend : items) : (legend ? legend : items);
|
||||
|
||||
// Depending on settings, only show a tooltip when there's something to be shown
|
||||
if (items.length >= 1 || settings.always_show_tooltip === true) {
|
||||
var svg_tip = $('.svg-tip').show();
|
||||
svg_tip.html(text);
|
||||
var svg_width = Math.round(svg_tip.width() / 2 + 5);
|
||||
var svg_height = svg_tip.height() * 2 + 10;
|
||||
|
||||
svg_tip.css({top: target_offset.top - svg_height - 5});
|
||||
svg_tip.css({left: target_offset.left - svg_width});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Append tooltip to display when the mouse enters the rect element
|
||||
// Default is display:none
|
||||
var appendTooltip = function () {
|
||||
if ($('.svg-tip').length === 0) {
|
||||
$('body').append('<div class="svg-tip svg-tip-one-line" style="display:none" ></div>');
|
||||
}
|
||||
};
|
||||
|
||||
// Default settings which can be overridden by the user
|
||||
var settings = $.extend({
|
||||
colors: {
|
||||
'default': '#eeeeee'
|
||||
},
|
||||
month_names: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
day_names: ['M', 'W', 'F', 'S'],
|
||||
start_monday: true,
|
||||
always_show_tooltip: false,
|
||||
stylize_today: true,
|
||||
final_date: new Date().toISOString().slice(0, 10),
|
||||
tooltip_style: 'default', // or 'custom'
|
||||
data: []
|
||||
}, options);
|
||||
|
||||
var _this = $(this);
|
||||
|
||||
start();
|
||||
|
||||
};
|
||||
|
||||
}(jQuery));
|
|
@ -36,7 +36,7 @@ $sub-section-title-margin: 8px
|
|||
$width: 130px
|
||||
min-width: $width
|
||||
max-width: $width
|
||||
|
||||
|
||||
& &__entity-text
|
||||
margin-left: 20px
|
||||
overflow: hidden
|
||||
|
@ -47,7 +47,7 @@ $sub-section-title-margin: 8px
|
|||
|
||||
& &__entity-link
|
||||
font-size: 1.2em
|
||||
|
||||
|
||||
& &__entity-title
|
||||
display: block
|
||||
|
||||
|
@ -55,7 +55,7 @@ $sub-section-title-margin: 8px
|
|||
color: $color-tertiary
|
||||
margin-left: 5px
|
||||
position: relative;
|
||||
top: -1px;
|
||||
top: -1px;
|
||||
|
||||
& &__entity-info
|
||||
max-width: 73%
|
||||
|
@ -107,7 +107,7 @@ $sub-section-title-margin: 8px
|
|||
object-fit: contain
|
||||
max-width: 150px
|
||||
object-position: top
|
||||
|
||||
|
||||
& &__img-origin
|
||||
cursor: zoom-in
|
||||
|
||||
|
@ -123,7 +123,7 @@ $sub-section-title-margin: 8px
|
|||
|
||||
& &__title--secondary
|
||||
color: $color-tertiary
|
||||
|
||||
|
||||
& &__fields
|
||||
display: inline-block
|
||||
vertical-align: top
|
||||
|
@ -192,7 +192,7 @@ $mark-review-padding-wider: 6px 0
|
|||
& &__more-link
|
||||
margin-left: 5px
|
||||
& &__mark-list
|
||||
|
||||
|
||||
& &__mark
|
||||
margin: 0
|
||||
padding: $mark-review-padding
|
||||
|
@ -218,7 +218,7 @@ $mark-review-padding-wider: 6px 0
|
|||
& &__empty
|
||||
|
||||
// includes reviews of an entity
|
||||
// sub section
|
||||
// sub section
|
||||
.entity-reviews
|
||||
// when used alone
|
||||
&:first-child
|
||||
|
@ -229,7 +229,7 @@ $mark-review-padding-wider: 6px 0
|
|||
& > a
|
||||
margin-right: 5px
|
||||
&--stand-alone
|
||||
margin-bottom: $section-title-margin
|
||||
margin-bottom: $section-title-margin
|
||||
& &__more-link
|
||||
margin-left: 5px
|
||||
& &__review-list
|
||||
|
@ -241,7 +241,7 @@ $mark-review-padding-wider: 6px 0
|
|||
&:last-child
|
||||
border: none
|
||||
&--wider
|
||||
padding: $mark-review-padding-wider
|
||||
padding: $mark-review-padding-wider
|
||||
|
||||
& &__review-time
|
||||
color: $color-light
|
||||
|
@ -250,7 +250,7 @@ $mark-review-padding-wider: 6px 0
|
|||
|
||||
& &__owner-link
|
||||
|
||||
& &__empty
|
||||
& &__empty
|
||||
|
||||
.dividing-line
|
||||
height: 0
|
||||
|
@ -312,7 +312,7 @@ $mark-review-padding-wider: 6px 0
|
|||
-webkit-line-clamp: 2
|
||||
|
||||
& &__empty
|
||||
|
||||
|
||||
// for drag and sort purpose
|
||||
&--placeholder
|
||||
border: dashed $color-tertiary 4px
|
||||
|
@ -328,7 +328,6 @@ $mark-review-padding-wider: 6px 0
|
|||
&--hidden
|
||||
opacity: 0.4
|
||||
|
||||
|
||||
.entity-sort-control
|
||||
display: flex
|
||||
justify-content: flex-end
|
||||
|
@ -367,7 +366,7 @@ $mark-review-padding-wider: 6px 0
|
|||
overflow: auto
|
||||
|
||||
& &__user-name
|
||||
|
||||
|
||||
& &__user-bio
|
||||
|
||||
& &__user-avatar
|
||||
|
@ -384,7 +383,7 @@ $mark-review-padding-wider: 6px 0
|
|||
margin-bottom: 10px
|
||||
&::after
|
||||
@include clear
|
||||
|
||||
|
||||
& &__info
|
||||
float: left
|
||||
|
||||
|
@ -400,7 +399,7 @@ $mark-review-padding-wider: 6px 0
|
|||
position: relative
|
||||
top: 3px
|
||||
left: -1px
|
||||
|
||||
|
||||
& &__actions
|
||||
float: right
|
||||
|
||||
|
@ -526,7 +525,7 @@ $mark-review-padding-wider: 6px 0
|
|||
|
||||
.entity-marks
|
||||
& &__mark-list
|
||||
|
||||
|
||||
& &__mark
|
||||
|
||||
& &__rating-star
|
||||
|
@ -550,7 +549,7 @@ $mark-review-padding-wider: 6px 0
|
|||
float: unset
|
||||
& &__actions
|
||||
float: unset
|
||||
|
||||
|
||||
.track-carousel
|
||||
&__content
|
||||
padding-bottom: 10px
|
||||
|
@ -560,18 +559,23 @@ $mark-review-padding-wider: 6px 0
|
|||
margin-right: 4.5%
|
||||
|
||||
|
||||
.calendar_view
|
||||
left: -20px
|
||||
position: relative
|
||||
|
||||
// Medium devices (tablets, 768px and up)
|
||||
@media (max-width: $medium-devices)
|
||||
pass
|
||||
.calendar_view
|
||||
overflow-x: scroll
|
||||
// Large devices (desktops, 992px and up)
|
||||
@media (max-width: $large-devices)
|
||||
.main-section-wrapper
|
||||
padding: $main-section-padding-mobile
|
||||
|
||||
|
||||
.entity-detail
|
||||
display: flex
|
||||
& &__info
|
||||
// display: flex
|
||||
// Extra large devices (large desktops, 1200px and up)
|
||||
@media (max-width: $x-large-devices)
|
||||
pass
|
||||
pass
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
<div class="announcement-modal__body">
|
||||
<ul>
|
||||
{% for ann in unread_announcements %}
|
||||
{% for ann in request.user.unread_announcements %}
|
||||
<li class="announcement">
|
||||
<a href="{% url 'management:retrieve' ann.pk %}">
|
||||
<h5 class="announcement__title">{{ ann.title }}</h5>
|
||||
|
|
|
@ -124,32 +124,6 @@
|
|||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="aside-section-wrapper aside-section-wrapper--transparent aside-section-wrapper--collapse">
|
||||
{% if request.user.is_staff and request.user == user%}
|
||||
<div class="report-panel">
|
||||
<h5 class="report-panel__label">{% trans '投诉信息' %}</h5>
|
||||
<a class="report-panel__all-link"
|
||||
href="{% url 'users:manage_report' %}">全部投诉</a>
|
||||
<div class="report-panel__body">
|
||||
<ul class="report-panel__report-list">
|
||||
{% for report in reports %}
|
||||
<li class="report-panel__report">
|
||||
<a href="{% url 'journal:user_profile' report.submit_user.mastodon_username %}"
|
||||
class="report-panel__user-link">{{ report.submit_user }}</a>{% trans '已投诉' %}<a
|
||||
href="{% url 'journal:user_profile' report.reported_user.mastodon_username %}"
|
||||
class="report-panel__user-link">{{ report.reported_user }}</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<div>{% trans '暂无新投诉' %}</div>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.db import models
|
||||
from django.db.models.fields import related
|
||||
from polymorphic.models import PolymorphicModel
|
||||
from users.models import User
|
||||
from catalog.common.models import Item, ItemCategory
|
||||
|
@ -24,8 +23,7 @@ from catalog.models import *
|
|||
from django.contrib.contenttypes.models import ContentType
|
||||
from .renderers import render_md, render_text
|
||||
from catalog.common import jsondata
|
||||
|
||||
from journal import renderers
|
||||
from django.db import connection
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -47,6 +45,17 @@ def q_visible_to(viewer, owner):
|
|||
return Q(visibility=0)
|
||||
|
||||
|
||||
def max_visiblity_to(viewer, owner):
|
||||
if viewer == owner:
|
||||
return 2
|
||||
# elif viewer.is_blocked_by(owner):
|
||||
# return Q(pk__in=[])
|
||||
elif viewer.is_authenticated and viewer.is_following(owner):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def query_visible(user):
|
||||
return (
|
||||
Q(visibility=0)
|
||||
|
@ -691,6 +700,31 @@ class ShelfManager:
|
|||
def get_manager_for_user(user):
|
||||
return ShelfManager(user)
|
||||
|
||||
def get_calendar_data(self, max_visiblity):
|
||||
shelf_id = self.get_shelf(ShelfType.COMPLETE).pk
|
||||
timezone_offset = timezone.now().strftime("%z")
|
||||
calendar_data = {}
|
||||
sql = "SELECT DATE_TRUNC('day', journal_shelfmember.created_time AT TIME ZONE %s) date, django_content_type.model type, COUNT(1) count FROM journal_shelfmember, catalog_item, django_content_type WHERE journal_shelfmember.item_id = catalog_item.id AND django_content_type.id = catalog_item.polymorphic_ctype_id AND parent_id = %s AND journal_shelfmember.created_time >= NOW() - INTERVAL '366 days' AND journal_shelfmember.visibility <= %s GROUP BY item_id, date, type;"
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql, [timezone_offset, shelf_id, max_visiblity])
|
||||
data = cursor.fetchall()
|
||||
for line in data:
|
||||
date = line[0].strftime("%Y-%m-%d")
|
||||
typ = line[1]
|
||||
if date not in calendar_data:
|
||||
calendar_data[date] = {"items": []}
|
||||
if typ[:2] == "tv":
|
||||
typ = "movie"
|
||||
elif typ == "album":
|
||||
typ = "music"
|
||||
elif typ == "edition":
|
||||
typ = "book"
|
||||
elif typ not in ["book", "movie", "music", "game"]:
|
||||
typ = "other"
|
||||
if typ not in calendar_data[date]["items"]:
|
||||
calendar_data[date]["items"].append(typ)
|
||||
return calendar_data
|
||||
|
||||
|
||||
User.shelf_manager = cached_property(ShelfManager.get_manager_for_user)
|
||||
User.shelf_manager.__set_name__(User, "shelf_manager")
|
||||
|
|
1
journal/templates/calendar_data.html
Normal file
1
journal/templates/calendar_data.html
Normal file
|
@ -0,0 +1 @@
|
|||
{{ calendar_data|json_script:"calendar_data" }}
|
|
@ -18,9 +18,11 @@
|
|||
{% endif %}
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ site_name }} - {{ user.mastodon_username }}的评论" href="{{ request.build_absolute_uri }}feed/reviews/">
|
||||
|
||||
{% include "common_libs.html" with jquery=0 %}
|
||||
{% include "common_libs.html" with jquery=1 %}
|
||||
<script src="{% static 'js/mastodon.js' %}" defer></script>
|
||||
<script src="{% static 'js/home.js' %}" defer></script>
|
||||
<script src="{% static 'lib/js/calendar_yearview_blocks.js' %}" defer></script>
|
||||
<link href="{% static 'lib/css/calendar_yearview_blocks.css' %}" media="all" rel="stylesheet"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -34,6 +36,38 @@
|
|||
|
||||
<div class="main-section-wrapper sortable">
|
||||
|
||||
{% if request.user.is_staff %}
|
||||
<div class="entity-sort" id="calendar_grid">
|
||||
<h5 class="entity-sort__label">书影音日历</h5>
|
||||
<div class="calendar_view" hx-get="{% url 'journal:user_calendar_data' user.mastodon_username %}" hx-trigger="load" hx-swap="innerHTML">
|
||||
<p style="text-align: center;">
|
||||
<i class="fas fa-fan fa-spin"></i>
|
||||
</p>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.body.addEventListener('htmx:afterSettle', function(evt) {
|
||||
if ($(evt.detail.elt).hasClass('calendar_view')) {
|
||||
$(evt.detail.elt).calendar_yearview_blocks({
|
||||
data: $(evt.detail.elt).text(),
|
||||
start_monday: false,
|
||||
always_show_tooltip: false,
|
||||
month_names: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
day_names: ['', '', ''],
|
||||
colors: {
|
||||
'default': '#eeeeee', // Default color
|
||||
'book': '#B4D2A5',
|
||||
'movie': '#7CBDFE',
|
||||
'music': '#FEA36D',
|
||||
'game': '#C5A290',
|
||||
'other': '#9D6AB0'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for category, category_shelves in shelf_list.items %}
|
||||
{% for shelf_type, shelf in category_shelves.items %}
|
||||
|
||||
|
@ -189,7 +223,7 @@
|
|||
{% include "partial/_footer.html" %}
|
||||
</div>
|
||||
|
||||
{% if unread_announcements %}
|
||||
{% if request.user.unread_announcements %}
|
||||
{% include "partial/_announcement.html" %}
|
||||
{% endif %}
|
||||
</body>
|
||||
|
|
|
@ -123,5 +123,10 @@ urlpatterns = [
|
|||
name="user_tag_list",
|
||||
),
|
||||
re_path(r"^users/(?P<user_name>[A-Za-z0-9_\-.@]+)/$", profile, name="user_profile"),
|
||||
re_path(
|
||||
r"^users/(?P<user_name>[A-Za-z0-9_\-.@]+)/calendar_data$",
|
||||
user_calendar_data,
|
||||
name="user_calendar_data",
|
||||
),
|
||||
path("users/<str:id>/feed/reviews/", ReviewFeed(), name="review_feed"),
|
||||
]
|
||||
|
|
|
@ -787,24 +787,10 @@ def profile(request, user_name):
|
|||
if not request.user.is_authenticated and user.get_preference().no_anonymous_view:
|
||||
return profile_anonymous(request, user_name)
|
||||
# access one's own home page
|
||||
if user == request.user:
|
||||
reports = Report.objects.order_by("-submitted_time").filter(is_read=False)
|
||||
unread_announcements = Announcement.objects.filter(
|
||||
pk__gt=request.user.read_announcement_index
|
||||
).order_by("-pk")
|
||||
try:
|
||||
request.user.read_announcement_index = Announcement.objects.latest("pk").pk
|
||||
request.user.save(update_fields=["read_announcement_index"])
|
||||
except ObjectDoesNotExist:
|
||||
# when there is no annoucenment
|
||||
pass
|
||||
# visit other's home page
|
||||
else:
|
||||
if user.is_blocked_by(request.user) or user.is_blocking(request.user):
|
||||
return render_user_blocked(request)
|
||||
# no these value on other's home page
|
||||
reports = None
|
||||
unread_announcements = None
|
||||
if user != request.user and (
|
||||
user.is_blocked_by(request.user) or user.is_blocking(request.user)
|
||||
):
|
||||
return render_user_blocked(request)
|
||||
|
||||
qv = q_visible_to(request.user, user)
|
||||
shelf_list = {}
|
||||
|
@ -866,7 +852,22 @@ def profile(request, user_name):
|
|||
],
|
||||
"liked_collections_count": liked_collections.count(),
|
||||
"layout": user.get_preference().profile_layout,
|
||||
"reports": reports,
|
||||
"unread_announcements": unread_announcements,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def user_calendar_data(request, user_name):
|
||||
if request.method != "GET":
|
||||
raise BadRequest()
|
||||
user = User.get(user_name)
|
||||
if user is None or not request.user.is_authenticated:
|
||||
return HttpResponse("")
|
||||
max_visiblity = max_visiblity_to(request.user, user)
|
||||
calendar_data = user.shelf_manager.get_calendar_data(max_visiblity)
|
||||
return render(
|
||||
request,
|
||||
"calendar_data.html",
|
||||
{
|
||||
"calendar_data": calendar_data,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
|
||||
{% include "common_libs.html" with jquery=0 %}
|
||||
<title>{{ site_name }} - {{ object.title }}</title>
|
||||
</head>
|
||||
|
||||
|
@ -44,4 +43,4 @@
|
|||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdn.staticfile.org/jquery/3.6.1/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="{% static 'css/boofilsic.min.css' %}">
|
||||
{% include "common_libs.html" with jquery=0 %}
|
||||
|
||||
<title>{{ site_name }} - {% trans '公告栏' %}</title>
|
||||
</head>
|
||||
|
@ -58,10 +57,10 @@
|
|||
<a href="{% url 'management:delete' announcement.pk %}">{% trans '删除' %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<p>{{ announcement.get_plain_content }}</p>
|
||||
</li>
|
||||
|
||||
|
||||
{% if not forloop.last %}
|
||||
<div class="dividing-line"></div>
|
||||
{% endif %}
|
||||
|
@ -71,16 +70,16 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
<!-- <div class="pagination">
|
||||
|
||||
|
||||
{% if object_list.pagination.has_prev %}
|
||||
<a href="?page=1&q={% if request.GET.q %}{{ request.GET.q }}{% endif %}"
|
||||
class="pagination__nav-link pagination__nav-link">«</a>
|
||||
<a href="?page={{ object_list.previous_page_number }}&q={% if request.GET.q %}{{ request.GET.q }}{% endif %}"
|
||||
class="pagination__nav-link pagination__nav-link--right-margin pagination__nav-link">‹</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% for page in object_list.pagination.page_range %}
|
||||
|
||||
|
||||
{% if page == object_list.pagination.current_page %}
|
||||
<a href="?page={{ page }}&q={% if request.GET.q %}{{ request.GET.q }}{% endif %}"
|
||||
class="pagination__page-link pagination__page-link--current">{{ page }}</a>
|
||||
|
@ -88,16 +87,16 @@
|
|||
<a href="?page={{ page }}&q={% if request.GET.q %}{{ request.GET.q }}{% endif %}"
|
||||
class="pagination__page-link">{{ page }}</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% if object_list.pagination.has_next %}
|
||||
<a href="?page={{ object_list.next_page_number }}&q={% if request.GET.q %}{{ request.GET.q }}{% endif %}"
|
||||
class="pagination__nav-link pagination__nav-link--left-margin">›</a>
|
||||
<a href="?page={{ object_list.pagination.last_page }}&q={% if request.GET.q %}{{ request.GET.q }}{% endif %}"
|
||||
class="pagination__nav-link">»</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
})
|
||||
</script>
|
||||
|
||||
{% if unread_announcements %}
|
||||
{% if request.user.unread_announcements %}
|
||||
{% include "partial/_announcement.html" %}
|
||||
{% endif %}
|
||||
</body>
|
||||
|
|
|
@ -17,19 +17,11 @@ PAGE_SIZE = 10
|
|||
def feed(request):
|
||||
if request.method != "GET":
|
||||
raise BadRequest()
|
||||
user = request.user
|
||||
unread = Announcement.objects.filter(pk__gt=user.read_announcement_index).order_by(
|
||||
"-pk"
|
||||
)
|
||||
if unread:
|
||||
user.read_announcement_index = Announcement.objects.latest("pk").pk
|
||||
user.save(update_fields=["read_announcement_index"])
|
||||
return render(
|
||||
request,
|
||||
"feed.html",
|
||||
{
|
||||
"top_tags": user.tag_manager.all_tags[:10],
|
||||
"unread_announcements": unread,
|
||||
"top_tags": request.user.tag_manager.all_tags[:10],
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import uuid
|
||||
import django.contrib.postgres.fields as postgres
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.utils import timezone
|
||||
|
@ -7,6 +8,7 @@ from django.core.serializers.json import DjangoJSONEncoder
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from common.utils import GenerateDateUUIDMediaFilePath
|
||||
from django.conf import settings
|
||||
from management.models import Announcement
|
||||
from mastodon.api import *
|
||||
from django.urls import reverse
|
||||
|
||||
|
@ -186,6 +188,19 @@ class User(AbstractUser):
|
|||
else:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def unread_announcements(self):
|
||||
unread_announcements = Announcement.objects.filter(
|
||||
pk__gt=self.read_announcement_index
|
||||
).order_by("-pk")
|
||||
try:
|
||||
self.read_announcement_index = Announcement.objects.latest("pk").pk
|
||||
self.save(update_fields=["read_announcement_index"])
|
||||
except ObjectDoesNotExist:
|
||||
# when there is no annoucenment
|
||||
pass
|
||||
return unread_announcements
|
||||
|
||||
@classmethod
|
||||
def get(cls, id):
|
||||
if isinstance(id, str):
|
||||
|
|
Loading…
Add table
Reference in a new issue