Django ブログサイト templates/detail.html 詳細画面
2020/07/04 (更新:2020/11/19)
詳細画面です。
テンプレート
head
{% extends 'blog/blog_base.html' %}
{% load i18n static %}
{% block head_title %}{% block head_post_title %}{{ object.content.title_text }}{% endblock %} - {% block head_author_title %}{{ view.kwargs.author.title_text }}{% endblock %}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css">
{% endblock %}
blog_main
{% block blog_main %}
<h1 class="mt-3">{% block post_title %}{{ object.content.title_text }}{% endblock %}</h1>
<p class="ml-1 mb-0">
<span class="post-created">{{ object.created_date|date:"Y/m/d" }}</span>
<span class="post-updated">({% trans 'Updated' %}:{{ object.updated_date|date:"Y/m/d" }})</span>
</p>
<div>
{% for bc in object.categories %}
<a href="#" data-category-text="{{ bc.category.category_text }}" class="badge category badge-light"><span>{{ bc.category.category_text }}</span></a>
{% endfor %}
</div>
<div class="mt-5">
{% block post_content %}{% endblock %}
</div>
{% if object.is_comment %}
<div>
<h2>{% trans 'Comment' %}</h2>
{% for comment in object.comments %}
<div class="comment" data-id="{{ comment.id }}" data-status="{{ comment.status }}">
<div>
<div class="comment-summary">
<span class="comment-name">{{ comment.name_text|default:'****' }}</span>
<span class="comment-pipe">|</span>
<span class="comment-created">{{ comment.created_date|date:"Y/m/d" }}</span>
{% if request.user.is_authenticated %}
<span class="comment-pipe">|</span>
<span class="comment-status">{{ comment.status_name }}</span>
{% endif %}
</div>
{% if request.user.is_authenticated %}
<div class="float-right">
{% if object.is_comment_edit %}
<button type="button" class="comment-edit btn btn-light">{% trans 'Edit' %}</button>
{% endif %}
{% if object.is_comment_reply %}
<button type="button" class="comment-reply btn btn-light">{% trans 'Reply' %}</button>
{% endif %}
</div>
{% endif %}
</div>
{{ comment.comment_text|linebreaks }}
{% if comment.replies %}
<div class="reply-container ml-5">
{% for reply in comment.replies %}
<div class="comment" data-id="{{ reply.id }}" data-status="{{ reply.status }}">
<div>
<div class="comment-summary">
<span class="comment-name">{{ reply.name_text|default:'****' }}</span>
<span class="comment-pipe">|</span>
<span class="comment-created">{{ reply.created_date|date:"Y/m/d" }}</span>
{% if request.user.is_authenticated %}
<span class="comment-pipe">|</span>
<span class="comment-status">{{ reply.status_name }}</span>
{% endif %}
</div>
<div class="float-right">
{% if object.is_comment_edit %}
<button type="button" class="comment-edit btn btn-light">{% trans 'Edit' %}</button>
{% endif %}
<button type="button" class="comment-reply btn btn-light invisible">{% trans 'Reply' %}</button>
</div>
</div>
{{ reply.comment_text|linebreaks }}
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% empty %}
<p>{% trans 'COMMENT_NOTHING' %}</p>
{% endfor %}
{% if object.is_comment_entry %}
<div class="mb-3">
<button type="button" id="comment_entry" class="btn btn-light">{% trans 'Comment Entry' %}</button>
</div>
{% endif %}
</div>
{% endif %}
modal
<div id="code_modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="code-content">
<div class="code-btn">
<button type="button" id="code_modal_clipboard" alt="Copy to clipboard">Copy</button>
</div>
<div class="code-body">
</div>
</div>
</div>
</div>
</div>
</div>
{% if object.is_comment %}
<div id="comment_modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Comment Entry" %}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="alert-container"></div>
<div class="comment-wrapper"></div>
<p id="comment_modal_message"></p>
<form id="comment_form" action="{% url 'blog:comment' view.kwargs.author_name object.id %}" method="POST" onsubmit="return false;">
<div class="form-group">
<label for="name_text">{% trans 'Name' %}</label>
<input type="text" id="name_text" name="name_text" placeholder="****">
</div>
<div class="form-group">
<label for="comment_text">{% trans 'Comment' %}</label><span class="pl-1">*</span>
<textarea id="comment_text" name="comment_text" class="form-control" rows="6"></textarea>
<div class="text-right">
<span class="comment-length"><span id="comment_length">0</span>/1000</span>
</div>
</div>
{% csrf_token %}
</form>
<p class="comment-note">
{% trans "COMMENT_NOTE" %}
</p>
</div>
<div class="modal-footer">
<button type="button" id="comment_confirm" class="btn btn-primary">{% trans 'Confirm' %}</button>
<button type="button" id="comment_back" class="btn btn-second">{% trans 'Back' %}</button>
<button type="button" id="comment_save" class="btn btn-primary">{% trans 'Save' %}</button>
</div>
</div>
</div>
</div>
{% if object.is_comment_edit %}
<div id="comment_edit_modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Comment Edit" %}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="comment-wrapper"></div>
<form id="comment_edit_form" method="POST" onsubmit="return false;">
<div class="form-group">
{{ comment_form.status }}
</div>
{% csrf_token %}
</form>
</div>
<div class="modal-footer">
<button type="button" id="comment_update" class="btn btn-primary">{% trans 'Update' %}</button>
</div>
</div>
</div>
</div>
{% endif %}
{% endif %}
URL とメッセージを定義し、ロジックは Javascript に実装します。
script
{% block extra_js %}
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.6/clipboard.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script>
$.extend(base, {
detail: {
actions: {
comment: '{% url "blog:comment" view.kwargs.author_name object.id %}',
reply: '{% url "blog:reply" view.kwargs.author_name 0 %}',
comment_update: '{% url "blog:comment_update" view.kwargs.author_name 0 %}'
},
messages: {
'Comment Entry': '{% trans "Comment Entry" %}',
'Reply Entry': '{% trans "Reply Entry" %}',
'COMMENT_ENTRY': '{% trans "COMMENT_ENTRY" %}',
'COMMENT_CONFIRM': '{% trans "COMMENT_CONFIRM" %}'
}
}
});
</script>
<script src="{% static 'blog/js/detail.js' %}"></script>
{% endblock %}
Javascript
コメントの登録は Ajax で処理します。
| コメント登録 | comment |
| コメント更新 | comment_update |
| 返信登録 | reply |
detail.js
$(function() {
// Comment Entry
$('#comment_entry').click(function() {
commentEntryModel(base.detail.actions.comment, 1);
commentSaveDone = function(data) {
showAlertModal('info', data.message);
};
});
$('#comment_confirm').click(function() {
commentConfirm();
});
$('#comment_save').click(function() {
commentSave();
});
$('#comment_back').click(function() {
commentEntry();
});
// Comment Reply
$('.comment-reply').click(function() {
var $comment = cloneComment($(this).closest('.comment'));
$('#comment_modal .comment-wrapper').empty().append($comment);
var action = base.actions.reply.replace(/\/0\//, f('/{0}/', $comment.data('id')));
commentEntryModel(action, 2);
commentSaveDone = function(data) {
redirect(20, data.message);
};
});
$('#comment_text').keydown(commentLength).blur(commentLength);
// Comment Edit
var $commentEdit;
$('.comment-edit').click(function() {
$commentEdit = $(this).closest('.comment');
var $comment = cloneComment($commentEdit);
var action = base.actions.comment_update.replace(/\/0\//, f('/{0}/', $comment.data('id')));
$('#comment_edit_form').attr('action', action);
$('#comment_edit_modal .comment-wrapper').empty().append($comment);
$('#comment_edit_form select[name="status"]').val($comment.data('status'));
$('#comment_edit_modal').modal('show');
});
$('#comment_update').click(function() {
commentUpdate($commentEdit);
});
// Code Clipboard
initClipboard('.clipboard', {
text: function(trigger) {
return $.trim($(trigger).closest('.code-content').find('code').text());
}
});
// Code Popup
var $modal = $('#code_modal');
$('.popup').click(function() {
var $codeContent = $(this).closest('.code-content');
var $pre = $codeContent.find('pre').clone();
$modal.find('.modal-title').text($codeContent.find('.code-title').text());
$modal.find('.code-body').empty().append($pre);
$modal.modal('show');
}).attr('data-original-title', 'Show with modal').tooltip();
initClipboard('#code_modal_clipboard', {
container: $modal[0],
text: function(trigger) {
return $.trim($(trigger).closest('.code-content').find('code').text());
}
});
});
function initClipboard(selector, options) {
new ClipboardJS(selector, options).on('success', function(e) {
showTooltip($(e.trigger), 'Copied!');
e.clearSelection();
}).on('error', function(e) {
showTooltip($(e.trigger), 'Error!');
e.clearSelection();
});
$(selector).attr('data-original-title', 'Copy to clipboard').tooltip();
}
function showTooltip($target, msg) {
var original = $target.attr('data-original-title');
$target.attr('data-original-title', msg).tooltip('show').attr('data-original-title', original);
}
function commentLength() {
var length = $(this).val().length;
$('#comment_length').text(length);
$('#comment_confirm').prop('disabled', !length);
}
function cloneComment($comment) {
var $clone = $comment.clone();
$clone.data('id', $comment.data('id'));
$clone.data('status', $comment.data('status'));
$clone.find('.comment-edit').parent().remove();
$clone.find('.reply-container').remove();
return $clone;
}
function commentEntryModel(action, mode) {
var $modal = $('#comment_modal');
$modal.find('form').attr('action', action);
$modal.find('.alert-container').empty();
$modal.find('.modal-title').text(mode == 1? base.detail.messages['Comment Entry'] : base.detail.messages['Reply Entry']);
$modal.find('.comment-wrapper').toggle(mode != 1);
$modal.find('.comment-note').toggle(mode == 1);
$modal.modal('show');
commentEntry();
$('#name_text,#comment_text').val('');
}
function commentEntry() {
$('#comment_modal_message').text(base.detail.messages['COMMENT_ENTRY']);
enabled($('#name_text,#comment_text'), true);
$('#comment_confirm').show();
$('#comment_back,#comment_save').hide();
$('#comment_text').focus();
}
function commentConfirm() {
$('#comment_modal .alert-container').empty();
$('#comment_modal_message').text(base.detail.messages['COMMENT_CONFIRM']);
enabled($('#name_text,#comment_text'), false);
$('#comment_confirm').hide();
$('#comment_back,#comment_save').show();
}
var commentSaveDone;
function commentSave() {
requestByForm($('#comment_form'),
function(data, textStatus, jqXHR) {
if (data.status == 1) {
$('#comment_modal').modal('hide');
commentSaveDone(data);
} else {
commentEntry();
showAlert('error', data.message, $('#comment_modal .alert-container').empty());
}
}
);
}
// Comment Edit
function commentUpdate($comment) {
requestByForm($('#comment_edit_form'),
function(data, textStatus, jqXHR) {
if (data.status == 1) {
showAlertModal('info', data.message);
$comment.data('status', data.data.status);
$comment.find('.comment-status').text(data.data.status_name);
} else {
showAlertModal('error', data.message);
}
$('#comment_edit_modal').modal('hide');
}
);
}
function enabled($targets, flag) {
$targets.toggleClass('form-control', flag)
.toggleClass('form-control-plaintext', !flag)
.prop('readonly', !flag);
}