summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--elm/Lib/Autocomplete.elm2
-rw-r--r--elm/ProducerEdit.elm231
-rw-r--r--lib/VNDB/Handler/Producers.pm8
-rw-r--r--lib/VNWeb/Elm.pm3
-rw-r--r--lib/VNWeb/Producers/Edit.pm120
-rw-r--r--lib/VNWeb/Producers/Elm.pm32
-rw-r--r--lib/VNWeb/VN/Edit.pm1
-rw-r--r--lib/VNWeb/VN/Elm.pm2
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
');
};