Add logic to create custom link

This commit is contained in:
2024-10-05 17:48:28 +10:00
parent 58b4c4910c
commit 7cbeeb90f6
21 changed files with 543 additions and 71 deletions
+3
View File
@@ -71,3 +71,6 @@ node_modules/
/data
**/__pycache__
links/__pycache__/
staticfiles/
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+19 -6
View File
@@ -1,13 +1,26 @@
from django import forms
from .models import Link
from django.utils.translation import gettext_lazy as _
from simplemde.fields import SimpleMDEField
class LinkForm(forms.ModelForm):
text = SimpleMDEField()
class Meta:
model = Link
fields = ['alias', 'original_url']
error_messages = {
'alias': {
'unique': _("This alias already exists. Please choose a different one."),
}
fields = ['alias', 'link_type', 'original_url', 'text']
widgets = {
'link_type': forms.RadioSelect(),
}
def clean(self):
cleaned_data = super().clean()
link_type = cleaned_data.get('link_type')
original_url = cleaned_data.get('original_url')
text = cleaned_data.get('text')
if link_type == Link.LinkType.LINK and not original_url:
raise forms.ValidationError(_("Original URL is required for Link type."))
elif link_type == Link.LinkType.CUSTOM and not text:
raise forms.ValidationError(_("Text is required for Custom type."))
return cleaned_data
@@ -0,0 +1,34 @@
# Generated by Django 5.0.8 on 2024-10-05 06:26
import ckeditor.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('links', '0003_clicklog'),
]
operations = [
migrations.AddField(
model_name='link',
name='link_type',
field=models.CharField(choices=[('LINK', 'Link'), ('CUSTOM', 'Custom')], default='LINK', max_length=10),
),
migrations.AddField(
model_name='link',
name='text',
field=ckeditor.fields.RichTextField(blank=True, null=True),
),
migrations.AlterField(
model_name='link',
name='alias',
field=models.SlugField(max_length=100, unique=True),
),
migrations.AlterField(
model_name='link',
name='original_url',
field=models.URLField(blank=True, null=True),
),
]
+18
View File
@@ -0,0 +1,18 @@
# Generated by Django 5.0.8 on 2024-10-05 07:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('links', '0004_link_link_type_link_text_alter_link_alias_and_more'),
]
operations = [
migrations.AlterField(
model_name='link',
name='text',
field=models.TextField(blank=True, null=True),
),
]
+14 -3
View File
@@ -1,11 +1,22 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class Link(models.Model):
alias = models.CharField(max_length=50, unique=True, db_index=True)
original_url = models.URLField()
class LinkType(models.TextChoices):
LINK = 'LINK', _('Link')
CUSTOM = 'CUSTOM', _('Custom')
alias = models.SlugField(max_length=100, unique=True)
original_url = models.URLField(blank=True, null=True)
text = models.TextField(blank=True, null=True) # 改为 TextField
link_type = models.CharField(
max_length=10,
choices=LinkType.choices,
default=LinkType.LINK,
)
click_count = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
click_count = models.IntegerField(default=0)
def __str__(self):
return self.alias
+130 -51
View File
@@ -1,61 +1,140 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'simplemde/simplemde.min.css' %}">
{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto mt-8">
<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">
<div class="px-6 py-4 bg-gray-50 border-b border-gray-200">
<h1 class="text-2xl font-bold text-gray-800">
{% if form.instance.pk %}{% trans "Edit Link" %}{% else %}{% trans "Create New Link" %}{% endif %}
</h1>
</div>
<div class="p-6">
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h1 class="text-2xl font-bold text-gray-900">
{% if form.instance.pk %}{% trans "Edit Link" %}{% else %}{% trans "Create New Link" %}{% endif %}
</h1>
</div>
<div class="px-4 py-5 sm:p-6">
<!-- Link Type Tabs -->
<div class="mb-4">
<label for="{{ field.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-1">{{ field.label }}</label>
{% if field.errors %}
<ul class="errorlist mb-2">
{% for error in field.errors %}
<li class="text-red-500 text-sm">{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
<input type="{{ field.field.widget.input_type }}"
name="{{ field.name }}"
id="{{ field.id_for_label }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50 {% if field.errors %}border-red-500{% endif %}"
{% if field.value %}value="{{ field.value }}"{% endif %}
{% if field.field.required %}required{% endif %}>
{% if field.help_text %}
<p class="mt-2 text-sm text-gray-500">{{ field.help_text }}</p>
{% endif %}
<div class="flex border-b border-gray-200">
<button type="button" class="py-2 px-4 text-sm font-medium text-center text-blue-600 border-b-2 border-blue-600 hover:text-blue-700 hover:border-blue-700 focus:outline-none" id="link-tab">
{% trans "Link" %}
</button>
<button type="button" class="py-2 px-4 text-sm font-medium text-center text-gray-500 border-b-2 border-transparent hover:text-gray-700 hover:border-gray-300 focus:outline-none" id="custom-tab">
{% trans "Custom" %}
</button>
</div>
</div>
{% endfor %}
<div class="mt-6 flex items-center justify-end space-x-3">
<a href="{% url 'link_list' %}" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded">
{% trans "Cancel" %}
</a>
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">
{% trans "Save" %}
</button>
</div>
</form>
<h2 class="text-xl font-semibold mt-8 mb-4">{% trans "Note:" %}</h2>
<div class="bg-gray-100 p-4 rounded-lg mb-4">
<p class="mt-2 text-sm text-gray-500">
{% trans "Please note that special characters are not allowed in the alias when creating or updating a link." %}
<br>
{% trans "For example:" %}
<ul class="list-disc list-inside mt-2 text-gray-600">
<li>{% trans "Valid alias: helloworld" %}</li>
<li>{% trans "Invalid alias: hello@world!" %}</li>
<li>{% trans "Accessing 'helloworld' as: hello-world or hello_world" %}</li>
</ul>
</p>
<!-- Link Type Description -->
<div id="link-description" class="bg-blue-50 border-l-4 border-blue-400 p-4 mb-4">
<p class="text-sm text-blue-700">
{% trans "Link type is used for redirecting to an external URL. Use this when you want to create a short alias for a long URL." %}
</p>
</div>
<div id="custom-description" class="bg-green-50 border-l-4 border-green-400 p-4 mb-4 hidden">
<p class="text-sm text-green-700">
{% trans "Custom type allows you to create a link with custom HTML content. Use this when you want to display a message or custom content instead of redirecting." %}
</p>
</div>
<form method="post" class="space-y-6">
{% csrf_token %}
<input type="hidden" name="link_type" id="id_link_type" value="LINK">
{% for field in form %}
{% if field.name != 'link_type' %}
<div class="space-y-1 {% if field.name == 'original_url' %}link-field{% elif field.name == 'text' %}custom-field hidden{% endif %}">
<label for="{{ field.id_for_label }}" class="block text-sm font-medium text-gray-700">
{{ field.label }}
</label>
<div class="mt-1">
{% if field.name == 'text' %}
{{ field }}
{% else %}
<input type="{{ field.field.widget.input_type }}"
name="{{ field.name }}"
id="{{ field.id_for_label }}"
value="{{ field.value|default_if_none:'' }}"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
{% if field.field.required %}required{% endif %}>
{% endif %}
</div>
{% if field.errors %}
<ul class="mt-1 text-sm text-red-600">
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
{% if field.help_text %}
<p class="mt-2 text-sm text-gray-500">{{ field.help_text }}</p>
{% endif %}
</div>
{% endif %}
{% endfor %}
<div class="flex justify-end space-x-3 mt-6">
<a href="{% url 'link_list' %}" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-gray-700 bg-gray-200 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
{% trans "Cancel" %}
</a>
<button type="submit" class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
{% trans "Save" %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'simplemde/simplemde.min.js' %}"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const linkTab = document.getElementById('link-tab');
const customTab = document.getElementById('custom-tab');
const linkFields = document.querySelectorAll('.link-field');
const customFields = document.querySelectorAll('.custom-field');
const linkDescription = document.getElementById('link-description');
const customDescription = document.getElementById('custom-description');
const linkTypeInput = document.getElementById('id_link_type');
function showLinkFields() {
linkTab.classList.add('text-blue-600', 'border-blue-600');
linkTab.classList.remove('text-gray-500', 'border-transparent');
customTab.classList.remove('text-blue-600', 'border-blue-600');
customTab.classList.add('text-gray-500', 'border-transparent');
linkFields.forEach(field => field.classList.remove('hidden'));
customFields.forEach(field => field.classList.add('hidden'));
linkDescription.classList.remove('hidden');
customDescription.classList.add('hidden');
linkTypeInput.value = 'LINK';
}
function showCustomFields() {
customTab.classList.add('text-blue-600', 'border-blue-600');
customTab.classList.remove('text-gray-500', 'border-transparent');
linkTab.classList.remove('text-blue-600', 'border-blue-600');
linkTab.classList.add('text-gray-500', 'border-transparent');
customFields.forEach(field => field.classList.remove('hidden'));
linkFields.forEach(field => field.classList.add('hidden'));
customDescription.classList.remove('hidden');
linkDescription.classList.add('hidden');
linkTypeInput.value = 'CUSTOM';
}
linkTab.addEventListener('click', showLinkFields);
customTab.addEventListener('click', showCustomFields);
// 初始化表单状态
showLinkFields();
var textArea = document.getElementById('id_text');
if (textArea) {
var simplemde = new SimpleMDE({ element: textArea });
} else {
console.error('SimpleMDE: Error. No element was found with id "id_text".');
}
});
</script>
{% endblock %}
+7 -7
View File
@@ -63,7 +63,7 @@ class LinkListView(ListView):
class LinkCreateView(CreateView):
model = Link
form_class = LinkForm
template_name = 'links/link_form.html'
template_name = 'links/link_form.html' # 确保这里的路径是正确的
success_url = reverse_lazy('link_list')
def get_initial(self):
@@ -74,9 +74,8 @@ class LinkCreateView(CreateView):
return initial
def form_valid(self, form):
# 将输入转换为小写
form.instance.alias = form.instance.alias.lower()
alias = form.cleaned_data.get('alias').lower() # 确保检查时使用小写
alias = form.cleaned_data.get('alias').lower()
if not self.is_valid_alias(alias):
form.add_error('alias', _("Alias cannot contain special characters."))
return self.form_invalid(form)
@@ -86,8 +85,7 @@ class LinkCreateView(CreateView):
return super().form_valid(form)
def is_valid_alias(self, alias):
# 检查别名是否包含特殊字符
return alias.isalnum() # 只允许字母和数字
return alias.isalnum()
class LinkUpdateView(UpdateView):
model = Link
@@ -96,9 +94,8 @@ class LinkUpdateView(UpdateView):
success_url = reverse_lazy('link_list')
def form_valid(self, form):
# 将输入转换为小写
form.instance.alias = form.instance.alias.lower()
alias = form.cleaned_data.get('alias').lower() # 确保检查时使用小写
alias = form.cleaned_data.get('alias').lower()
if not self.is_valid_alias(alias):
form.add_error('alias', _("Alias cannot contain special characters."))
return self.form_invalid(form)
@@ -113,6 +110,9 @@ class LinkUpdateView(UpdateView):
messages.error(self.request, f"{error}")
return super().form_invalid(form)
def is_valid_alias(self, alias):
return alias.isalnum()
class LinkDeleteView(DeleteView):
model = Link
template_name = 'links/link_confirm_delete.html'
+279
View File
@@ -717,6 +717,18 @@ video {
margin-top: 2rem;
}
.ml-3 {
margin-left: 0.75rem;
}
.mr-3 {
margin-right: 0.75rem;
}
.ml-2 {
margin-left: 0.5rem;
}
.block {
display: block;
}
@@ -757,6 +769,10 @@ video {
height: 100vh;
}
.h-6 {
height: 1.5rem;
}
.w-1\/12 {
width: 8.333333%;
}
@@ -785,6 +801,10 @@ video {
width: 100%;
}
.w-11 {
width: 2.75rem;
}
.min-w-full {
min-width: 100%;
}
@@ -809,6 +829,10 @@ video {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.cursor-pointer {
cursor: pointer;
}
.select-all {
-webkit-user-select: all;
-moz-user-select: all;
@@ -869,6 +893,18 @@ video {
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.space-y-1 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
}
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
@@ -914,6 +950,10 @@ video {
border-radius: 0.375rem;
}
.rounded-full {
border-radius: 9999px;
}
.rounded-l-md {
border-top-left-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
@@ -936,6 +976,14 @@ video {
border-top-width: 1px;
}
.border-b-2 {
border-bottom-width: 2px;
}
.border-l-4 {
border-left-width: 4px;
}
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
@@ -956,6 +1004,25 @@ video {
border-color: rgb(234 179 8 / var(--tw-border-opacity));
}
.border-transparent {
border-color: transparent;
}
.border-blue-400 {
--tw-border-opacity: 1;
border-color: rgb(96 165 250 / var(--tw-border-opacity));
}
.border-blue-600 {
--tw-border-opacity: 1;
border-color: rgb(37 99 235 / var(--tw-border-opacity));
}
.border-green-400 {
--tw-border-opacity: 1;
border-color: rgb(74 222 128 / var(--tw-border-opacity));
}
.bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
@@ -1000,6 +1067,26 @@ video {
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
}
.bg-blue-600 {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
.bg-yellow-50 {
--tw-bg-opacity: 1;
background-color: rgb(254 252 232 / var(--tw-bg-opacity));
}
.bg-blue-50 {
--tw-bg-opacity: 1;
background-color: rgb(239 246 255 / var(--tw-bg-opacity));
}
.bg-green-50 {
--tw-bg-opacity: 1;
background-color: rgb(240 253 244 / var(--tw-bg-opacity));
}
.p-2 {
padding: 0.5rem;
}
@@ -1047,6 +1134,11 @@ video {
padding-bottom: 1rem;
}
.py-5 {
padding-top: 1.25rem;
padding-bottom: 1.25rem;
}
.text-left {
text-align: left;
}
@@ -1176,6 +1268,26 @@ video {
color: rgb(161 98 7 / var(--tw-text-opacity));
}
.text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.text-yellow-600 {
--tw-text-opacity: 1;
color: rgb(202 138 4 / var(--tw-text-opacity));
}
.text-blue-700 {
--tw-text-opacity: 1;
color: rgb(29 78 216 / var(--tw-text-opacity));
}
.text-green-700 {
--tw-text-opacity: 1;
color: rgb(21 128 61 / var(--tw-text-opacity));
}
.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);
@@ -1221,6 +1333,75 @@ video {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
.after\:absolute::after {
content: var(--tw-content);
position: absolute;
}
.after\:left-\[2px\]::after {
content: var(--tw-content);
left: 2px;
}
.after\:top-\[2px\]::after {
content: var(--tw-content);
top: 2px;
}
.after\:h-5::after {
content: var(--tw-content);
height: 1.25rem;
}
.after\:w-5::after {
content: var(--tw-content);
width: 1.25rem;
}
.after\:rounded-full::after {
content: var(--tw-content);
border-radius: 9999px;
}
.after\:border::after {
content: var(--tw-content);
border-width: 1px;
}
.after\:border-gray-300::after {
content: var(--tw-content);
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.after\:bg-white::after {
content: var(--tw-content);
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.after\:transition-all::after {
content: var(--tw-content);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.after\:content-\[\'\'\]::after {
--tw-content: '';
content: var(--tw-content);
}
.hover\:border-gray-300:hover {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
.hover\:border-blue-700:hover {
--tw-border-opacity: 1;
border-color: rgb(29 78 216 / var(--tw-border-opacity));
}
.hover\:bg-blue-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
@@ -1246,6 +1427,11 @@ video {
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}
.hover\:bg-blue-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
}
.hover\:text-blue-800:hover {
--tw-text-opacity: 1;
color: rgb(30 64 175 / var(--tw-text-opacity));
@@ -1276,6 +1462,16 @@ video {
color: rgb(127 29 29 / var(--tw-text-opacity));
}
.hover\:text-gray-700:hover {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.hover\:text-blue-700:hover {
--tw-text-opacity: 1;
color: rgb(29 78 216 / var(--tw-text-opacity));
}
.hover\:underline:hover {
text-decoration-line: underline;
}
@@ -1285,6 +1481,11 @@ video {
border-color: rgb(147 197 253 / var(--tw-border-opacity));
}
.focus\:border-indigo-300:focus {
--tw-border-opacity: 1;
border-color: rgb(165 180 252 / var(--tw-border-opacity));
}
.focus\:outline-none:focus {
outline: 2px solid transparent;
outline-offset: 2px;
@@ -1312,10 +1513,57 @@ video {
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
}
.focus\:ring-gray-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity));
}
.focus\:ring-indigo-200:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(199 210 254 / var(--tw-ring-opacity));
}
.focus\:ring-opacity-50:focus {
--tw-ring-opacity: 0.5;
}
.focus\:ring-offset-2:focus {
--tw-ring-offset-width: 2px;
}
.peer:checked ~ .peer-checked\:bg-blue-600 {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
.peer:checked ~ .peer-checked\:after\:translate-x-full::after {
content: var(--tw-content);
--tw-translate-x: 100%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.peer:checked ~ .peer-checked\:after\:border-white::after {
content: var(--tw-content);
--tw-border-opacity: 1;
border-color: rgb(255 255 255 / var(--tw-border-opacity));
}
.peer:focus ~ .peer-focus\:outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
.peer:focus ~ .peer-focus\:ring-4 {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.peer:focus ~ .peer-focus\:ring-blue-300 {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
}
@media (min-width: 640px) {
.sm\:inline {
display: inline;
@@ -1324,6 +1572,15 @@ video {
.sm\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sm\:p-6 {
padding: 1.5rem;
}
.sm\:px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
@media (min-width: 768px) {
@@ -1340,4 +1597,26 @@ video {
.lg\:grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.lg\:px-8 {
padding-left: 2rem;
padding-right: 2rem;
}
}
@media (prefers-color-scheme: dark) {
.dark\:border-gray-600 {
--tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity));
}
.dark\:bg-gray-700 {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
}
.peer:focus ~ .dark\:peer-focus\:ring-blue-800 {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(30 64 175 / var(--tw-ring-opacity));
}
}
+1
View File
@@ -9,3 +9,4 @@ django-tailwind==3.8.0
fontawesome-free==5.15.3
gunicorn==22.0.0
whitenoise==5.3.0
django-simplemde-x==1.2.0
+2
View File
@@ -49,6 +49,8 @@
cursor: text;
}
</style>
{{ form.media }}
{% block extra_css %}{% endblock %}
</head>
<body class="bg-gray-100">
<nav class="bg-custom-blue text-white p-4 fixed w-full top-0 z-10">
Binary file not shown.
Binary file not shown.
+17 -3
View File
@@ -16,6 +16,7 @@ INSTALLED_APPS = [
'links',
'tailwind',
'new_theme',
'simplemde',
]
ROOT_URLCONF = 'url_manager.urls'
@@ -35,7 +36,7 @@ MIDDLEWARE = [
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 确保这一行存在
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -66,9 +67,9 @@ DATABASES = {
# 静态文件设置
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'new_theme/static'),
os.path.join(BASE_DIR, 'static'),
]
# 默认自动字段类型
@@ -108,3 +109,16 @@ INTERNAL_IPS = [
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', 'http://localhost:8000').split(',')
# SimpleMDE 配置
SIMPLEMDE_OPTIONS = {
'placeholder': 'Type here...',
'status': False,
'autosave': {
'enabled': True,
'delay': 10000,
},
}
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
content: [
'./templates/**/*.html',
'./links/templates/**/*.html',
'./**/*.js',
// 添加其他需要扫描的文件路径
],
theme: {
extend: {},
},
plugins: [],
}
+7 -1
View File
@@ -1,9 +1,11 @@
from django.contrib import admin
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
urlpatterns = [
path('i18n/', include('django.conf.urls.i18n')), # Move this outside of i18n_patterns
path('i18n/', include('django.conf.urls.i18n')),
path('search/', include('links.search_urls')),
]
@@ -11,3 +13,7 @@ urlpatterns += i18n_patterns(
path('admin/', admin.site.urls),
path('', include('links.urls')),
)
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)