mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
Rename newsletter to post
This commit is contained in:
Binary file not shown.
+4
-2
@@ -1,13 +1,15 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from . import page_views
|
||||
from . import newsletter_views
|
||||
from . import post_views
|
||||
from . import api_views
|
||||
|
||||
# Create a router and register our viewsets with it
|
||||
router = DefaultRouter(trailing_slash=False)
|
||||
router.register('pages', page_views.PageViewSet, basename='api-pages')
|
||||
router.register('newsletters', newsletter_views.NewsletterViewSet, basename='api-newsletters')
|
||||
# for API compatibility
|
||||
router.register('newsletters', post_views.PostViewSet, basename='api-newsletters')
|
||||
router.register('posts', post_views.PostViewSet, basename='api-posts')
|
||||
router.register('music', api_views.MusicViewSet, basename='api-music')
|
||||
|
||||
# The API URLs are determined automatically by the router
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
from django import forms
|
||||
from .models import Link, Page, Newsletter, ImageCollection, Image, IPTVChannel, Tag
|
||||
from .models import Link, Page, Post, ImageCollection, Image, IPTVChannel, Tag
|
||||
from simplemde.fields import SimpleMDEField
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.validators import URLValidator
|
||||
@@ -62,9 +62,9 @@ class PageForm(forms.ModelForm):
|
||||
'tags': forms.SelectMultiple(attrs={'class': 'select2'}),
|
||||
}
|
||||
|
||||
class NewsletterForm(forms.ModelForm):
|
||||
class PostForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Newsletter
|
||||
model = Post
|
||||
fields = ['title', 'type', 'content']
|
||||
widgets = {
|
||||
'content': forms.Textarea(attrs={'rows': 20}),
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.0.10 on 2025-02-21 06:46
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('links', '0029_page_tags'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='Newsletter',
|
||||
new_name='Post',
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='post',
|
||||
options={'ordering': ['-created_at'], 'verbose_name': 'Post', 'verbose_name_plural': 'Posts'},
|
||||
),
|
||||
]
|
||||
+7
-7
@@ -244,8 +244,8 @@ class Screenshot(models.Model):
|
||||
return os.path.join(settings.MEDIA_URL, self.path)
|
||||
return None
|
||||
|
||||
class Newsletter(models.Model):
|
||||
class NewsletterType(models.TextChoices):
|
||||
class Post(models.Model):
|
||||
class PostType(models.TextChoices):
|
||||
TECHNOLOGY = 'technology', _('Technology')
|
||||
WORLD_NEWS = 'world_news', _('World News')
|
||||
LOCAL_NEWS = 'local_news', _('Local News')
|
||||
@@ -254,8 +254,8 @@ class Newsletter(models.Model):
|
||||
type = models.CharField(
|
||||
_('Type'),
|
||||
max_length=20,
|
||||
choices=NewsletterType.choices,
|
||||
default=NewsletterType.TECHNOLOGY
|
||||
choices=PostType.choices,
|
||||
default=PostType.TECHNOLOGY
|
||||
)
|
||||
content = models.TextField(_('Content'))
|
||||
created_at = models.DateTimeField(_('Created at'), auto_now_add=True)
|
||||
@@ -263,14 +263,14 @@ class Newsletter(models.Model):
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
verbose_name = _('Newsletter')
|
||||
verbose_name_plural = _('Newsletters')
|
||||
verbose_name = _('Post')
|
||||
verbose_name_plural = _('Posts')
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('newsletter-detail', kwargs={'pk': self.pk})
|
||||
return reverse('post-detail', kwargs={'pk': self.pk})
|
||||
|
||||
class ImageCollection(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
|
||||
@@ -7,23 +7,23 @@ from rest_framework.response import Response
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from django.utils import timezone
|
||||
from datetime import datetime
|
||||
from .models import Newsletter
|
||||
from .forms import NewsletterForm
|
||||
from .serializers import NewsletterSerializer
|
||||
from .models import Post
|
||||
from .forms import PostForm
|
||||
from .serializers import PostSerializer
|
||||
|
||||
class NewsletterListView(ListView):
|
||||
model = Newsletter
|
||||
template_name = 'links/newsletter_list.html'
|
||||
context_object_name = 'newsletters'
|
||||
class PostListView(ListView):
|
||||
model = Post
|
||||
template_name = 'links/post_list.html'
|
||||
context_object_name = 'posts'
|
||||
paginate_by = 10
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = Newsletter.objects.all().order_by('-created_at')
|
||||
queryset = Post.objects.all().order_by('-created_at')
|
||||
|
||||
# Type filter
|
||||
newsletter_type = self.request.GET.get('type')
|
||||
if newsletter_type:
|
||||
queryset = queryset.filter(type=newsletter_type)
|
||||
post_type = self.request.GET.get('type')
|
||||
if post_type:
|
||||
queryset = queryset.filter(type=post_type)
|
||||
|
||||
# Date range filter
|
||||
date_from = self.request.GET.get('date_from')
|
||||
@@ -48,8 +48,8 @@ class NewsletterListView(ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
# Add newsletter types for dropdown
|
||||
context['newsletter_types'] = Newsletter.NewsletterType.choices
|
||||
# Add post types for dropdown
|
||||
context['post_types'] = Post.PostType.choices
|
||||
|
||||
# Add selected filters to context
|
||||
context['selected_type'] = self.request.GET.get('type', '')
|
||||
@@ -65,46 +65,46 @@ class NewsletterListView(ListView):
|
||||
|
||||
return context
|
||||
|
||||
class NewsletterDetailView(DetailView):
|
||||
model = Newsletter
|
||||
template_name = 'links/newsletter_detail.html'
|
||||
class PostDetailView(DetailView):
|
||||
model = Post
|
||||
template_name = 'links/post_detail.html'
|
||||
|
||||
class NewsletterCreateView(CreateView):
|
||||
model = Newsletter
|
||||
form_class = NewsletterForm
|
||||
template_name = 'links/newsletter_form.html'
|
||||
success_url = reverse_lazy('newsletter-list')
|
||||
class PostCreateView(CreateView):
|
||||
model = Post
|
||||
form_class = PostForm
|
||||
template_name = 'links/post_form.html'
|
||||
success_url = reverse_lazy('post-list')
|
||||
|
||||
class NewsletterUpdateView(UpdateView):
|
||||
model = Newsletter
|
||||
form_class = NewsletterForm
|
||||
template_name = 'links/newsletter_form.html'
|
||||
success_url = reverse_lazy('newsletter-list')
|
||||
class PostUpdateView(UpdateView):
|
||||
model = Post
|
||||
form_class = PostForm
|
||||
template_name = 'links/post_form.html'
|
||||
success_url = reverse_lazy('post-list')
|
||||
|
||||
class NewsletterDeleteView(DeleteView):
|
||||
model = Newsletter
|
||||
template_name = 'links/newsletter_confirm_delete.html'
|
||||
success_url = reverse_lazy('newsletter-list')
|
||||
class PostDeleteView(DeleteView):
|
||||
model = Post
|
||||
template_name = 'links/post_confirm_delete.html'
|
||||
success_url = reverse_lazy('post-list')
|
||||
|
||||
class PublicNewsletterView(DetailView):
|
||||
model = Newsletter
|
||||
template_name = 'links/public_newsletter.html'
|
||||
context_object_name = 'newsletter'
|
||||
class PublicPostView(DetailView):
|
||||
model = Post
|
||||
template_name = 'links/public_post.html'
|
||||
context_object_name = 'post'
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
try:
|
||||
return super().get_object(queryset)
|
||||
except Http404:
|
||||
raise Http404("Newsletter not found")
|
||||
raise Http404("Post not found")
|
||||
|
||||
class StandardResultsSetPagination(PageNumberPagination):
|
||||
page_size = 10
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 100
|
||||
|
||||
class NewsletterViewSet(viewsets.ModelViewSet):
|
||||
queryset = Newsletter.objects.all().order_by('-created_at')
|
||||
serializer_class = NewsletterSerializer
|
||||
class PostViewSet(viewsets.ModelViewSet):
|
||||
queryset = Post.objects.all().order_by('-created_at')
|
||||
serializer_class = PostSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
+14
-14
@@ -5,7 +5,7 @@ from django.views.generic import ListView
|
||||
from django.db.models import Q, Value, CharField
|
||||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
from .models import Link, Page, Newsletter
|
||||
from .models import Link, Page, Post
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,8 +28,8 @@ class SearchView(ListView):
|
||||
results = self._get_link_results(query)
|
||||
elif type_filter == 'page':
|
||||
results = self._get_page_results(query)
|
||||
elif type_filter == 'newsletter':
|
||||
results = self._get_newsletter_results(query)
|
||||
elif type_filter == 'post':
|
||||
results = self._get_post_results(query)
|
||||
else:
|
||||
# Get all results
|
||||
results = self._get_all_results(query)
|
||||
@@ -59,19 +59,19 @@ class SearchView(ListView):
|
||||
model_type=Value('page', output_field=CharField())
|
||||
)
|
||||
|
||||
def _get_newsletter_results(self, query):
|
||||
return Newsletter.objects.filter(
|
||||
def _get_post_results(self, query):
|
||||
return Post.objects.filter(
|
||||
Q(title__icontains=query) |
|
||||
Q(content__icontains=query)
|
||||
).values('id', 'title', 'type', 'created_at').annotate(
|
||||
model_type=Value('newsletter', output_field=CharField())
|
||||
model_type=Value('post', output_field=CharField())
|
||||
)
|
||||
|
||||
def _get_all_results(self, query):
|
||||
links = self._get_link_results(query)
|
||||
pages = self._get_page_results(query)
|
||||
newsletters = self._get_newsletter_results(query)
|
||||
return list(chain(links, pages, newsletters))
|
||||
posts = self._get_post_results(query)
|
||||
return list(chain(links, pages, posts))
|
||||
|
||||
def _sort_results(self, results, sort_order):
|
||||
if sort_order == 'newest':
|
||||
@@ -107,16 +107,16 @@ def search(request):
|
||||
'url': link.original_url
|
||||
} for link in links])
|
||||
|
||||
# Search newsletters
|
||||
newsletters = Newsletter.objects.filter(
|
||||
# Search posts
|
||||
posts = Post.objects.filter(
|
||||
Q(title__icontains=query) |
|
||||
Q(content__icontains=query)
|
||||
)[:5]
|
||||
results.extend([{
|
||||
'type': 'newsletter',
|
||||
'title': newsletter.title,
|
||||
'url': f'/ui/newsletters/{newsletter.id}'
|
||||
} for newsletter in newsletters])
|
||||
'type': 'post',
|
||||
'title': post.title,
|
||||
'url': f'/ui/posts/{post.id}'
|
||||
} for post in posts])
|
||||
|
||||
else:
|
||||
results = []
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Page, Newsletter, ImageCollection, Image
|
||||
from .models import Page, Post, ImageCollection, Image
|
||||
|
||||
class PageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
@@ -9,9 +9,9 @@ class PageSerializer(serializers.ModelSerializer):
|
||||
read_only_fields = ['id', 'screenshot_path', 'process_status',
|
||||
'created_at', 'updated_at']
|
||||
|
||||
class NewsletterSerializer(serializers.ModelSerializer):
|
||||
class PostSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Newsletter
|
||||
model = Post
|
||||
fields = ['id', 'title', 'type', 'content', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
extra_kwargs = {
|
||||
|
||||
+3
-3
@@ -17,11 +17,11 @@
|
||||
<!-- Content -->
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
{% trans "Delete Newsletter" %}
|
||||
{% trans "Delete Post" %}
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<p class="text-sm text-gray-500">
|
||||
{% trans "Are you sure you want to delete this newsletter?" %}
|
||||
{% trans "Are you sure you want to delete this post?" %}
|
||||
</p>
|
||||
<p class="mt-1 text-sm font-medium text-gray-900">
|
||||
"{{ object.title }}"
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="mt-6 flex justify-end space-x-3">
|
||||
<a href="{% url 'newsletter-detail' object.pk %}"
|
||||
<a href="{% url 'post-detail' object.pk %}"
|
||||
class="inline-flex justify-center items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
+23
-23
@@ -31,7 +31,7 @@
|
||||
margin: 1rem;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
|
||||
/* Think Panel Styles */
|
||||
.think-panel {
|
||||
border: 1px solid #e5e7eb;
|
||||
@@ -39,7 +39,7 @@
|
||||
margin: 1rem 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.think-header {
|
||||
background-color: #f9fafb;
|
||||
padding: 1rem;
|
||||
@@ -50,28 +50,28 @@
|
||||
font-size: 1rem;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
|
||||
.think-header:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
|
||||
.think-content {
|
||||
padding: 0;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out, padding 0.3s ease-out;
|
||||
}
|
||||
|
||||
|
||||
.think-content.expanded {
|
||||
padding: 1rem;
|
||||
max-height: 2000px; /* Adjust based on your needs */
|
||||
transition: max-height 0.5s ease-in, padding 0.3s ease-in;
|
||||
}
|
||||
|
||||
|
||||
.think-icon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.think-icon.expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@@ -84,13 +84,13 @@
|
||||
<div class="modal-content">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-medium text-gray-900" id="modal-title">
|
||||
{% trans "Delete Newsletter" %}
|
||||
{% trans "Delete Post" %}
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
{% trans "Are you sure you want to delete this newsletter? This action cannot be undone." %}
|
||||
{% trans "Are you sure you want to delete this post? This action cannot be undone." %}
|
||||
</p>
|
||||
<div class="mt-4 flex space-x-3">
|
||||
<form method="post" action="{% url 'newsletter-delete' newsletter.pk %}">
|
||||
<form method="post" action="{% url 'post-delete' post.pk %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<!-- Back Button -->
|
||||
<div class="mb-6">
|
||||
<a href="{% url 'newsletter-list' %}"
|
||||
<a href="{% url 'post-list' %}"
|
||||
class="inline-flex items-center p-2 text-gray-600 hover:text-gray-800 hover:bg-gray-50 rounded-md"
|
||||
title="{% trans 'Back to List' %}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -125,15 +125,15 @@
|
||||
</div>
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex space-x-2">
|
||||
<a href="{% url 'newsletter-update' newsletter.pk %}"
|
||||
<a href="{% url 'post-update' post.pk %}"
|
||||
class="p-2 text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded-md"
|
||||
title="{% trans 'Edit Newsletter' %}">
|
||||
title="{% trans 'Edit Post' %}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'public-newsletter' newsletter.pk %}"
|
||||
<a href="{% url 'public-post' post.pk %}"
|
||||
target="_blank"
|
||||
class="p-2 text-green-600 hover:text-green-800 hover:bg-green-50 rounded-md"
|
||||
title="{% trans 'Open Public View' %}">
|
||||
@@ -144,7 +144,7 @@
|
||||
</a>
|
||||
<button onclick="openDeleteModal()"
|
||||
class="p-2 text-red-600 hover:text-red-800 hover:bg-red-50 rounded-md"
|
||||
title="{% trans 'Delete Newsletter' %}">
|
||||
title="{% trans 'Delete Post' %}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
@@ -157,13 +157,13 @@
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<!-- Title and Type -->
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900">{{ newsletter.title }}</h1>
|
||||
<h1 class="text-3xl font-bold text-gray-900">{{ post.title }}</h1>
|
||||
<div class="mt-2">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
{% if newsletter.type == 'technology' %}bg-purple-100 text-purple-800
|
||||
{% elif newsletter.type == 'world_news' %}bg-green-100 text-green-800
|
||||
{% if post.type == 'technology' %}bg-purple-100 text-purple-800
|
||||
{% elif post.type == 'world_news' %}bg-green-100 text-green-800
|
||||
{% else %}bg-blue-100 text-blue-800{% endif %}">
|
||||
{{ newsletter.get_type_display }}
|
||||
{{ post.get_type_display }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -173,13 +173,13 @@
|
||||
|
||||
<!-- Metadata -->
|
||||
<div class="mb-6 text-sm text-gray-500">
|
||||
<div>{% trans "Created" %}: {{ newsletter.created_at|date:"Y-m-d H:i" }}</div>
|
||||
<div>{% trans "Last updated" %}: {{ newsletter.updated_at|date:"Y-m-d H:i" }}</div>
|
||||
<div>{% trans "Created" %}: {{ post.created_at|date:"Y-m-d H:i" }}</div>
|
||||
<div>{% trans "Last updated" %}: {{ post.updated_at|date:"Y-m-d H:i" }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Content with improved markdown styling -->
|
||||
<article class="prose">
|
||||
{{ newsletter.content|markdown|safe }}
|
||||
{{ post.content|markdown|safe }}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,7 +219,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const panel = this.parentElement;
|
||||
const content = panel.querySelector('.think-content');
|
||||
const icon = panel.querySelector('.think-icon');
|
||||
|
||||
|
||||
content.classList.toggle('expanded');
|
||||
icon.classList.toggle('expanded');
|
||||
});
|
||||
@@ -27,9 +27,9 @@
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-6">
|
||||
{% if form.instance.pk %}
|
||||
{% trans "Edit Newsletter" %}
|
||||
{% trans "Edit Post" %}
|
||||
{% else %}
|
||||
{% trans "New Newsletter" %}
|
||||
{% trans "New Post" %}
|
||||
{% endif %}
|
||||
</h1>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<input type="text" name="{{ form.title.name }}" id="{{ form.title.id_for_label }}"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
value="{{ form.title.value|default:'' }}"
|
||||
placeholder="{% trans 'Newsletter Title' %}">
|
||||
placeholder="{% trans 'Post Title' %}">
|
||||
</div>
|
||||
{% if form.title.errors %}
|
||||
<p class="mt-2 text-sm text-red-600">{{ form.title.errors.0 }}</p>
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-end space-x-3">
|
||||
<a href="{% url 'newsletter-list' %}"
|
||||
<a href="{% url 'post-list' %}"
|
||||
class="inline-flex justify-center py-2 px-4 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
@@ -114,7 +114,7 @@ var simplemde = new SimpleMDE({
|
||||
spellChecker: false,
|
||||
autosave: {
|
||||
enabled: true,
|
||||
unique_id: "newsletter_content_{{ form.instance.pk|default:'new' }}"
|
||||
unique_id: "post_content_{{ form.instance.pk|default:'new' }}"
|
||||
},
|
||||
toolbar: [
|
||||
"bold", "italic", "heading", "|",
|
||||
@@ -4,9 +4,9 @@
|
||||
{% block content %}
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">{% trans "Newsletters" %}</h1>
|
||||
<a href="{% url 'newsletter-create' %}" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
|
||||
{% trans "Add Newsletter" %}
|
||||
<h1 class="text-2xl font-bold text-gray-900">{% trans "Posts" %}</h1>
|
||||
<a href="{% url 'post-create' %}" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
|
||||
{% trans "Add Post" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<select name="type" id="type"
|
||||
class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md">
|
||||
<option value="">{% trans "All Types" %}</option>
|
||||
{% for value, label in newsletter_types %}
|
||||
{% for value, label in post_types %}
|
||||
<option value="{{ value }}" {% if selected_type == value %}selected{% endif %}>
|
||||
{{ label }}
|
||||
</option>
|
||||
@@ -44,7 +44,7 @@
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
{% if is_filtered %}
|
||||
<a href="{% url 'newsletter-list' %}"
|
||||
<a href="{% url 'post-list' %}"
|
||||
class="text-gray-600 hover:text-gray-800 px-4 py-2 rounded-md border border-gray-300 h-10 inline-flex items-center">
|
||||
{% trans "Clear" %}
|
||||
</a>
|
||||
@@ -53,28 +53,28 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Newsletter List -->
|
||||
<!-- Post List -->
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-md">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{% for newsletter in newsletters %}
|
||||
{% for post in posts %}
|
||||
<li class="hover:bg-gray-50">
|
||||
<div class="px-4 py-4 sm:px-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<a href="{% url 'newsletter-detail' newsletter.pk %}" class="text-lg font-medium text-blue-600 hover:text-blue-800">
|
||||
{{ newsletter.title }}
|
||||
<a href="{% url 'post-detail' post.pk %}" class="text-lg font-medium text-blue-600 hover:text-blue-800">
|
||||
{{ post.title }}
|
||||
</a>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
{% if newsletter.type == 'technology' %}bg-purple-100 text-purple-800
|
||||
{% elif newsletter.type == 'world_news' %}bg-green-100 text-green-800
|
||||
{% if post.type == 'technology' %}bg-purple-100 text-purple-800
|
||||
{% elif post.type == 'world_news' %}bg-green-100 text-green-800
|
||||
{% else %}bg-blue-100 text-blue-800{% endif %}">
|
||||
{{ newsletter.get_type_display }}
|
||||
{{ post.get_type_display }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<a href="{% url 'newsletter-detail' newsletter.pk %}"
|
||||
<a href="{% url 'post-detail' post.pk %}"
|
||||
class="p-1 text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded"
|
||||
title="{% trans 'View Details' %}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -84,7 +84,7 @@
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'newsletter-update' newsletter.pk %}"
|
||||
<a href="{% url 'post-update' post.pk %}"
|
||||
class="p-1 text-green-600 hover:text-green-800 hover:bg-green-50 rounded"
|
||||
title="{% trans 'Edit' %}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -92,7 +92,7 @@
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'newsletter-delete' newsletter.pk %}"
|
||||
<a href="{% url 'post-delete' post.pk %}"
|
||||
class="p-1 text-red-600 hover:text-red-800 hover:bg-red-50 rounded"
|
||||
title="{% trans 'Delete' %}">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -103,13 +103,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
{% trans "Updated" %}: {{ newsletter.updated_at|date:"Y-m-d H:i" }}
|
||||
{% trans "Updated" %}: {{ post.updated_at|date:"Y-m-d H:i" }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="px-4 py-4 text-center text-gray-500">
|
||||
{% trans "No newsletters found." %}
|
||||
{% trans "No post found." %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
+9
-9
@@ -5,31 +5,31 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ newsletter.title }}</title>
|
||||
<title>{{ post.title }}</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
||||
<link href="{% static 'css/markdown.css' %}" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<div class="max-w-4xl mx-auto mt-10 p-8 bg-white shadow-md rounded-lg">
|
||||
<!-- Newsletter Header -->
|
||||
<!-- Post Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900">{{ newsletter.title }}</h1>
|
||||
<h1 class="text-3xl font-bold text-gray-900">{{ post.title }}</h1>
|
||||
<div class="mt-2 flex items-center space-x-4">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||
{% if newsletter.type == 'technology' %}bg-purple-100 text-purple-800
|
||||
{% elif newsletter.type == 'world_news' %}bg-green-100 text-green-800
|
||||
{% if post.type == 'technology' %}bg-purple-100 text-purple-800
|
||||
{% elif post.type == 'world_news' %}bg-green-100 text-green-800
|
||||
{% else %}bg-blue-100 text-blue-800{% endif %}">
|
||||
{{ newsletter.get_type_display }}
|
||||
{{ post.get_type_display }}
|
||||
</span>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ newsletter.created_at|date:"Y-m-d H:i" }}
|
||||
{{ post.created_at|date:"Y-m-d H:i" }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Newsletter Content -->
|
||||
<!-- Post Content -->
|
||||
<div class="prose">
|
||||
{{ newsletter.content|markdown|safe }}
|
||||
{{ post.content|markdown|safe }}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
@@ -51,7 +51,7 @@
|
||||
<option value="">{% trans "All Types" %}</option>
|
||||
<option value="link" {% if selected_type == 'link' %}selected{% endif %}>{% trans "Links" %}</option>
|
||||
<option value="page" {% if selected_type == 'page' %}selected{% endif %}>{% trans "Pages" %}</option>
|
||||
<option value="newsletter" {% if selected_type == 'newsletter' %}selected{% endif %}>{% trans "Newsletters" %}</option>
|
||||
<option value="post" {% if selected_type == 'post' %}selected{% endif %}>{% trans "Posts" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -101,15 +101,15 @@
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-600 truncate">{{ result.original_url }}</p>
|
||||
{% elif result.model_type == 'newsletter' %}
|
||||
{% elif result.model_type == 'post' %}
|
||||
<div class="flex items-center space-x-2">
|
||||
<h3 class="text-lg font-medium text-blue-600 truncate">
|
||||
<a href="{% url 'newsletter-detail' result.id %}" class="hover:text-blue-800">
|
||||
<a href="{% url 'post-detail' result.id %}" class="hover:text-blue-800">
|
||||
{{ result.title }}
|
||||
</a>
|
||||
</h3>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">
|
||||
{% trans "Newsletter" %}
|
||||
{% trans "Post" %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-2 sm:flex sm:justify-between">
|
||||
|
||||
+11
-11
@@ -2,7 +2,7 @@ from django.urls import path, include
|
||||
from . import views
|
||||
from . import page_views
|
||||
from . import search_views
|
||||
from . import newsletter_views
|
||||
from . import post_views
|
||||
from . import collection_views
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
@@ -34,12 +34,12 @@ urlpatterns = [
|
||||
path('fetch-page-info/', page_views.fetch_page_info, name='fetch-page-info'),
|
||||
path('ui/screenshots/', page_views.ScreenshotGalleryView.as_view(), name='screenshot-gallery'),
|
||||
|
||||
# Newsletters
|
||||
path('ui/newsletters/', newsletter_views.NewsletterListView.as_view(), name='newsletter-list'),
|
||||
path('ui/newsletters/new/', newsletter_views.NewsletterCreateView.as_view(), name='newsletter-create'),
|
||||
path('ui/newsletters/<int:pk>/', newsletter_views.NewsletterDetailView.as_view(), name='newsletter-detail'),
|
||||
path('ui/newsletters/<int:pk>/edit/', newsletter_views.NewsletterUpdateView.as_view(), name='newsletter-update'),
|
||||
path('ui/newsletters/<int:pk>/delete/', newsletter_views.NewsletterDeleteView.as_view(), name='newsletter-delete'),
|
||||
# Posts
|
||||
path('ui/posts/', post_views.PostListView.as_view(), name='post-list'),
|
||||
path('ui/posts/new/', post_views.PostCreateView.as_view(), name='post-create'),
|
||||
path('ui/posts/<int:pk>/', post_views.PostDetailView.as_view(), name='post-detail'),
|
||||
path('ui/posts/<int:pk>/edit/', post_views.PostUpdateView.as_view(), name='post-update'),
|
||||
path('ui/posts/<int:pk>/delete/', post_views.PostDeleteView.as_view(), name='post-delete'),
|
||||
|
||||
# Collection slideshow
|
||||
path('ui/collections/<uuid:pk>/slideshow/',
|
||||
@@ -50,10 +50,10 @@ urlpatterns = [
|
||||
path('ui/api-docs/',
|
||||
TemplateView.as_view(template_name='links/api_docs.html'),
|
||||
name='api-docs'),
|
||||
# Public newsletter URL
|
||||
path('public/newsletters/<int:pk>/',
|
||||
newsletter_views.PublicNewsletterView.as_view(),
|
||||
name='public-newsletter'),
|
||||
# Public Post URL
|
||||
path('public/posts/<int:pk>/',
|
||||
post_views.PublicPostView.as_view(),
|
||||
name='public-post'),
|
||||
|
||||
# Include collection URLs
|
||||
path('', include('links.collection_urls')),
|
||||
|
||||
+6
-15
@@ -87,24 +87,15 @@
|
||||
{% trans "Pages" %}
|
||||
</div>
|
||||
</a>
|
||||
<a href="{% url 'newsletter-list' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
<a href="{% url 'post-list' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9.5a2 2 0 00-2-2h-2M3 9l9-6 9 6m-1.5 11.5v-5.3a2 2 0 00-2-2h-13a2 2 0 00-2 2v5.3"/>
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9.5a2 2 0 00-2-2h-2"/>
|
||||
</svg>
|
||||
{% trans "Newsletters" %}
|
||||
{% trans "Posts" %}
|
||||
</div>
|
||||
</a>
|
||||
<a href="{% url 'tools' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||||
</svg>
|
||||
{% trans "Tools" %}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{% url 'screenshot-gallery' %}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -136,7 +127,7 @@
|
||||
<div class="flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
||||
d="M15 10l4.553-4.586A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
{% trans "IPTV" %}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user