diff options
-rw-r--r-- | elm/Lib/Autocomplete.elm | 2 | ||||
-rw-r--r-- | elm/ProducerEdit.elm | 231 | ||||
-rw-r--r-- | lib/VNDB/Handler/Producers.pm | 8 | ||||
-rw-r--r-- | lib/VNWeb/Elm.pm | 3 | ||||
-rw-r--r-- | lib/VNWeb/Producers/Edit.pm | 120 | ||||
-rw-r--r-- | lib/VNWeb/Producers/Elm.pm | 32 | ||||
-rw-r--r-- | lib/VNWeb/VN/Edit.pm | 1 | ||||
-rw-r--r-- | lib/VNWeb/VN/Elm.pm | 2 |
8 files changed, 380 insertions, 19 deletions
diff --git a/elm/Lib/Autocomplete.elm b/elm/Lib/Autocomplete.elm index 30a8ee29..5c5dd33d 100644 --- a/elm/Lib/Autocomplete.elm +++ b/elm/Lib/Autocomplete.elm @@ -140,7 +140,7 @@ vnSource = producerSource : SourceConfig m GApi.ApiProducerResult producerSource = - { source = Endpoint (\s -> GP.send { search = s }) + { source = Endpoint (\s -> GP.send { search = [s], hidden = False }) <| \x -> case x of GApi.ProducerResult e -> Just e _ -> Nothing diff --git a/elm/ProducerEdit.elm b/elm/ProducerEdit.elm new file mode 100644 index 00000000..f301d20c --- /dev/null +++ b/elm/ProducerEdit.elm @@ -0,0 +1,231 @@ +module ProducerEdit exposing (main) + +import Html exposing (..) +import Html.Events exposing (..) +import Html.Attributes exposing (..) +import Browser +import Browser.Navigation exposing (load) +import Lib.Util exposing (..) +import Lib.Html exposing (..) +import Lib.TextPreview as TP +import Lib.Autocomplete as A +import Lib.Api as Api +import Lib.Editsum as Editsum +import Gen.Producers as GP +import Gen.ProducerEdit as GPE +import Gen.Types as GT +import Gen.Api as GApi + + +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 + , editsum : Editsum.Model + , ptype : String + , name : String + , original : String + , alias : String + , lang : String + , website : String + , lWikidata : Maybe Int + , desc : TP.Model + , rel : List GPE.RecvRelations + , relSearch : A.Model GApi.ApiProducerResult + , id : Maybe Int + , dupCheck : Bool + , dupProds : List GApi.ApiProducerResult + } + + +init : GPE.Recv -> Model +init d = + { state = Api.Normal + , editsum = { authmod = d.authmod, editsum = TP.bbcode d.editsum, locked = d.locked, hidden = d.hidden } + , ptype = d.ptype + , name = d.name + , original = d.original + , alias = d.alias + , lang = d.lang + , website = d.website + , lWikidata = d.l_wikidata + , desc = TP.bbcode d.desc + , rel = d.relations + , relSearch = A.init "" + , id = d.id + , dupCheck = False + , dupProds = [] + } + + +encode : Model -> GPE.Send +encode model = + { id = model.id + , editsum = model.editsum.editsum.data + , hidden = model.editsum.hidden + , locked = model.editsum.locked + , ptype = model.ptype + , name = model.name + , original = model.original + , alias = model.alias + , lang = model.lang + , website = model.website + , l_wikidata = model.lWikidata + , desc = model.desc.data + , relations = List.map (\p -> { pid = p.pid, relation = p.relation }) model.rel + } + +prodConfig : A.Config Msg GApi.ApiProducerResult +prodConfig = { wrap = RelSearch, id = "relationadd", source = A.producerSource } + +type Msg + = Editsum Editsum.Msg + | Submit + | Submitted GApi.Response + | PType String + | Name String + | Original String + | Alias String + | Lang String + | Website String + | LWikidata (Maybe Int) + | Desc TP.Msg + | RelDel Int + | RelRel Int String + | RelSearch (A.Msg GApi.ApiProducerResult) + | DupSubmit + | DupResults GApi.Response + + +update : Msg -> Model -> (Model, Cmd Msg) +update msg model = + case msg of + Editsum m -> let (nm,nc) = Editsum.update m model.editsum in ({ model | editsum = nm }, Cmd.map Editsum nc) + PType s -> ({ model | ptype = s }, Cmd.none) + Name s -> ({ model | name = s, dupProds = [] }, Cmd.none) + Original s -> ({ model | original = s, dupProds = [] }, Cmd.none) + Alias s -> ({ model | alias = s, dupProds = [] }, Cmd.none) + Lang s -> ({ model | lang = s }, Cmd.none) + Website s -> ({ model | website = s }, Cmd.none) + LWikidata n-> ({ model | lWikidata = n }, Cmd.none) + Desc m -> let (nm,nc) = TP.update m model.desc in ({ model | desc = nm }, Cmd.map Desc nc) + + RelDel idx -> ({ model | rel = delidx idx model.rel }, Cmd.none) + RelRel idx rel -> ({ model | rel = modidx idx (\p -> { p | relation = rel }) model.rel }, Cmd.none) + RelSearch m -> + let (nm, c, res) = A.update prodConfig m model.relSearch + in case res of + Nothing -> ({ model | relSearch = nm }, c) + Just p -> + if List.any (\l -> l.pid == p.id) model.rel + then ({ model | relSearch = A.clear nm "" }, c) + else ({ model | relSearch = A.clear nm "", rel = model.rel ++ [{ pid = p.id, name = p.name, original = p.original, relation = "old" }] }, Cmd.none) + + DupSubmit -> + if List.isEmpty model.dupProds + then ({ model | state = Api.Loading }, GP.send { hidden = True, search = model.name :: model.original :: String.lines model.alias } DupResults) + else ({ model | dupCheck = True, dupProds = [] }, Cmd.none) + DupResults (GApi.ProducerResult prods) -> + if List.isEmpty prods + then ({ model | state = Api.Normal, dupCheck = True, dupProds = [] }, Cmd.none) + else ({ model | state = Api.Normal, dupProds = prods }, Cmd.none) + DupResults r -> ({ model | state = Api.Error r }, Cmd.none) + + 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) + + +isValid : Model -> Bool +isValid model = not + ( (model.name /= "" && model.name == model.original) + || hasDuplicates (List.map (\p -> p.pid) model.rel) + ) + + +view : Model -> Html Msg +view model = + let + titles = + [ formField "name::Name (romaji)" [ inputText "name" model.name Name (style "width" "500px" :: GPE.valName) ] + , formField "original::Original name" + [ inputText "original" model.original Original (style "width" "500px" :: GPE.valOriginal) + , if model.name /= "" && model.name == model.original + then b [ class "standout" ] [ br [] [], text "Should not be the same as the Name (romaji). Leave blank is the original name is already in the latin alphabet" ] + else if model.original /= "" && String.toLower model.name /= String.toLower model.original && not (containsNonLatin model.original) + then b [ class "standout" ] [ br [] [], text "Original name does not seem to contain any non-latin characters. Leave this field empty if the name is already in the latin alphabet" ] + else text "" + ] + , formField "alias::Aliases" + [ inputTextArea "alias" model.alias Alias (rows 3 :: GPE.valAlias) + , br [] [] + , if hasDuplicates <| String.lines <| String.toLower model.alias + then b [ class "standout" ] [ text "List contains duplicate aliases.", br [] [] ] + else text "" + , text "(Un)official aliases, separated by a newline." + ] + ] + + geninfo = + [ formField "ptype::Type" [ inputSelect "ptype" model.ptype PType [] GT.producerTypes ] ] + ++ titles ++ + [ formField "lang::Primary language" [ inputSelect "lang" model.lang Lang [] GT.languages ] + , formField "website::Website" [ inputText "website" model.website Website GPE.valWebsite ] + , formField "l_wikidata::Wikidata ID" [ inputWikidata "l_wikidata" model.lWikidata LWikidata ] + , formField "desc::Description" + [ TP.view "desc" model.desc Desc 600 (style "height" "180px" :: GPE.valDesc) [ b [ class "standout" ] [ text "English please!" ] ] ] + + , tr [ class "newpart" ] [ td [ colspan 2 ] [ text "Database relations" ] ] + , formField "Related producers" + [ if List.isEmpty model.rel then text "" + else table [] <| List.indexedMap (\i p -> tr [] + [ td [ style "text-align" "right" ] [ b [ class "grayedout" ] [ text <| "p" ++ String.fromInt p.pid ++ ":" ] ] + , td [ style "text-align" "right"] [ a [ href <| "/p" ++ String.fromInt p.pid ] [ text p.name ] ] + , td [] + [ text "is an " + , inputSelect "" p.relation (RelRel i) [] GT.producerRelations + , text " of this producer" + ] + , td [] [ inputButton "remove" (RelDel i) [] ] + ] + ) model.rel + , A.view prodConfig model.relSearch [placeholder "Add Producer..."] + ] + ] + + newform () = + form_ DupSubmit (model.state == Api.Loading) + [ div [ class "mainbox" ] [ h1 [] [ text "Add a new producer" ], table [ class "formtable" ] titles ] + , div [ class "mainbox" ] + [ if List.isEmpty model.dupProds then text "" else + div [] + [ h1 [] [ text "Possible duplicates" ] + , text "The following is a list of producers that match the name(s) you gave. " + , text "Please check this list to avoid creating a duplicate producer entry. " + , text "Be especially wary of items that have been deleted! To see why an entry has been deleted, click on its title." + , ul [] <| List.map (\p -> li [] + [ a [ href <| "/p" ++ String.fromInt p.id ] [ text p.name ] + , if p.hidden then b [ class "standout" ] [ text " (deleted)" ] else text "" + ] + ) model.dupProds + ] + , fieldset [ class "submit" ] [ submitButton (if List.isEmpty model.dupProds then "Continue" else "Continue anyway") model.state (isValid model) ] + ] + ] + + fullform () = + form_ Submit (model.state == Api.Loading) + [ div [ class "mainbox" ] [ h1 [] [ text "Edit producer" ], table [ class "formtable" ] geninfo ] + , div [ class "mainbox" ] [ fieldset [ class "submit" ] + [ Html.map Editsum (Editsum.view model.editsum) + , submitButton "Submit" model.state (isValid model) + ] + ] + ] + in if model.id == Nothing && not model.dupCheck then newform () else fullform () diff --git a/lib/VNDB/Handler/Producers.pm b/lib/VNDB/Handler/Producers.pm index d8b2cea1..6e70e512 100644 --- a/lib/VNDB/Handler/Producers.pm +++ b/lib/VNDB/Handler/Producers.pm @@ -9,8 +9,8 @@ use VNDB::Types; TUWF::register( - qr{p/add} => \&addform, - qr{p(?:([1-9]\d*)(?:\.([1-9]\d*))?/edit|/new)} + qr{old/p/add} => \&addform, + qr{old/p(?:([1-9]\d*)(?:\.([1-9]\d*))?/edit|/new)} => \&edit, qr{p/([a-z0]|all)} => \&list, qr{xml/producers\.xml} => \&pxml, @@ -68,7 +68,7 @@ sub addform { end 'div'; } - $self->htmlForm({ frm => $frm, action => '/p/add', continue => @$l ? 2 : 1 }, + $self->htmlForm({ frm => $frm, action => '/old/p/add', continue => @$l ? 2 : 1 }, vn_add => [ 'Add a new producer', [ input => name => 'Name (romaji)', short => 'name' ], [ input => name => 'Original name', short => 'original' ], @@ -151,7 +151,7 @@ sub edit { $self->htmlHeader(title => $title, noindex => 1); $self->htmlMainTabs('p', $p, 'edit') if $pid; $self->htmlEditMessage('p', $p, $title); - $self->htmlForm({ frm => $frm, action => $pid ? "/p$pid/edit" : '/p/new', editsum => 1 }, + $self->htmlForm({ frm => $frm, action => $pid ? "/old/p$pid/edit" : '/old/p/new', editsum => 1 }, 'pedit_geninfo' => [ 'General info', [ select => name => 'Type', short => 'type', options => [ map [ $_, $PRODUCER_TYPE{$_} ], keys %PRODUCER_TYPE ] ], diff --git a/lib/VNWeb/Elm.pm b/lib/VNWeb/Elm.pm index 55de9c99..13165959 100644 --- a/lib/VNWeb/Elm.pm +++ b/lib/VNWeb/Elm.pm @@ -94,6 +94,7 @@ our %apis = ( id => { id => 1 }, name => {}, original => { required => 0, default => '' }, + hidden => { anybool => 1 }, } } ], StaffResult => [ { aoh => { # Response to 'Staff' id => { id => 1 }, @@ -413,6 +414,8 @@ sub write_types { $data .= def vnLengths => 'List (Int, String)' => list map tuple($_, string $VN_LENGTH{$_}{txt}.($VN_LENGTH{$_}{time}?" ($VN_LENGTH{$_}{time})":'')), keys %VN_LENGTH; $data .= def vnRelations=> 'List (String, String)' => list map tuple(string $_, string $VN_RELATION{$_}{txt}), keys %VN_RELATION; $data .= def creditTypes=> 'List (String, String)' => list map tuple(string $_, string $CREDIT_TYPE{$_}), keys %CREDIT_TYPE; + $data .= def producerRelations=> 'List (String, String)' => list map tuple(string $_, string $PRODUCER_RELATION{$_}{txt}), keys %PRODUCER_RELATION; + $data .= def producerTypes=> 'List (String, String)' => list map tuple(string $_, string $PRODUCER_TYPE{$_}), keys %PRODUCER_TYPE; $data .= def curYear => Int => (gmtime)[5]+1900; write_module Types => $data; diff --git a/lib/VNWeb/Producers/Edit.pm b/lib/VNWeb/Producers/Edit.pm new file mode 100644 index 00000000..27f922a6 --- /dev/null +++ b/lib/VNWeb/Producers/Edit.pm @@ -0,0 +1,120 @@ +package VNWeb::Producers::Edit; + +use VNWeb::Prelude; + + +my $FORM = { + id => { required => 0, id => 1 }, + ptype => { enum => \%PRODUCER_TYPE }, + name => { maxlength => 200 }, + original => { required => 0, default => '', maxlength => 200 }, + alias => { required => 0, default => '', maxlength => 500 }, + lang => { enum => \%LANGUAGE }, + website => { required => 0, default => '', weburl => 1 }, + l_wikidata => { required => 0, uint => 1, max => (1<<31)-1 }, + desc => { required => 0, default => '', maxlength => 5000 }, + relations => { sort_keys => 'pid', aoh => { + pid => { id => 1 }, + relation => { enum => \%PRODUCER_RELATION }, + name => { _when => 'out' }, + original => { _when => 'out', required => 0, default => '' }, + } }, + hidden => { anybool => 1 }, + locked => { anybool => 1 }, + + authmod => { _when => 'out', anybool => 1 }, + editsum => { _when => 'in out', editsum => 1 }, +}; + +my $FORM_OUT = form_compile out => $FORM; +my $FORM_IN = form_compile in => $FORM; +my $FORM_CMP = form_compile cmp => $FORM; + + +TUWF::get qr{/$RE{prev}/edit} => sub { + my $e = db_entry p => tuwf->capture('id'), tuwf->capture('rev') or return tuwf->resNotFound; + return tuwf->resDenied if !can_edit p => $e; + + $e->{authmod} = auth->permDbmod; + $e->{editsum} = $e->{chrev} == $e->{maxrev} ? '' : "Reverted to revision p$e->{id}.$e->{chrev}"; + $e->{ptype} = delete $e->{type}; + + enrich_merge pid => 'SELECT id AS pid, name, original FROM producers WHERE id IN', $e->{relations}; + + framework_ title => "Edit $e->{name}", type => 'p', dbobj => $e, tab => 'edit', + sub { + editmsg_ p => $e, "Edit $e->{name}"; + elm_ ProducerEdit => $FORM_OUT, $e; + }; +}; + + +TUWF::get qr{/p/add}, sub { + return tuwf->resDenied if !can_edit p => undef; + + framework_ title => 'Add producer', + sub { + editmsg_ p => undef, 'Add producer'; + elm_ ProducerEdit => $FORM_OUT, { elm_empty($FORM_OUT)->%*, lang => 'ja' }; + }; +}; + + +elm_api ProducerEdit => $FORM_OUT, $FORM_IN, sub { + my $data = shift; + my $new = !$data->{id}; + my $e = $new ? { id => 0 } : db_entry p => $data->{id} or return tuwf->resNotFound; + return elm_Unauth if !can_edit p => $e; + + if(!auth->permDbmod) { + $data->{hidden} = $e->{hidden}||0; + $data->{locked} = $e->{locked}||0; + } + $data->{desc} = bb_subst_links $data->{desc}; + $data->{alias} =~ s/\n\n+/\n/; + + $data->{relations} = [] if $data->{hidden}; + validate_dbid 'SELECT id FROM producers WHERE id IN', map $_->{pid}, $data->{relations}->@*; + die "Relation with self" if grep $_->{pid} == $e->{id}, $data->{relations}->@*; + + $e->{ptype} = $e->{type}; + $data->{type} = $data->{ptype}; + return elm_Unchanged if !$new && !form_changed $FORM_CMP, $data, $e; + my($id,undef,$rev) = db_edit p => $e->{id}, $data; + update_reverse($id, $rev, $e, $data); + elm_Redirect "/p$id.$rev"; +}; + + +sub update_reverse { + my($id, $rev, $old, $new) = @_; + + my %old = map +($_->{pid}, $_), $old->{relations} ? $old->{relations}->@* : (); + my %new = map +($_->{pid}, $_), $new->{relations}->@*; + + # Updates to be performed, pid => { pid => x, relation => y } or undef if the relation should be removed. + my %upd; + + for my $i (keys %old, keys %new) { + if($old{$i} && !$new{$i}) { + $upd{$i} = undef; + } elsif(!$old{$i} || $old{$i}{relation} ne $new{$i}{relation}) { + $upd{$i} = { + pid => $id, + relation => $PRODUCER_RELATION{ $new{$i}{relation} }{reverse}, + }; + } + } + + for my $i (keys %upd) { + my $e = db_entry p => $i; + $e->{relations} = [ + $upd{$i} ? $upd{$i} : (), + grep $_->{pid} != $id, $e->{relations}->@* + ]; + $e->{editsum} = "Reverse relation update caused by revision p$id.$rev"; + db_edit p => $i, $e, 1; + } +} + +1; diff --git a/lib/VNWeb/Producers/Elm.pm b/lib/VNWeb/Producers/Elm.pm index ea541130..a41f831c 100644 --- a/lib/VNWeb/Producers/Elm.pm +++ b/lib/VNWeb/Producers/Elm.pm @@ -2,22 +2,30 @@ package VNWeb::Producers::Elm; use VNWeb::Prelude; -elm_api Producers => undef, { search => {} }, sub { - my $q = shift->{search}; - my $qs = $q =~ s/[%_]//gr; +elm_api Producers => undef, { + search => { type => 'array', values => { required => 0, default => '' } }, + hidden => { anybool => 1 }, +}, sub { + my($data) = @_; + my @q = grep length $_, $data->{search}->@*; + die "No query" if !@q; elm_ProducerResult tuwf->dbPagei({ results => 15, page => 1 }, - 'SELECT p.id, p.name, p.original + 'SELECT p.id, p.name, p.original, p.hidden FROM (', - sql_join('UNION ALL', - $q =~ /^$RE{pid}$/ ? sql('SELECT 1, id FROM producers WHERE id =', \"$+{id}") : (), - sql('SELECT 1+substr_score(lower(name),' , \$qs, '), id FROM producers WHERE name ILIKE', \"%$qs%"), - sql('SELECT 10+substr_score(lower(original),', \$qs, '), id FROM producers WHERE original ILIKE', \"%$qs%"), - sql('SELECT 100, id FROM producers WHERE alias ILIKE', \"%$qs%"), - ), ') x(prio, id) + sql_join('UNION ALL', map { + my $qs = s/[%_]//gr; + ( + /^$RE{pid}$/ ? sql('SELECT 1, id FROM producers WHERE id =', \"$+{id}") : (), + sql('SELECT 1+substr_score(lower(name),' , \$qs, '), id FROM producers WHERE name ILIKE', \"%$qs%"), + sql('SELECT 10+substr_score(lower(original),', \$qs, "), id FROM producers WHERE translate(original,' ','') ILIKE", \("%$qs%" =~ s/ //gr)), + sql('SELECT 100, id FROM producers WHERE alias ILIKE', \"%$qs%"), + ) + } @q), + ') x(prio, id) JOIN producers p ON p.id = x.id - WHERE NOT p.hidden - GROUP BY p.id, p.name, p.original + WHERE', sql_and($data->{hidden} ? () : 'NOT p.hidden'), ' + GROUP BY p.id, p.name, p.original, p.hidden ORDER BY MIN(x.prio), p.name '); }; diff --git a/lib/VNWeb/VN/Edit.pm b/lib/VNWeb/VN/Edit.pm index b1172750..048af953 100644 --- a/lib/VNWeb/VN/Edit.pm +++ b/lib/VNWeb/VN/Edit.pm @@ -71,7 +71,6 @@ TUWF::get qr{/$RE{vrev}/edit} => sub { my $e = db_entry v => tuwf->capture('id'), tuwf->capture('rev') or return tuwf->resNotFound; return tuwf->resDenied if !can_edit v => $e; - $e->{image_sex} = $e->{image_vio} = undef; $e->{authmod} = auth->permDbmod; $e->{editsum} = $e->{chrev} == $e->{maxrev} ? '' : "Reverted to revision v$e->{id}.$e->{chrev}"; diff --git a/lib/VNWeb/VN/Elm.pm b/lib/VNWeb/VN/Elm.pm index 2ab3ddf2..3aded8e2 100644 --- a/lib/VNWeb/VN/Elm.pm +++ b/lib/VNWeb/VN/Elm.pm @@ -25,7 +25,7 @@ elm_api VN => undef, { ') x(prio, id) JOIN vn v ON v.id = x.id WHERE', sql_and($data->{hidden} ? () : 'NOT v.hidden'), ' - GROUP BY v.id, v.title, v.original + GROUP BY v.id, v.title, v.original, v.hidden ORDER BY MIN(x.prio), v.title '); }; |