summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2020-08-16 08:58:50 +0200
committerYorhel <git@yorhel.nl>2020-08-16 09:00:38 +0200
commit487c9a4a3fa4109559aa2a25184224b992705eef (patch)
tree9b6e3bf2f63c3bfabf135e5aac71fda5d5d3a554
parentf32a4b4f3038a4eed239b9b857aefd70281e076f (diff)
Discussions: Split post editing out of Discussions::Edit + support editing review comments
This split simplifies Discussions::Edit a little bit and allows Discussions::PostEdit to be generic enough to handle editing review comments as well.
-rw-r--r--elm/Discussions/Edit.elm30
-rw-r--r--elm/Discussions/PostEdit.elm108
-rw-r--r--lib/VNWeb/Discussions/Edit.pm93
-rw-r--r--lib/VNWeb/Discussions/PostEdit.pm88
-rw-r--r--lib/VNWeb/Reviews/Page.pm2
5 files changed, 244 insertions, 77 deletions
diff --git a/elm/Discussions/Edit.elm b/elm/Discussions/Edit.elm
index 9a98733b..6008cdef 100644
--- a/elm/Discussions/Edit.elm
+++ b/elm/Discussions/Edit.elm
@@ -26,7 +26,6 @@ main = Browser.element
type alias Model =
{ state : Api.State
, tid : Maybe String
- , num : Maybe Int
, can_mod : Bool
, can_private : Bool
, locked : Bool
@@ -50,7 +49,6 @@ init d =
, can_mod = d.can_mod
, can_private = d.can_private
, tid = d.tid
- , num = d.num
, locked = d.locked
, hidden = d.hidden
, private = d.private
@@ -73,7 +71,6 @@ searchConfig = { wrap = BoardSearch, id = "boardadd", source = A.boardSource }
encode : Model -> GDE.Send
encode m =
{ tid = m.tid
- , num = m.num
, locked = m.locked
, hidden = m.hidden
, private = m.private
@@ -148,8 +145,6 @@ update msg model =
view : Model -> Html Msg
view model =
let
- thread = model.tid == Nothing || model.num == Just 1
-
board n bd =
li [] <|
[ text "["
@@ -184,7 +179,7 @@ view model =
else text ""
]
- poll () =
+ poll =
[ tr [ class "newpart" ] [ td [ colspan 2 ] [ text "" ] ]
, formField "" [ label [] [ inputCheck "" model.pollEnabled PollEnabled, text " Add poll" ] ]
] ++
@@ -211,26 +206,22 @@ view model =
in
form_ Submit (model.state == Api.Loading)
[ div [ class "mainbox" ]
- [ h1 [] [ text <| if model.tid == Nothing then "Create new thread" else "Edit post" ]
+ [ h1 [] [ text <| if model.tid == Nothing then "Create new thread" else "Edit thread" ]
, table [ class "formtable" ] <|
- [ if thread
- then formField "title::Thread title" [ inputText "title" (Maybe.withDefault "" model.title) Title (style "width" "400px" :: required True :: GDE.valTitle) ]
- else formField "Topic" [ a [ href <| "/" ++ Maybe.withDefault "" model.tid ] [ text (Maybe.withDefault "" model.title) ] ]
- , if thread && model.can_mod
+ [ formField "title::Thread title" [ inputText "title" (Maybe.withDefault "" model.title) Title (style "width" "400px" :: required True :: GDE.valTitle) ]
+ , if model.can_mod
then formField "" [ label [] [ inputCheck "" model.locked Locked, text " Locked" ] ]
else text ""
, if model.can_mod
then formField "" [ label [] [ inputCheck "" model.hidden Hidden, text " Hidden" ] ]
else text ""
- , if thread && model.can_private
+ , if model.can_private
then formField "" [ label [] [ inputCheck "" model.private Private, text " Private" ] ]
else text ""
, if model.tid /= Nothing && model.can_mod
then formField "" [ label [] [ inputCheck "" model.nolastmod Nolastmod, text " Don't update last modification timestamp" ] ]
else text ""
- , if thread
- then formField "boardadd::Boards" (boards ())
- else text ""
+ , formField "boardadd::Boards" (boards ())
, tr [ class "newpart" ] [ td [ colspan 2 ] [ text "" ] ]
, formField "msg::Message"
[ TP.view "msg" model.msg Content 700 ([rows 12, cols 50] ++ GDE.valMsg)
@@ -239,15 +230,10 @@ view model =
]
]
]
- ++ (if thread then poll () else [])
+ ++ poll
++ (if not model.can_mod || model.tid == Nothing then [] else
[ tr [ class "newpart" ] [ td [ colspan 2 ] [ text "DANGER ZONE" ] ]
- , formField ""
- [ inputCheck "" model.delete Delete
- , text <| " Permanently delete this " ++ if thread then "thread and all replies." else "post."
- , text <| if thread then "" else " This causes all replies after this one to be renumbered."
- , text <| " This action can not be reverted, only do this with obvious spam!"
- ]
+ , formField "" [ inputCheck "" model.delete Delete, text " Permanently delete this thread and all replies. This action can not be reverted, only do this with obvious spam!" ]
])
]
, div [ class "mainbox" ]
diff --git a/elm/Discussions/PostEdit.elm b/elm/Discussions/PostEdit.elm
new file mode 100644
index 00000000..0eb787d2
--- /dev/null
+++ b/elm/Discussions/PostEdit.elm
@@ -0,0 +1,108 @@
+module Discussions.PostEdit exposing (main)
+
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Browser
+import Browser.Navigation exposing (load)
+import Lib.Html exposing (..)
+import Lib.TextPreview as TP
+import Lib.Api as Api
+import Gen.Api as GApi
+import Gen.DiscussionsPostEdit as GPE
+
+
+main : Program GPE.Recv Model Msg
+main = Browser.element
+ { init = \e -> (init e, Cmd.none)
+ , view = view
+ , update = update
+ , subscriptions = always Sub.none
+ }
+
+
+type alias Model =
+ { state : Api.State
+ , id : String
+ , num : Int
+ , can_mod : Bool
+ , hidden : Bool
+ , nolastmod : Bool
+ , delete : Bool
+ , msg : TP.Model
+ }
+
+
+init : GPE.Recv -> Model
+init d =
+ { state = Api.Normal
+ , id = d.id
+ , num = d.num
+ , can_mod = d.can_mod
+ , hidden = d.hidden
+ , nolastmod = False
+ , delete = False
+ , msg = TP.bbcode d.msg
+ }
+
+encode : Model -> GPE.Send
+encode m =
+ { id = m.id
+ , num = m.num
+ , hidden = m.hidden
+ , nolastmod = m.nolastmod
+ , delete = m.delete
+ , msg = m.msg.data
+ }
+
+
+type Msg
+ = Hidden Bool
+ | Nolastmod Bool
+ | Delete Bool
+ | Content TP.Msg
+ | Submit
+ | Submitted GApi.Response
+
+
+update : Msg -> Model -> (Model, Cmd Msg)
+update msg model =
+ case msg of
+ Hidden b -> ({ model | hidden = b }, Cmd.none)
+ Nolastmod b -> ({ model | nolastmod=b }, Cmd.none)
+ Delete b -> ({ model | delete = b }, Cmd.none)
+ Content m -> let (nm,nc) = TP.update m model.msg in ({ model | msg = nm }, Cmd.map Content nc)
+
+ Submit -> ({ model | state = Api.Loading }, GPE.send (encode model) Submitted)
+ Submitted (GApi.Redirect s) -> (model, load s)
+ Submitted r -> ({ model | state = Api.Error r }, Cmd.none)
+
+
+view : Model -> Html Msg
+view model =
+ form_ Submit (model.state == Api.Loading)
+ [ div [ class "mainbox" ]
+ [ h1 [] [ text "Edit post" ]
+ , table [ class "formtable" ] <|
+ [ formField "Post" [ a [ href <| "/" ++ model.id ++ "." ++ String.fromInt model.num ] [ text <| "#" ++ String.fromInt model.num ++ " on " ++ model.id ] ]
+ , if model.can_mod
+ then formField "" [ label [] [ inputCheck "" model.hidden Hidden, text " Hidden" ] ]
+ else text ""
+ , if model.can_mod
+ then formField "" [ label [] [ inputCheck "" model.nolastmod Nolastmod, text " Don't update last modification timestamp" ] ]
+ else text ""
+ , tr [ class "newpart" ] [ td [ colspan 2 ] [ text "" ] ]
+ , formField "msg::Message"
+ [ TP.view "msg" model.msg Content 700 ([rows 12, cols 50] ++ GPE.valMsg)
+ [ b [ class "standout" ] [ text " (English please!) " ]
+ , a [ href "/d9#3" ] [ text "Formatting" ]
+ ]
+ ]
+ ]
+ ++ (if not model.can_mod then [] else
+ [ tr [ class "newpart" ] [ td [ colspan 2 ] [ text "DANGER ZONE" ] ]
+ , formField "" [ inputCheck "" model.delete Delete, text " Permanently delete this post. This action can not be reverted, only do this with obvious spam!" ]
+ ])
+ ]
+ , div [ class "mainbox" ]
+ [ fieldset [ class "submit" ] [ submitButton "Submit" model.state True ] ]
+ ]
diff --git a/lib/VNWeb/Discussions/Edit.pm b/lib/VNWeb/Discussions/Edit.pm
index ec1218c0..0ddf5376 100644
--- a/lib/VNWeb/Discussions/Edit.pm
+++ b/lib/VNWeb/Discussions/Edit.pm
@@ -6,9 +6,7 @@ use VNWeb::Discussions::Lib;
my $FORM = {
tid => { required => 0, vndbid => 't' }, # Thread ID, only when editing a post
- num => { required => 0, id => 1 }, # Post number, only when editing
- # Only when num = 1 || tid = undef
title => { required => 0, maxlength => 50 },
boards => { required => 0, sort_keys => [ 'boardtype', 'iid' ], aoh => {
btype => { enum => \%BOARD_TYPE },
@@ -23,9 +21,9 @@ my $FORM = {
can_mod => { anybool => 1, _when => 'out' },
can_private => { anybool => 1, _when => 'out' },
- locked => { anybool => 1 }, # When can_mod && (num = 1 || tid = undef)
+ locked => { anybool => 1 }, # When can_mod
hidden => { anybool => 1 }, # When can_mod
- private => { anybool => 1 }, # When can_private && (num = 1 || tid = undef)
+ private => { anybool => 1 }, # When can_private
nolastmod => { anybool => 1, _when => 'in' }, # When can_mod
delete => { anybool => 1 }, # When can_mod
@@ -39,48 +37,39 @@ my $FORM_IN = form_compile in => $FORM;
elm_api DiscussionsEdit => $FORM_OUT, $FORM_IN, sub {
my($data) = @_;
my $tid = $data->{tid};
- my $num = $data->{num} || 1;
my $t = !$tid ? {} : tuwf->dbRowi('
- SELECT t.id, tp.num, t.poll_question, t.poll_max_options, tp.hidden, tp.uid AS user_id,', sql_totime('tp.date'), 'AS date
+ SELECT t.id, t.poll_question, t.poll_max_options, t.hidden, tp.uid AS user_id,', sql_totime('tp.date'), 'AS date
FROM threads t
- JOIN threads_posts tp ON tp.tid = t.id AND tp.num =', \$num,
- 'WHERE t.id =', \$tid,
+ JOIN threads_posts tp ON tp.tid = t.id AND tp.num = 1
+ WHERE t.id =', \$tid,
'AND', sql_visible_threads());
return tuwf->resNotFound if $tid && !$t->{id};
return elm_Unauth if !can_edit t => $t;
if($tid && $data->{delete} && auth->permBoardmod) {
- auth->audit($t->{user_id}, 'post delete', "deleted $tid.$num");
- if($num == 1) {
- tuwf->dbExeci('DELETE FROM threads WHERE id =', \$tid);
- tuwf->dbExeci(q{DELETE FROM notifications WHERE ltype = 't' AND iid = vndbid_num(}, \$tid, ')');
- return elm_Redirect '/t';
- } else {
- tuwf->dbExeci('DELETE FROM threads_posts WHERE tid =', \$tid, 'AND num =', \$num);
- tuwf->dbExeci(q{DELETE FROM notifications WHERE ltype = 't' AND iid = vndbid_num(}, \$tid, ') AND subid =', \$num);
- return elm_Redirect "/$tid";
- }
- }
- auth->audit($t->{user_id}, 'post edit', "edited $tid.$num") if $tid && $t->{user_id} != auth->uid;
-
- my $pollchanged = !$data->{tid} && $data->{poll};
- if($num == 1) {
- die "Invalid title" if !length $data->{title};
- die "Invalid boards" if !$data->{boards} || grep +(!$BOARD_TYPE{$_->{btype}}{dbitem})^(!$_->{iid}), $data->{boards}->@*;
-
- validate_dbid 'SELECT id FROM vn WHERE id IN', map $_->{btype} eq 'v' ? $_->{iid} : (), $data->{boards}->@*;
- validate_dbid 'SELECT id FROM producers WHERE id IN', map $_->{btype} eq 'p' ? $_->{iid} : (), $data->{boards}->@*;
- # Do not validate user boards here, it's possible to have threads assigned to deleted users.
-
- die "Invalid max_options" if $data->{poll} && $data->{poll}{max_options} > $data->{poll}{options}->@*;
- $pollchanged = 1 if $tid && $data->{poll} && (
- $data->{poll}{question} ne ($t->{poll_question}||'')
- || $data->{poll}{max_options} != $t->{poll_max_options}
- || join("\n", $data->{poll}{options}->@*) ne
- join("\n", map $_->{option}, tuwf->dbAlli('SELECT option FROM threads_poll_options WHERE tid =', \$tid, 'ORDER BY id')->@*)
- )
+ auth->audit($t->{user_id}, 'post delete', "deleted $tid.1");
+ tuwf->dbExeci('DELETE FROM threads WHERE id =', \$tid);
+ tuwf->dbExeci(q{DELETE FROM notifications WHERE ltype = 't' AND iid = vndbid_num(}, \$tid, ')');
+ return elm_Redirect '/t';
}
+ auth->audit($t->{user_id}, 'post edit', "edited $tid.1") if $tid && $t->{user_id} != auth->uid;
+
+
+ die "Invalid title" if !length $data->{title};
+ die "Invalid boards" if !$data->{boards} || grep +(!$BOARD_TYPE{$_->{btype}}{dbitem})^(!$_->{iid}), $data->{boards}->@*;
+
+ validate_dbid 'SELECT id FROM vn WHERE id IN', map $_->{btype} eq 'v' ? $_->{iid} : (), $data->{boards}->@*;
+ validate_dbid 'SELECT id FROM producers WHERE id IN', map $_->{btype} eq 'p' ? $_->{iid} : (), $data->{boards}->@*;
+ # Do not validate user boards here, it's possible to have threads assigned to deleted users.
+
+ die "Invalid max_options" if $data->{poll} && $data->{poll}{max_options} > $data->{poll}{options}->@*;
+ my $pollchanged = (!$tid && $data->{poll}) || ($tid && $data->{poll} && (
+ $data->{poll}{question} ne ($t->{poll_question}||'')
+ || $data->{poll}{max_options} != $t->{poll_max_options}
+ || join("\n", $data->{poll}{options}->@*) ne
+ join("\n", map $_->{option}, tuwf->dbAlli('SELECT option FROM threads_poll_options WHERE tid =', \$tid, 'ORDER BY id')->@*)
+ ));
my $thread = {
title => $data->{title},
@@ -94,13 +83,11 @@ elm_api DiscussionsEdit => $FORM_OUT, $FORM_IN, sub {
private => $data->{private}
) : (),
};
- tuwf->dbExeci('UPDATE threads SET', $thread, 'WHERE id =', \$tid) if $tid && $num == 1;
+ tuwf->dbExeci('UPDATE threads SET', $thread, 'WHERE id =', \$tid) if $tid;
$tid = tuwf->dbVali('INSERT INTO threads', $thread, 'RETURNING id') if !$tid;
- if($num == 1) {
- tuwf->dbExeci('DELETE FROM threads_boards WHERE tid =', \$tid);
- tuwf->dbExeci('INSERT INTO threads_boards', { tid => $tid, type => $_->{btype}, iid => $_->{iid}//0 }) for $data->{boards}->@*;
- }
+ tuwf->dbExeci('DELETE FROM threads_boards WHERE tid =', \$tid);
+ tuwf->dbExeci('INSERT INTO threads_boards', { tid => $tid, type => $_->{btype}, iid => $_->{iid}//0 }) for $data->{boards}->@*;
if($pollchanged) {
tuwf->dbExeci('DELETE FROM threads_poll_options WHERE tid =', \$tid);
@@ -109,30 +96,29 @@ elm_api DiscussionsEdit => $FORM_OUT, $FORM_IN, sub {
my $post = {
tid => $tid,
- num => $num,
+ num => 1,
msg => bb_subst_links($data->{msg}),
$data->{tid} ? () : (uid => auth->uid),
- auth->permBoardmod && $num != 1 ? (hidden => $data->{hidden}) : (),
!$data->{tid} || (auth->permBoardmod && $data->{nolastmod}) ? () : (edited => sql 'NOW()')
};
tuwf->dbExeci('INSERT INTO threads_posts', $post) if !$data->{tid};
- tuwf->dbExeci('UPDATE threads_posts SET', $post, 'WHERE', { tid => $tid, num => $num }) if $data->{tid};
+ tuwf->dbExeci('UPDATE threads_posts SET', $post, 'WHERE', { tid => $tid, num => 1 }) if $data->{tid};
- elm_Redirect "/$tid.$num";
+ elm_Redirect "/$tid.1";
};
-TUWF::get qr{(?:/t/(?<board>$BOARD_RE)/new|/$RE{postid}/edit)}, sub {
+TUWF::get qr{(?:/t/(?<board>$BOARD_RE)/new|/$RE{tid}\.1/edit)}, sub {
my($board_type, $board_id) = (tuwf->capture('board')||'') =~ /^([^0-9]+)([0-9]*)$/;
- my($tid, $num) = (tuwf->capture('id'), tuwf->capture('num'));
+ my $tid = tuwf->capture('id');
$board_type = 'ge' if $board_type && $board_type eq 'an' && !auth->permBoardmod;
my $t = !$tid ? {} : tuwf->dbRowi('
- SELECT t.id, tp.tid, tp.num, t.title, t.locked, t.private, t.hidden AS thread_hidden, t.poll_question, t.poll_max_options, tp.hidden, tp.msg, tp.uid AS user_id,', sql_totime('tp.date'), 'AS date
+ SELECT t.id, tp.tid, t.title, t.locked, t.private, t.hidden, t.poll_question, t.poll_max_options, tp.msg, tp.uid AS user_id,', sql_totime('tp.date'), 'AS date
FROM threads t
- JOIN threads_posts tp ON tp.tid = t.id AND tp.num =', \$num,
- 'WHERE t.id =', \$tid,
+ JOIN threads_posts tp ON tp.tid = t.id AND tp.num = 1
+ WHERE t.id =', \$tid,
'AND', sql_visible_threads());
return tuwf->resNotFound if $tid && !$t->{id};
return tuwf->resDenied if !can_edit t => $t;
@@ -159,16 +145,15 @@ TUWF::get qr{(?:/t/(?<board>$BOARD_RE)/new|/$RE{postid}/edit)}, sub {
$t->{can_mod} = auth->permBoardmod;
$t->{can_private} = auth->isMod;
- $t->{hidden} = $tid && $num == 1 ? $t->{thread_hidden}//0 : $t->{hidden}//0;
+ $t->{hidden} //= 0;
$t->{msg} //= '';
$t->{title} //= tuwf->reqGet('title');
$t->{tid} //= undef;
- $t->{num} //= undef;
$t->{private} //= auth->isMod && tuwf->reqGet('priv') ? 1 : 0;
$t->{locked} //= 0;
$t->{delete} = 0;
- framework_ title => $tid ? 'Edit post' : 'Create new thread', sub {
+ framework_ title => $tid ? 'Edit thread' : 'Create new thread', sub {
elm_ 'Discussions.Edit' => $FORM_OUT, $t;
};
};
diff --git a/lib/VNWeb/Discussions/PostEdit.pm b/lib/VNWeb/Discussions/PostEdit.pm
new file mode 100644
index 00000000..05df61f4
--- /dev/null
+++ b/lib/VNWeb/Discussions/PostEdit.pm
@@ -0,0 +1,88 @@
+package VNWeb::Discussions::PostEdit;
+# Also used for editing review comments, which follow the exact same format.
+
+use VNWeb::Prelude;
+use VNWeb::Discussions::Lib;
+
+
+my $FORM = {
+ id => { vndbid => ['t','w'] },
+ num => { id => 1 },
+
+ can_mod => { anybool => 1, _when => 'out' },
+ hidden => { anybool => 1 }, # When can_mod
+ nolastmod => { anybool => 1, _when => 'in' }, # When can_mod
+ delete => { anybool => 1 }, # When can_mod
+
+ msg => { maxlength => 32768 },
+};
+
+my $FORM_OUT = form_compile out => $FORM;
+my $FORM_IN = form_compile in => $FORM;
+
+
+sub _info {
+ my($id,$num) = @_;
+ tuwf->dbRowi('
+ SELECT t.id, tp.num, tp.hidden, tp.msg, tp.uid AS user_id,', sql_totime('tp.date'), 'AS date
+ FROM threads t
+ JOIN threads_posts tp ON tp.tid = t.id AND tp.num =', \$num, '
+ WHERE t.id =', \$id, 'AND', sql_visible_threads(),'
+ UNION ALL
+ SELECT id, num, hidden, msg, uid AS user_id,', sql_totime('date'), 'AS date
+ FROM reviews_posts WHERE id =', \$id, 'AND num =', \$num
+ );
+}
+
+
+elm_api DiscussionsPostEdit => $FORM_OUT, $FORM_IN, sub {
+ my($data) = @_;
+ my $id = $data->{id};
+ my $num = $data->{num};
+
+ my $t = _info $id, $num;
+ return tuwf->resNotFound if !$t->{id};
+ return elm_Unauth if !can_edit t => $t;
+
+ if($data->{delete} && auth->permBoardmod) {
+ auth->audit($t->{user_id}, 'post delete', "deleted $id.$num");
+ tuwf->dbExeci('DELETE FROM threads_posts WHERE tid =', \$id, 'AND num =', \$num);
+ tuwf->dbExeci('DELETE FROM reviews_posts WHERE id =', \$id, 'AND num =', \$num);
+ tuwf->dbExeci(q{DELETE FROM notifications WHERE ltype = 't' AND iid = vndbid_num(}, \$id, ') AND subid =', \$num);
+ return elm_Redirect "/$id";
+ }
+ auth->audit($t->{user_id}, 'post edit', "edited $id.$num") if $t->{user_id} != auth->uid;
+
+ my $post = {
+ tid => $id,
+ num => $num,
+ msg => bb_subst_links($data->{msg}),
+ auth->permBoardmod ? (hidden => $data->{hidden}) : (),
+ (auth->permBoardmod && $data->{nolastmod}) ? () : (edited => sql 'NOW()')
+ };
+ tuwf->dbExeci('UPDATE threads_posts SET', $post, 'WHERE', { tid => $id, num => $num });
+ $post->{id} = delete $post->{tid};
+ tuwf->dbExeci('UPDATE reviews_posts SET', $post, 'WHERE', { id => $id, num => $num });
+
+ elm_Redirect "/$id.$num";
+};
+
+
+TUWF::get qr{/(?:$RE{tid}|$RE{wid})\.$RE{num}/edit}, sub {
+ my($id, $num) = (tuwf->capture('id'), tuwf->capture('num'));
+ tuwf->pass if $id =~ /^t/ && $num == 1; # t#.1 goes to Discussions::Edit.
+
+ my $t = _info $id, $num;
+ return tuwf->resNotFound if $id && !$t->{id};
+ return tuwf->resDenied if !can_edit t => $t;
+
+ $t->{can_mod} = auth->permBoardmod;
+ $t->{delete} = 0;
+
+ framework_ title => 'Edit post', sub {
+ elm_ 'Discussions.PostEdit' => $FORM_OUT, $t;
+ };
+};
+
+
+1;
diff --git a/lib/VNWeb/Reviews/Page.pm b/lib/VNWeb/Reviews/Page.pm
index 56e8ca8f..69af9054 100644
--- a/lib/VNWeb/Reviews/Page.pm
+++ b/lib/VNWeb/Reviews/Page.pm
@@ -114,7 +114,7 @@ TUWF::get qr{/$RE{wid}(?:(?<sep>[\./])$RE{num})?}, sub {
h1_ class => 'boxtitle', 'Comments'; # XXX: How does this look with pagination?
VNWeb::Discussions::Thread::posts_($w, $posts, $page);
}
- # TODO: "Add comment" form + fix post editing and reporting.
+ # TODO: "Add comment" form + fix post reporting.
};
};