Showing up link type

This commit is contained in:
2024-10-05 18:13:05 +10:00
parent 60bfcf2017
commit a4357ac92a
6 changed files with 240 additions and 86 deletions
+1
View File
@@ -64,6 +64,7 @@ coverage.xml
3.12/lib/python3.12/site-packages/
3.12/include
3.12/bin
# Tailwind CSS
/static/css/dist/
BIN
View File
Binary file not shown.
+171 -84
View File
@@ -1,103 +1,190 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block content %}
<div class="max-w-4xl mx-auto mt-8">
<div class="bg-white shadow-md rounded-lg overflow-hidden">
<div class="px-6 py-4 bg-gray-50 border-b border-gray-200 flex justify-between items-center">
<h1 class="text-2xl font-bold text-gray-800">{% trans "Link Details" %}</h1>
<a href="{% url 'link_update' link.pk %}" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">
<div class="max-w-4xl mx-auto mt-8 px-4 sm:px-6 lg:px-8">
<div class="bg-white shadow-md rounded-lg overflow-hidden mb-8">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200 flex items-center justify-between">
<h1 class="text-2xl font-bold text-gray-900 flex items-center">
{% trans "Link Details" %}
{% if link.link_type == 'LINK' %}
<span class="ml-2 bg-blue-100 text-blue-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded-full flex items-center">
<i class="fas fa-link mr-1"></i>{% trans "Link" %}
</span>
{% else %}
<span class="ml-2 bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded-full flex items-center">
<i class="fas fa-code mr-1"></i>{% trans "Custom" %}
</span>
{% endif %}
</h1>
<a href="{% url 'link_update' link.pk %}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
{% trans "Edit" %}
</a>
</div>
<div class="px-4 py-5 sm:p-6">
<dl class="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">{% trans "Alias" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.alias }}</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">{% trans "Click Count" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.click_count }}</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">{% trans "Created At" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.created_at|date:"Y-m-d H:i" }}</dd>
</div>
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">{% trans "Updated At" %}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ link.updated_at|date:"Y-m-d H:i" }}</dd>
</div>
{% if link.link_type == 'LINK' %}
<div class="sm:col-span-2">
<dt class="text-sm font-medium text-gray-500">{% trans "Original URL" %}</dt>
<dd class="mt-1 text-sm text-gray-900">
<a href="{{ link.original_url }}" target="_blank" class="text-blue-600 hover:underline">{{ link.original_url }}</a>
</dd>
</div>
{% endif %}
</dl>
</div>
</div>
<div class="p-6">
<h2 class="text-xl font-semibold text-gray-700 mb-4">{{ link.alias }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<p class="text-sm text-gray-600 mb-2">{% trans "Original URL" %}:</p>
<a href="{{ link.original_url }}" target="_blank" class="text-blue-600 hover:text-blue-800 break-all">{{ link.original_url }}</a>
</div>
<div>
<p class="text-sm text-gray-600 mb-2">{% trans "Total Clicks" %}:</p>
<p class="text-lg font-semibold">{{ link.click_count }}</p>
</div>
<div>
<p class="text-sm text-gray-600 mb-2">{% trans "Created At" %}:</p>
<p class="utc-time" data-utc="{{ link.created_at|date:'c' }}">{{ link.created_at|date:"Y-m-d H:i" }}</p>
</div>
<div>
<p class="text-sm text-gray-600 mb-2">{% trans "Last Updated" %}:</p>
<p class="utc-time" data-utc="{{ link.updated_at|date:'c' }}">{{ link.updated_at|date:"Y-m-d H:i" }}</p>
</div>
</div>
</div>
</div>
<div class="mt-8 bg-white shadow-md rounded-lg overflow-hidden">
<div class="px-6 py-4 bg-gray-50 border-b border-gray-200">
<h2 class="text-xl font-bold text-gray-800">{% trans "Click Statistics" %}</h2>
{% if link.link_type == 'CUSTOM' %}
<div class="bg-white shadow-md rounded-lg overflow-hidden mb-8">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h2 class="text-xl font-bold text-gray-900">{% trans "Custom Content" %}</h2>
</div>
<div class="px-4 py-5 sm:p-6">
<div class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl max-w-none">
{{ rendered_text|safe }}
</div>
</div>
</div>
<div class="p-6">
{% if click_stats != '[]' %}
<div class="w-full h-64">
<canvas id="clickChart"></canvas>
{% endif %}
<div class="bg-white shadow-md rounded-lg overflow-hidden">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h2 class="text-xl font-bold text-gray-900">{% trans "Click Statistics" %}</h2>
</div>
<div class="px-4 py-5 sm:p-6">
<canvas id="clickChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
var ctx = document.getElementById('clickChart').getContext('2d');
var clickData = JSON.parse('{{ click_stats|safe }}');
var labels = clickData.map(item => item.date);
var data = clickData.map(item => item.count);
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: '{% trans "Clicks" %}',
data: data,
borderColor: 'rgb(59, 130, 246)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.1,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
</script>
{% else %}
<p class="text-gray-600 italic">{% trans "No click data available yet." %}</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const utcTimes = document.querySelectorAll('.utc-time');
utcTimes.forEach(function(element) {
const utcTime = new Date(element.dataset.utc);
const localTime = utcTime.toLocaleString();
element.textContent = localTime;
document.addEventListener('DOMContentLoaded', function() {
var ctx = document.getElementById('clickChart').getContext('2d');
var clickData = {{ click_stats|safe }};
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: clickData.map(item => item.date),
datasets: [{
label: '{% trans "Clicks" %}',
data: clickData.map(item => item.count),
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '{% trans "Number of Clicks" %}'
}
},
x: {
title: {
display: true,
text: '{% trans "Date" %}'
}
}
}
}
});
});
});
</script>
{% endblock %}
{% block extra_css %}
<style>
.prose {
color: #374151;
max-width: 65ch;
}
.prose p {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
.prose a {
color: #2563eb;
text-decoration: underline;
}
.prose strong {
font-weight: 600;
}
.prose ul {
margin-top: 1.25em;
margin-bottom: 1.25em;
list-style-type: disc;
padding-left: 1.625em;
}
.prose ol {
margin-top: 1.25em;
margin-bottom: 1.25em;
list-style-type: decimal;
padding-left: 1.625em;
}
.prose h1 {
font-size: 2.25em;
margin-top: 0;
margin-bottom: 0.8888889em;
line-height: 1.1111111;
}
.prose h2 {
font-size: 1.5em;
margin-top: 2em;
margin-bottom: 1em;
line-height: 1.3333333;
}
.prose h3 {
font-size: 1.25em;
margin-top: 1.6em;
margin-bottom: 0.6em;
line-height: 1.6;
}
.prose img {
margin-top: 2em;
margin-bottom: 2em;
}
.prose code {
color: #111827;
font-weight: 600;
}
.prose pre {
color: #e5e7eb;
background-color: #1f2937;
overflow-x: auto;
font-size: 0.875em;
line-height: 1.7142857;
margin-top: 1.7142857em;
margin-bottom: 1.7142857em;
border-radius: 0.375rem;
padding-top: 0.8571429em;
padding-right: 1.1428571em;
padding-bottom: 0.8571429em;
padding-left: 1.1428571em;
}
</style>
{% endblock %}
+7 -1
View File
@@ -16,6 +16,7 @@ from django.http import JsonResponse, HttpResponse
from django.core.serializers import serialize
import random
from django.utils.text import slugify
import markdown
class LinkListView(ListView):
model = Link
@@ -161,12 +162,17 @@ class LinkDetailView(DetailView):
date=TruncDate('clicked_at')
).values('date').annotate(count=Count('id')).order_by('date')
# 将查询结果转换为列表,并将日期转换为字符串
# Convert query results to list and format dates as strings
click_stats_list = list(click_stats)
for item in click_stats_list:
item['date'] = item['date'].strftime('%Y-%m-%d')
context['click_stats'] = json.dumps(click_stats_list, cls=DjangoJSONEncoder)
# Convert markdown to HTML if the link is a custom type
if self.object.link_type == Link.LinkType.CUSTOM:
context['rendered_text'] = markdown.markdown(self.object.text)
return context
def search_aliases(request):
+59
View File
@@ -729,6 +729,10 @@ video {
margin-left: 0.5rem;
}
.mr-1 {
margin-right: 0.25rem;
}
.block {
display: block;
}
@@ -817,6 +821,10 @@ video {
max-width: 20rem;
}
.max-w-none {
max-width: none;
}
.flex-grow {
flex-grow: 1;
}
@@ -875,6 +883,15 @@ video {
gap: 1rem;
}
.gap-x-4 {
-moz-column-gap: 1rem;
column-gap: 1rem;
}
.gap-y-8 {
row-gap: 2rem;
}
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
@@ -1087,6 +1104,16 @@ video {
background-color: rgb(240 253 244 / var(--tw-bg-opacity));
}
.bg-blue-100 {
--tw-bg-opacity: 1;
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
}
.bg-green-100 {
--tw-bg-opacity: 1;
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
}
.p-2 {
padding: 0.5rem;
}
@@ -1139,6 +1166,16 @@ video {
padding-bottom: 1.25rem;
}
.px-2\.5 {
padding-left: 0.625rem;
padding-right: 0.625rem;
}
.py-0\.5 {
padding-top: 0.125rem;
padding-bottom: 0.125rem;
}
.text-left {
text-align: left;
}
@@ -1288,6 +1325,20 @@ video {
color: rgb(21 128 61 / var(--tw-text-opacity));
}
.text-blue-800 {
--tw-text-opacity: 1;
color: rgb(30 64 175 / var(--tw-text-opacity));
}
.text-green-800 {
--tw-text-opacity: 1;
color: rgb(22 101 52 / var(--tw-text-opacity));
}
.underline {
text-decoration-line: underline;
}
.shadow {
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -1565,6 +1616,14 @@ video {
}
@media (min-width: 640px) {
.sm\:col-span-1 {
grid-column: span 1 / span 1;
}
.sm\:col-span-2 {
grid-column: span 2 / span 2;
}
.sm\:inline {
display: inline;
}
+2 -1
View File
@@ -9,4 +9,5 @@ django-tailwind==3.8.0
fontawesome-free==5.15.3
gunicorn==22.0.0
whitenoise==5.3.0
django-simplemde-x==1.2.0
django-simplemde==0.1.4
markdown==3.7