summaryrefslogtreecommitdiff
path: root/elm
diff options
context:
space:
mode:
Diffstat (limited to 'elm')
-rw-r--r--elm/Discussions/Edit.elm34
-rw-r--r--elm/Discussions/PostEdit.elm108
-rw-r--r--elm/Discussions/Reply.elm2
-rw-r--r--elm/Report.elm50
-rw-r--r--elm/Reviews/Comment.elm52
-rw-r--r--elm/Reviews/Edit.elm195
-rw-r--r--elm/Reviews/Vote.elm70
-rw-r--r--elm/UList/VNPage.elm33
-rw-r--r--elm/User/Edit.elm3
-rw-r--r--elm/VNEdit.elm20
-rw-r--r--elm/iv.js22
-rw-r--r--elm/sethash.js8
12 files changed, 524 insertions, 73 deletions
diff --git a/elm/Discussions/Edit.elm b/elm/Discussions/Edit.elm
index f8873fa7..6008cdef 100644
--- a/elm/Discussions/Edit.elm
+++ b/elm/Discussions/Edit.elm
@@ -25,8 +25,7 @@ main = Browser.element
type alias Model =
{ state : Api.State
- , tid : Maybe Int
- , num : Maybe Int
+ , tid : Maybe String
, 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 <| "/t" ++ String.fromInt (Maybe.withDefault 0 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 [])
- ++ (if not model.can_mod then [] 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/elm/Discussions/Reply.elm b/elm/Discussions/Reply.elm
index a8d25434..3581c91f 100644
--- a/elm/Discussions/Reply.elm
+++ b/elm/Discussions/Reply.elm
@@ -22,7 +22,7 @@ main = Browser.element
type alias Model =
{ state : Api.State
- , tid : Int
+ , tid : String
, old : Bool
, msg : TP.Model
}
diff --git a/elm/Report.elm b/elm/Report.elm
index 0bb48b50..f63a9411 100644
--- a/elm/Report.elm
+++ b/elm/Report.elm
@@ -31,14 +31,16 @@ type Msg
type alias ReasonLabel =
{ label : String
- , vis : String -> String -> Bool -- Given an rtype & objectid, returns whether it should be listed
+ , vis : String -> Bool -- Given an objectid, returns whether it should be listed
, submit : Bool -- Whether it allows submission of the form
- , msg : String -> String -> List (Html Msg) -- Message to display
+ , msg : String -> List (Html Msg) -- Message to display
}
-vis _ _ = True
-nomsg _ _ = []
+vis _ = True
+nomsg _ = []
+objtype s o = String.any (\c -> String.startsWith (String.fromChar c) o) s
+editable = objtype "vrpcs"
initial = { label = "-- Select --" , vis = vis, submit = False , msg = nomsg }
reasons : List ReasonLabel
@@ -55,19 +57,19 @@ reasons =
, msg = nomsg
}
, { label = "Off-topic / wrong board"
- , vis = \t _ -> t == "t"
+ , vis = objtype "tw"
, submit = True
, msg = nomsg
}
, { label = "Unwelcome behavior"
- , vis = \t _ -> t == "t"
+ , vis = objtype "tw"
, submit = True
, msg = nomsg
}
, { label = "Unmarked spoilers"
, vis = vis
, submit = True
- , msg = \t o -> if not (t == "db" && not (String.startsWith "d" o)) then [] else
+ , msg = \o -> if editable o then [] else
[ text "VNDB is an open wiki, it is often easier if you removed the spoilers yourself by "
, a [ href ("/" ++ o ++ "/edit") ] [ text " editing the entry" ]
, text ". You likely know more about this entry than our moderators, after all. "
@@ -78,9 +80,9 @@ reasons =
]
}
, { label = "Incorrect information"
- , vis = \t o -> t == "db" && not (String.startsWith "d" o)
+ , vis = editable
, submit = False
- , msg = \_ o ->
+ , msg = \o ->
[ text "VNDB is an open wiki, you can correct the information in this database yourself by "
, a [ href ("/" ++ o ++ "/edit") ] [ text " editing the entry" ]
, text ". You likely know more about this entry than our moderators, after all. "
@@ -91,9 +93,9 @@ reasons =
]
}
, { label = "Missing information"
- , vis = \t o -> t == "db" && not (String.startsWith "d" o)
+ , vis = editable
, submit = False
- , msg = \_ o ->
+ , msg = \o ->
[ text "VNDB is an open wiki, you can add any missing information to this database yourself. "
, text "You likely know more about this entry than our moderators, after all. "
, br [] []
@@ -103,9 +105,9 @@ reasons =
]
}
, { label = "Not a visual novel"
- , vis = \t o -> t == "db" && String.startsWith "v" o
+ , vis = objtype "v"
, submit = False
- , msg = \_ _ ->
+ , msg = \_ ->
[ text "If you suspect that this entry does not adhere to our "
, a [ href "/d2#1" ] [ text "inclusion criteria" ]
, text ", please report it in "
@@ -114,19 +116,29 @@ reasons =
]
}
, { label = "Does not belong here"
- , vis = \t o -> t == "db" && not (String.startsWith "v" o || String.startsWith "d" o)
+ , vis = \o -> editable o && not (objtype "v" o)
, submit = True
, msg = nomsg
}
, { label = "Duplicate entry"
- , vis = \t o -> t == "db" && not (String.startsWith "d" o)
+ , vis = editable
, submit = True
- , msg = \_ _ -> [ text "Please include a link to the entry that this is a duplicate of." ]
+ , msg = \_ -> [ text "Please include a link to the entry that this is a duplicate of." ]
}
, { label = "Other"
, vis = vis
, submit = True
- , msg = nomsg
+ , msg = \o ->
+ if editable o
+ then [ text "Keep in mind that VNDB is an open wiki, you can edit most of the information in this database."
+ , br [] []
+ , text "Reports for issues that do not require a moderator to get involved will most likely be ignored."
+ , br [] []
+ , text "If you need help with contributing to the database, feel free to ask around on the "
+ , a [ href "/t/db" ] [ text "discussion board" ]
+ , text "."
+ ]
+ else []
}
]
@@ -143,7 +155,7 @@ update msg (state,model) =
view : Model -> Html Msg
view (state,model) =
let
- lst = List.filter (\l -> l.vis model.rtype model.object) reasons
+ lst = List.filter (\l -> l.vis model.object) reasons
cur = List.filter (\l -> l.label == model.reason) lst |> List.head |> Maybe.withDefault initial
in
form_ Submit (state == Api.Loading)
@@ -163,7 +175,7 @@ view (state,model) =
else text "We generally do not provide feedback on reports, but you may leave your email address in the message if you wish to be available for clarification."
]
, formField "reason::Reason" [ inputSelect "reason" model.reason Reason [style "width" "300px"] <| List.map (\l->(l.label,l.label)) lst ]
- , formField "" (cur.msg model.rtype model.object)
+ , formField "" (cur.msg model.object)
] ++ if not cur.submit then [] else
[ formField "message::Message" [ inputTextArea "message" model.message Message [] ]
, formField "" [ submitButton "Submit" state True ]
diff --git a/elm/Reviews/Comment.elm b/elm/Reviews/Comment.elm
new file mode 100644
index 00000000..fba37168
--- /dev/null
+++ b/elm/Reviews/Comment.elm
@@ -0,0 +1,52 @@
+module Reviews.Comment 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.ReviewsComment as GRC
+
+
+main : Program GRC.Send Model Msg
+main = Browser.element
+ { init = \e -> ((Api.Normal, e.id, TP.bbcode ""), Cmd.none)
+ , view = view
+ , update = update
+ , subscriptions = always Sub.none
+ }
+
+type alias Model = (Api.State, String, TP.Model)
+
+type Msg
+ = Content TP.Msg
+ | Submit
+ | Submitted GApi.Response
+
+
+update : Msg -> Model -> (Model, Cmd Msg)
+update msg (state,id,content) =
+ case msg of
+ Content m -> let (nm,nc) = TP.update m content in ((state,id,nm), Cmd.map Content nc)
+ Submit -> ((Api.Loading,id,content), GRC.send { msg = content.data, id = id } Submitted)
+ Submitted (GApi.Redirect s) -> ((state,id,content), load s)
+ Submitted r -> ((Api.Error r,id,content), Cmd.none)
+
+
+view : Model -> Html Msg
+view (state,_,content) =
+ form_ Submit (state == Api.Loading)
+ [ div [ class "mainbox" ]
+ [ fieldset [ class "submit" ]
+ [ TP.view "msg" content Content 600 ([rows 4, cols 50] ++ GRC.valMsg)
+ [ b [] [ text "Comment" ]
+ , b [ class "standout" ] [ text " (English please!) " ]
+ , a [ href "/d9#3" ] [ text "Formatting" ]
+ ]
+ , submitButton "Submit" state True
+ ]
+ ]
+ ]
diff --git a/elm/Reviews/Edit.elm b/elm/Reviews/Edit.elm
new file mode 100644
index 00000000..925de964
--- /dev/null
+++ b/elm/Reviews/Edit.elm
@@ -0,0 +1,195 @@
+module Reviews.Edit 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 Lib.Util exposing (..)
+import Lib.RDate as RDate
+import Gen.Api as GApi
+import Gen.ReviewsEdit as GRE
+import Gen.ReviewsDelete as GRD
+
+
+main : Program GRE.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 : Maybe String
+ , vid : Int
+ , vntitle : String
+ , rid : Maybe Int
+ , spoiler : Bool
+ , locked : Bool
+ , isfull : Bool
+ , text : TP.Model
+ , releases : List GRE.RecvReleases
+ , delete : Bool
+ , delState : Api.State
+ , mod : Bool
+ }
+
+
+init : GRE.Recv -> Model
+init d =
+ { state = Api.Normal
+ , id = d.id
+ , vid = d.vid
+ , vntitle = d.vntitle
+ , rid = d.rid
+ , spoiler = d.spoiler
+ , locked = d.locked
+ , isfull = d.isfull
+ , text = TP.bbcode d.text
+ , releases = d.releases
+ , delete = False
+ , delState = Api.Normal
+ , mod = d.mod
+ }
+
+
+encode : Model -> GRE.Send
+encode m =
+ { id = m.id
+ , vid = m.vid
+ , rid = m.rid
+ , spoiler = m.spoiler
+ , locked = m.locked
+ , isfull = m.isfull
+ , text = m.text.data
+ }
+
+
+type Msg
+ = Release (Maybe Int)
+ | Full Bool
+ | Spoiler Bool
+ | Locked Bool
+ | Text TP.Msg
+ | Submit
+ | Submitted GApi.Response
+ | Delete Bool
+ | DoDelete
+ | Deleted GApi.Response
+
+
+update : Msg -> Model -> (Model, Cmd Msg)
+update msg model =
+ case msg of
+ Release i -> ({ model | rid = i }, Cmd.none)
+ Full b -> ({ model | isfull = b }, Cmd.none)
+ Spoiler b -> ({ model | spoiler = b }, Cmd.none)
+ Locked b -> ({ model | locked = b }, Cmd.none)
+ Text m -> let (nm,nc) = TP.update m model.text in ({ model | text = nm }, Cmd.map Text nc)
+
+ Submit -> ({ model | state = Api.Loading }, GRE.send (encode model) Submitted)
+ Submitted (GApi.Redirect s) -> (model, load s)
+ Submitted r -> ({ model | state = Api.Error r }, Cmd.none)
+
+ Delete b -> ({ model | delete = b }, Cmd.none)
+ DoDelete -> ({ model | delState = Api.Loading }, GRD.send ({ id = Maybe.withDefault "" model.id }) Deleted)
+ Deleted GApi.Success -> (model, load <| "/v" ++ String.fromInt model.vid)
+ Deleted r -> ({ model | delState = Api.Error r }, Cmd.none)
+
+
+showrel r = "[" ++ (RDate.format (RDate.expand r.released)) ++ " " ++ (String.join "," r.lang) ++ "] " ++ r.title ++ " (r" ++ String.fromInt r.id ++ ")"
+
+view : Model -> Html Msg
+view model =
+ let minChars = if model.isfull then 1000 else 200
+ maxChars = if model.isfull then 100000 else 800
+ len = String.length model.text.data
+ in
+ form_ Submit (model.state == Api.Loading)
+ [ div [ class "mainbox" ]
+ [ h1 [] [ text <| if model.id == Nothing then "Submit a review" else "Edit review" ]
+ , p [] [ b [] [ text "Rules" ] ]
+ , ul []
+ [ li [] [ text "Submit only reviews you have written yourself!" ]
+ , li [] [ text "Reviews must be in English." ]
+ , li [] [ text "Try to be as objective as possible." ]
+ , li [] [ text "If you have published the review elsewhere (e.g. a personal blog), feel free to include a link at the end of the review. Formatting tip: ", em [] [ text "[Originally published at <link>]" ] ]
+ , li [] [ text "Your vote (if any) will be displayed alongside the review, even if you have marked your list as private." ]
+ ]
+ , br [] []
+ ]
+ , div [ class "mainbox" ]
+ [ table [ class "formtable" ]
+ [ formField "Subject" [ a [ href <| "/v"++String.fromInt model.vid ] [ text model.vntitle ] ]
+ , formField ""
+ [ inputSelect "" model.rid Release [style "width" "500px" ] <|
+ (Nothing, "No release selected")
+ :: List.map (\r -> (Just r.id, showrel r)) model.releases
+ ++ if model.rid == Nothing || List.any (\r -> Just r.id == model.rid) model.releases then [] else [(model.rid, "Deleted or moved release: r"++Maybe.withDefault "" (Maybe.map String.fromInt model.rid))]
+ , br [] []
+ , text "You do not have to select a release, but indicating which release your review is based on gives more context."
+ ]
+ , tr [ class "newpart" ] [ td [ colspan 2 ] [ text "" ] ]
+ , formField "Review type"
+ [ label [] [ inputRadio "type" (model.isfull == False) (\_ -> Full False), b [] [ text " Mini review" ]
+ , text <| " - Recommendation-style, maximum 800 characters." ]
+ , br [] []
+ , label [] [ inputRadio "type" (model.isfull == True ) (\_ -> Full True ), b [] [ text " Full review" ]
+ , text " - Longer, more detailed." ]
+ , br [] []
+ , b [ class "grayedout" ] [ text "You can always switch between review types later." ]
+ ]
+ , tr [ class "newpart" ] [ td [ colspan 2 ] [ text "" ] ]
+ , formField ""
+ [ label [] [ inputCheck "" model.spoiler Spoiler, text " This review contains spoilers." ]
+ , br [] []
+ , b [ class "grayedout" ] [ text "You do not have to check this option if all spoilers in your review are marked with [spoiler] tags." ]
+ ]
+ , if not model.mod then text "" else
+ formField "" [ label [] [ inputCheck "" model.locked Locked, text " Locked for commenting." ] ]
+ , tr [ class "newpart" ] [ td [ colspan 2 ] [ text "" ] ]
+ , formField "text::Review"
+ [ TP.view "sum" model.text Text 700 ([rows (if model.isfull then 30 else 10), cols 50] ++ GRE.valText)
+ [ a [ href "/d9#3" ] [ text "BBCode formatting supported" ] ]
+ , div [ style "width" "700px", style "text-align" "right" ] <|
+ let num c s = if c then b [ class " standout" ] [ text s ] else text s
+ in
+ [ num (len < minChars) (String.fromInt minChars)
+ , text " / "
+ , b [] [ text (String.fromInt len) ]
+ , text " / "
+ , num (len > maxChars) (if model.isfull then "∞" else String.fromInt maxChars)
+ ]
+ ]
+ ]
+ ]
+ , div [ class "mainbox" ]
+ [ fieldset [ class "submit" ]
+ [ submitButton "Submit" model.state (len <= maxChars && len >= minChars)
+ ]
+ ]
+ , if model.id == Nothing then text "" else
+ div [ class "mainbox" ]
+ [ h1 [] [ text "Delete review" ]
+ , table [ class "formtable" ] [ formField ""
+ [ label [] [ inputCheck "" model.delete Delete, text " Delete this review." ]
+ , if not model.delete then text "" else span []
+ [ br [] []
+ , b [ class "standout" ] [ text "WARNING:" ]
+ , text " Deleting this review is a permanent action and can not be reverted!"
+ , br [] []
+ , br [] []
+ , inputButton "Confirm delete" DoDelete []
+ , case model.delState of
+ Api.Loading -> span [ class "spinner" ] []
+ Api.Error e -> b [ class "standout" ] [ text <| Api.showResponse e ]
+ Api.Normal -> text ""
+ ]
+ ] ]
+ ]
+ ]
diff --git a/elm/Reviews/Vote.elm b/elm/Reviews/Vote.elm
new file mode 100644
index 00000000..490a2b78
--- /dev/null
+++ b/elm/Reviews/Vote.elm
@@ -0,0 +1,70 @@
+module Reviews.Vote exposing (main)
+
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Html.Events exposing (..)
+import Browser
+import Lib.Util exposing (..)
+import Lib.Html exposing (..)
+import Lib.Api as Api
+import Gen.Api as GApi
+import Gen.ReviewsVote as GRV
+
+
+main : Program GRV.Recv Model Msg
+main = Browser.element
+ { init = \d -> (init d, Cmd.none)
+ , subscriptions = always Sub.none
+ , view = view
+ , update = update
+ }
+
+type alias Model =
+ { state : Api.State
+ , id : String
+ , my : Maybe Bool
+ , overrule : Bool
+ , mod : Bool
+ }
+
+init : GRV.Recv -> Model
+init d =
+ { state = Api.Normal
+ , id = d.id
+ , my = d.my
+ , overrule = d.overrule
+ , mod = d.mod
+ }
+
+type Msg
+ = Vote Bool
+ | Overrule Bool
+ | Saved GApi.Response
+
+
+update : Msg -> Model -> (Model, Cmd Msg)
+update msg model =
+ let save m = ({ m | state = Api.Loading }, GRV.send { id = m.id, my = m.my, overrule = m.overrule } Saved)
+ in
+ case msg of
+ Vote b -> save { model | my = if model.my == Just b then Nothing else Just b }
+ Overrule b -> let nm = { model | overrule = b } in if isJust model.my then save nm else (nm, Cmd.none)
+
+ Saved GApi.Success -> ({ model | state = Api.Normal }, Cmd.none)
+ Saved e -> ({ model | state = Api.Error e }, Cmd.none)
+
+
+view : Model -> Html Msg
+view model =
+ let but opt lbl = a [ href "#", onClickD (Vote opt), classList [("votebut", True), ("myvote", model.my == Just opt)] ] [ text lbl ]
+ in
+ span []
+ [ case model.state of
+ Api.Loading -> span [ class "spinner" ] []
+ Api.Error e -> b [ class "standout" ] [ text (Api.showResponse e) ]
+ Api.Normal -> text "Was this review helpful? "
+ , but True "yes"
+ , text " / "
+ , but False "no"
+ , if not model.mod then text "" else label [] [ text " / ", inputCheck "" model.overrule Overrule, text " O" ]
+ ]
diff --git a/elm/UList/VNPage.elm b/elm/UList/VNPage.elm
index 64c5f99a..b41e6ea1 100644
--- a/elm/UList/VNPage.elm
+++ b/elm/UList/VNPage.elm
@@ -18,26 +18,7 @@ import Gen.UListDel as GDE
import UList.LabelEdit as LE
import UList.VoteEdit as VE
--- We don't have a Gen.* module for this (yet), so define these manually
-type alias RecvLabels =
- { id : Int
- , label : String
- , private : Bool
- }
-
-type alias Recv =
- { uid : Int
- , vid : Int
- , onlist : Bool
- , canvote : Bool
- , vote : Maybe String
- , labels : List RecvLabels
- , selected : List Int
- , notes : String
- }
-
-
-main : Program Recv Model Msg
+main : Program GVN.VNPage Model Msg
main = Browser.element
{ init = \f -> (init f, Cmd.none)
, subscriptions = \model -> Sub.batch [ Sub.map Labels (DD.sub model.labels.dd), Sub.map Vote (DD.sub model.vote.dd) ]
@@ -46,7 +27,7 @@ main = Browser.element
}
type alias Model =
- { flags : Recv
+ { flags : GVN.VNPage
, onlist : Bool
, del : Bool
, state : Api.State -- For adding/deleting; Vote and label edit widgets have their own state
@@ -58,7 +39,7 @@ type alias Model =
, notesVis : Bool
}
-init : Recv -> Model
+init : GVN.VNPage -> Model
init f =
{ flags = f
, onlist = f.onlist
@@ -165,9 +146,11 @@ view model =
, td []
[ a [ href "#", onClickD NotesToggle ] [ text "💬" ]
, span [ class "spinner", classList [("hidden", model.notesState /= Api.Loading)] ] []
- , case model.notesState of
- Api.Error e -> b [ class "standout" ] [ text <| Api.showResponse e ]
- _ -> text ""
+ , case (model.notesState, model.vote.vote /= Nothing && model.flags.canreview, model.flags.review) of
+ (Api.Error e, _, _) -> b [ class "standout" ] [ text <| Api.showResponse e ]
+ (_, False, _) -> text ""
+ (_, True, Nothing) -> a [ href ("/v" ++ String.fromInt model.flags.vid ++ "/addreview") ] [ text " write a review »" ]
+ (_, True, Just w) -> a [ href ("/" ++ w ++ "/edit") ] [ text " edit review »" ]
]
]
else text ""
diff --git a/elm/User/Edit.elm b/elm/User/Edit.elm
index c51f37b4..d09c77ae 100644
--- a/elm/User/Edit.elm
+++ b/elm/User/Edit.elm
@@ -61,6 +61,7 @@ init d =
type AdminMsg
= PermBoard Bool
+ | PermReview Bool
| PermBoardmod Bool
| PermEdit Bool
| PermImgvote Bool
@@ -108,6 +109,7 @@ updateAdmin : AdminMsg -> GUE.SendAdmin -> GUE.SendAdmin
updateAdmin msg model =
case msg of
PermBoard b -> { model | perm_board = b }
+ PermReview b -> { model | perm_review = b }
PermBoardmod b -> { model | perm_boardmod = b }
PermEdit b -> { model | perm_edit = b }
PermImgvote b -> { model | perm_imgvote = b }
@@ -189,6 +191,7 @@ view model =
, formField "Permissions"
[ text "Fields marked with * indicate permissions assigned to new users by default", br_ 1
, perm opts.perm_boardmod <| label [] [ inputCheck "" m.perm_board (Admin << PermBoard), text " board*", br_ 1 ]
+ , perm opts.perm_boardmod <| label [] [ inputCheck "" m.perm_review (Admin << PermReview), text " review*", br_ 1 ]
, perm False <| label [] [ inputCheck "" m.perm_boardmod (Admin << PermBoardmod), text " boardmod", br_ 1 ]
, perm opts.perm_dbmod <| label [] [ inputCheck "" m.perm_edit (Admin << PermEdit), text " edit*", br_ 1 ]
, perm opts.perm_imgmod <| label [] [ inputCheck "" m.perm_imgvote (Admin << PermImgvote), text " imgvote* (existing votes will stop counting when unset)", br_ 1 ]
diff --git a/elm/VNEdit.elm b/elm/VNEdit.elm
index 0397b492..4fadbf2d 100644
--- a/elm/VNEdit.elm
+++ b/elm/VNEdit.elm
@@ -280,9 +280,17 @@ update msg model =
Submitted r -> ({ model | state = Api.Error r }, Cmd.none)
+-- TODO: Fuzzier matching? Exclude stuff like 'x Edition', etc.
+relAlias : Model -> Maybe GVE.RecvReleases
+relAlias model =
+ let a = String.toLower model.alias |> String.lines |> List.filter (\l -> l /= "") |> Set.fromList
+ in List.filter (\r -> Set.member (String.toLower r.title) a || Set.member (String.toLower r.original) a) model.releases |> List.head
+
+
isValid : Model -> Bool
isValid model = not
( (model.title /= "" && model.title == model.original)
+ || relAlias model /= Nothing
|| not (Img.isValid model.image)
|| List.any (\(_,i,r) -> r == Nothing || not (Img.isValid i)) model.screenshots
|| hasDuplicates (List.map (\s -> (s.aid, s.role)) model.staff)
@@ -313,8 +321,16 @@ view model =
, br [] []
, if hasDuplicates <| String.lines <| String.toLower model.alias
then b [ class "standout" ] [ text "List contains duplicate aliases.", br [] [] ]
- else text ""
- -- TODO: Warn when release titles are entered?
+ else
+ case relAlias model of
+ Nothing -> text ""
+ Just r -> span []
+ [ b [ class "standout" ] [ text "Release titles should not be added as alias." ]
+ , br [] []
+ , text "Release: "
+ , a [ href <| "/r"++String.fromInt r.id ] [ text r.title ]
+ , br [] [], br [] []
+ ]
, text "List of alternative titles or abbreviations. One line for each alias. Can include both official (japanese/english) titles and unofficial titles used around net."
, br [] []
, text "Titles that are listed in the releases should not be added here!"
diff --git a/elm/iv.js b/elm/iv.js
index 167b0c93..06bb6f5a 100644
--- a/elm/iv.js
+++ b/elm/iv.js
@@ -24,6 +24,8 @@ var ivimg;
var ivfull;
var ivnext;
var ivprev;
+var ivhovernext;
+var ivhoverprev;
var ivload;
var ivflag;
@@ -62,6 +64,16 @@ function create_div() {
ivnext.textContent = 'next »';
ivlinks.appendChild(ivnext);
+ ivhoverprev = document.createElement('a');
+ ivhoverprev.onclick = show;
+ ivhoverprev.className = "left-pane";
+ ivimg.appendChild(ivhoverprev);
+
+ ivhovernext = document.createElement('a');
+ ivhovernext.onclick = show;
+ ivhovernext.className = "right-pane";
+ ivimg.appendChild(ivhovernext);
+
ivflag = document.createElement('a');
ivlinks.appendChild(ivflag);
@@ -137,13 +149,15 @@ function show(ev) {
imgh = Math.floor(opt[0].split('x')[1]);
create_div();
+ var imgs = ivimg.getElementsByTagName("img")
+ if (imgs.length !== 0)
+ ivimg.getElementsByTagName("img")[0].remove()
var img = document.createElement('img');
img.src = u;
ivfull.href = u;
img.onclick = ivClose;
img.onload = function() { ivload.style.display = 'none' };
- ivimg.textContent = '';
ivimg.appendChild(img);
var flag = opt[2] ? opt[2].match(/^([0-2])([0-2])([0-9]+)$/) : null;
@@ -161,6 +175,8 @@ function show(ev) {
ivload.style.display = 'block';
fixnav(ivprev, opt[1], idx, -1);
fixnav(ivnext, opt[1], idx, 1);
+ fixnav(ivhoverprev, opt[1], idx, -1);
+ fixnav(ivhovernext, opt[1], idx, 1);
resize();
document.addEventListener('click', ivClose);
@@ -180,7 +196,9 @@ window.ivClose = function(ev) {
document.removeEventListener('keydown', keydown);
window.removeEventListener('resize', resize);
ivparent.style.display = 'none';
- ivimg.textContent = '';
+ var imgs = ivimg.getElementsByTagName("img")
+ if (imgs.length !== 0)
+ ivimg.getElementsByTagName("img")[0].remove()
return false;
};
diff --git a/elm/sethash.js b/elm/sethash.js
new file mode 100644
index 00000000..7b054d0b
--- /dev/null
+++ b/elm/sethash.js
@@ -0,0 +1,8 @@
+// Emulate setting a location.hash if none has been set.
+if(pageVars.sethash && location.hash.length <= 1) {
+ var e = document.getElementById(pageVars.sethash);
+ if(e) {
+ e.scrollIntoView();
+ e.classList.add('target');
+ }
+}