#?SuikaWikiConfig/2.0

Plugin:
  @Name: WikiEdit
  @Description:
    @@@: WikiPage editing components
    @@lang:en
  @License: %%GPL%%
  @Author:
    @@Name:
      @@@@: Wakaba
      @@@lang:ja
      @@@script:Latn
    @@Mail[list]: w@suika.fam.cx
  @Date.RCS: $Date: 2004/02/08 08:52:03 $
  @RequiredModule[list]:
    Digest::SHA1
  @RequiredPlugin[list]:
    WikiView
    WikiStruct
    WikiLinking
    WikiFormCore
    WikiFormText
    WikiResource
    HTML
  @Use:
    use Message::Util::Error;

PluginConst:
  @NS_XHTML1:
    http://www.w3.org/1999/xhtml

ViewDefinition:
  @Mode: edit
  @Condition:
    @@http-method[list]:
      GET
      HEAD
  @Description:
    @@@: Edit WikiPage content as whole, as a plaintext
    @@lang: en
  @template:
    @@http-status-code: 200
    @@media-type: text/html
    @@use-media-type-charset: 1
    @@expires: %%edit%%
    @@body:
	%html-document (
          title => {%res(name=>{Edit:WebPageTitle});}p,
          link-meta => {%template (name => links);
                        %html-meta(name => ROBOTS, content => NOINDEX);}p,
          content => {
            %template (
              name => ws--page,
              -content => {%template (name => we--edit-body);},
            );
          }p,
        );

ViewDefinition:
  @Mode: write
  @Condition:
    @@http-method[list]:
      POST
  @Description:
    @@@: Saving modified (new) WikiPage content
    @@lang: en
  @Use:
    use Message::Util::Error;
  @method:
    @@@:
      my $touch = $self->{view}->{wiki}->{input}->parameter ('we--touch');
      
      $self->{view}->{wiki}->{var}->{db}->{read_only}->{'content'} = 0;
      $self->{view}->{wiki}->{var}->{db}->{read_only}->{'lastmodified'} = 0
        if $touch;
      $self->{view}->init_db;

      my $page = $self->{view}->{wiki}->{var}->{page};

      ## Check confliction
      my $current_content;
      try {
        $current_content = $self->{view}->{wiki}->{db}->get (content => $page);
      } catch SuikaWiki::DB::Util::Error with {
        $self->{view}->{wiki}->view_in_mode (mode => '-wdb--fatal-error');
        throw SuikaWiki::DB::Util::Error -type => 'ERROR_REPORTED';
      };
      if (length $current_content) {
        my $current_digest = __FUNCPACK__->digest ($current_content,
                                                  normalize => 1);
        my $prev_digest
             = $self->{view}->{wiki}->{input}->parameter ('we--digest');
        if ($current_digest ne $prev_digest) {
          return $self->{view}->{wiki}->view_in_mode
                   (mode => '-conflict');
        }
      }
      
      my $new_content
           = $self->{view}->{wiki}->{input}->parameter ('we--content');    
      if (length $new_content) {
        try {
          $self->{view}->{wiki}->{db}->set (content => $page => $new_content);
        } catch SuikaWiki::DB::Util::Error with {
          $self->{view}->{wiki}->view_in_mode (mode => '-wdb--fatal-error');
          throw SuikaWiki::DB::Util::Error -type => 'ERROR_REPORTED';
        };
        if ($touch) {
          try {
            $self->{view}->{wiki}->{db}->set (lastmodified => $page => time);
          } catch SuikaWiki::DB::Util::Error with {
          };
        }
        
        my $new_mode = $self->{view}->{wiki}->{input}->parameter
                         ('we--mode-modified');
        $new_mode =~ s/[^0-9A-Za-z._-]+//g;
        my $uri = $self->{view}->{wiki}->uri_reference
                     (page => $page,
                      mode => $new_mode,
                      up_to_date => 1,
                      param => {we__mode_modified => $new_mode});
        
        require SuikaWiki::Output::HTTP;
        my $output = SuikaWiki::Output::HTTP->new
          (wiki => $self->{view}->{wiki});
        $output->set_redirect (uri => $uri, status_code => 303);
        $output->output (output => 'http-cgi');
      } else {
        try {
          $self->{view}->{wiki}->{db}->delete (content => $page);
          $self->{view}->{wiki}->{db}->delete (lastmodified => $page) if $touch;
        } catch SuikaWiki::DB::Util::Error with {
          $self->{view}->{wiki}->view_in_mode (mode => '-wdb--fatal-error');
          throw SuikaWiki::DB::Util::Error -type => 'ERROR_REPORTED';
        };
        $self->{view}->{wiki}->view_in_mode (mode => '-deleted');
      }
    @@Name: main

ViewDefinition:
  @Mode: adminedit
  @Condition:
    @@http-method[list]:
      GET
      HEAD
  @Description:
    @@@: Edit WikiPage content as whole, as a plaintext (administrator mode)
    @@lang: en
  @template:
    @@http-status-code: 200
    @@media-type: application/xhtml+xml
    @@use-media-type-charset: 1
    @@expires: %%edit%%
    @@body:
	%html-document (
          title     => {%res(name=>{Edit:Admin:WebPageTitle});}p,
          link-meta => {%template (name => links);
                        %html-meta(name=>ROBOTS,content=>NOINDEX);}p,
          content   => {
	    %template (
              name => ws--page,
              -content => {%template (name => we--adminedit-body);},
            );
          }p,
        );

ViewDefinition:
  @Mode: -conflict
  @Condition:
    @@http-method[list]:
      GET
      HEAD
  @Description:
    @@@: Confliction message
    @@lang: en
  @template:
    @@http-status-code: 409
    @@media-type: text/html
    @@use-media-type-charset: 1
    @@body:
	%html-document (
          title     => {%res(name=>{Edit:Conflict:WebPageTitle});}p,
          link-meta => {%template(name=>links);
                        %html-meta(name=>ROBOTS,content=>NOINDEX);}p,
          content   => {
	    %template (
              name => ws--page,
              -content => {
                %section (
                  title   => {%res(name=>{Edit:Conflict:Title});}p, heading,
                  content => {%template (name => we--conflict-body);}p,
                );
              },
            );
          }p,
        );

ViewDefinition:
  @Mode: -deleted
  @Condition:
    @@http-method[list]:
      GET
      HEAD
  @Expires:%%view%%
  @Description:
    @@@: Confliction message
    @@lang: en
  @template:
    @@http-status-code: 200
    @@media-type: text/html
    @@use-media-type-charset: 1
    @@body:
	%html-document (
          title     => {%res (name => {Edit:Deleted:WebPageTitle});}p,
          link-meta => {%template (name => links);
                        %html-meta (name => ROBOTS, content => NOINDEX);}p,
          content   => {
	    %template (
              name => ws--page,
              -content => {
                %section (
                  title   => {%res(name=>{Edit:Deleted:Title});}p, heading,
                  content => {
                    %paragraph (content => {%res
                                  (name => {Edit:Deleted:Description});}p);
                  }p,
                );
              },
            );
          }p,
        );

ViewFragment:
  @Name: links
  @Description:
    @@@: Link to edit mode of the WikiPage
    @@lang:en
  @Formatting:
    %link-wiki(mode => edit, up-to-date,
               rel => edit, class => wiki-cmd,
               description => {%res(name=>{Link:Edit:Description});}p);
    %link-wiki(mode => adminedit, up-to-date,
               rel=>edit, class => wiki-cmd,
               description => {%res(name=>{Link:AdminEdit:Description});}p);

ViewFragment:
  @Name: navbar
  @Description:
    @@@: Link to edit mode of the WikiPage
  @Order: 10
  @Formatting:
    %link-to-wikipage (
      mode  => edit,
      rel   => edit,
      up-to-date,
      label => {%link-to-it (
        class => wiki-cmd,
        label => {%res (name => EditThisPage);}p,
        description => {%res (name => EditThisPageLong);}p,
      );},
      page-anchor-name => edit,
    ); 

ViewFragment:
  @Template[list]:
     we--conflict-body
  @Order: 30
  @Description:
    @@@: Conflict report
    @@lang:en
  @Formatting:
        %paragraph (content=>{%res(name=>{Edit:Conflict:Description});}p);
        %we--conflict-modified-content;
        %section (
          id      => edit-conflict-diff,
          title   => {%res(name=>{Edit:Conflict:Diff:Title});}p, heading,
          content => {
            %paragraph (
              content => {%res(name=>{Edit:Conflict:Diff:Description});}p);
            %conflict-diff;
          }p,
        );

ViewFragment:
  @Template[list]: we--edit-body
  @Order:80
  @Description:
    @@@: Editing WikiPage form section
    @@lang:en
  @Formatting:
                %section (
                  id => edit,
                  title => {%res (name => {Edit:Title});}p, heading,
                  content => {%edit-form;}p,
                );

ViewFragment:
  @Template[list]: we--adminedit-body
  @Order:80
  @Description:
    @@@: Editing WikiPage form section (Admin edit mode)
    @@lang:en
  @Formatting:
                %section (
                  id      => edit,
                  title   => {%res(name=>{Edit:Title});}p, heading,
                  content => {%edit-form(admin);}p,
                );

ViewFragment:
  @Template[list]: we--conflict-body
  @Order: 80
  @Description:
    @@@: Editing-WikiPage-form section (conflict mode)
    @@lang:en
  @Formatting:
        %section (
          id      => edit,
          title   => {%res(name=>{Edit:Title});}p, heading,
          content => {
            %paragraph(name=>{%res(name=>{Edit:Conflict:Edit:Description});}p);
            %edit-form;
          }p,
        );

ViewFragment:
  @Name: we--edit
  @Description:
    @@@: Edit form --- main textarea
    @@lang:en
  @Order: 0
  @Formatting:
    %textarea (id  => we--content,  size => {%res (name=>{Edit:Form:Size});}p,
               source =>  content, lines => {%res (name=>{Edit:Form:Lines});}p);
ViewFragment:
  @Name: we--edit
  @Description:
    @@@: Submit button
    @@lang:en
  @Order: 200
  @Formatting:
    %submit(label=>{%res(name=>{Edit:Save});}p);

FormattingRule:
  @Category[list]: view
  @Name: edit-form
  @Description:
    @@@: Provides WikiPage editing form
    @@lang: en
  @Parameter:
    @@Name: admin
    @@Type: boolean
    @@Default: {0}
    @@Description:
      @@@@: Whether administrator's editing mode or not
      @@@lang:en
  @Parameter:
    @@Name: page
    @@Type: WikiName
    @@Default: (auto)
    @@Description:
      @@@@: WikiPage that is editing
      @@@lang:en
  @Formatting:
    __ATTRTEXT:%admin__;__ATTRTEXT:%page__;
    my $page = $o->{wiki}->name ($p->{page} || $o->{wiki}->{var}->{page});
    my $template = $o->{wiki}->{view}->assemble_template ('we__edit');
    local $o->{var} = {
      #content
      is_admin_mode => $p->{admin},
      is_conflict_mode => 0,
      #is_new_page_template
    };
    $o->{var}->{content} = __FUNCPACK__->get_content ($o, $page);
    SuikaWiki::Plugin->module_package ('WikiFormCore')
                     ->make_form_in_html
                         ($p->{-parent}, $template,
                          wiki => $o->{wiki},
                          o => $o,
                          index => -1,
                          output => {
                            mode => 'write',
                            page => $page,
                            hidden => sub {
    my ($hidden, $o) = @_;
    for ($hidden->append_new_node (type => '#element',
                                   namespace_uri => $NS_XHTML1,
                                   local_name => 'input')) {
      $_->set_attribute (type => 'hidden');
      $_->set_attribute (name => 'we--digest');
      $_->set_attribute (value => ($o->{var}->{is_new_page_template}?'':
                                   __FUNCPACK__->digest ($o->{var}->{content},
                                                         normalize => 1)));
      $_->option (use_EmptyElemTag => 1);
    }
                            },
                          });


FormattingRule:
  @Category[list]: view
  @Name: we--conflict-modified-content
  @Description:
    @@@:
      Modified content that is conflicted with current content
    @@lang:en
  @Formatting:
    $p->{-parent}->append_new_node (type => '#element',
                                    namespace_uri => $NS_XHTML1,
                                    local_name => 'pre')
      ->append_text (scalar $o->{wiki}->{input}->parameter ('we--content'));

FormattingRule:
  @Category[list]:view
  @Name: conflict-diff
  @Description:
    @@@:
      Provides marked diff between latest WikiPage content and 
      submitted one
    @@lang:en
  @Formatting:
    my $before = scalar $o->{wiki}->{input}->parameter ('we--content');
    my $after;
    try {
      $after = $o->{wiki}->{db}->get (content => $o->{wiki}->{var}->{page});
    } catch SuikaWiki::DB::Util::Error with {
      $after = undef;
    };
    __FUNCPACK__->diff_in_html (\$before => \$after, $p->{-parent});

Function:
  @Name: diff_in_html
  @Main:
    my (undef, $before, $after, $parent, %opt) = @_;
    require Algorithm::Diff;
    my $diff = $parent->append_new_node (type => '#element',
                                         namespace_uri => $NS_XHTML1,
                                         local_name => 'pre');
    $diff->set_attribute (class => 'diff');
    for (Algorithm::Diff::diff 
           ([split /\x0D?\x0A/, $$before]=>[split /\x0D?\x0A/, $$after])) {
          for (@{$_}) {
            my ($sign, $lineno, $text) = @{$_};
            my $line = $diff->append_new_node (type => '#element',
                           namespace_uri => $NS_XHTML1,
                           local_name => ({qw/+ ins - del/}->{$sign}||'span'));
            
            $line->set_attribute (class => 'line');
            for ($line->append_new_node (type => '#element',
                                         namespace_uri => $NS_XHTML1,
                                         local_name => 'span')) {
              $_->set_attribute (class => 'lineno');
              $_->append_text ($lineno);
            }
            $line->append_text (' ');
            for ($line->append_new_node (type => '#element',
                                         namespace_uri => $NS_XHTML1,
                                         local_name => 'span')) {
              $_->set_attribute (class => 'sign');
              $_->append_text ($sign);
            }
            $line->append_text (' ');
            for ($line->append_new_node (type => '#element',
                                         namespace_uri => $NS_XHTML1,
                                         local_name => 'span')) {
              $_->set_attribute (class => 'content');
              $_->append_text ($text);
            }
            $line->append_text ("\n"); 
          } # diff lines
        }

FormattingRule:
  @Name:mode-after-edit-selection
  @Category[list]:form-input
  @Parameter:
    @@Name: description
    @@Type: text
    @@Default: (none)
    @@Description: 
      @@@@: Human readable description about this input
      @@@lang: en
  @Formatting:
 ## TODO: media-type prop support
    my $magic = '';
    $magic = $1 if $o->{var}->{content} =~ m/^([^\x0A\x0D]+)/s;
    
    my $name = 'we--mode-modified';
    my $selected_mode = $o->{wiki}->{input}->parameter ($name);
    unless ($selected_mode) {
      if ($magic =~ /C(?:on(?:fig|st)|SS)/) {
        $selected_mode = 'edit';
      } else {
        $selected_mode = 'default';
      }
    }
    
    my $SELECT = $p->{-parent}->append_new_node (type => '#element',
                                                 namespace_uri => $NS_XHTML1,
                                                 local_name => 'select');
    $SELECT->set_attribute (name => $name);
    __ATTRTEXT:%description__;
    $SELECT->set_attribute (title => $p->{description})
      if length $p->{description};
    for (qw/default read edit/) {
      for my $OPTION ($SELECT->append_new_node (type => '#element',
                                                namespace_uri => $NS_XHTML1,
                                                local_name => 'option')) {
        $OPTION->set_attribute (value => $_);
        my $label = SuikaWiki::Plugin->module_package ('WikiResource')
                      ->append_node (name => 'Edit:SaveAnd:'.$_.':Label',
                                     param => $o,
                                     wiki => $o->{wiki});
        $OPTION->set_attribute (label => $label->inner_text);
        $OPTION->append_node ($label);
        $OPTION->set_attribute
          (title => SuikaWiki::Plugin->module_package ('WikiResource')
                      ->append_node (name => 'Edit:SaveAnd:'.$_.':Description',
                                     param => $o,
                                     wiki => $o->{wiki})->inner_text);
        $OPTION->set_attribute (selected => 'selected')
          if $selected_mode eq $_;
      }
    }

FormattingRule:
  @Category[list]:form-input
  @Name:we--update-lastmodified-datetime
  @Description:
    @@@:
      This direction force to update last-modified date-time 
      of the WikiPage modified.
    \
      Allowing user to select whether last-modified date-time should be 
      updated, use another formatting rule provided by WikiFormSelection 
      module.
    \
      WikiForm other that editing form might not be affected by this 
      parameter.  See also "we--touch" parameter.
    @@lang:en
  @Formatting:
    for ($p->{-parent}->append_new_node
                          (type => '#element',
                           namespace_uri => $NS_XHTML1,
                           local_name => 'input')) {
      $_->set_attribute (type => 'hidden');
      $_->set_attribute (name => 'we--touch');
      $_->set_attribute (value => 'on');
      $_->option (use_EmptyElemTag => 1);
    }                           


Resource:
  @Edit:Conflict:Title:
    @@@:Confliction detected!
    @@lang:en
  @Edit:Conflict:WebPageTitle:
    @@@:%page-name; (Edit : 409 CONFLICTION)
    @@lang:en
  @Edit:Deleted:Description:
    @@@:Content of WikiPage %page-name; has been removed.
    @@lang:en
  @Edit:Deleted:Title:
    @@@:WikiPage Deleted
    @@lang:en
  @Edit:Deleted:WebPageTitle:
    @@@:%page-name; (Deleted)
    @@lang:en
  @Edit:Form:Lines:15
  @Edit:Form:Size:18
  @Edit:Save:
    @@@:Save
    @@lang:en
  @Edit:SaveAnd:Description:
    @@@: Select what to do after saving content
    @@lang: en
  @Edit:SaveAnd:default:Description:
    @@@: Save modified content and show content with your default view mode
    @@lang:en
  @Edit:SaveAnd:default:Label:
    @@@: Default view
    @@lang:en
  @Edit:SaveAnd:edit:Description:
    @@@: Save modified content and show content in the edit mode again
    @@lang:en
  @Edit:SaveAnd:edit:Label:
    @@@: Edit again
    @@lang:en
  @Edit:SaveAnd:read:Description:
    @@@: Save modified content and show content in the read mode
    @@lang:en
  @Edit:SaveAnd:read:Label:
    @@@: Show content
    @@lang:en
  @EditThisPage:
    @@@: Edit
    @@lang:en
  @EditThisPageLong:
    @@@: Edit this WikiPage
    @@lang:en
  @Edit:WebPageTitle:
    @@@: %page-name; (Modification)
    @@lang:en
  @Edit:Title:
    @@@: Modification of the WikiPage
    @@lang:en
  @Link:AdminEdit:Description:
    @@@: Edit this WikiPage (administrator's mode)
    @@lang:en
  @Link:Edit:Description:
    @@@: Edit this WikiPage
    @@lang:en
  @Mode:adminedit:
    @@@: Editing (for administrator)
    @@lang: en
  @Mode:edit:
    @@@: Editing
    @@lang: en

Function:
  @Name:digest
  @Description:
    @@@: Compute digest of given string
    @@lang:en
  @Main:
    my (undef, $s, %opt) = @_;
    require Digest::SHA1;
    if ($opt{normalize}) {
      $s =~ s/\x0D\x0A/\x0A/g;
      $s =~ tr/\x0D/\x0A/;
    }
    Digest::SHA1::sha1_base64 ($s);

Function:
  @Name:get_content
  @Description:
    @@@:Get WikiPage content to be modified.  For new (currently not exist) 
        WikiPage, new page template content is returned.
    @@lang:en
  @Main:
    my (undef, $o, $page) = @_;
    my $content;
    try {
      $content = $o->{wiki}->{db}->get (content => $page);
      
      unless (length $content) {
        ## TODO: use namespaced template page
        $content = $o->{wiki}->{db}->get
          (content => $o->{wiki}->{config}->{page}->{NewPageTemplate});
        $o->{var}->{is_new_page_template} = 1;
      }
    } catch SuikaWiki::DB::Util::Error with {
      $o->{wiki}->view_in_mode (mode => '-wdb--fatal-error');
      throw SuikaWiki::DB::Util::Error -type => 'ERROR_REPORTED';
    };
    $content;

Parameter:
  @Name: we--content
  @Type: text
  @Default: ""
  @Description:
    @@@:
      Content of WikiPage (to be)
    @@lang:en

Parameter:
  @Name: we--digest
  @Type: "Base64ed SHA1"
  @Default: {undef}
  @Description:
    @@@:
      SHA1 digest value of the WikiPage content before the modification.
      This value is used to detect confliction of modifying.
      Empty value (undef or length-zero-string) means that
      there were no WikiPage content.
    @@lang:en

Parameter:
  @Name: we--mode-modified
  @Type: mode-name
  @Default: "default"
  @Description:
    @@@:
      WikiEngine running mode to be specified after modification is completed
    @@lang:en

Parameter:
  @Name: we--touch
  @Type: 
    form:checkbox-value
  @Default: "off"
  @Description:
    @@@:
      Whether last-modified date-time of the WikiPage should be updated or not
      when the WikiPage is modified.
    @@lang:en