Update style of custom link page

This commit is contained in:
2024-10-05 19:39:15 +10:00
parent a4357ac92a
commit 014236a722
8 changed files with 187 additions and 13 deletions
BIN
View File
Binary file not shown.
+4
View File
@@ -1,5 +1,6 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
class Link(models.Model):
class LinkType(models.TextChoices):
@@ -21,6 +22,9 @@ class Link(models.Model):
def __str__(self):
return self.alias
def get_absolute_url(self):
return reverse('link_detail', kwargs={'pk': self.pk})
class ClickLog(models.Model):
link = models.ForeignKey(Link, on_delete=models.CASCADE)
clicked_at = models.DateTimeField(auto_now_add=True)
+86
View File
@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ link.alias }}</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss-typography/0.4.0/typography.min.css" rel="stylesheet">
<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>
</head>
<body class="bg-gray-100">
<div class="max-w-3xl mx-auto mt-10 p-4 bg-white shadow-md rounded-lg">
<div class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl max-w-none">
{{ rendered_text|safe }}
</div>
</div>
</body>
</html>
+34 -10
View File
@@ -46,14 +46,14 @@
{% for field in form %}
{% if field.name != 'link_type' %}
<!-- Render fields based on link type -->
<div class="space-y-1 {% if field.name == 'original_url' %}link-field{% if is_custom %}hidden{% endif %}{% elif field.name == 'text' %}custom-field{% if not is_custom %}hidden{% endif %}{% endif %}">
<div class="space-y-1 {% if field.name == 'original_url' %}link-field{% if is_custom %}hidden{% endif %}{% elif field.name == 'text' %} custom-field {% if not is_custom %}hidden{% endif %}{% endif %}">
<label for="{{ field.id_for_label }}" class="block text-sm font-medium text-gray-700 {% if field.name == 'original_url' and is_custom or field.name == 'text' and not is_custom %}hidden{% endif %}">
{{ field.label }}
</label>
<div class="mt-1">
{% if field.name == 'text' %}
<!-- Render text field only for custom links -->
{% if is_custom %}
{% if is_custom or is_new %}
{{ field }}
{% endif %}
{% elif field.name == 'original_url' %}
@@ -123,6 +123,8 @@
const linkDescription = document.getElementById('link-description');
const customDescription = document.getElementById('custom-description');
const linkTypeInput = document.getElementById('id_link_type');
const textField = document.querySelector('.custom-field');
let simplemde;
function showLinkFields() {
linkTab.classList.add('text-blue-600', 'border-blue-600');
@@ -131,15 +133,23 @@
customTab.classList.add('text-gray-500', 'border-transparent');
linkFields.forEach(field => {
field.classList.remove('hidden');
field.querySelector('label').classList.remove('hidden');
const label = field.querySelector('label');
if (label) label.classList.remove('hidden');
});
customFields.forEach(field => {
field.classList.add('hidden');
field.querySelector('label').classList.add('hidden');
const label = field.querySelector('label');
if (label) label.classList.add('hidden');
});
linkDescription.classList.remove('hidden');
customDescription.classList.add('hidden');
linkTypeInput.value = 'LINK';
if (simplemde) {
//simplemde.toTextArea();
simplemde = null;
}
textField.classList.add('hidden');
}
function showCustomFields() {
@@ -149,15 +159,28 @@
linkTab.classList.add('text-gray-500', 'border-transparent');
customFields.forEach(field => {
field.classList.remove('hidden');
field.querySelector('label').classList.remove('hidden');
const label = field.querySelector('label');
if (label) label.classList.remove('hidden');
});
linkFields.forEach(field => {
field.classList.add('hidden');
field.querySelector('label').classList.add('hidden');
const label = field.querySelector('label');
if (label) label.classList.add('hidden');
});
customDescription.classList.remove('hidden');
linkDescription.classList.add('hidden');
linkTypeInput.value = 'CUSTOM';
initializeSimpleMDE();
textField.classList.remove('hidden');
}
function initializeSimpleMDE() {
if (!simplemde) {
var textArea = document.getElementById('id_text');
if (textArea) {
simplemde = new SimpleMDE({ element: textArea });
}
}
}
linkTab.addEventListener('click', showLinkFields);
@@ -170,10 +193,11 @@
showLinkFields();
}
// Initialize SimpleMDE for custom links
var textArea = document.getElementById('id_text');
if (textArea) {
var simplemde = new SimpleMDE({ element: textArea });
// Check if we're creating a new link
const isNewLink = {{ form.instance.pk|yesno:"false,true" }};
if (isNewLink) {
// Hide text field by default for new links
textField.classList.add('hidden');
}
});
</script>
+13 -1
View File
@@ -51,6 +51,9 @@
{% endif %}
</a>
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-1/12">
{% trans "Type" %}
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-1/3">{% trans "Original URL" %}</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-1/12">
<a href="?sort=clicks&order={% if current_sort == 'clicks' and current_order == 'asc' %}desc{% else %}asc{% endif %}" class="flex items-center">
@@ -94,6 +97,15 @@
<td class="px-6 py-4 whitespace-nowrap">
<a href="{% url 'redirect_to_original' link.alias %}" target="_blank" class="text-blue-600 hover:text-blue-900">{{ link.alias }}</a>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center">
{% if link.link_type == 'LINK' %}
<i class="fas fa-link text-blue-500"></i>
{% else %}
<i class="fas fa-code text-green-500"></i>
{% endif %}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="truncate max-w-xs" title="{{ link.original_url }}">
<a href="{{ link.original_url }}" target="_blank" class="text-blue-600 hover:text-blue-900">{{ link.original_url }}</a>
@@ -114,7 +126,7 @@
</tr>
{% empty %}
<tr>
<td colspan="7" class="px-6 py-4 whitespace-nowrap text-center text-gray-500">{% trans "No links available" %}</td>
<td colspan="8" class="px-6 py-4 whitespace-nowrap text-center text-gray-500">{% trans "No links available" %}</td>
</tr>
{% endfor %}
</tbody>
+28 -2
View File
@@ -74,6 +74,11 @@ class LinkCreateView(CreateView):
initial['alias'] = alias
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['is_new'] = True
return context
def form_valid(self, form):
form.instance.alias = form.instance.alias.lower()
alias = form.cleaned_data.get('alias').lower()
@@ -83,6 +88,11 @@ class LinkCreateView(CreateView):
if Link.objects.filter(alias=alias).exists():
form.add_error('alias', _("This alias is already in use. Please choose another one."))
return self.form_invalid(form)
# Set original_url for custom links
if form.cleaned_data.get('link_type') == Link.LinkType.CUSTOM:
form.instance.original_url = f'/custom/{alias}'
return super().form_valid(form)
def is_valid_alias(self, alias):
@@ -92,7 +102,6 @@ class LinkUpdateView(UpdateView):
model = Link
form_class = LinkForm
template_name = 'links/link_form.html'
success_url = reverse_lazy('link_list')
def get_initial(self):
initial = super().get_initial()
@@ -115,7 +124,9 @@ class LinkUpdateView(UpdateView):
if Link.objects.filter(alias=alias).exclude(pk=self.object.pk).exists():
form.add_error('alias', _("This alias is already in use. Please choose another one."))
return self.form_invalid(form)
return super().form_valid(form)
response = super().form_valid(form)
# Redirect to the link detail page after successful update
return redirect(reverse('link_detail', kwargs={'pk': self.object.pk}))
def form_invalid(self, form):
for field, errors in form.errors.items():
@@ -299,3 +310,18 @@ def export_links(request):
response = HttpResponse(json.dumps(formatted_data, indent=2), content_type='application/json')
response['Content-Disposition'] = 'attachment; filename="links_export.json"'
return response
class CustomLinkView(DetailView):
model = Link
template_name = 'links/custom_link.html'
context_object_name = 'link'
def get_object(self, queryset=None):
alias = self.kwargs.get('alias').lower()
return get_object_or_404(Link, alias=alias, link_type=Link.LinkType.CUSTOM)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Render markdown text as HTML
context['rendered_text'] = markdown.markdown(self.object.text)
return context
+18
View File
@@ -733,6 +733,10 @@ video {
margin-right: 0.25rem;
}
.mt-10 {
margin-top: 2.5rem;
}
.block {
display: block;
}
@@ -825,6 +829,10 @@ video {
max-width: none;
}
.max-w-3xl {
max-width: 48rem;
}
.flex-grow {
flex-grow: 1;
}
@@ -1335,6 +1343,16 @@ video {
color: rgb(22 101 52 / var(--tw-text-opacity));
}
.text-blue-500 {
--tw-text-opacity: 1;
color: rgb(59 130 246 / var(--tw-text-opacity));
}
.text-green-500 {
--tw-text-opacity: 1;
color: rgb(34 197 94 / var(--tw-text-opacity));
}
.underline {
text-decoration-line: underline;
}
+4
View File
@@ -3,10 +3,14 @@ from django.urls import path, include
from django.conf.urls.i18n import i18n_patterns
from django.conf import settings
from django.conf.urls.static import static
from links.views import LinkDetailView, LinkUpdateView, CustomLinkView
urlpatterns = [
path('i18n/', include('django.conf.urls.i18n')),
path('search/', include('links.search_urls')),
path('link/<int:pk>/', LinkDetailView.as_view(), name='link_detail'),
path('link/<int:pk>/edit/', LinkUpdateView.as_view(), name='link_update'),
path('custom/<slug:alias>/', CustomLinkView.as_view(), name='custom_link'),
]
urlpatterns += i18n_patterns(