Add floating window for new threads

New threads get a floating window just like new replies have. This only
pops up on the board detail view.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-06-28 23:55:44 -07:00
parent 6ad2a72b86
commit 6c11d210e8
8 changed files with 151 additions and 58 deletions

View File

@@ -22,8 +22,9 @@ class PostForm(ModelForm):
model = Post model = Post
fields = ["subject", "name", "text", "image"] fields = ["subject", "name", "text", "image"]
def __init__(self, *args, board, ip, **kwargs): def __init__(self, *args, user, board, ip, **kwargs):
super(PostForm, self).__init__(*args, **kwargs) super(PostForm, self).__init__(*args, **kwargs)
self.user = user
self.instance.board = board self.instance.board = board
self.instance.ip = ip self.instance.ip = ip
@@ -42,10 +43,9 @@ class ReplyForm(PostForm):
model = Post model = Post
fields = ["name", "text", "bump", "capcode", "image"] fields = ["name", "text", "bump", "capcode", "image"]
def __init__(self, *args, user, op, **kwargs): def __init__(self, *args, op, **kwargs):
super(ReplyForm, self).__init__(*args, **kwargs) super(ReplyForm, self).__init__(*args, **kwargs)
self.instance.op = op self.instance.op = op
self.user = user
def clean(self): def clean(self):
super().clean() super().clean()

View File

@@ -1,6 +1,7 @@
const OPEN = "open"; const OPEN = "open";
const CLOSED = "closed"; const CLOSED = "closed";
const replyWindowName = "reply-window"; const replyWindowName = "reply-window";
const postWindowName = "post-window";
function documentClick(e) { function documentClick(e) {
let sender = e.target; let sender = e.target;
@@ -59,7 +60,7 @@ function openReplyWindow(replyUrl) {
if (window.top.jsFrame.containsWindowName(replyWindowName)) { if (window.top.jsFrame.containsWindowName(replyWindowName)) {
// if there's already a new reply window, don't override it and just let // if there's already a new reply window, don't override it and just let
// it continue to exist. // it continue to exist.
return; return getReplyWindow();
} }
let replyWindow = window.top.jsFrame.create({ let replyWindow = window.top.jsFrame.create({
@@ -70,6 +71,7 @@ function openReplyWindow(replyUrl) {
url: replyUrl url: replyUrl
}); });
replyWindow.show(); replyWindow.show();
return replyWindow;
} }
function getReplyWindow() { function getReplyWindow() {
@@ -77,19 +79,50 @@ function getReplyWindow() {
} }
function replyTextbox() { function replyTextbox() {
return $("iframe").contents().find("#id_text"); replyWindow = getReplyWindow();
if (!replyWindow) {
return null;
}
return replyWindow.$("#id_text")
} }
function replyAppend(toAdd) { function replyAppend(toAdd) {
let replyWindow = getReplyWindow();
let textbox = replyTextbox(); let textbox = replyTextbox();
if (textbox.length === 0) { if (!textbox) {
return; $(replyWindow.iframe, "#id_text").on("load", (e) => {
} replyAppend(toAdd);
let caret = textbox[0].selectionStart; })
} else {
let caret = textbox.selectionStart;
textbox = $(textbox);
let text = textbox.val(); let text = textbox.val();
textbox.val(text.substring(0, caret) + toAdd + text.substring(caret)); textbox.val(text.substring(0, caret) + toAdd + text.substring(caret));
textbox.focus(); textbox.focus();
} }
}
////////////////////////////////////////////////////////////////////////////////
// Reply window
////////////////////////////////////////////////////////////////////////////////
function openPostWindow(postUrl) {
if (window.top.jsFrame.containsWindowName(postWindowName)) {
return;
}
let postWindow = window.top.jsFrame.create({
title: "New Thread",
name: postWindowName,
width: 385,
height: 350,
url: postUrl,
});
postWindow.show();
}
function getPostWindow() {
return window.top.jsFrame.getWindowByName(postWindowName);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Events // Events

View File

@@ -23,23 +23,9 @@
<div class="column">&nbsp;</div> <div class="column">&nbsp;</div>
<div class="column"> <div class="column">
<div class="row"> <div class="row">
<h2>{% translate "Create a new thread" %}</h2> <h2>
</div> <a id="create_post" href="{% url 'board:post_create' url=board.url %}">{% translate "Create a new thread" %}</a>
<div class="row"> </h2>
<form method="post" action="{% url 'board:board_detail' url=board.url %}" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ form.as_table }}
<tr>
<th>&nbsp;</th>
<td>
{% translate "Max image size" %}:
{{ max_upload_size|measure_bytes }}
</td>
</tr>
<tr><th>&nbsp;</th><td><input type="submit" value="Submit" /></td></tr>
</table>
</form>
</div> </div>
</div> </div>
<div class="column">&nbsp;</div> <div class="column">&nbsp;</div>
@@ -85,4 +71,12 @@
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</div> </div>
<script>
const POST_URL = "{% url 'board:post_create' url=board.url %}";
$("#create_post").on("click", (e) => {
e.preventDefault();
openPostWindow(POST_URL);
});
</script>
{% endblock content %} {% endblock content %}

View File

@@ -0,0 +1,34 @@
{% extends "board/base.html" %}
{% load i18n post_body %}
{% block content %}
<form method="post" action="{% url 'board:post_create' url=board.url %}" enctype="multipart/form-data">
{% csrf_token %}
<table>
{% for field in form %}
{% if field.name != "capcode" %}
<tr>
<th>{{field.label_tag}}</th>
<td>{{field}}</td>
</tr>
{% endif %}
{% endfor %}
{% if capcodes %}
<tr>
<th>{{form.capcode.label_tag}}</th>
<td>
{{form.capcode}}
</td>
</tr>
{% endif %}
<tr>
<th>&nbsp;</th>
<td>
{% translate "Max image size" %}:
{{ max_upload_size|measure_bytes }}
</td>
</tr>
<tr><td>&nbsp;</td><td><input type="submit" value="Submit" /></td></tr>
</table>
</form>
{% endblock content %}

View File

@@ -54,14 +54,7 @@ $("#create_reply").on("click", (e) => {
$(window).on("quote", (e, postId) => { $(window).on("quote", (e, postId) => {
openReplyWindow(REPLY_URL); openReplyWindow(REPLY_URL);
let toAdd = ">>" + postId + "\n"; let toAdd = ">>" + postId + "\n";
let textbox = replyTextbox();
if (typeof textbox === "undefined" || textbox.length === 0) {
$("iframe").on("load", () => {
replyAppend(toAdd); replyAppend(toAdd);
}); });
} else {
replyAppend(toAdd);
}
});
</script> </script>
{% endblock content %} {% endblock content %}

View File

@@ -15,16 +15,22 @@ function isIframe() {
} }
} }
$(window).on("load", () => {
setTimeout(function() { setTimeout(function() {
if(isIframe()) { let params = new URLSearchParams(window.location.search);
// check for possible windows let next = params.get("next");
let replyWindow = getReplyWindow(); let target = isIframe()
if(replyWindow) { ? window.top
replyWindow.closeFrame(); : window;
if(next) {
target.location = next;
if(next.includes("#")) {
target.location.reload();
} }
} else { } else {
window.close(); target.close();
} }
}, 1000 * {{window_timeout}}); }, 1000 * {{window_timeout}});
});
</script> </script>
{% endblock content %} {% endblock content %}

View File

@@ -10,6 +10,7 @@ urlpatterns = [
path("<slug:url>/", BoardView.as_view(), name="board_detail"), path("<slug:url>/", BoardView.as_view(), name="board_detail"),
path("<slug:url>/page/<int:page>/", BoardView.as_view(), name="board_detail"), path("<slug:url>/page/<int:page>/", BoardView.as_view(), name="board_detail"),
path("<slug:url>/post/<int:id>/", PostView.as_view(), name="post_detail"), path("<slug:url>/post/<int:id>/", PostView.as_view(), name="post_detail"),
path("<slug:url>/post/create/", PostCreateView.as_view(), name="post_create"),
path("<slug:url>/reply/<int:id>/", ReplyCreateView.as_view(), name="reply_create"), path("<slug:url>/reply/<int:id>/", ReplyCreateView.as_view(), name="reply_create"),
path("post/success/", PostSuccessView.as_view(), name="post_success"), path("post/success/", PostSuccessView.as_view(), name="post_success"),
# Reports # Reports

View File

@@ -2,9 +2,9 @@ from typing import Any, Dict
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model, get_user from django.contrib.auth import get_user_model, get_user
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q from django.db.models import Q
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.http.request import QueryDict
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.views.generic import edit from django.views.generic import edit
@@ -22,6 +22,7 @@ __all__ = (
"BanSuccessView", "BanSuccessView",
"BannedView", "BannedView",
"BoardView", "BoardView",
"PostCreateView",
"PostView", "PostView",
"PostSuccessView", "PostSuccessView",
"ReplyCreateView", "ReplyCreateView",
@@ -77,9 +78,8 @@ class CreateView(edit.CreateView):
return super(CreateView, self).dispatch(request, *args, **kwargs) return super(CreateView, self).dispatch(request, *args, **kwargs)
class BoardView(CreateView): class BoardView(TemplateView):
model = Post model = Board
form_class = PostForm
slug_field = "url" slug_field = "url"
slug_url_kwarg = "url" slug_url_kwarg = "url"
template_name = "board/board_detail.html" template_name = "board/board_detail.html"
@@ -92,6 +92,10 @@ class BoardView(CreateView):
) )
return super(BoardView, self).get(request, *args, **kwargs) return super(BoardView, self).get(request, *args, **kwargs)
def dispatch(self, request, *args, **kwargs):
self.board = get_object_or_404(Board, url=kwargs["url"])
return super(BoardView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
page = self.kwargs["page"] page = self.kwargs["page"]
@@ -114,6 +118,33 @@ class BoardView(CreateView):
return super(BoardView, self).get_context_data(**kwargs) return super(BoardView, self).get_context_data(**kwargs)
class PostCreateView(CreateView):
model = Post
form_class = PostForm
slug_field = "url"
slug_url_kwarg = "url"
template_name = "board/post_create_view.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["board"] = self.board
context["max_upload_size"] = settings.MAX_UPLOAD_SIZE
context["capcodes"] = get_objects_for_user(
get_user(self.request), "board.use_capcode"
)
return context
def get_form_kwargs(self):
kwargs = super(PostCreateView, self).get_form_kwargs()
kwargs["user"] = self.request.user
return kwargs
def get_success_url(self) -> str:
query = QueryDict(mutable=True)
query["next"] = self.get_form().instance.get_absolute_url()
return reverse("board:post_success") + "?" + query.urlencode()
class ReplyCreateView(CreateView): class ReplyCreateView(CreateView):
model = Post model = Post
form_class = ReplyForm form_class = ReplyForm
@@ -141,10 +172,14 @@ class ReplyCreateView(CreateView):
kwargs["user"] = self.request.user kwargs["user"] = self.request.user
return kwargs return kwargs
def get_success_url(self) -> str:
query = QueryDict(mutable=True)
query["next"] = self.get_form().instance.get_absolute_url()
return reverse("board:post_success") + "?" + query.urlencode()
class PostView(CreateView):
class PostView(TemplateView):
model = Post model = Post
form_class = ReplyForm
slug_field = "url" slug_field = "url"
slug_url_kwarg = "url" slug_url_kwarg = "url"
@@ -158,13 +193,10 @@ class PostView(CreateView):
return super(PostView, self).get_context_data(**kwargs) return super(PostView, self).get_context_data(**kwargs)
def get_form_kwargs(self): def dispatch(self, request, *args, **kwargs):
kwargs = super(PostView, self).get_form_kwargs() # Set the board on this object
post_id = self.kwargs["id"] self.board = get_object_or_404(Board, url=kwargs["url"])
post = get_object_or_404(Post, id=post_id) return super(PostView, self).dispatch(request, *args, **kwargs)
kwargs["op"] = post
kwargs["user"] = self.request.user
return kwargs
class PostSuccessView(TemplateView): class PostSuccessView(TemplateView):