word/edit: preserve unknown json values
[sheet.git] / word / edit.plp
index d8de60249c7a54945d048885acc4fb747d87357a..4a6daaad4a19ab503cd0680ec13479e11baa7934 100644 (file)
@@ -1,6 +1,8 @@
 <(../common.inc.plp)><:
 
+my $editorurl = '/word/edit';
 s{\Aedit(/|\z)}{} for $Request // ();
+
 Html({
        title => 'words cheat sheet admin',
        version => '1.0',
@@ -21,7 +23,6 @@ my $db = eval {
 } or Abort('Database error', 501, $@);
 
 my $user = eval {
-       my $rootpath = '/word/edit';
        if (defined $post{username}) {
                $cookie{login} = EncodeURI(join ':', @post{qw( username pass )});
        }
@@ -30,7 +31,7 @@ my $user = eval {
                if (AddCookie(CGI::Cookie->new(
                        -name    => 'login',
                        -value   => '',
-                       -path    => $rootpath,
+                       -path    => $editorurl,
                        -expires => 'now',
                )->as_string)) {
                        delete $cookie{login};
@@ -50,7 +51,7 @@ my $user = eval {
                my $httpcookie = CGI::Cookie->new(
                        -name    => 'login',
                        -value   => join(':', @{$found}{qw( username pass )}),
-                       -path    => $rootpath,
+                       -path    => $editorurl,
                ) or die "prepared object is empty\n";
                AddCookie($httpcookie->as_string);
        } or Abort(["Unable to create login cookie", $@], 403);
@@ -71,9 +72,10 @@ my $user = eval {
 };
 
 my %lang = (
+       '' => ['(reference)'],
        nl => ["\N{REGIONAL INDICATOR SYMBOL LETTER N}\N{REGIONAL INDICATOR SYMBOL LETTER L}", 'nederlands'],
        en => ["\N{REGIONAL INDICATOR SYMBOL LETTER G}\N{REGIONAL INDICATOR SYMBOL LETTER B}", 'english'],
-       eo => ['<span style="color:green">★</span>', 'esperanto'],
+       eo => [qq'<span style="color:green">\N{BLACK STAR}</span>', 'esperanto'],
        ru => ["\N{REGIONAL INDICATOR SYMBOL LETTER R}\N{REGIONAL INDICATOR SYMBOL LETTER U}", 'русский'],
        zh => ["\N{REGIONAL INDICATOR SYMBOL LETTER C}\N{REGIONAL INDICATOR SYMBOL LETTER N}", '中文'],
        la => ["\N{PUSHPIN}", 'latin'],
@@ -88,7 +90,9 @@ my %wordcol = (
        prio    => [
                {-label => 'Level', -select => sub {
                        my ($row) = @_;
-                       my @enum = qw[ essential basic common distinctive optional invisible ];
+                       my @enum = qw[
+                               essential ubiquitous basic common distinctive specialised rare invisible
+                       ];
                        return {
                                ('' => 'parent') x (defined $row->{ref}),
                                map { $_ => $enum[$_] } 0 .. $#enum
@@ -105,17 +109,19 @@ my %wordcol = (
                return "data/word/org/$_[0]->{id}.jpg";
        }},
        convert => {-label => 'Convert options', -json => 'image', -multiple => 1, -src => sub {
-               return "data/word/en/$_[0]->{id}.jpg";
+               return "data/word/32/$_[0]->{id}.jpg";
        }},
+       crop32  => {-json => 'image', type => 'hidden'}, # set by javascript interface
        story   => {-label => 'Story', type => 'textarea', hidden => 'hidden'},
 );
 
 if (my $search = $fields{q}) {
-       my %filter = (form => {ilike => '%'.$search.'%'});
+       my %filter = $search eq '^' ? (cat => undef, ref => undef) :
+               (form => {ilike => '%'.parseinput($search).'%'});
        my $results = $db->select(word => '*', \%filter);
        say '<h1>Search</h1><ul>';
        printf("<li><small>%s</small> %s %s</li>\n",
-               $_->{id}, showlink($_->{form}, "/writer/$_->{id}"),
+               $_->{id}, showlink($_->{form}, "$editorurl/$_->{id}"),
                sprintf('<img src="/%s" style="height:3ex; width:auto" />', $wordcol{convert}->{-src}->($_)) x defined $_->{image}
        ) for $results->hashes;
        say "</ul>\n";
@@ -146,7 +152,6 @@ elsif (defined $post{form}) {{
                my @val = map { parseinput($_) } $post{'@'.$col}->@*;
                my $val = $colinfo->{-multiple} && @val ? \@val : $val[-1];
                if (my $jsoncol = $colinfo->{-json}) {
-                       defined $val and
                        $row->{$jsoncol}->{$col} = $val;  # hash will be encoded
                }
                else {
@@ -154,9 +159,15 @@ elsif (defined $post{form}) {{
                }
        }
        my $imagecol = $row->{image};  # backup image subcolumns
-       ref $_ eq 'HASH' and $_ = encode_json($_) for values %{$row};
+       while (my ($col, $val) = each %{$row}) {
+               # convert json subcolumns to database string
+               ref $val eq 'HASH' or next;
+               $val = { %{decode_json($_)}, %{$val} } for $replace->{$col} // ();  # preserve unknown
+               defined $val->{$_} or delete $val->{$_} for keys %{$val};  # delete emptied
+               $row->{$col} = encode_json($val);
+       }
 
-       if (!$row->{form}) {
+       if (!$row->{form} and $row->{lang}) {
                if ($row->{ref} ne 'delete') {
                        Alert("Empty title",
                                "Confirm removal by setting <em>Reference</em> to <q>delete</q>."
@@ -213,7 +224,7 @@ elsif (defined $post{form}) {{
        $reimage++ if $fields{rethumb};  # force refresh
        if ($reimage) {
                eval {
-                       $image->convert($wordcol{convert}->{-src}->($row), $imagecol->{convert});
+                       $image->generate($wordcol{convert}->{-src}->($row), $imagecol);
                } or do {
                        my ($warn, @details) = ref $@ ? @{$@} : $@;
                        Alert([ "Thumbnail image not generated", $warn ], @details);
@@ -221,13 +232,13 @@ elsif (defined $post{form}) {{
        }
 }}
 else {
-       $row->{lang} //= $user->{editlang}->[0];
+       $row->{lang} //= $user->{editlang}->[0] unless exists $row->{lang};
        $row->{$_} = $get{$_} for keys %get;
-       $row->{prio} = defined $row->{ref} ? undef : 1 unless exists $row->{prio};
+       $row->{prio} = defined $row->{ref} ? undef : 4 unless exists $row->{prio};
 }
 
 eval {
-       my $imagerow = $row->{image} && decode_json(delete $row->{image}) || {};
+       my $imagerow = $row->{image} && JSON->new->decode(delete $row->{image}) || {};
        while (my ($col, $val) = each %{$imagerow}) {
                $row->{$col} = $val;
        }
@@ -254,9 +265,12 @@ for my $col (@wordcols) {
                printf '<span class=inline>';
                print $row->input($col => $attr);
                if (my $imgsrc = $attr->{-src}) {
-                       printf('<img id="%spreview" src="/%s" alt="%s"%s />',
-                               $col, $_, $row->{form}, $col eq 'source' && ' hidden'
+                       my $hide = $col eq 'source';
+                       printf '<span id="%spreview">', $col unless $hide;
+                       printf('<img src="/%s" alt="%s"%s />',
+                               $_, $row->{form}, $hide && qq( id="${col}preview" hidden)
                        ) for grep { -e } $imgsrc->($row);
+                       printf '</span>' unless $hide;
                }
                print $row->input($_ => delete $wordcol{$_}) for @span;
                print '</span>';
@@ -264,7 +278,7 @@ for my $col (@wordcols) {
 }
 
 if (not $row->{ref}) {
-       printf '<li><label for="%s">%s</label><div><ul class="inline" id="%1$s">',
+       printf '<li><label for="%s">%s</label><div><ul class="inline multiinput" id="%1$s">',
                'trans', 'Translations';
        my @children = !$row->{id} ? () :
                $db->select(word => '*', {ref => $row->{id}}, 'lang, id')->hashes;
@@ -283,7 +297,7 @@ if (not $row->{ref}) {
                printf(
                        $ref->{id} ? '<a id="%s" href="%s">%s</a></li>' :
                        '<input id="%s" name="%1$s" value="%3$s" />',
-                       "trans-$ref->{lang}", "/writer/$ref->{id}", Entity($ref->{form} // ''),
+                       "trans-$ref->{lang}", "$editorurl/$ref->{id}", Entity($ref->{form} // ''),
                );
        }
        say '</ul></div></li>';
@@ -292,7 +306,7 @@ if (not $row->{ref}) {
 </ul>
 <p>
        <input type="submit" value="Save" />
-       <input type="submit" value="New" formaction="/writer?copy=cat" />
+       <input type="submit" value="New" formaction="<:= $editorurl :>?copy=cat" />
 </p>
 </form>
 
@@ -306,20 +320,24 @@ if ($row->{id}) {
 say '<ul>';
 my $parents = $db->select(word => '*', [{id => $row->{cat}}, {id => $row->{ref}}]);
 while (my $ref = $parents->hash) {
-       printf '<li><a href="/writer/%d">%s</a></li>', $ref->{id}, Entity($ref->{form});
+       printf '<li><a href="%s/%d">%s</a></li>', $editorurl, $ref->{id}, Entity($ref->{form});
 }
 say "<li><strong>$_</strong></li>" for Entity($row->{form});
 my $children = $db->select(word => '*', {cat => $row->{id}, ref => undef}, 'grade, id');
 while (my $ref = $children->hash) {
-       printf '<li><a href="/writer/%d">%s</a></li>', $ref->{id}, Entity($ref->{form});
+       printf '<li><a href="%s/%d">%s</a></li>', $editorurl, $ref->{id}, Entity($ref->{form});
 }
 :>
-<li><form action="/writer">
+<li><form action="<:= $editorurl :>">
        <input type="hidden" name="cat" value="<:= $row->{id} :>" />
        <input type="hidden" name="lang" value="<:= $row->{lang} :>" />
        <input type="submit" value="Add" />
 </form></li>
 </ul>
+
+<form id="search">
+       <input type="search" name="q" value="" placeholder="search" /><button type="submit">🔍</button>
+</form>
 </section>
 <:
 }