#?SuikaWikiConfig/2.0 Plugin: @Name: WikiEdit @Description: @@@: WikiPage editing components @@lang:en @License: %%Perl%% @Author: @@Name: @@@@: Wakaba @@@lang:ja @@@script:Latn @@Mail[list]: w@suika.fam.cx @Date.RCS: $Date: 2004/08/06 03:37:04 $ @RequiredModule[list]: Digest::SHA1 @RequiredPlugin[list]: WikiView WikiStruct WikiLinking WikiFormCore WikiFormText WikiResource HTML @Use: use Message::Util::Error; @Namespace: @@edit: http://suika.fam.cx/~wakaba/-temp/2004/7/25/sw-edit# @@media-type: http://suika.fam.cx/~wakaba/-temp/2004/04/24/mt# 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 $wiki = $self->{view}->{wiki}; my $touch = $wiki->{input}->parameter ('we--touch'); $wiki->{var}->{db}->{read_only}->{'content'} = 0; $wiki->{var}->{db}->{read_only}->{'content_prop'} = 0; $wiki->{var}->{db}->{read_only}->{'lastmodified'} = 0 if $touch; $wiki->{var}->{db}->{read_only}->{'referer'} = 0; $wiki->init_db; my $page = $wiki->{var}->{page}; ## Check confliction my $current_content; try { $current_content = $self->{view}->{wiki}->{db}->get (content => $page); } catch SuikaWiki::DB::Util::Error with { my $err = shift; if ($err->{-type} eq 'ERROR_REPORTED') { $err->throw; } else { $self->{view}->{wiki}->view_in_mode (mode => '-wdb--fatal-error'); throw SuikaWiki::DB::Util::Error -type => 'ERROR_REPORTED'; } }; my $current_digest = ''; if (length $current_content) { $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) { ## Really modified? my $new_digest = __FUNCPACK__->digest ($new_content, normalize => 1); if ($current_digest ne $new_digest) { ## Update content try { $self->{view}->{wiki}->{db}->set (content => $page => $new_content); } catch SuikaWiki::DB::Util::Error with { my $err = shift; if ($err->{-type} eq 'ERROR_REPORTED') { $err->throw; } else { $self->{view}->{wiki}->view_in_mode (mode => '-wdb--fatal-error'); throw SuikaWiki::DB::Util::Error -type => 'ERROR_REPORTED'; } }; my $mt = $wiki->{input}->parameter ('wf--media-type'); my $prop = $wiki->{db}->get (content_prop => $page); my $old_mt = $prop->get_attribute_value (, default => undef); UPDATE_MEDIATYPE: { last UPDATE_MEDIATYPE if not $mt or $mt eq '#asis'; CHECK_MEDIATYPE: { for (keys %{$wiki->{config} ->{}||{}}) { last CHECK_MEDIATYPE if $mt eq $_; } undef $mt; last UPDATE_MEDIATYPE; } $prop->set_attribute ( => $mt); $wiki->{db}->set (content_prop => $page => $prop); } undef $mt if $mt eq '#asis'; ## Post-write events if ($old_mt and $old_mt ne $mt) { my $format = $wiki->{plugin}->module_package ('WikiFormat') ->handler (\$new_content, serialized_media_type => $old_mt, content_prop => $prop, wiki => $wiki); $format->content_type_changed_from (wiki => $wiki, page => $page, old_content => \$current_content, new_content => \$new_content, content_prop => $prop); } my $format = $wiki->{plugin}->module_package ('WikiFormat') ->handler (\$new_content, serialized_media_type => $mt, content_prop => $prop, wiki => $wiki); $format->content_written (wiki => $wiki, page => $page, old_content => \$current_content, new_content => \$new_content, content_prop => $prop); ## Update lastmodified if ($touch) { try { $self->{view}->{wiki}->{db}->set (lastmodified => $page => time); } catch SuikaWiki::DB::Util::Error with { my $err = shift; $err->throw if $err->{-type} eq 'ERROR_REPORTED'; }; } } # really modified 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 { my $prop = $wiki->{db}->get (content_prop => $page); try { $self->{view}->{wiki}->{db}->delete (content => $page); $self->{view}->{wiki}->{db}->delete (content_prop => $page); $self->{view}->{wiki}->{db}->delete (lastmodified => $page) if $touch; } catch SuikaWiki::DB::Util::Error with { my $err = shift; if ($err->{-type} eq 'ERROR_REPORTED') { $err->throw; } else { $self->{view}->{wiki}->view_in_mode (mode => '-wdb--fatal-error'); throw SuikaWiki::DB::Util::Error -type => 'ERROR_REPORTED'; } }; ## Post-write event my $format = $wiki->{plugin}->module_package ('WikiFormat') ->handler (\$new_content, content_prop => $prop, wiki => $wiki); $format->content_removed (wiki => $wiki, page => $page, content_prop => $prop); $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 }; my $content = __FUNCPACK__->get_content ($o, $page); $o->{var}->{content} = $content->{content}; $o->{var}->{content_prop} = $content->{content_prop}; 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 { my $err = shift; $err->throw if $err->{-type} eq 'ERROR_REPORTED'; $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: context @@Type: mode-name @@Default: (current) @@Description: @@@@: Type of "edit" mode @@@lang: en @Parameter: @@Name: description @@Type: text @@Default: (none) @@Description: @@@@: Human readable description about this input @@@lang: en @Formatting: my $name = 'we--mode-modified'; my $selected_mode = $o->{wiki}->{input}->parameter ($name); if ($o->{var}->{content_prop} and not $selected_mode) { my $format = __FUNCPACK{WikiFormat}__ ->handler (\$o->{var}->{content}, content_prop => $o->{var}->{content_prop}, wiki => $o->{wiki}); __ATTRTEXT:%context__; $selected_mode = $format->prop (. ($p->{context} || $o->{wiki}->{var}->{mode})) || 'default'; } __ATTRTEXT:%id__;__ATTRNODE:%label__; my $id = __FUNCPACK{WikiFormCore}__ ->control_id ($o, local_id => $name); my $has_label = 0; if ($p->{label}->count) { $has_label = 1; for ($p->{-parent}->append_new_node (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'label')) { $_->set_attribute (for => $id->{global_id}); $_->append_node ($p->{label}); } } my $SELECT = $p->{-parent}->append_new_node (type => '#element', namespace_uri => $NS_XHTML1, local_name => 'select'); $SELECT->set_attribute (name => $name); $SELECT->set_attribute (id => $id->{global_id}) if $has_label; __ATTRTEXT:%description__; $SELECT->set_attribute (title => $p->{description}) if length $p->{description}; $SELECT->set_attribute (disabled => 'disabled') if $o->{form}->{disabled}; __ATTRTEXT:%class__; my @class = split /\s+/, $p->{class}; push @class, 'require' if $o->{form}->{require}->{id}->{$p->{id}}; $SELECT->set_attribute (class => join ' ', @class) if @class; my %outputed; for (qw/default read edit/, $selected_mode) { next if $outputed{$_}; $outputed{$_} = 1; 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, like: \ \ %check (id => we--touch, \ label => {%res (name => {Edit:UpdateTimeStamp});}p, \ description => {%res \ (name => {Edit:UpdateTimeStamp:Description});}p, \ default); \ 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:adminedit:Description: @@@: Save modified content and show content in the edit mode (for administrator) again @@lang:en @Edit:SaveAnd:adminedit:Label: @@@: Editing @@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: @@@: Editing @@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; my $content_prop; my $original_content_prop; try { $content = $o->{wiki}->{db}->get (content => $page); $content_prop = $o->{wiki}->{db}->get (content_prop => $page); unless (length $content) { ## TODO: use namespaced template page $content = $o->{wiki}->{db}->get (content => $o->{wiki}->{config}->{page}->{NewPageTemplate}); $original_content_prop = $content_prop; $content_prop = $o->{wiki}->{db}->get (content_prop => $o->{wiki}->{config}->{page}->{NewPageTemplate}) ->clone; $o->{var}->{is_new_page_template} = 1; } } catch SuikaWiki::DB::Util::Error with { my $err = shift; $err->throw if $err->{-type} eq 'ERROR_REPORTED'; $o->{wiki}->view_in_mode (mode => '-wdb--fatal-error'); throw SuikaWiki::DB::Util::Error -type => 'ERROR_REPORTED'; }; if ($original_content_prop) { for (@{$original_content_prop->child_nodes}) { $content_prop->append_node ($_); } } unless ($content_prop->get_attribute_value (, default => undef)) { if ($content =~ m[^(?:\#\?|/\*\s*)([\w.+-]+(?:/[\w.+-]+)?)]) { $content_prop->set_attribute ( => 'MAGIC:'.$1.'##'); } else { $content_prop->set_attribute ( => 'IMT:text/x-suikawiki;version="0.9"##'); } } {content => $content, content_prop => $content_prop}; 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