Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions techblog_cms/static/js/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,41 @@
document.addEventListener('DOMContentLoaded', function() {
// Initialize any JavaScript functionality
// Mobile menu toggle functionality can be added here
const menuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');

if (!menuButton || !mobileMenu) {
return;
}

const closeMenu = () => {
mobileMenu.classList.add('hidden');
menuButton.setAttribute('aria-expanded', 'false');
};

menuButton.addEventListener('click', (event) => {
event.preventDefault();
const isHidden = mobileMenu.classList.contains('hidden');

if (isHidden) {
mobileMenu.classList.remove('hidden');
menuButton.setAttribute('aria-expanded', 'true');
} else {
closeMenu();
}
});

mobileMenu.querySelectorAll('a').forEach((link) => {
link.addEventListener('click', () => {
closeMenu();
});
});

document.addEventListener('click', (event) => {
if (
!mobileMenu.classList.contains('hidden') &&
!mobileMenu.contains(event.target) &&
!menuButton.contains(event.target)
) {
closeMenu();
}
});
});
8 changes: 4 additions & 4 deletions techblog_cms/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@
{% include 'components/header.html' %}
{% endblock %}

<div class="container mx-auto mt-6 px-4">
<div class="flex flex-col md:flex-row">
<div class="container mx-auto px-4 pt-24 md:pt-28">
<div class="flex flex-col md:flex-row md:items-start gap-8">
{% block sidebar %}
{% include 'components/sidebar.html' %}
{% endblock %}

{% block content %}
{% include 'components/main_content.html' %}
{% endblock %}
</div>
</div>

{% block scripts %}
<script src="{% static 'js/article.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
{% endblock %}
</body>
</html>
54 changes: 34 additions & 20 deletions techblog_cms/templates/components/header.html
Original file line number Diff line number Diff line change
@@ -1,40 +1,54 @@
<header class="fixed top-0 w-full bg-white shadow-sm z-50">
<nav class="container mx-auto px-4">
<div class="flex flex-wrap items-center justify-between h-20">
<div class="flex items-center justify-between h-20">
<!-- Logo -->
<a href="{% url 'home' %}" class="text-xl font-bold text-gray-800">
<a href="{% url 'home' %}" class="flex-shrink-0 text-xl font-bold text-gray-800">
{{ blog_title|default:"TechBlog CMS" }}
</a>

<!-- Search Box (medium screens and up) -->
<div class="hidden md:block flex-grow max-w-xl mx-4">
<input type="text" id="searchBox" placeholder="キーワードとユーザー検索"
<input type="text" id="searchBox" placeholder="キーワードとユーザー検索"
class="w-full px-4 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>

<!-- Mobile menu button -->
<button id="mobile-menu-button" class="md:hidden">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
<div class="flex items-center space-x-3">
<!-- Navigation and Auth buttons (desktop) -->
<div class="hidden md:flex items-center space-x-4">
<a href="{% url 'home' %}" class="text-gray-600 hover:text-gray-900">Home</a>
<a href="{% url 'article_list' %}" class="text-gray-600 hover:text-gray-900">Articles</a>
<a href="{% url 'categories' %}" class="text-gray-600 hover:text-gray-900">Categories</a>
<a href="{% url 'login' %}" class="px-4 py-2 bg-blue-500 text-white font-semibold rounded hover:bg-blue-600 transition">
ログイン
</a>
<a href="#" class="text-blue-500 hover:underline">ユーザー登録</a>
</div>

<!-- Navigation and Auth buttons -->
<div class="hidden md:flex items-center space-x-4">
<a href="{% url 'home' %}" class="text-gray-600 hover:text-gray-900">Home</a>
<a href="{% url 'article_list' %}" class="text-gray-600 hover:text-gray-900">Articles</a>
<a href="{% url 'categories' %}" class="text-gray-600 hover:text-gray-900">Categories</a>
<a href="{% url 'login' %}" class="px-4 py-2 bg-blue-500 text-white font-semibold rounded hover:bg-blue-600 transition">
ログイン
</a>
<a href="#" class="text-blue-500 hover:underline">ユーザー登録</a>
<!-- Mobile menu button -->
<button id="mobile-menu-button" type="button"
class="md:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
aria-controls="mobile-menu" aria-expanded="false" aria-label="メインメニューを開く">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
</div>

<!-- Mobile search (visible on mobile only) -->
<div class="md:hidden pb-4">
<input type="text" id="mobileSearchBox" placeholder="キーワードとユーザー検索"
<input type="text" id="mobileSearchBox" placeholder="キーワードとユーザー検索"
class="w-full px-4 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>

<!-- Mobile navigation menu -->
<div id="mobile-menu" class="hidden md:hidden pb-4 border-t border-gray-200">
<div class="pt-4 flex flex-col space-y-3">
<a href="{% url 'login' %}" class="text-gray-700 hover:text-blue-600 font-medium">ログイン</a>
<a href="{% url 'article_list' %}" class="text-gray-700 hover:text-blue-600 font-medium">記事一覧</a>
<a href="{% url 'categories' %}" class="text-gray-700 hover:text-blue-600 font-medium">カテゴリ一覧</a>
<a href="{% url 'tags' %}" class="text-gray-700 hover:text-blue-600 font-medium">タグ一覧</a>
</div>
</div>
</nav>
</header>
2 changes: 1 addition & 1 deletion techblog_cms/templates/components/sidebar.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<aside class="sidebar w-full md:w-64 md:mr-8">
<aside class="sidebar hidden md:block md:w-64 md:flex-shrink-0">
<div class="mb-8">
<h3 class="text-lg font-semibold mb-4">Categories</h3>
<ul class="space-y-2">
Expand Down
34 changes: 34 additions & 0 deletions techblog_cms/templates/tag_detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{% extends 'base.html' %}

{% block title %}{{ tag.name }} - Tags - {{ block.super }}{% endblock %}

{% block content %}
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex flex-wrap items-center justify-between gap-4">
<div>
<h1 class="text-3xl font-bold text-gray-800">#{{ tag.name }}</h1>
<p class="text-gray-600 mt-2">{{ articles|length }} 件の記事がこのタグに紐付いています。</p>
</div>
<a href="{% url 'tags' %}" class="text-blue-500 hover:underline">← タグ一覧に戻る</a>
</div>

<div class="mt-6 space-y-4">
{% for article in articles %}
<div class="border-b border-gray-200 pb-4">
<h2 class="text-xl font-semibold text-blue-600 hover:text-blue-800">
<a href="{% url 'article_detail' article.slug %}">{{ article.title }}</a>
</h2>
<p class="text-gray-600 mt-2">{{ article.excerpt|default:article.content|truncatewords:30 }}</p>
<div class="text-sm text-gray-500 mt-2">
<span>Published: {{ article.created_at|date:"M d, Y" }}</span>
{% if article.category %}
<span class="ml-4">Category: {{ article.category.name }}</span>
{% endif %}
</div>
</div>
{% empty %}
<p class="text-gray-500">このタグが付いた記事はまだありません。</p>
{% endfor %}
</div>
</div>
{% endblock %}
22 changes: 22 additions & 0 deletions techblog_cms/templates/tag_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% extends 'base.html' %}

{% block title %}Tags - {{ block.super }}{% endblock %}

{% block content %}
<div class="bg-white rounded-lg shadow-md p-6">
<h1 class="text-3xl font-bold text-gray-800 mb-6">タグ一覧</h1>

{% if tags %}
<div class="flex flex-wrap gap-3">
{% for tag in tags %}
<a href="{% url 'tag' slug=tag.slug %}"
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-full hover:bg-blue-100 hover:text-blue-700 transition">
#{{ tag.name }}
</a>
{% endfor %}
</div>
{% else %}
<p class="text-gray-500">タグはまだ登録されていません。</p>
{% endif %}
</div>
{% endblock %}
2 changes: 2 additions & 0 deletions techblog_cms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
re_path(r'^articles/(?P<slug>[\w\-]+)/$', views.article_detail_view, name='article_detail'),
path('categories/', views.categories_view, name='categories'),
path('categories/<slug:slug>/', views.category_view, name='category'),
path('tags/', views.tags_view, name='tags'),
path('tags/<slug:slug>/', views.tag_view, name='tag'),
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
path('dashboard/', views.dashboard_view, name='dashboard'),
Expand Down
93 changes: 87 additions & 6 deletions techblog_cms/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
from django.core.paginator import Paginator
from .models import Article, Category
from .models import Article, Category, Tag
from techblog_cms.templatetags.markdown_filter import markdown_to_html
from django.conf import settings
from django.http import HttpResponseNotFound
Expand All @@ -18,28 +18,109 @@ def index(request):

def home_view(request):
articles = Article.objects.filter(published=True).order_by('-created_at')[:10]
return render(request, 'home.html', {'articles': articles})
categories = Category.objects.all()
tags = Tag.objects.all()
return render(
request,
'home.html',
{
'articles': articles,
'categories': categories,
'tags': tags,
},
)

def article_list_view(request):
articles = Article.objects.filter(published=True).order_by('-created_at')
return render(request, 'article_list.html', {'articles': articles})
categories = Category.objects.all()
tags = Tag.objects.all()
return render(
request,
'article_list.html',
{
'articles': articles,
'categories': categories,
'tags': tags,
},
)

def categories_view(request):
categories = Category.objects.all()
return render(request, 'category_list.html', {'categories': categories})
tags = Tag.objects.all()
return render(
request,
'category_list.html',
{
'categories': categories,
'tags': tags,
},
)

def category_view(request, slug):
category = get_object_or_404(Category, slug=slug)
articles = category.article_set.filter(published=True).order_by('-created_at')
return render(request, 'category_detail.html', {'category': category, 'articles': articles})
categories = Category.objects.all()
tags = Tag.objects.all()
return render(
request,
'category_detail.html',
{
'category': category,
'articles': articles,
'categories': categories,
'tags': tags,
},
)

def tags_view(request):
tags = Tag.objects.all()
categories = Category.objects.all()
return render(
request,
'tag_list.html',
{
'tags': tags,
'categories': categories,
},
)

def tag_view(request, slug):
tag = get_object_or_404(Tag, slug=slug)
if request.user.is_authenticated:
articles = tag.article_set.order_by('-created_at')
else:
articles = tag.article_set.filter(published=True).order_by('-created_at')

categories = Category.objects.all()
tags = Tag.objects.all()
return render(
request,
'tag_detail.html',
{
'tag': tag,
'articles': articles,
'categories': categories,
'tags': tags,
},
)

def article_detail_view(request, slug):
# ログインしている場合は下書き記事も表示可能
if request.user.is_authenticated:
article = get_object_or_404(Article, slug=slug)
else:
article = get_object_or_404(Article, slug=slug, published=True)
return render(request, 'article_detail.html', {'article': article})
categories = Category.objects.all()
tags = Tag.objects.all()
return render(
request,
'article_detail.html',
{
'article': article,
'categories': categories,
'tags': tags,
},
)
def admin_guard(request):
"""Direct /admin/ access guard. Show 404 if HIDE_ADMIN_URL is True."""
if getattr(settings, 'HIDE_ADMIN_URL', False):
Expand Down