diff --git a/board/forms.py b/board/forms.py index 14b9d73..b04d735 100644 --- a/board/forms.py +++ b/board/forms.py @@ -1,6 +1,6 @@ from django.conf import settings from django.forms import ModelForm -from board.models import Post +from board.models import Post, Report from hcaptcha.fields import hCaptchaField @@ -41,3 +41,20 @@ class ReplyForm(PostForm): super(ReplyForm, self).__init__(*args, **kwargs) self.instance.op = op self.instance.reply = reply + + +class ReportForm(ModelForm): + """ + A form used to create reports on posts. + + This requires the board and the IP address to be specified. + """ + + class Meta: + model = Report + fields = ["reason"] + + def __init__(self, *args, op, board, ip, **kwargs): + super(ReportForm, self).__init__(*args, **kwargs) + self.instance.ip = ip + self.instance.post = op diff --git a/board/models.py b/board/models.py index 5e2565d..371963d 100644 --- a/board/models.py +++ b/board/models.py @@ -212,7 +212,7 @@ class ReportReason(models.Model): # Urgency. If true, this post is probably reported as illegal content. urgent = models.BooleanField(default=False) # This is a board-specific report reason - board = models.ForeignKey("Board", on_delete=models.CASCADE, null=True) + board = models.ForeignKey("Board", on_delete=models.CASCADE, null=True, blank=True) def __str__(self): return self.reason diff --git a/board/static/board/post.js b/board/static/board/post.js index f6f4737..bf14495 100644 --- a/board/static/board/post.js +++ b/board/static/board/post.js @@ -1,9 +1,81 @@ -function doQuote(sender) { - let id_text = $("#id_text"); - let caret = id_text[0].selectionStart; - let text = id_text.val(); - let to_add = ">>" + sender.target.innerText + "\n"; - id_text.val(text.substring(0, caret) + to_add + text.substring(caret)); +const OPEN = "open"; +const CLOSED = "closed"; + +function documentClick(e) { + let sender = e.target; + let id = sender.getAttribute("data-id"); + if (!id) { + return; + } + + switch (id) { + case "post_menu_button": { + openMenu(e); + }; break; + } } -$(document).on("click", ".post_id", doQuote); \ No newline at end of file +function doQuote(e) { + let idText = $("#id_text"); + let caret = idText[0].selectionStart; + let text = idText.val(); + let toAdd = ">>" + e.target.innerText + "\n"; + idText.val(text.substring(0, caret) + toAdd + text.substring(caret)); +} + +function closeMenu(e) { + $(document).off("click", closeMenu); + $(".post_menu").remove(); +} + +function openMenu(e) { + e.preventDefault(); + let sender = e.target; + let reportButton = $("") + .text("Report") + .attr("href", "#") + .on("click", (e) => { return openReportWindow(e, $(sender.parentElement)); }); + let menuList = $("") + .append($("Actions").addClass("post_menu_item")) + .append( + $('
  • ') + .addClass("post_menu_item") + .append(reportButton) + ) + .addClass("post_menu_items"); + let rect = sender.getBoundingClientRect(); + let menu = $("
    ") + .addClass("post_menu") + .css({ + top: rect.bottom + 3 + window.pageYOffset + "px", + left: rect.left + 3 + window.pageXOffset + "px", + }) + .append(menuList); + $("body").append(menu); + $(document).on("click", closeMenu); +} + +function openReportWindow(e, postElement) { + e.preventDefault(); + // If there's already a report window open, close it and open this one. + if (window.top.reportWindow) { + window.top.reportWindow.close(); + } + //let postId = sender.parentElement.getAttribute("id").substring(1); + let reportUrl = postElement.attr("data-report-url"); + window.reportWindow = new WinBox("New Report", { + url: reportUrl, + modal: true, + onclose: function (force) { + window.top.reportWindow = null; + } + }); +} + +function onLoad(e) { + window.reportWindow = null; +} + +$(document).on("click", documentClick); +$(document).on("click", ".post_id", doQuote); +$(window).on("load", onLoad); \ No newline at end of file diff --git a/board/static/board/style.css b/board/static/board/style.css index f0b7e1b..c47dd3d 100644 --- a/board/static/board/style.css +++ b/board/static/board/style.css @@ -1,3 +1,7 @@ +body { + background-color: #ededed; +} + hr { color: #ededed; } @@ -26,7 +30,29 @@ hr { text-align: center; } -/* Posts */ +.post_menu { + position: absolute; + background: #ededed; + outline: 1px solid #555; +} + +.post_menu_items { + list-style-type: none; + padding: 0; + margin: 0; +} + +.post_menu_item { + padding: 3px; +} + +.post_menu_button { + text-decoration: none; +} + +/******************************************************************************* + Posts +********************************************************************************/ /*.post_body { }*/ .post_image_info { font-size: small; diff --git a/board/templates/board/post_snippet.html b/board/templates/board/post_snippet.html index f20bf0c..ec86764 100644 --- a/board/templates/board/post_snippet.html +++ b/board/templates/board/post_snippet.html @@ -1,6 +1,6 @@ {% load post_body %} {% load l10n %} -
    +
    {# Image #} {% if post.thumbnail %} {# Image info #} @@ -19,7 +19,6 @@ {% endif %} {# Post ID, username, time #} -
    #. {{post.id}} {% if post.subject %} @@ -32,8 +31,10 @@ {% if reply_link %} [{% localize on %}Reply{% endlocalize %}] {% endif %} + {# "X replies elided" dialog for OPs on the board #} +
    {% if replies_elided > 0 %}
    diff --git a/board/templates/board/report_form.html b/board/templates/board/report_form.html new file mode 100644 index 0000000..c610a97 --- /dev/null +++ b/board/templates/board/report_form.html @@ -0,0 +1,16 @@ +{% extends "board/base.html" %} +{% load l10n %} +{# Title #} +{% block title %}{% localize on %}Reporting post {{post.id}}{% endlocalize %}{% endblock %} +{# Body #} +{% block content %} +
    +
    + {% csrf_token %} + + {{ form.as_table }} + +
     
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/board/templates/board/report_success.html b/board/templates/board/report_success.html new file mode 100644 index 0000000..6b25fa8 --- /dev/null +++ b/board/templates/board/report_success.html @@ -0,0 +1,29 @@ +{% extends "board/base.html" %} +{% load l10n %} +{# Title #} +{% block title %}{% localize on %}Report success{% endlocalize %}{% endblock %} +{# Body #} +{% block content %} +
    + {% localize on %}Post reported. This window will close in 1 second.{% endlocalize %} +
    + + +{% endblock %} \ No newline at end of file diff --git a/board/urls.py b/board/urls.py index e9ea480..5085fd4 100644 --- a/board/urls.py +++ b/board/urls.py @@ -1,6 +1,7 @@ from django.urls import path from django.conf import settings from django.conf.urls.static import static +from django.views.generic.base import TemplateView from board.views import * @@ -9,6 +10,12 @@ urlpatterns = [ path("/", BoardView.as_view(), name="board_detail"), path("/page//", BoardView.as_view(), name="board_detail"), path("/post//", PostView.as_view(), name="post_detail"), + path("/report//", ReportView.as_view(), name="report_form"), + path( + "report/success/", + TemplateView.as_view(template_name="board/report_success.html"), + name="report_success", + ), ] # TODO - make this conditional so we can serve images up with whatever server we want urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/board/views.py b/board/views.py index ef0fd7e..e829d81 100644 --- a/board/views.py +++ b/board/views.py @@ -3,11 +3,11 @@ from django.http import Http404, HttpResponseRedirect from django.shortcuts import render, get_object_or_404 from django.views.generic import DetailView from django.views.generic.edit import CreateView -from django.urls import reverse -from board.models import Post, Board -from board.forms import PostForm, ReplyForm +from django.urls import reverse, reverse_lazy +from board.models import Post, Board, Report +from board.forms import PostForm, ReplyForm, ReportForm -__all__ = ("BoardView", "PostView") +__all__ = ("BoardView", "PostView", "ReportView") def get_client_ip(request): @@ -107,3 +107,27 @@ class PostView(CreatePostView): kwargs["op"] = post kwargs["reply"] = post return kwargs + + +class ReportView(CreatePostView): + model = Report + form_class = ReportForm + success_url = reverse_lazy("board:report_success") + + def get_context_data(self, **kwargs): + return super(ReportView, self).get_context_data(**kwargs) + + @property + def board_url(self) -> str: + return self.kwargs["url"] + + @property + def post_id(self) -> int: + return self.kwargs["id"] + + def get_form_kwargs(self): + kwargs = super(ReportView, self).get_form_kwargs() + post_id = self.kwargs["id"] + post = get_object_or_404(Post, id=post_id) + kwargs["op"] = post + return kwargs