mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Showing up link type
This commit is contained in:
@@ -64,6 +64,7 @@ coverage.xml
|
||||
|
||||
3.12/lib/python3.12/site-packages/
|
||||
3.12/include
|
||||
3.12/bin
|
||||
|
||||
# Tailwind CSS
|
||||
/static/css/dist/
|
||||
|
||||
Binary file not shown.
@@ -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
@@ -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):
|
||||
|
||||
Vendored
+59
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user