/[pub]/suikawiki/script/lib/SuikaWiki/Plugin/WikiEdit.wp2
Suika

Contents of /suikawiki/script/lib/SuikaWiki/Plugin/WikiEdit.wp2

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.6 - (hide annotations) (download)
Fri Jan 16 07:49:41 2004 UTC (21 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.5: +39 -28 lines
Use new implementation of WikiResource

1 wakaba 1.1 #?SuikaWikiConfig/2.0
2    
3     Plugin:
4     @Name: WikiEdit
5     @Description:
6     @@@: WikiPage editing components
7     @@lang:en
8     @License: %%GPL%%
9     @Author:
10     @@Name:
11     @@@@: Wakaba
12     @@@lang:ja
13     @@@script:Latn
14     @@Mail[list]: w@suika.fam.cx
15 wakaba 1.6 @Date.RCS: $Date: 2003/12/29 13:03:19 $
16 wakaba 1.1 @RequiredModule[list]:
17 wakaba 1.2 SuikaWiki::Name::Space
18 wakaba 1.1 Digest::SHA1
19 wakaba 1.2 @RequiredPlugin[list]:
20     WikiView
21     WikiStruct
22     WikiLinking
23     WikiFormCore
24 wakaba 1.3 WikiFormText
25 wakaba 1.6 WikiResource
26 wakaba 1.2 HTML
27 wakaba 1.1
28     PluginConst:
29     @NS_XHTML1:
30     http://www.w3.org/1999/xhtml
31    
32     ViewDefinition:
33     @Mode: edit
34     @Condition:
35     @@http-method[list]:
36     GET
37     HEAD
38     @Description:
39     @@@: Edit WikiPage content as whole, as a plaintext
40     @@lang: en
41     @template:
42     @@http-status-code: 200
43     @@media-type: text/html
44     @@use-media-type-charset: 1
45     @@expires: %%edit%%
46     @@body:
47     %html-document (
48     title => {%res(name=>{Edit:WebPageTitle});}p,
49 wakaba 1.2 link-meta => {%template (name => links);
50     %html-meta(name => ROBOTS, content => NOINDEX);}p,
51 wakaba 1.1 content => {
52     %template (
53     name => ws--page,
54 wakaba 1.2 -content => {%template (name => we--edit-body);},
55 wakaba 1.1 );
56     }p,
57     );
58    
59     ViewDefinition:
60     @Mode: write
61     @Condition:
62     @@http-method[list]:
63     POST
64     @Description:
65     @@@: Saving modified (new) WikiPage content
66     @@lang: en
67     @method:
68     @@@:
69     my $touch = $self->{view}->{wiki}->{input}->parameter ('we--touch');
70    
71     $self->{view}->{wiki}->{var}->{db}->{read_only}->{'content'} = 0;
72     $self->{view}->{wiki}->{var}->{db}->{read_only}->{'lastmodified'} = 0
73     if $touch;
74     $self->{view}->init_db;
75    
76     my $page = $self->{view}->{wiki}->{var}->{page};
77    
78     ## TODO: Implements access control
79     #if (&frozen_reject()) {
80     #}
81    
82 wakaba 1.5 if (not __FUNCPACK__->page_is_editable ($page)) {
83 wakaba 1.1 ## TODO: Implements this
84     # &_do_view_msg (-view => '-error', -page => $main::form{mypage},
85     # error_message => &Resource ('Error:ThisPageIsUneditable'));
86     # return;
87     die "Uneditable!";
88     }
89    
90     ## Check confliction
91     my $current_content = $self->{view}->{wiki}->{db}->get (content => $page);
92     if (length $current_content) {
93 wakaba 1.5 my $current_digest = __FUNCPACK__->digest ($current_content,
94 wakaba 1.2 normalize => 1);
95 wakaba 1.1 my $prev_digest
96     = $self->{view}->{wiki}->{input}->parameter ('we--digest');
97     if ($current_digest ne $prev_digest) {
98 wakaba 1.2 return $self->{view}->{wiki}->view_in_mode
99     (mode => '-conflict');
100 wakaba 1.1 }
101     }
102    
103     my $new_content
104     = $self->{view}->{wiki}->{input}->parameter ('we--content');
105     if (length $new_content) {
106     $self->{view}->{wiki}->{db}->set (content => $page => $new_content);
107     if ($touch) {
108     $self->{view}->{wiki}->{db}->set (lastmodified => $page => time);
109     }
110 wakaba 1.5
111 wakaba 1.1 my $new_mode = $self->{view}->{wiki}->{input}->parameter
112     ('we--mode-modified');
113     $new_mode =~ s/[^0-9A-Za-z._-]+//g;
114 wakaba 1.5 my $uri = $self->{view}->{wiki}->uri_reference
115     (page => $page,
116     mode => $new_mode,
117     up_to_date => 1,
118     param => {we__mode_modified => $new_mode});
119 wakaba 1.1
120     require SuikaWiki::Output::HTTP;
121     my $output = SuikaWiki::Output::HTTP->new
122     (wiki => $self->{view}->{wiki});
123     $output->set_redirect (uri => $uri, status_code => 303);
124     $output->output (output => 'http-cgi');
125     } else {
126 wakaba 1.5 $self->{view}->{wiki}->{db}->delete (content => $page);
127     $self->{view}->{wiki}->{db}->delete (lastmodified => $page) if $touch;
128 wakaba 1.2 $self->{view}->{wiki}->view_in_mode (mode => '-deleted');
129 wakaba 1.1 }
130     @@Name: main
131    
132     ViewDefinition:
133     @Mode: adminedit
134     @Condition:
135     @@http-method[list]:
136     GET
137     HEAD
138     @Description:
139     @@@: Edit WikiPage content as whole, as a plaintext (administrator mode)
140     @@lang: en
141     @template:
142     @@http-status-code: 200
143     @@media-type: application/xhtml+xml
144     @@use-media-type-charset: 1
145     @@expires: %%edit%%
146     @@body:
147 wakaba 1.2 %html-document (
148     title => {%res(name=>{Edit:Admin:WebPageTitle});}p,
149 wakaba 1.6 link-meta => {%template (name => links);
150 wakaba 1.2 %html-meta(name=>ROBOTS,content=>NOINDEX);}p,
151     content => {
152     %template (
153     name => ws--page,
154     -content => {%template (name => we--adminedit-body);},
155     );
156     }p,
157     );
158 wakaba 1.1
159 wakaba 1.2 ViewDefinition:
160 wakaba 1.1 @Mode: -conflict
161     @Condition:
162     @@http-method[list]:
163     GET
164     HEAD
165     @Description:
166     @@@: Confliction message
167     @@lang: en
168     @template:
169     @@http-status-code: 409
170     @@media-type: text/html
171     @@use-media-type-charset: 1
172     @@body:
173 wakaba 1.2 %html-document (
174     title => {%res(name=>{Edit:Conflict:WebPageTitle});}p,
175     link-meta => {%template(name=>links);
176     %html-meta(name=>ROBOTS,content=>NOINDEX);}p,
177     content => {
178     %template (
179     name => ws--page,
180     -content => {
181 wakaba 1.6 %section (
182 wakaba 1.2 title => {%res(name=>{Edit:Conflict:Title});}p, heading,
183     content => {%template (name => we--conflict-body);}p,
184     );
185     },
186     );
187     }p,
188     );
189    
190     ViewDefinition:
191     @Mode: -deleted
192     @Condition:
193     @@http-method[list]:
194     GET
195     HEAD
196     @Expires:%%view%%
197     @Description:
198     @@@: Confliction message
199     @@lang: en
200     @template:
201     @@http-status-code: 200
202     @@media-type: text/html
203     @@use-media-type-charset: 1
204     @@body:
205     %html-document (
206     title => {%res (name => {Edit:Deleted:WebPageTitle});}p,
207     link-meta => {%template (name => links);
208     %html-meta (name => ROBOTS, content => NOINDEX);}p,
209     content => {
210     %template (
211     name => ws--page,
212     -content => {
213 wakaba 1.6 %section (
214 wakaba 1.2 title => {%res(name=>{Edit:Deleted:Title});}p, heading,
215     content => {
216     %paragraph (content => {%res
217     (name => {Edit:Deleted:Description});}p);
218     }p,
219     );
220     },
221     );
222     }p,
223     );
224 wakaba 1.1
225     ViewFragment:
226     @Name: links
227     @Description:
228     @@@: Link to edit mode of the WikiPage
229     @@lang:en
230     @Formatting:
231 wakaba 1.6 %link-wiki(mode => edit, up-to-date,
232     rel => edit, class => wiki-cmd,
233     description => {%res(name=>{Link:Edit:Description});}p);
234     %link-wiki(mode => adminedit, up-to-date,
235     rel=>edit, class => wiki-cmd,
236     description => {%res(name=>{Link:AdminEdit:Description});}p);
237 wakaba 1.1
238     ViewFragment:
239     @Name: navbar
240     @Description:
241     @@@: Link to edit mode of the WikiPage
242     @Order: 10
243     @Formatting:
244 wakaba 1.3 %link-to-wikipage (
245     mode => edit,
246     rel => edit,
247     up-to-date,
248     label => {%link-to-it (
249     class => wiki-cmd,
250     label => {%res (name => EditThisPage);}p,
251     description => {%res (name => EditThisPageLong);}p,
252     );},
253     page-anchor-name => edit,
254     );
255 wakaba 1.2
256     ViewFragment:
257     @Template[list]:
258     we--conflict-body
259     @Order: 30
260     @Description:
261     @@@: Conflict report
262     @@lang:en
263     @Formatting:
264     %paragraph (content=>{%res(name=>{Edit:Conflict:Description});}p);
265     %we--conflict-modified-content;
266 wakaba 1.6 %section (
267 wakaba 1.2 id => edit-conflict-diff,
268     title => {%res(name=>{Edit:Conflict:Diff:Title});}p, heading,
269     content => {
270     %paragraph (
271     content => {%res(name=>{Edit:Conflict:Diff:Description});}p);
272     %conflict-diff;
273     }p,
274     );
275    
276     ViewFragment:
277     @Template[list]: we--edit-body
278     @Order:80
279     @Description:
280     @@@: Editing WikiPage form section
281     @@lang:en
282     @Formatting:
283 wakaba 1.6 %section (
284 wakaba 1.2 id => edit,
285     title => {%res (name => {Edit:Title});}p, heading,
286     content => {%edit-form;}p,
287     );
288    
289     ViewFragment:
290     @Template[list]: we--adminedit-body
291     @Order:80
292     @Description:
293     @@@: Editing WikiPage form section (Admin edit mode)
294     @@lang:en
295     @Formatting:
296 wakaba 1.6 %section (
297 wakaba 1.2 id => edit,
298     title => {%res(name=>{Edit:Title});}p, heading,
299     content => {%edit-form(admin);}p,
300     );
301    
302     ViewFragment:
303     @Template[list]: we--conflict-body
304     @Order: 80
305     @Description:
306     @@@: Editing-WikiPage-form section (conflict mode)
307     @@lang:en
308     @Formatting:
309 wakaba 1.6 %section (
310 wakaba 1.2 id => edit,
311     title => {%res(name=>{Edit:Title});}p, heading,
312     content => {
313     %paragraph(name=>{%res(name=>{Edit:Conflict:Edit:Description});}p);
314     %edit-form;
315     }p,
316     );
317 wakaba 1.1
318     ViewFragment:
319     @Name: we--edit
320     @Description:
321     @@@: Edit form --- main textarea
322     @@lang:en
323     @Order: 0
324     @Formatting:
325 wakaba 1.5 %textarea (id => we--content, size => {%res (name=>{Edit:Form:Size});}p,
326 wakaba 1.6 source => content, lines => {%res (name=>{Edit:Form:Lines});}p);
327 wakaba 1.1 ViewFragment:
328     @Name: we--edit
329     @Description:
330     @@@: Submit button
331     @@lang:en
332     @Order: 200
333     @Formatting:
334     %submit(label=>{%res(name=>{Edit:Save});}p);
335    
336     FormattingRule:
337     @Category[list]: view
338     @Name: edit-form
339     @Description:
340     @@@: Provides WikiPage editing form
341     @@lang: en
342     @Parameter:
343     @@Name: admin
344     @@Type: boolean
345     @@Default: {0}
346     @@Description:
347     @@@@: Whether administrator's editing mode or not
348     @@@lang:en
349 wakaba 1.5 @Parameter:
350     @@Name: page
351     @@Type: WikiName
352     @@Default: (auto)
353     @@Description:
354     @@@@: WikiPage that is editing
355     @@@lang:en
356 wakaba 1.1 @Formatting:
357 wakaba 1.5 __ATTRTEXT:%admin__;__ATTRTEXT:%page__;
358 wakaba 1.3
359 wakaba 1.5 my $page = $p->{page} ? [split m#//#, $p->{page}]
360     : $o->{wiki}->{var}->{page};
361     my $template = $o->{wiki}->{view}->assemble_template ('we__edit');
362     local $o->{var} = {
363 wakaba 1.1 #content
364     is_admin_mode => $p->{admin},
365     is_conflict_mode => 0,
366     #is_new_page_template
367     };
368 wakaba 1.5 $o->{var}->{content} = __FUNCPACK__->get_content ($o, $page);
369     SuikaWiki::Plugin->module_package ('WikiFormCore')
370     ->make_form_in_html
371     ($p->{-parent}, $template,
372     wiki => $o->{wiki},
373     o => $o,
374     index => -1,
375     output => {
376     mode => 'write',
377     page => $page,
378     hidden => sub {
379     my ($hidden, $o) = @_;
380 wakaba 1.3 for ($hidden->append_new_node (type => '#element',
381 wakaba 1.5 namespace_uri => $NS_XHTML1,
382     local_name => 'input')) {
383 wakaba 1.1 $_->set_attribute (type => 'hidden');
384     $_->set_attribute (name => 'we--digest');
385 wakaba 1.2 $_->set_attribute (value => ($o->{var}->{is_new_page_template}?'':
386 wakaba 1.5 __FUNCPACK__->digest ($o->{var}->{content},
387     normalize => 1)));
388 wakaba 1.1 $_->option (use_EmptyElemTag => 1);
389     }
390 wakaba 1.5 },
391     });
392    
393 wakaba 1.1
394     FormattingRule:
395     @Category[list]: view
396 wakaba 1.2 @Name: we--conflict-modified-content
397 wakaba 1.1 @Description:
398     @@@:
399 wakaba 1.2 Modified content that is conflicted with current content
400 wakaba 1.1 @@lang:en
401     @Formatting:
402 wakaba 1.3 $p->{-parent}->append_new_node (type => '#element',
403 wakaba 1.2 namespace_uri => $NS_XHTML1,
404 wakaba 1.3 local_name => 'pre')
405     ->append_text (scalar $o->{wiki}->{input}->parameter ('we--content'));
406 wakaba 1.1
407     FormattingRule:
408     @Category[list]:view
409     @Name: conflict-diff
410     @Description:
411     @@@:
412     Provides marked diff between latest WikiPage content and
413     submitted one
414     @@lang:en
415     @Formatting:
416 wakaba 1.6 my $before = scalar $o->{wiki}->{input}->parameter ('we--content');
417     my $after = $o->{wiki}->{db}->get (content => $o->{wiki}->{var}->{page});
418     __FUNCPACK__->diff_in_html (\$before => \$after, $p->{-parent});
419    
420     Function:
421     @Name: diff_in_html
422     @Main:
423     my (undef, $before, $after, $parent, %opt) = @_;
424 wakaba 1.3 require Algorithm::Diff;
425 wakaba 1.6 my $diff = $parent->append_new_node (type => '#element',
426     namespace_uri => $NS_XHTML1,
427     local_name => 'pre');
428 wakaba 1.3 $diff->set_attribute (class => 'diff');
429 wakaba 1.6 for (Algorithm::Diff::diff
430     ([split /\x0D?\x0A/, $$before]=>[split /\x0D?\x0A/, $$after])) {
431 wakaba 1.1 for (@{$_}) {
432     my ($sign, $lineno, $text) = @{$_};
433 wakaba 1.3 my $line = $diff->append_new_node (type => '#element',
434 wakaba 1.2 namespace_uri => $NS_XHTML1,
435     local_name => ({qw/+ ins - del/}->{$sign}||'span'));
436    
437     $line->set_attribute (class => 'line');
438     for ($line->append_new_node (type => '#element',
439     namespace_uri => $NS_XHTML1,
440     local_name => 'span')) {
441     $_->set_attribute (class => 'lineno');
442     $_->append_text ($lineno);
443     }
444     $line->append_text (' ');
445     for ($line->append_new_node (type => '#element',
446     namespace_uri => $NS_XHTML1,
447     local_name => 'span')) {
448     $_->set_attribute (class => 'sign');
449     $_->append_text ($sign);
450     }
451     $line->append_text (' ');
452     for ($line->append_new_node (type => '#element',
453     namespace_uri => $NS_XHTML1,
454     local_name => 'span')) {
455     $_->set_attribute (class => 'content');
456     $_->append_text ($text);
457     }
458     $line->append_text ("\n");
459     } # diff lines
460 wakaba 1.1 }
461    
462     FormattingRule:
463     @Name:mode-after-edit-selection
464 wakaba 1.5 @Category[list]:form-input
465 wakaba 1.1 @Formatting:
466 wakaba 1.3 __ATTRTEXT:%name__;
467 wakaba 1.1 my $magic = '';
468     $magic = $1 if $o->{var}->{content} =~ m/^([^\x0A\x0D]+)/s;
469    
470 wakaba 1.5 my $name = 'we--mode-modified';
471 wakaba 1.1 my $selected_mode = $o->{wiki}->{input}->parameter ($name);
472     unless ($selected_mode) {
473     if ($magic =~ /C(?:on(?:fig|st)|SS)/) {
474     $selected_mode = 'edit';
475     } else {
476     $selected_mode = 'default';
477     }
478     }
479    
480 wakaba 1.3 my $SELECT = $p->{-parent}->append_new_node (type => '#element',
481     namespace_uri => $NS_XHTML1,
482     local_name => 'select');
483 wakaba 1.1 $SELECT->set_attribute (name => $name);
484     for (qw/default read edit/) {
485     for my $OPTION ($SELECT->append_new_node (type => '#element',
486     namespace_uri => $NS_XHTML1,
487     local_name => 'option')) {
488     $OPTION->set_attribute (value => $_);
489 wakaba 1.6 my $label = SuikaWiki::Plugin->module_package ('WikiResource')
490     ->append_node (name => 'Edit:SaveAnd:'.$_.':Label',
491     param => $o,
492     wiki => $o->{wiki});
493     $OPTION->set_attribute (label => $label->inner_text);
494     $OPTION->append_node ($label);
495 wakaba 1.1 $OPTION->set_attribute
496 wakaba 1.6 (title => SuikaWiki::Plugin->module_package ('WikiResource')
497     ->append_node (name => 'Edit:SaveAnd:'.$_.':Description',
498     param => $o,
499     wiki => $o->{wiki})->inner_text);
500 wakaba 1.1 $OPTION->set_attribute (selected => 'selected')
501     if $selected_mode eq $_;
502     }
503     }
504    
505 wakaba 1.5 FormattingRule:
506     @Category[list]:form-input
507     @Name:we--update-lastmodified-datetime
508     @Description:
509     @@@:
510     This direction force to update last-modified date-time
511     of the WikiPage modified.
512     \
513     Allowing user to select whether last-modified date-time should be
514     updated, use another formatting rule provided by WikiFormSelection
515     module.
516     \
517     WikiForm other that editing form might not be affected by this
518     parameter. See also "we--touch" parameter.
519     @@lang:en
520     @Formatting:
521     for ($p->{-parent}->append_new_node
522     (type => '#element',
523     namespace_uri => $NS_XHTML1,
524     local_name => 'input')) {
525     $_->set_attribute (type => 'hidden');
526     $_->set_attribute (name => 'we--touch');
527     $_->set_attribute (value => 'on');
528     $_->option (use_EmptyElemTag => 1);
529     }
530    
531    
532 wakaba 1.1 Resource:
533 wakaba 1.2 @Edit:Conflict:Title:
534     @@@:Confliction detected!
535     @@lang:en
536     @Edit:Conflict:WebPageTitle:
537     @@@:%page-name; (Edit : 409 CONFLICTION)
538     @@lang:en
539     @Edit:Deleted:Description:
540     @@@:Content of WikiPage %page-name; has been removed.
541     @@lang:en
542     @Edit:Deleted:Title:
543     @@@:WikiPage Deleted
544     @@lang:en
545     @Edit:Deleted:WebPageTitle:
546     @@@:%page-name; (Deleted)
547     @@lang:en
548 wakaba 1.1 @Edit:Form:Lines:15
549     @Edit:Form:Size:18
550     @Edit:Save:
551     @@@:Save
552     @@lang:en
553     @Edit:SaveAnd:default:Description:
554     @@@: Save modified content and show content with your default view mode
555     @@lang:en
556     @Edit:SaveAnd:default:Label:
557     @@@: Default view
558     @@lang:en
559     @Edit:SaveAnd:edit:Description:
560     @@@: Save modified content and show content in the edit mode again
561     @@lang:en
562     @Edit:SaveAnd:edit:Label:
563     @@@: Edit again
564     @@lang:en
565     @Edit:SaveAnd:read:Description:
566     @@@: Save modified content and show content in the read mode
567     @@lang:en
568     @Edit:SaveAnd:read:Label:
569     @@@: Show content
570     @@lang:en
571     @EditThisPage:
572     @@@: Edit
573     @@lang:en
574     @EditThisPageLong:
575     @@@: Edit this WikiPage
576     @@lang:en
577     @Edit:WebPageTitle:
578     @@@: %page-name; (Modification)
579     @@lang:en
580     @Edit:Title:
581     @@@: Modification of the WikiPage
582     @@lang:en
583     @Link:AdminEdit:Description:
584     @@@: Edit this WikiPage (administrator's mode)
585     @@lang:en
586     @Link:Edit:Description:
587     @@@: Edit this WikiPage
588     @@lang:en
589    
590     Function:
591 wakaba 1.2 @Name:digest
592     @Description:
593     @@@: Compute digest of given string
594     @@lang:en
595     @Main:
596 wakaba 1.5 my (undef, $s, %opt) = @_;
597 wakaba 1.2 require Digest::SHA1;
598     if ($opt{normalize}) {
599     $s =~ s/\x0D\x0A/\x0A/g;
600     $s =~ tr/\x0D/\x0A/;
601     }
602     Digest::SHA1::sha1_base64 ($s);
603    
604     Function:
605 wakaba 1.1 @Name:get_content
606     @Description:
607     @@@:Get WikiPage content to be modified. For new (currently not exist)
608     WikiPage, new page template content is returned.
609     @@lang:en
610     @Main:
611 wakaba 1.5 my (undef, $o, $page) = @_;
612 wakaba 1.1 my $content;
613     $content = $o->{wiki}->{db}->get (content => $page);
614    
615     unless (length $content) {
616     ## TODO: use namespaced template page
617     $content = $o->{wiki}->{db}->get
618     (content => $o->{wiki}->{config}->{page}->{NewPageTemplate});
619     $o->{var}->{is_new_page_template} = 1;
620     }
621     $content;
622    
623     Function:
624     @Name:page_is_editable
625     @Description:
626     @@@:
627     Returns whether the WikiPage is editable or not.
628     Note that this function is temporary.
629     @@lang:en
630     @Main:
631 wakaba 1.5 my (undef, $page) = @_;
632 wakaba 1.1 $page = join '//', $page;
633     return 0 unless SuikaWiki::Name::Space::validate_name ($page);
634     return 0 if $page =~ /[\x00-\x20\[\]\x7F]/;
635     1;
636    
637     Parameter:
638 wakaba 1.2 @Name: we--content
639     @Type: text
640     @Default: ""
641     @Description:
642     @@@:
643     Content of WikiPage (to be)
644     @@lang:en
645    
646     Parameter:
647 wakaba 1.1 @Name: we--digest
648     @Type: "Base64ed SHA1"
649     @Default: {undef}
650     @Description:
651     @@@:
652     SHA1 digest value of the WikiPage content before the modification.
653     This value is used to detect confliction of modifying.
654     Empty value (undef or length-zero-string) means that
655     there were no WikiPage content.
656 wakaba 1.2 @@lang:en
657    
658     Parameter:
659     @Name: we--mode-modified
660     @Type: mode-name
661     @Default: "default"
662     @Description:
663     @@@:
664     WikiEngine running mode to be specified after modification is completed
665 wakaba 1.1 @@lang:en
666    
667     Parameter:
668     @Name: we--touch
669 wakaba 1.5 @Type:
670     form:checkbox-value
671 wakaba 1.1 @Default: "off"
672     @Description:
673     @@@:
674     Whether last-modified date-time of the WikiPage should be updated or not
675     when the WikiPage is modified.
676     @@lang:en

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24