#?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: 2003/10/30 07:45:10 $ @RequiredModule[list]: SuikaWiki::Name::Space Digest::SHA1 @RequiredPlugin[list]: WikiView WikiStruct WikiLinking WikiFormCore HTML 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 @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}; ## TODO: Implements access control #if (&frozen_reject()) { #} if (not __FUNCPACK__::page_is_editable ($page)) { ## TODO: Implements this # &_do_view_msg (-view => '-error', -page => $main::form{mypage}, # error_message => &Resource ('Error:ThisPageIsUneditable')); # return; die "Uneditable!"; } ## Check confliction my $current_content = $self->{view}->{wiki}->{db}->get (content => $page); 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) { ## TODO: Implements this # _do_view_msg (-view => '-conflict', -page => $page_name); # die "@{[join '//', @$page]}: Conflict : (current) $current_digest [$current_content] vs (prev) $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) { $self->{view}->{wiki}->{db}->set (content => $page => $new_content); if ($touch) { $self->{view}->{wiki}->{db}->set (lastmodified => $page => time); } ## TODO: Use SuikaWiki 3 interface my $new_mode = $self->{view}->{wiki}->{input}->parameter ('we--mode-modified'); $new_mode =~ s/[^0-9A-Za-z._-]+//g; my $uri = SuikaWiki::Plugin->_uri_wiki_page (join ('//', @$page), mode => $new_mode, with_lm => 1, absolute => 1); $uri .= qq<;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 { for (qw/content lastmodified/) { $self->{view}->{wiki}->{db}->delete (content => $page); } $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 => {%predefined-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 (level=>2, 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 (level=>2, 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,rel=>edit,class=>wiki-cmd, title=>{%res(name=>{Link:Edit:Description});}p,up-to-date); %link-wiki(mode=>adminedit,rel=>edit,class=>wiki-cmd, title=>{%res(name=>{Link:AdminEdit:Description});}p,up-to-date); ViewFragment: @Name: navbar @Description: @@@: Link to edit mode of the WikiPage @Order: 10 @Formatting: %anchor-wiki (mode=>edit,rel=>edit,class=>wiki-cmd, label=>{%res(name=>EditThisPage);}p, title=>{%res(name=>EditThisPageLong);}p, accesskey=>E,add-param=>{#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 (level=>3, 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 (level=>2, 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 (level=>2, 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 (level=>2, 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, 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 @Formatting: if (not __FUNCPACK__::page_is_editable ($o->{wiki}->{var}->{page})) { $r = '<p>'.$o->resource('Error:ThisPageIsUneditable',escape=>1).'</p>'; # } elsif (!$p->{admin} && &main::is_frozen ($o->{page})) { # $r = '<p>'.$o->resource('Error:ThisPageIsUneditable',escape=>1).'</p>'; } else { #$r = '<p>'.$o->resource('Error:PasswordIsNotSpecified',escape=>1).'</p>' if $p->{admin}; $r = Message::Markup::XML->new (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'form'); $r->set_attribute (method => 'post'); ## TODO: Use SuikaWiki 3 interface $r->set_attribute (action => $o->uri ('wiki')); $r->set_attribute ('accept-charset' => 'iso-2022-jp utf-8'); for ($r->append_new_node (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'input')) { $_->set_attribute (type => 'hidden'); $_->set_attribute (name => 'mypage'); ## TODO: Use SuikaWiki 3 interface $_->set_attribute (value => join '//', @{$o->{wiki}->{var}->{page}}); $_->option (use_EmptyElemTag => 1); } for ($r->append_new_node (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'input')) { $_->set_attribute (type => 'hidden'); $_->set_attribute (name => 'mode'); $_->set_attribute (value => 'write'); $_->option (use_EmptyElemTag => 1); } ## _charset_ hack for ($r->append_new_node (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'input')) { $_->set_attribute (type => 'hidden'); $_->set_attribute (name => '_charset_'); $_->option (use_EmptyElemTag => 1); } my $ovar_orig = $o->{var}; $o->{var} = { #content is_admin_mode => $p->{admin}, is_conflict_mode => 0, #is_new_page_template }; $o->{var}->{content} = __FUNCPACK__::get_content ($o, $o->{wiki}->{var}->{page}); for ($r->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); } my $formatter = $o->formatter ('we__edit'); $r->append_node ($formatter->replace ($o->{wiki}->{view}->assemble_template ('we__edit'), $o, {formatter => $formatter}), node_or_text => 1); $o->{var} = $ovar_orig; } FormattingRule: @Category[list]: view @Name: we--conflict-modified-content @Description: @@@: Modified content that is conflicted with current content @@lang:en @Formatting: $r = Message::Markup::XML->new (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'pre'); $r->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: require Algorithm::Diff; $r = Message::Markup::XML->new (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'pre'); $r->set_attribute (class => 'diff'); for (Algorithm::Diff::diff ( [split /\x0D?\x0A/, scalar $o->{wiki}->{input}->parameter ('we--content')], [split /\x0D?\x0A/, $o->{wiki}->{db}->get (content => $o->{wiki}->{var}->{page})] )) { for (@{$_}) { my ($sign, $lineno, $text) = @{$_}; my $ename = $sign eq '+' ? 'ins' : $sign eq '-' ? 'del' : 'span'; my $line = $r->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]:we--edit @Parameter: @@Name: name @@Type: html4:name @@Default:"we--mode-modified" @@Description: @@@@:Form control name for the selection @@@lang:en @Formatting: my $magic = ''; $magic = $1 if $o->{var}->{content} =~ m/^([^\x0A\x0D]+)/s; my $name = $p->{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 = Message::Markup::XML->new (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'select'); $SELECT->set_attribute (name => $name); 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 = $o->resource ('Edit:SaveAnd:'.$_.':Label'); $OPTION->set_attribute (label => $label); $OPTION->append_text ($label); $OPTION->set_attribute (title => $o->resource ('Edit:SaveAnd:'.$_.':Description')); $OPTION->set_attribute (selected => 'selected') if $selected_mode eq $_; } } $SELECT; 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: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 Function: @Name:digest @Description: @@@: Compute digest of given string @@lang:en @Main: my ($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 ($o, $page) = @_; my $content; $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; } $content; Function: @Name:page_is_editable @Description: @@@: Returns whether the WikiPage is editable or not. Note that this function is temporary. @@lang:en @Main: my ($page) = @_; $page = join '//', $page; return 0 unless SuikaWiki::Name::Space::validate_name ($page); return 0 if $page =~ /[\x00-\x20\[\]\x7F]/; 1; 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