summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/style.css3
-rw-r--r--elm/ImageFlagging.elm53
-rw-r--r--lib/VNWeb/Elm.pm2
-rw-r--r--lib/VNWeb/Images/Vote.pm48
-rw-r--r--sql/func.sql4
-rw-r--r--sql/schema.sql3
-rw-r--r--sql/triggers.sql2
-rw-r--r--util/updates/2020-04-25-imgflag-overrule.sql4
8 files changed, 79 insertions, 40 deletions
diff --git a/data/style.css b/data/style.css
index 2b4974d2..2bacbb1e 100644
--- a/data/style.css
+++ b/data/style.css
@@ -1115,8 +1115,11 @@ p.filselect i { font-style: normal }
.imageflag > div:nth-child(4) ul li label { display: inline-block; border: 1px solid $secborder$; padding: 7px; width: 100px; white-space: nowrap; margin: 2px 0; cursor: pointer }
.imageflag > div:nth-child(4) ul li.sel label,
.imageflag > div:nth-child(4) ul li label:hover { background-color: $secbg$ }
+.imageflag > div:nth-child(4) ul li.overrule label { position: relative; left: 80px; border: none; padding: 0 }
.imageflag > div:nth-child(6) { min-height: 200px; padding: 15px 0 }
.imageflag > div:nth-child(6) table { margin: 0 auto }
+.imageflag > div:nth-child(6) table tr.ignored td:nth-child(2),
+.imageflag > div:nth-child(6) table tr.ignored td:nth-child(3) { text-decoration: line-through; color: $grayedout$ }
.imageflag > div:nth-child(6) table td { min-width: 50px }
.imageflag .fullscreen { position: fixed; left: 0; top: 0; width: 100%; height: 100%; background-repeat: no-repeat; background-position: center; background-size: contain; background-color: #000 }
diff --git a/elm/ImageFlagging.elm b/elm/ImageFlagging.elm
index 2ea7eb36..e864b2c4 100644
--- a/elm/ImageFlagging.elm
+++ b/elm/ImageFlagging.elm
@@ -39,6 +39,7 @@ type alias Model =
, showVotes : Bool
, myVotes : Int
, nsfwToken : String
+ , mod : Bool
, images : Array.Array GApi.ApiImageResult
, index : Int
, desc : (Maybe Int, Maybe Int)
@@ -59,6 +60,7 @@ init d =
, showVotes = d.single
, myVotes = d.my_votes
, nsfwToken = d.nsfw_token
+ , mod = d.mod
, images = Array.fromList d.images
, index = if d.single then 0 else List.length d.images
, desc = Maybe.withDefault (Nothing,Nothing) <| Maybe.map (\i -> (i.my_sexual, i.my_violence)) <| if d.single then List.head d.images else Nothing
@@ -72,29 +74,29 @@ init d =
}
-keyToVote : Model -> String -> Maybe (Maybe Int, Maybe Int)
+keyToVote : Model -> String -> Maybe (Maybe Int, Maybe Int, Bool)
keyToVote model k =
- let (s,v) = Maybe.withDefault (Nothing,Nothing) <| Maybe.map (\i -> (i.my_sexual, i.my_violence)) <| Array.get model.index model.images
+ let (s,v,o) = Maybe.withDefault (Nothing,Nothing,False) <| Maybe.map (\i -> (i.my_sexual, i.my_violence, i.my_overrule)) <| Array.get model.index model.images
in case k of
- "1" -> Just (Just 0, Just 0)
- "2" -> Just (Just 1, Just 0)
- "3" -> Just (Just 2, Just 0)
- "4" -> Just (Just 0, Just 1)
- "5" -> Just (Just 1, Just 1)
- "6" -> Just (Just 2, Just 1)
- "7" -> Just (Just 0, Just 2)
- "8" -> Just (Just 1, Just 2)
- "9" -> Just (Just 2, Just 2)
- "s" -> Just (Just 0, v)
- "d" -> Just (Just 1, v)
- "f" -> Just (Just 2, v)
- "j" -> Just (s, Just 0)
- "k" -> Just (s, Just 1)
- "l" -> Just (s, Just 2)
+ "1" -> Just (Just 0, Just 0, o)
+ "2" -> Just (Just 1, Just 0, o)
+ "3" -> Just (Just 2, Just 0, o)
+ "4" -> Just (Just 0, Just 1, o)
+ "5" -> Just (Just 1, Just 1, o)
+ "6" -> Just (Just 2, Just 1, o)
+ "7" -> Just (Just 0, Just 2, o)
+ "8" -> Just (Just 1, Just 2, o)
+ "9" -> Just (Just 2, Just 2, o)
+ "s" -> Just (Just 0, v, o)
+ "d" -> Just (Just 1, v, o)
+ "f" -> Just (Just 2, v, o)
+ "j" -> Just (s, Just 0, o)
+ "k" -> Just (s, Just 1, o)
+ "l" -> Just (s, Just 2, o)
_ -> Nothing
keydown : Model -> JD.Decoder Msg
-keydown model = JD.andThen (\k -> keyToVote model k |> Maybe.map (\(s,v) -> JD.succeed (Desc s v)) |> Maybe.withDefault (JD.fail "")) (JD.field "key" JD.string)
+keydown model = JD.andThen (\k -> keyToVote model k |> Maybe.map (\(s,v,_) -> JD.succeed (Desc s v)) |> Maybe.withDefault (JD.fail "")) (JD.field "key" JD.string)
keyup : Model -> JD.Decoder Msg
keyup model =
@@ -104,7 +106,7 @@ keyup model =
"ArrowRight" -> JD.succeed Next
"v" -> JD.succeed (Fullscreen (not model.fullscreen))
"Escape" -> JD.succeed (Fullscreen False)
- _ -> keyToVote model k |> Maybe.map (\(s,v) -> JD.succeed (Vote s v True)) |> Maybe.withDefault (JD.fail "")
+ _ -> keyToVote model k |> Maybe.map (\(s,v,o) -> JD.succeed (Vote s v o True)) |> Maybe.withDefault (JD.fail "")
) (JD.field "key" JD.string)
@@ -114,7 +116,7 @@ type Msg
| Fullscreen Bool
| Desc (Maybe Int) (Maybe Int)
| Load GApi.Response
- | Vote (Maybe Int) (Maybe Int) Bool
+ | Vote (Maybe Int) (Maybe Int) Bool Bool
| Save
| Saved GApi.Response
| Prev
@@ -158,18 +160,18 @@ update msg model =
in pre (nc, Cmd.none)
Load e -> ({ model | loadState = Api.Error e }, Cmd.none)
- Vote s v _ ->
+ Vote s v o _ ->
case Array.get model.index model.images of
Nothing -> (model, Cmd.none)
Just i ->
- let m = { model | saved = False, images = Array.set model.index { i | my_sexual = s, my_violence = v } model.images }
+ let m = { model | saved = False, images = Array.set model.index { i | my_sexual = s, my_violence = v, my_overrule = o } model.images }
adv = if not m.single && (i.my_sexual == Nothing || i.my_violence == Nothing) then 1 else 0
in case (i.token,s,v) of
-- Complete vote, mark it as a change and go to next image
(Just token, Just xs, Just xv) -> desc <| pre <| save <| load
({ m | index = m.index + adv
, myVotes = m.myVotes + adv
- , changes = Dict.insert i.id { id = i.id, token = token, sexual = xs, violence = xv } m.changes
+ , changes = Dict.insert i.id { id = i.id, token = token, sexual = xs, violence = xv, overrule = o } m.changes
}, Cmd.none)
-- Otherwise just save it internally
_ -> (m, Cmd.none)
@@ -202,7 +204,7 @@ view model =
let sel = i.my_sexual == s && i.my_violence == v
in li [ classList [("sel", sel || (s /= i.my_sexual && Tuple.first model.desc == s) || (v /= i.my_violence && Tuple.second model.desc == v))] ]
[ label [ onMouseOver (Desc s v), onMouseOut (Desc i.my_sexual i.my_violence) ]
- [ input [ type_ "radio", onCheck (Vote s v), checked sel, onFocus (Focus lid), id lid ] [], text lbl ]
+ [ input [ type_ "radio", onCheck (Vote s v i.my_overrule), checked sel, onFocus (Focus lid), id lid ] [], text lbl ]
]
votestats i =
@@ -220,7 +222,7 @@ view model =
]
, table [] <|
List.map (\v ->
- tr []
+ tr [ classList [("ignored", v.ignore)]]
[ td [ Ffi.innerHtml v.user ] []
, td [] [ text <| if v.sexual == 0 then "Safe" else if v.sexual == 1 then "Suggestive" else "Explicit" ]
, td [] [ text <| if v.violence == 0 then "Tame" else if v.violence == 1 then "Violent" else "Brutal" ]
@@ -286,6 +288,7 @@ view model =
, but i (Just 0) i.my_violence "vio0" " Safe"
, but i (Just 1) i.my_violence "vio1" " Suggestive"
, but i (Just 2) i.my_violence "vio2" " Explicit"
+ , if model.mod then li [ class "overrule" ] [ label [ title "Overrule" ] [ inputCheck "" i.my_overrule (\b -> Vote i.my_sexual i.my_violence b True), text " Overrule" ] ] else text ""
]
, ul []
[ li [] [ b [] [ text "Violence" ] ]
diff --git a/lib/VNWeb/Elm.pm b/lib/VNWeb/Elm.pm
index 7cce9109..f945989c 100644
--- a/lib/VNWeb/Elm.pm
+++ b/lib/VNWeb/Elm.pm
@@ -93,6 +93,7 @@ our %apis = (
violence_stddev => { num => 1, required => 0 },
my_sexual => { uint => 1, required => 0 },
my_violence => { uint => 1, required => 0 },
+ my_overrule => { anybool => 1 },
entry => { required => 0, type => 'hash', keys => {
id => {},
title => {},
@@ -102,6 +103,7 @@ our %apis = (
uid => { uint => 1, required => 0 },
sexual => { uint => 1 },
violence => { uint => 1 },
+ ignore => { anybool => 1 },
} },
} } ],
);
diff --git a/lib/VNWeb/Images/Vote.pm b/lib/VNWeb/Images/Vote.pm
index 507590c8..60cb0787 100644
--- a/lib/VNWeb/Images/Vote.pm
+++ b/lib/VNWeb/Images/Vote.pm
@@ -30,6 +30,7 @@ sub enrich_image {
, i.c_sexual_avg AS sexual_avg, i.c_sexual_stddev AS sexual_stddev
, i.c_violence_avg AS violence_avg, i.c_violence_stddev AS violence_stddev
, iv.sexual AS my_sexual, iv.violence AS my_violence
+ , COALESCE(EXISTS(SELECT 1 FROM image_votes iv0 WHERE iv0.id = i.id AND iv0.ignore) AND NOT iv.ignore, FALSE) AS my_overrule
, COALESCE('v'||v.id, 'c'||c.id, 'v'||vsv.id) AS entry_id
, COALESCE(v.title, c.name, vsv.title) AS entry_title
FROM images i
@@ -42,7 +43,7 @@ sub enrich_image {
}, $l;
enrich votes => id => id => sub { sql '
- SELECT iv.id, iv.uid, iv.sexual, iv.violence, ', sql_user(), '
+ SELECT iv.id, iv.uid, iv.sexual, iv.violence, iv.ignore OR (u.id IS NOT NULL AND NOT u.perm_imgvote) AS ignore, ', sql_user(), '
FROM image_votes iv
LEFT JOIN users u ON u.id = iv.uid
WHERE iv.id IN', $_,
@@ -63,15 +64,11 @@ sub enrich_image {
}
-sub my_votes {
- auth ? tuwf->dbVali('SELECT c_imgvotes FROM users WHERE id =', \auth->uid) : 0
-}
-
-
my $SEND = form_compile any => {
images => $VNWeb::Elm::apis{ImageResult}[0],
single => { anybool => 1 },
warn => { anybool => 1 },
+ mod => { anybool => 1 },
my_votes => { uint => 1 },
pWidth => { uint => 1 }, # Set by JS
pHeight => { uint => 1 }, # ^
@@ -127,20 +124,49 @@ elm_api ImageVote => undef, {
token => {},
sexual => { uint => 1, range => [0,2] },
violence => { uint => 1, range => [0,2] },
+ overrule => { anybool => 1 },
} },
}, sub {
my($data) = @_;
return elm_Unauth if !auth->permImgvote;
return elm_CSRF if !validate_token $data->{votes};
+
+ # Find out if any of these images are being overruled
+ enrich_merge id => sub { sql 'SELECT id, bool_or(ignore) AS overruled FROM image_votes WHERE id IN', $_, 'GROUP BY id' }, $data->{votes};
+ enrich_merge id => sql('SELECT id, NOT ignore AS my_overrule FROM image_votes WHERE uid =', \auth->uid, 'AND id IN'),
+ grep $_->{overruled}, $data->{votes}->@* if auth->permDbmod;
+
for($data->{votes}->@*) {
- $_->{uid} = auth->uid ;
- delete $_->{token};
- tuwf->dbExeci('INSERT INTO image_votes', $_, 'ON CONFLICT (id, uid) DO UPDATE SET', $_, ', date = now()');
+ $_->{overrule} = 0 if !auth->permDbmod;
+ my $d = {
+ id => $_->{id},
+ uid => auth->uid(),
+ sexual => $_->{sexual},
+ violence => $_->{violence},
+ ignore => !$_->{overrule} && !$_->{my_overrule} && $_->{overruled} ? 1 : 0,
+ };
+ tuwf->dbExeci('INSERT INTO image_votes', $d, 'ON CONFLICT (id, uid) DO UPDATE SET', $d, ', date = now()');
+ tuwf->dbExeci('UPDATE image_votes SET ignore =', \($_->{overrule}?1:0), 'WHERE uid IS DISTINCT FROM', \auth->uid, 'AND id =', \$_->{id})
+ if !$_->{overrule} != !$_->{my_overrule};
}
elm_Success
};
+sub my_votes {
+ auth ? tuwf->dbVali('SELECT c_imgvotes FROM users WHERE id =', \auth->uid) : 0
+}
+
+
+sub imgflag_ {
+ elm_ 'ImageFlagging', $SEND, {
+ my_votes => my_votes(),
+ nsfw_token => viewset(show_nsfw => 1),
+ mod => auth->permDbmod()||0,
+ @_
+ };
+}
+
TUWF::get qr{/img/vote}, sub {
return tuwf->resDenied if !auth->permImgvote;
@@ -150,7 +176,7 @@ TUWF::get qr{/img/vote}, sub {
enrich_token 1, $recent;
framework_ title => 'Image flagging', sub {
- elm_ 'ImageFlagging', $SEND, { images => [ reverse @$recent ], single => 0, warn => 1, my_votes => my_votes(), nsfw_token => viewset(show_nsfw => 1) };
+ imgflag_ images => [ reverse @$recent ], single => 0, warn => 1;
};
};
@@ -165,7 +191,7 @@ TUWF::get qr{/img/$RE{imgid}}, sub {
enrich_token defined($l->[0]{my_sexual}) || auth->permDbmod(), $l; # XXX: permImgmod?
framework_ title => "Image flagging for $id", sub {
- elm_ 'ImageFlagging', $SEND, { images => $l, single => 1, warn => !tuwf->samesite(), my_votes => my_votes(), nsfw_token => viewset(show_nsfw => 1) };
+ imgflag_ images => $l, single => 1, warn => !tuwf->samesite();
};
};
diff --git a/sql/func.sql b/sql/func.sql
index 6f0401f2..f2e836ec 100644
--- a/sql/func.sql
+++ b/sql/func.sql
@@ -166,8 +166,8 @@ BEGIN
ELSE 0 END AS weight
FROM (
SELECT i.id, count(iv.id) AS votecount
- , avg(sexual) AS sexual_avg, stddev_pop(sexual) AS sexual_stddev
- , avg(violence) AS violence_avg, stddev_pop(violence) AS violence_stddev
+ , avg(sexual) FILTER(WHERE NOT iv.ignore) AS sexual_avg, stddev_pop(sexual) FILTER(WHERE NOT iv.ignore) AS sexual_stddev
+ , avg(violence) FILTER(WHERE NOT iv.ignore) AS violence_avg, stddev_pop(violence) FILTER(WHERE NOT iv.ignore) AS violence_stddev
FROM images i
LEFT JOIN image_votes iv ON iv.id = i.id
LEFT JOIN users u ON u.id = iv.uid
diff --git a/sql/schema.sql b/sql/schema.sql
index a8add72e..03c9e218 100644
--- a/sql/schema.sql
+++ b/sql/schema.sql
@@ -220,7 +220,8 @@ CREATE TABLE image_votes (
uid integer, -- [pub]
sexual smallint NOT NULL CHECK(sexual >= 0 AND sexual <= 2), -- [pub]
violence smallint NOT NULL CHECK(violence >= 0 AND violence <= 2), -- [pub]
- date timestamptz NOT NULL DEFAULT NOW() -- [pub]
+ date timestamptz NOT NULL DEFAULT NOW(),-- [pub]
+ ignore boolean NOT NULL DEFAULT false -- [pub]
);
-- login_throttle
diff --git a/sql/triggers.sql b/sql/triggers.sql
index d06faf65..b7ade0c4 100644
--- a/sql/triggers.sql
+++ b/sql/triggers.sql
@@ -285,4 +285,4 @@ END
$$ LANGUAGE plpgsql;
CREATE TRIGGER image_votes_cache1 AFTER INSERT OR DELETE ON image_votes FOR EACH ROW EXECUTE PROCEDURE update_images_cache();
-CREATE TRIGGER image_votes_cache2 AFTER UPDATE ON image_votes FOR EACH ROW WHEN ((OLD.id, OLD.sexual, OLD.violence) IS DISTINCT FROM (NEW.id, NEW.sexual, NEW.violence)) EXECUTE PROCEDURE update_images_cache();
+CREATE TRIGGER image_votes_cache2 AFTER UPDATE ON image_votes FOR EACH ROW WHEN ((OLD.id, OLD.sexual, OLD.violence, OLD.ignore) IS DISTINCT FROM (NEW.id, NEW.sexual, NEW.violence, NEW.ignore)) EXECUTE PROCEDURE update_images_cache();
diff --git a/util/updates/2020-04-25-imgflag-overrule.sql b/util/updates/2020-04-25-imgflag-overrule.sql
new file mode 100644
index 00000000..19f8d8a5
--- /dev/null
+++ b/util/updates/2020-04-25-imgflag-overrule.sql
@@ -0,0 +1,4 @@
+ALTER TABLE image_votes ADD COLUMN ignore boolean NOT NULL DEFAULT false;
+DROP TRIGGER image_votes_cache2 ON image_votes;
+CREATE TRIGGER image_votes_cache2 AFTER UPDATE ON image_votes FOR EACH ROW WHEN ((OLD.id, OLD.sexual, OLD.violence, OLD.ignore) IS DISTINCT FROM (NEW.id, NEW.sexual, NEW.violence, NEW.ignore)) EXECUTE PROCEDURE update_images_cache();
+\i sql/func.sql