mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Add logic to create custom link
This commit is contained in:
@@ -71,3 +71,6 @@ node_modules/
|
||||
/data
|
||||
|
||||
**/__pycache__
|
||||
links/__pycache__/
|
||||
|
||||
staticfiles/
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+19
-6
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
+14
-3
@@ -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
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
Vendored
+279
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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')
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
content: [
|
||||
'./templates/**/*.html',
|
||||
'./links/templates/**/*.html',
|
||||
'./**/*.js',
|
||||
// 添加其他需要扫描的文件路径
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
+7
-1
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user