/[pub]/suikawiki/script/wiki.cgi
Suika

Contents of /suikawiki/script/wiki.cgi

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.7 - (hide annotations) (download)
Mon Feb 4 15:27:22 2002 UTC (22 years, 9 months ago) by wakaba
Branch: MAIN
Changes since 1.6: +6 -7 lines
*** empty log message ***

1 wakaba 1.2 #!/usr/bin/perl
2     use lib "../lib";
3     use CGI::Carp 'fatalsToBrowser';
4     use Algorithm::Diff qw(traverse_sequences);
5     # use strict;
6     #
7     # yukiwiki.cgi - Yet another WikiWikiWeb clone.
8     #
9     # Copyright (C) 2000,2001 by Hiroshi Yuki.
10     # <hyuki@hyuki.com>
11     # http://www.hyuki.com/yukiwiki/
12     #
13     # This program is free software; you can redistribute it and/or modify
14     # it under the terms of the GNU General Public License as published by
15     # the Free Software Foundation; either version 2 of the License, or
16     # (at your option) any later version.
17     #
18     # This program is distributed in the hope that it will be useful,
19     # but WITHOUT ANY WARRANTY; without even the implied warranty of
20     # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21     # GNU General Public License for more details.
22     #
23 wakaba 1.4 # $Id: wiki.cgi,v 1.3 2002/02/04 14:36:18 wakaba Exp $
24 wakaba 1.2 ##############################
25     my $version = "1.6.6";
26     ##############################
27     # 単独テストのときには 1 にする。
28     my $testing = 0;
29     ##############################
30     # 漢字ライブラリ
31     my $jcodelib = 'jcode.pl';
32     ##############################
33     # 保存・表示の漢字コード
34     my $kanjicode = 'euc'; # 'sjis' 'euc'
35     my $charset = 'euc-jisx0213'; # 'Shift_JIS' 'EUC-JP'
36     ##############################
37     # dbmopenが使えるなら1、使えないなら0
38     my $dbmopen = 0;
39     ##############################
40     # データベース名(.pag, .dir, .dbなどは不要)
41     # $dbmopen = 1のときはデータベース名、
42     # $dbmopen = 0のときはディレクトリ名になる。
43 wakaba 1.3 my $dbname = './wikidata';
44 wakaba 1.2 my $diffdbname = './wikidiff';
45     ##############################
46     # 修正者の氏名(自由に変更してください)
47     my $modifier = 'suika';
48     ##############################
49     # 修正者のWebページ(自由に変更してください)
50     my $modifierlink = 'http://suika.fam.cx/';
51     ##############################
52     # このページのURL
53     my $thisurl = 'wiki';
54     ##############################
55     # 開始ページ名
56     my $toppage = 'HomePage';
57     ##############################
58     # 最終更新ページ名
59     my $whatsnew = 'RecentChanges';
60     ##############################
61     # 最終更新に掲載するページ数
62     my $maxnew = 50;
63     ##############################
64     # アイコンファイル名(カラー版)
65     my $iconfile = '';
66     ##############################
67     # アイコンファイル名(モノクロ版)
68     # my $iconfile = '';
69     ##############################
70     # ページを変更したときにtouchするファイル(''なら何もしない)
71     my $touchfile = 'touch.txt';
72     ##############################
73     # プレビュー用の背景色
74     my $preview_color = '#FFCCCC';
75     ##############################
76     # 全ページのスタイル
77     my $style = <<'EOD';
78     pre, dl, ul, ol, p, blockquote { line-height:120%; }
79     a { text-decoration: none; }
80     a:link { color: #0000FF; background-color: #FFFFFF; }
81     a:visited { color: #9900CC; background-color: #FFFFFF; }
82     a:hover { text-decoration: underline; }
83     EOD
84     ##############################
85     # テキスト入力部分の大きさ
86     my $cols = 80;
87     my $rows = 20;
88     ##############################
89     my %form = ();
90     my %database = ();
91     my %diffbase = ();
92     my $diff_text = '';
93     my @diff_added = ();
94     my @diff_deleted = ();
95     my $msgrefA;
96     my $msgrefB;
97     ##############################
98     # 編集不可ページ名一覧
99     my @uneditable = ( $whatsnew );
100     ##############################
101     # リンク用の正規表現
102     # YukiWikiのリンクは2種類ある。
103     #
104     # (1) WikiName (RecentChangesとかFrontPageのようなもの)
105     # (2) BracketName ([[結城浩]]とか[[トラブルシュート]]のようなもの)
106     #
107     # ※シフトJISの2バイト目には ']' が来うるので、
108     # 文字']'を1つ多くとるようにしている。
109     #
110     my $WikiName = '([A-Z][a-z]+([A-Z][a-z]+)+)';
111     my $BracketName = '\[\[([^>\s]+?\]?)\]\]';
112    
113     # アイコン部分のタグ
114     my $IconTag = ''; #<<"EOD";
115     #<a href="http://www.hyuki.com/yukiwiki/"><img src="$iconfile"
116     # border="0" width="80" height="80" alt="[YukiWiki]" /></a>
117     #EOD
118    
119     require "$jcodelib";
120    
121     &init_form($kanjicode);
122    
123     if ($testing) {
124     %form = (
125     # 'mycmd' => 'write',
126     'mycmd' => 'read',
127     #'mycmd' => 'search',
128     #'mycmd' => 'edit',
129     'mymsg' => <<"EOD",
130     はじめまして。
131     これからいろいろ書き込みますね。
132     LinkPageも見てください。
133     TestPageはどうでしょうか。
134     どうぞよろしく。
135     http://www.hyuki.com/
136     [[結城浩]]
137     EOD
138     'mypage' => '<結城浩>',
139     'myword' => '結',
140     # '3C8C8B8FE98D5F3E' => '',
141     # 'TestPage' => '',
142     );
143     }
144     &main;
145     exit(0);
146    
147     # メイン
148     sub main {
149     &normalize_form;
150     if ($dbmopen) {
151     if (!dbmopen(%database, $dbname, 0666)) {
152     &print_error("(dbmopen) $dbname が作れません。");
153     }
154     } else {
155     if (!tie(%database, "YukiWikiDB", $dbname)) {
156     &print_error("(tie error)");
157     }
158     }
159    
160     # myspecial対応
161     foreach (keys %form) {
162     if (/^myspecial_(.*)/) {
163     $form{mycmd} = $1;
164     last;
165     }
166     }
167    
168     if ($form{mycmd} eq 'read') {
169     &do_read;
170     } elsif ($form{mycmd} eq 'preview') {
171     &do_preview;
172     } elsif ($form{mycmd} eq 'write') {
173     &do_write;
174     } elsif ($form{mycmd} eq 'edit') {
175     &do_edit;
176     } elsif ($form{mycmd} eq 'reedit') {
177     &do_reedit;
178     } elsif ($form{mycmd} eq 'search') {
179     &do_search;
180     } elsif ($form{mycmd} eq 'list') {
181     &do_list;
182     } elsif ($form{mycmd} eq 'diff') {
183     &do_diff;
184     } else {
185     $form{mypage} = $toppage;
186     &do_read;
187     }
188     if ($dbmopen) {
189     dbmclose(%database);
190     } else {
191     untie(%database);
192     }
193     }
194    
195     # ページの表示
196     sub do_read {
197     my $page_name = $form{mypage};
198     my $percent_name = &encode_percent($page_name);
199     &print_header($page_name);
200     print qq|<h1>$IconTag<a href="$thisurl?mycmd=search;myword=$percent_name">$page_name</a></h1>\n|;
201     &print_toolbar($page_name);
202     print &convert_html(&get_page($page_name));
203     &print_footer;
204     }
205    
206     # ページの編集
207     sub do_edit {
208     if (not &is_editable($form{mypage})) {
209     # 編集不可ページは表示のみ
210     &do_read;
211     return;
212     }
213     &editpage(&get_page($form{mypage}));
214     }
215    
216     # ページの再編集
217     sub do_reedit {
218     if (not &is_editable($form{mypage})) {
219     # 編集不可ページは表示のみ
220     &do_read;
221     } else {
222     &editpage($form{mymsg});
223     }
224     }
225    
226     sub editpage {
227     my $page_msg = shift;
228     my $page_name = $form{mypage};
229     my $digest = &calc_message_digest($page_msg);
230     &print_header($page_name);
231     print qq|<h1>$IconTag${page_name}の編集</h1>\n|;
232     &print_toolbar($page_name);
233     $page_msg = &escape($page_msg);
234     print <<"EOD";
235     <form action="$thisurl" method="post">
236 wakaba 1.4 <!--<input type="hidden" name="mycmd" value="preview">-->
237 wakaba 1.2 <input type="hidden" name="mypage" value="$page_name">
238     <input type="hidden" name="mydigest" value="$digest">
239     <textarea cols="$cols" rows="$rows" name="mymsg" wrap="virtual">$page_msg</textarea><br>
240 wakaba 1.4 <input type="submit" name="myspecial_preview" value="確認">
241     <input type="submit" name="myspecial_write" value="確認せず変更">
242 wakaba 1.2 </form>
243     <hr>
244     <h3>テキスト整形のルール</h3>
245    
246     <p>通常は入力した文字がそのまま出力されますが、
247     以下のルールに従ってテキスト整形を行うことができます。</p>
248    
249     <ul>
250     <li>
251     空行は段落の区切りとなります。
252    
253     <li>
254     HTMLのタグは書けません。
255    
256     <li>
257     ''強調''のようにシングルクォート二つではさむと、強調になります。
258    
259     <li>
260     '''更に強調'''のようにシングルクォート三つではさむと、更に強調になります。
261    
262     <li>
263     ----のようにマイナス4つがあると、水平線になります。
264    
265     <li>
266     *を行頭に書くと大見出しになります。
267    
268     <li>
269     **を行頭に書くと小見出しになります。
270    
271     <li>
272     -を行頭に書くと箇条書きになります。- -- --- の3レベルがあります。
273    
274     <li>
275     :を行頭に書くと用語と解説文が作れます。
276    
277     <pre>
278     :用語1:いろいろ書いた解説文1
279     :用語2:いろいろ書いた解説文2
280     :用語3:いろいろ書いた解説文3
281     </pre>
282    
283     <li>
284     http://www.hyuki.com/ のようなURLは自動的にリンクになります。
285    
286     <li>
287     YukiWikiのように大文字小文字を混ぜた英文字列を書くと、
288     YukiWikiのページ名になります。
289    
290     <li>
291     [[結城浩]]のように二重の大かっこ[[ ]]でくくった文字列を書くと、
292     YukiWikiのページ名になります。
293     大かっこの中にはスペースを含めてはいけません。
294     日本語も使えます。
295    
296     <li>
297     行頭がスペースで始まっていると、
298     その段落は整形済み扱われます。
299     プログラムを書き込むときに使うと便利です。
300    
301     <li>
302     &gt; を行頭に書くと、
303     引用文が書けます。
304     &gt; の数が多いとインデントが深くなります(3レベルまで)。
305    
306     </ul>
307     EOD
308     &print_footer;
309     }
310    
311     # ページの検索
312     sub do_search {
313     if ($form{myword}) {
314     &print_header('検索結果');
315     print qq|<h1>$IconTag$form{myword}の検索結果</h1>\n|;
316     &print_toolbar();
317     print qq|<ul>\n|;
318     my $count = 0;
319     foreach my $page_name (sort keys %database) { # sortするのは無謀かな
320     if ($database{$page_name} =~ /\Q$form{'myword'}\E/) {
321     my $encoded = &encode_percent($page_name);
322     print qq|<li><a href="$thisurl?mycmd=read;mypage=$encoded">$page_name</a>\n|;
323     $count++;
324     }
325     }
326     print qq|</ul>\n|;
327     if ($count > 0) {
328     print qq|<p><strong>$form{myword}</strong>を含むページは、上に示す<strong>$count</strong> ページです。</p>\n|;
329     } else {
330     print qq|<p><strong>$form{myword}</strong>を含むページは見つかりません。</p>\n|;
331     }
332     } else {
333     &print_header('単語検索');
334     print qq|<h1>$IconTag単語検索</h1>\n|;
335     &print_toolbar();
336     }
337     print <<"EOD";
338     <p>
339     <form action="$thisurl" method="post">
340     <input type="hidden" name="mycmd" value="search">
341     <input type="text" name="myword" size="20" value="$form{myword}">
342     <input type="submit" value="単語検索">
343     </form>
344     </p>
345     EOD
346     &print_footer;
347     }
348    
349     # ページの一覧
350     sub do_list {
351     &print_header('ページ一覧');
352     print qq|<h1>$IconTag ページ一覧</h1>\n|;
353     &print_toolbar();
354     print qq|<ul>\n|;
355     foreach my $page_name (sort keys %database) { # sortするのは無謀かな
356     my $encoded = &encode_percent($page_name);
357     print qq|<li><a href="$thisurl?mycmd=read;mypage=$encoded">$page_name</a>\n|
358     }
359     print qq|</ul>\n|;
360     &print_footer;
361     }
362    
363     # プレビュー
364     sub do_preview {
365     my $page_name = $form{mypage};
366     my $escapedmsg = &escape($form{mymsg});
367     &print_header($page_name);
368     print qq|<h1>$IconTag${page_name}のプレビュー</h1>\n|;
369     &print_toolbar($page_name);
370     # local $percent_name = &encode_percent($page_name);
371     print qq|<p>以下のプレビューを確認して、よければページ下部のボタンで更新してください。</p>\n|;
372     if ($form{mymsg}) {
373     print qq|<table width="100%" bgcolor="$preview_color" ><tr><td>\n|;
374     # print &convert_html($escapedmsg);
375     print &convert_html($form{mymsg});
376     print qq|</td></tr></table>\n|;
377     } else {
378     print qq|<p>(ページの内容は空です。更新するとこのページは<strong>削除</strong>されます。)</p>\n|;
379     }
380     &print_preview_buttons($page_name, $escapedmsg, $form{mydigest});
381     &print_footer;
382     }
383    
384     sub print_preview_buttons {
385     my ($page_name, $escapedmsg, $digest) = @_;
386     print <<"EOD";
387     <form action="$thisurl" method="post">
388     <textarea cols="$cols" rows="$rows" name="mymsg" wrap="virtual">$escapedmsg</textarea>
389     <br />
390     <input type="hidden" name="mypage" value="$page_name">
391     <input type="hidden" name="mydigest" value="$digest">
392     <input type="submit" name="myspecial_preview" value="再度プレビュー">
393     <input type="submit" name="myspecial_write" value="ページの更新">
394     </form>
395     EOD
396     }
397    
398     # 書き込む
399     sub do_write {
400     if (not &is_editable($form{mypage})) {
401     # 編集不可ページは表示のみ
402     &do_read;
403     return;
404     }
405    
406     my $page_name = $form{mypage};
407    
408     # digestを使って、更新の衝突チェック
409     my $original_digest = &calc_message_digest(&get_page($page_name));
410     if ($form{mydigest} ne $original_digest) {
411     &print_header($page_name);
412     print qq|<h1>$IconTag${page_name}で【更新の衝突】が起きました</h1>\n|;
413     print <<"EOD";
414     <p>あなたがこのページを編集している間に、
415     他の人が同じページを更新してしまったようです。
416     </p><p>
417     以下に、あなたの編集したテキストがありますので、
418     あなたの編集内容が失われないように、
419     いますぐ、メモ帳などにコピー&ペーストしてください。
420     </p><p>
421     コピー&ペーストが済んでから、
422     最新の内容を見て再度編集し直してください。
423     最新の内容は
424     <a target="_blank" href="$thisurl?mycmd=read;mypage=$form{mypage}">$form{mypage}</a>
425     で見ることができます。
426     </p>
427     EOD
428     # &print_toolbar($page_name);
429     &print_preview_buttons($page_name, &escape($form{mymsg}), $form{mydigest});
430     &print_footer;
431     return;
432     }
433    
434     # diff生成
435     {
436     &opendiff;
437     my @msg1 = split(/\n/, &get_page($page_name));
438     my @msg2 = split(/\n/, $form{mymsg});
439     $msgrefA = \@msg1;
440     $msgrefB = \@msg2;
441     &diff_check;
442     $diffbase{$form{mypage}} = $diff_text;
443     $diff_text = '';
444     &closediff;
445     }
446    
447     &print_header($page_name);
448     &set_page($page_name, $form{mymsg});
449     if ($form{mymsg}) {
450     print qq|<h1>$IconTag${page_name}を更新しました</h1>\n|;
451     &print_toolbar($page_name);
452     print &convert_html(&get_page($page_name));
453     } else {
454     print qq|<h1>$IconTag${page_name}を削除しました</h1>\n|;
455     &print_toolbar($page_name);
456     print qq|<p>${page_name}を削除しました。</p>\n|;
457     }
458     &print_footer;
459     # 更新されたのでタッチしておく。
460     if ($touchfile) {
461     open(FILE, "> $touchfile");
462     print FILE "\n";
463     close(FILE);
464     }
465     }
466    
467     # ページの変更点
468     sub do_diff {
469     if (not &is_editable($form{mypage})) {
470     # 編集不可ページは表示のみ
471     &do_read;
472     return;
473     }
474     &opendiff;
475     &print_header($form{mypage} . 'の変更点');
476     print qq|<h1>$IconTag <a href="$thisurl?mycmd=read&mypage=$form{mypage}">$form{mypage}</a>の変更点</h1>\n|;
477     &print_toolbar();
478     $_ = &escape($diffbase{$form{mypage}});
479     print <<"EOD";
480     <ul>
481     <li>追加された行は<ins>青色</ins>です。
482     <li>削除された行は<del>赤色</del>です。
483     <li><a href="$thisurl?mycmd=read;mypage=$form{mypage}">$form{mypage}</a>へ行く。
484     </ul>
485     <hr />
486     EOD
487     print qq|<pre>\n|;
488     foreach (split(/\n/, $_)) {
489     if (/^\+(.*)/) {
490     print qq|<ins>$1</ins>\n|;
491     } elsif (/^\-(.*)/) {
492     print qq|<del>$1</del>\n|;
493     } elsif (/^\=(.*)/) {
494     print qq|$1\n|;
495     } else {
496     print qq|??? $_\n|;
497     }
498     }
499     print qq|</pre>\n|;
500     &print_footer;
501     &closediff;
502     }
503    
504     sub opendiff {
505     if ($dbmopen) {
506     if (!dbmopen(%diffbase, $diffdbname, 0666)) {
507     &print_error("(dbmopen) $diffdbname が作れません。");
508     }
509     } else {
510     if (!tie(%diffbase, "YukiWikiDB", $diffdbname)) {
511     &print_error("(tie error)");
512     }
513     }
514     }
515    
516     sub closediff {
517     if ($dbmopen) {
518     dbmclose(%diffbase);
519     } else {
520     untie(%diffbase);
521     }
522     }
523    
524     # フォームからの情報を連想配列 %form に入れる
525     # &init_form('euc');
526     sub init_form {
527     my ($charcode) = @_;
528     my $query;
529     if ($ENV{REQUEST_METHOD} =~ /^post$/i) {
530     read(STDIN, $query, $ENV{CONTENT_LENGTH});
531     } else {
532     $query = $ENV{QUERY_STRING};
533     }
534     my @assocarray = split(/[&;]/, $query);
535     foreach my $assoc (@assocarray) {
536     my ($property, $value) = split(/=/, $assoc);
537     $value =~ tr/+/ /;
538     $value =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg;
539     &jcode::convert(\$value, $charcode);
540     $form{$property} = $value;
541     }
542     }
543    
544     # エラーページを出力する
545     sub print_error {
546     my ($msg) = @_;
547     &print_header('Error');
548     print "<h1>$IconTag$msg</h1></body></html>";
549     exit(0);
550     }
551    
552     sub escape {
553     my ($line) = shift;
554     $line =~ s|<|&lt;|g;
555     $line =~ s|>|&gt;|g;
556     $line =~ s|"|&quot;|g;
557     # $line =~ s|\&|&amp;|g;
558     return $line;
559     }
560    
561     sub inline {
562     my ($line) = shift;
563     $line = &escape($line);
564     $line =~ s|'''([^']+?)'''|<strong>$1</strong>|g;
565     $line =~ s|''([^']+?)''|<em>$1</em>|g;
566     $line =~ s!
567 wakaba 1.7 (
568     (?:&lt;(?:mailto|http|https|ftp|urn):[\x21-\x7E]*)&gt;
569     | (?:$WikiName) # LocalLinkLikeThis
570     | (?:$BracketName) # [[日本語リンク]]
571     )
572 wakaba 1.2 !
573     &make_link($1)
574     !gex;
575     return $line;
576     }
577    
578     # ページのタイトルからページの内容を得る
579     sub get_page {
580     my $page_name = shift;
581     return $database{$page_name};
582     }
583    
584     # ページの内容を与える
585     # &set_page($title, $txt)
586     sub set_page {
587     # ページを更新する
588     my $title = $_[0];
589     $database{$title} = $_[1];
590     # 空ページなら削除する
591     unless ($database{$title}) {
592     delete $database{$title};
593     }
594     # RecentChangesを更新する
595     my $delim = ' - ';
596     my @pages = split(/\n/, $database{$whatsnew});
597     my $datestr = &get_current_datestr;
598     unshift(@pages, qq|-$datestr$delim$title|);
599     # 同一ページの更新は最新のもののみにし、
600     # 存在しないページはスキップする。
601     my %count;
602     my @newpages;
603     foreach my $line (@pages) {
604     my ($prefix, $title) = split(/$delim/, $line);
605     $count{$title}++;
606     if ($count{$title} == 1 and exists($database{$title})) {
607     push(@newpages, qq|$prefix - $title|);
608     }
609     }
610     # ここで本当に更新
611     $database{$whatsnew} = join("\n", splice(@newpages, 0, $maxnew));
612     }
613    
614     # ページのヘッダを出力
615     sub print_header {
616     my $title = shift;
617     print <<"EOD";
618     Content-type: text/html
619    
620     <html><head>
621     <title>$title</title>
622     <style type="text/css">
623     $style
624     </style>
625     </head>
626     <body>
627     EOD
628     }
629    
630     # ツールバーを出力
631     sub print_toolbar {
632     my $page_name = shift;
633     my $percent_name = &encode_percent($page_name);
634     my $editlink = '';
635     if ($page_name ne '' and &is_editable($page_name)) {
636     $editlink = <<"EOD";
637     <a href="$thisurl?mycmd=edit;mypage=$percent_name">編集</a> |
638     <a href="$thisurl?mycmd=diff;mypage=$percent_name">差分</a> |
639     EOD
640     }
641     print <<"EOD";
642     <p>
643     [
644     <a href="$thisurl?mycmd=read;mypage=$toppage">トップ</a> |
645     <a href="$thisurl?mycmd=list">一覧</a> |
646     $editlink
647     <a href="$thisurl?mycmd=search">単語検索</a> |
648     <a href="$thisurl?mycmd=read;mypage=$whatsnew">最終更新</a>
649     ]
650     </p>
651     EOD
652     }
653    
654     # ページのフッタを出力
655     sub print_footer {
656     print <<"EOD";
657     <address>
658     <a href="http://www.hyuki.com/yukiwiki/">YukiWiki</a> 1.6.6 Copyright (C) 2000,2001 by <a href="http://www.hyuki.com/">Hiroshi Yuki.</a>
659     + <a href="$modifierlink">$modifier</a> ${version}.
660     [<a href="/" title="このサーバーの首頁">/</a>
661     <a href="/map" title="このサーバーの案内">地図</a>
662     <a href="/search/" title="このサーバーの検索">検索</a>]
663     </address>
664     </body></html>
665     EOD
666     }
667    
668     # URLやページの名前からリンクを作る
669     sub make_link {
670     my $name = shift;
671 wakaba 1.7 $name =~ s/^&lt;(.*)&gt;$/$1/;
672 wakaba 1.5 if ($name =~ /^(http|https|ftp).*?(\.png|\.jpeg|\.jpg)?$/) {
673 wakaba 1.2 if ($2) {
674     return qq|<a href="$name"><img border="0" src="$name" /></a>|;
675     } else {
676 wakaba 1.5 return qq|&lt;<a href="$name">$name</a>&gt;|;
677 wakaba 1.2 }
678     } elsif ($name =~ /^mailto:(.*)/) {
679     my $address = $1;
680 wakaba 1.5 return qq|&lt;<a href="$name">$address</a>&gt;|;
681     } elsif ($name =~ /^urn:[0-9A-Za-z_:-]+/) {
682     return qq|&lt;<a href="/uri-res/N2L?${name}">$name</a>&gt;|;
683 wakaba 1.2 } elsif ($database{$name}) {
684     my $percent_name = &encode_percent($name);
685     return qq|<a href="$thisurl?mycmd=read;mypage=$percent_name">$name</a>|;
686     } else {
687     my $percent_name = &encode_percent($name);
688     return qq|$name<a href="$thisurl?mycmd=edit;mypage=$percent_name">?</a>|;
689     }
690     }
691    
692     # %xx の形式にエンコードする
693     # これは、
694     # http://www.hyuki.com/yukiwiki/yukiwiki.cgi?mycmd=read&mypage=%3C%8C%8B%8F%E9%8D_%3E
695     # という形式のために使われる。
696     # '<結城浩>' → '%3C%8C%8B%8F%E9%8D_%3E'
697     sub encode_percent {
698     my $name = shift;
699     my $encoded = '';
700     foreach my $ch (split(//, $name)) {
701     if ($ch =~ /[A-Za-z0-9_]/) {
702     $encoded .= $ch;
703     } else {
704     $encoded .= '%' . sprintf("%02X", ord($ch));
705     }
706     }
707     return $encoded;
708     }
709    
710     # テキスト本体をHTMLに変換する
711     sub convert_html {
712     my ($txt) = shift;
713     my (@txt) = split(/\n/, $txt);
714     foreach (@txt) {
715     chomp;
716     if (/^\*\*(.*)/) {
717     push(@result, splice(@saved), '<h3>' . &inline($1) . '</h3>');
718     } elsif (/^\*(.*)/) {
719     push(@result, splice(@saved), '<h2>' . &inline($1) . '</h2>');
720     } elsif (/^----/) {
721     push(@result, splice(@saved), '<hr>');
722     } elsif (/^(-{1,3})(.*)/) {
723     &back_push('ul', length($1));
724     push(@result, '<li>' . &inline($2) . '</li>');
725     } elsif (/^:([^:]+):(.*)/) {
726     &back_push('dl', 1);
727     push(@result, '<dt>' . &inline($1) . '</dt>', '<dd>' . &inline($2) . '</dd>');
728     } elsif (/^(>{1,3})(.*)/) {
729     &back_push('blockquote', length($1));
730     push(@result, &inline($2));
731     } elsif (/^\s*$/) {
732     push(@result, splice(@saved));
733     unshift(@saved, "</p>");
734     push(@result, "<p>");
735     } elsif (/^(\s+.*)$/) {
736     &back_push('pre', 1);
737     push(@result, &escape($1)); # Not &inline, but &escape
738     } else {
739     push(@result, &inline($_));
740     }
741     }
742     push(@result, splice(@saved));
743     return join("\n", @result);
744     }
745    
746     # &back_push($tag, $count)
747     # $tagのタグを$levelレベルまで詰める。
748     sub back_push {
749     my ($tag, $level) = @_;
750     while (@saved > $level) {
751     push(@result, shift(@saved));
752     }
753     if ($saved[0] ne "</$tag>") {
754     push(@result, splice(@saved));
755     }
756     while (@saved < $level) {
757     unshift(@saved, "</$tag>");
758     push(@result, "<$tag>");
759     }
760     }
761    
762     # 編集可能ページか?
763     sub is_editable {
764     my ($pagename) = @_;
765     foreach (@uneditable) {
766     if ($pagename eq $_) {
767     return 0;
768     }
769     }
770     if (&is_valid_name($pagename)) {
771     return 1;
772     }
773     return 0;
774     }
775    
776     # Validな名前か?
777     sub is_valid_name {
778     my ($pagename) = @_;
779     if ($pagename =~ /^$WikiName$/) {
780     return 1;
781     } elsif ($pagename =~ /^$BracketName$/) {
782     return 1;
783     } else {
784     return 0;
785     }
786     }
787    
788     # 現在時刻の文字列を得る
789     sub get_current_datestr {
790     my (@wdays) = ( "日", "月", "火", "水", "木", "金", "土" );
791     my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time);
792     return sprintf("%4d-%02d-%02d (%s) %02d:%02d:%02d",
793     $year + 1900, $mon + 1, $mday, $wdays[$wday], $hour, $min, $sec);
794     }
795    
796     # URL?SomePageや、
797     # URL?[[結城浩]]の形式だった場合、(not yet)
798     # 強制的にmycmdをreadにして$formの内容を設定する。
799     sub normalize_form {
800     foreach my $key (keys %form) {
801     if ($key =~ /^$WikiName$/) {
802     $form{mycmd} = 'read';
803     $form{mypage} = $1;
804     last;
805     } elsif ($key =~ /^$BracketName$/) {
806     $form{mycmd} = 'read';
807     $form{mypage} = $1;
808     last;
809     }
810     }
811     }
812    
813     # 変換テストを行なうときのサンプル
814     sub print_sample {
815     my $txt = &convert_html(<<"EOD");
816     *大見出し1
817     **小見出し1-1
818     -項目1
819     -項目2
820     -項目3
821     段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
822     段落1段落1段落1段落1段落1段落''強調''1段落1段落1段落1段落1段落1
823     段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
824    
825     段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
826     段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
827     段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
828     **小見出し1-2
829     :用語1:いろいろ書いた解説文1と''強調単語''
830     段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
831     段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
832     段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
833     :用語2:いろいろ書いた解説文2
834     :用語3:いろいろ書いた解説文3
835     ----
836     *大見出し2
837     **小見出し2-1
838 wakaba 1.5 &lt;http://suika.fam.cx/&gt;
839 wakaba 1.2 **小見出し2-2
840    
841     [[結城浩]]
842    
843     段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
844     段落1段落1段落1段落'''強調'''1段落1段落1段落1段落1段落1段落1段落1
845     段落1段落1段落1段落'''''強い強調'''''1段落1段落1段落1段落1段落1段落1段落1段落1
846     >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
847     >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
848     >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
849    
850     レベル0レベル0レベル0レベル0レベル0レベル0
851    
852     >レベル1
853     >レベル1
854     >レベル1
855     >>レベル2
856     >>レベル2
857     >>レベル2
858     >>>レベル3
859     -はろ1
860     --はろ2
861     ろろろろ2
862     ---はろ3
863     --はろ2
864     ---はろ3
865     --はろ2
866     ---はろ3
867     >>>レベル3
868     >>>レベル3
869     >>>レベル3
870     EOD
871     print $txt;
872     exit;
873     }
874    
875     sub diff_check {
876     traverse_sequences(
877     $msgrefA, $msgrefB,
878     {
879     MATCH => \&df_match,
880     DISCARD_A => \&df_delete,
881     DISCARD_B => \&df_add,
882     }
883     );
884     &diff_flush;
885     }
886    
887     sub diff_flush {
888     $diff_text .= join('', map { "-$_\n" } splice(@diff_deleted));
889     $diff_text .= join('', map { "+$_\n" } splice(@diff_added));
890     }
891    
892     sub df_match {
893     my ($a, $b) = @_;
894     &diff_flush;
895     $diff_text .= "=$msgrefA->[$a]\n";
896     }
897    
898     sub df_delete {
899     my ($a, $b) = @_;
900     push(@diff_deleted, $msgrefA->[$a]);
901     }
902    
903     sub df_add {
904     my ($a, $b) = @_;
905     push(@diff_added, $msgrefB->[$b]);
906     }
907    
908     sub calc_message_digest { # You have to use MD5...
909     my $text = shift;
910     my @text = split(//, $text);
911     my $len = length($text);
912     my $checksum = 0;
913     foreach (@text) {
914     $checksum += ord($_);
915     $checksum = ($checksum * 2) % 65536 + (($checksum & 32768) ? 1 : 0); # 16bit rotate
916     }
917     return "$len:$checksum";
918     }
919    
920     # Definition of YukiWikiDB
921     package YukiWikiDB;
922    
923     my $debug = 1;
924    
925     # Constructor
926     sub new {
927     return shift->TIEHASH(@_);
928     }
929    
930     # tying
931     sub TIEHASH {
932     my ($class, $dbname) = @_;
933     my $self = {
934     dir => $dbname,
935     keys => [],
936     };
937     if (not -d $self->{dir}) {
938     if (!mkdir($self->{dir}, 0777)) {
939     print "mkdir(" . $self->{dir} . ") fail\n" if ($debug);
940     return undef;
941     }
942     }
943     return bless($self, $class);
944     }
945    
946     # Store
947     sub STORE {
948     my ($self, $key, $val) = @_;
949     my $file = &make_filename($self, $key);
950     if (open(FILE,"> $file")) {
951     binmode(FILE);
952     print FILE $val;
953     close(FILE);
954     return $self->{$key} = $val;
955     } else {
956     print "$file create error.";
957     }
958     }
959    
960     # Fetch
961     sub FETCH {
962     my ($self, $key) = @_;
963     my $file = &make_filename($self, $key);
964     if (open(FILE, $file)) {
965     local $/;
966     $self->{$key} = <FILE>;
967     close(FILE);
968     }
969     return $self->{$key};
970     }
971    
972     # Exists
973     sub EXISTS {
974     my ($self, $key) = @_;
975     my $file = &make_filename($self, $key);
976     return -e($file);
977     }
978    
979     # Delete
980     sub DELETE {
981     my ($self, $key) = @_;
982     my $file = &make_filename($self, $key);
983     unlink $file;
984     return delete $self->{$key};
985     }
986    
987     sub FIRSTKEY {
988     my ($self) = @_;
989     opendir(DIR, $self->{dir}) or die $self->{dir};
990     @{$self->{keys}} = grep /\.txt$/, readdir(DIR);
991     foreach my $name (@{$self->{keys}}) {
992     $name =~ s/\.txt$//;
993     $name =~ s/[0-9A-F][0-9A-F]/pack("C", hex($&))/eg;
994     }
995     return shift @{$self->{keys}};
996     }
997    
998     sub NEXTKEY {
999     my ($self) = @_;
1000     return shift @{$self->{keys}};
1001     }
1002    
1003     sub make_filename {
1004     my ($self, $key) = @_;
1005     my $enkey = '';
1006     foreach my $ch (split(//, $key)) {
1007     $enkey .= sprintf("%02X", ord($ch));
1008     }
1009     return $self->{dir} . "/$enkey.txt";
1010     }
1011     __END__
1012    
1013     =head1 NAME
1014    
1015     YukiWiki - 自由にページを追加・削除・編集できるWebページ構築CGI
1016    
1017     Copyright (C) 2000,2001 by Hiroshi Yuki.
1018     結城浩 <hyuki@hyuki.com>
1019     http://www.hyuki.com/
1020     http://www.hyuki.com/yukiwiki/
1021    
1022     =head1 SYNOPSIS
1023    
1024     http://www.hyuki.com/yukiwiki/yukiwiki.cgi
1025    
1026     =head1 DESCRIPTION
1027    
1028     YukiWiki(結城ウィキィ)は参加者が自由にページを追加・削除・編集できる
1029     不思議なWebページ群を作るCGIです。
1030     Webで動作する掲示板とちょっと似ていますが、
1031     Web掲示板が単にメッセージを追加するだけなのに対して、
1032     YukiWikiは、Webページ全体を自由に変更することができます。
1033    
1034     YukiWikiは、Cunningham & CunninghamのWikiWikiWebの
1035     仕様を参考にして独自に作られました。
1036    
1037     YukiWikiはPerlで書かれたCGIスクリプトとして実現されていますので、
1038     Perlが動作するWebサーバならば比較的容易に設置できます。
1039    
1040     あとはdbmopenが使える環境ならば設置できます(Version 1.5.0以降ならdbmopenが使えなくても設置できます)。
1041    
1042    
1043     YukiWikiはフリーソフトです。
1044     ご自由にお使いください。
1045    
1046     =head1 設置方法
1047    
1048     =head2 入手
1049    
1050     YukiWikiの最新版は、
1051     http://www.hyuki.com/yukiwiki/
1052     から入手できます。
1053    
1054     =head2 ファイル一覧
1055    
1056     readme.txt ドキュメント
1057     yukiwiki.cgi YukiWiki本体
1058     yukiwiki.gif ロゴ(カラー版)
1059     yukimono.gif ロゴ(モノクロ版)
1060     jcode.pl 漢字コードライブラリ
1061    
1062     =head2 インストール
1063    
1064     =over 4
1065    
1066     =item 1.
1067    
1068     アーカイブを解く。
1069    
1070     =item 2.
1071    
1072     yukiwiki.cgiのはじめの方にある設定を確認します。
1073     通常は何もしなくてよいが、
1074     はじめは$touchfileを''にした方がよいでしょう。
1075    
1076     =item 3.
1077    
1078     yukiwiki.cgiとjcode.plを同じところに設置します。
1079    
1080     =item 4.
1081    
1082     サイズ0のyukiwiki.dbというファイルを設置します。
1083     (Perlシステムによってはyukiwiki.pag, yukiwiki.dir)
1084    
1085     =item 5.
1086    
1087     yukiwiki.cgiにブラウザからアクセスします。
1088    
1089     =back
1090    
1091     =head2 パーミッション
1092    
1093     ファイル パーミッション 転送モード
1094     yukiwiki.cgi 755 ASCII
1095     yukiwiki.gif 644 BINARY
1096     yukimono.gif 644 BINARY
1097     jcode.pl 644 ASCII
1098    
1099     $dbmopen = 1; にした場合:
1100     yukiwiki.db 666 BINARY
1101     (yukiwiki.pag, yukiwiki.dirの場合もあり)
1102    
1103     $dbmopen = 0; にした場合: (カレントディレクトリを777にしておく)
1104     . 777 (転送しない)
1105    
1106     =head1 データのバックアップ方法
1107    
1108     $dbmopen = 1;の場合は、
1109     データはすべてyukiwiki.db(.dir, .pag)に入る。
1110     これをバックアップすればよい。
1111    
1112     $dbmopen = 0;の場合は、
1113     yukiwikiというディレクトリができるので、
1114     これ以下をバックアップすればよい。
1115    
1116     =head1 新しいページの作り方
1117    
1118     =over 4
1119    
1120     =item 1.
1121    
1122     まず、適当なページ(例えばFrontPage)を選び、
1123     ページの下にある「編集」リンクをたどります。
1124    
1125     =item 2.
1126    
1127     するとテキスト入力ができる状態になるので、
1128     そこにNewPageのような単語
1129     (大文字小文字混在している英文字列)
1130     を書いて「保存」します。
1131    
1132     =item 3.
1133    
1134     保存すると、FrontPageのページが書き換わり、
1135     あなたが書いたNewPageという文字列の後ろに ? というリンクが表示されます。
1136     この ? はそのページがまだ存在しないことを示す印です。
1137    
1138     =item 4.
1139    
1140     その ? をクリックすると新しいページNewPageができますので、
1141     あなたの好きな文章をその新しいページに書いて保存します。
1142    
1143     =item 5.
1144    
1145     NewPageページができるとFrontPageの ? は消えて、リンクとなります。
1146    
1147     =back
1148    
1149     =head1 テキスト整形のルール
1150    
1151     =over 4
1152    
1153     =item *
1154    
1155     連続した複数行はフィルされて表示されます。
1156    
1157     =item *
1158    
1159     空行は段落C<< <p> >>の区切りとなります。
1160    
1161     =item *
1162    
1163     HTMLのタグは書けません。
1164    
1165     =item *
1166    
1167     B<''ボールド''>のようにシングルクォート二つではさむと、
1168     ボールドC<< <b> >>になります。
1169    
1170     =item *
1171    
1172     B<'''イタリック'''>のようにシングルクォート三つではさむと、
1173     イタリックC<< <i> >>になります。
1174    
1175     =item *
1176    
1177     B<---->のようにマイナス4つがあると、
1178     水平線C<< <hr> >>になります。
1179    
1180     =item *
1181    
1182     行をB<*>ではじめると、
1183     大見出しC<< <h2> >>になります。
1184    
1185     =item *
1186    
1187     行をB<**>ではじめると、
1188     小見出しC<< <h3> >>になります。
1189    
1190     =item *
1191    
1192     行をマイナス-ではじめると、
1193     箇条書きC<< <ul> >>になります。
1194     マイナスの数が増えるとレベルが下がります(3レベルまで)
1195    
1196     -項目1
1197     --項目1-1
1198     --項目1-2
1199     -項目2
1200     -項目3
1201     --項目3-1
1202     ---項目3-1-1
1203     ---項目3-1-2
1204     --項目3-2
1205    
1206     =item *
1207    
1208     コロンを使うと、
1209     用語と解説文のリストC<< <dl> >>が書けます。
1210    
1211     :用語1:いろいろ書いた解説文1
1212     :用語2:いろいろ書いた解説文2
1213     :用語3:いろいろ書いた解説文3
1214    
1215     =item *
1216    
1217     リンク
1218    
1219     =over 4
1220    
1221     =item *
1222    
1223     LinkToSomePageやFrontPageのように、
1224     英単語の最初の一文字を大文字にしたものが
1225     二つ以上連続したものはYukiWikiのページ名となり、
1226     それが文章中に含まれるとリンクになります。
1227    
1228     =item *
1229    
1230     http://www.hyuki.com/ のようなURLは自動的にリンクになります。
1231    
1232     =item *
1233    
1234     二重の大かっこ[[ ]]でくくった文字列も、
1235     YukiWikiのページ名になります。
1236     大かっこの中にはスペースを含めてはいけません。
1237     日本語も使えます。
1238    
1239     =back
1240    
1241     =item *
1242    
1243     行頭がスペースやタブで始まっていると、
1244     それは整形済みの段落C<< <pre> >>として扱われます。
1245     プログラムの表示などに使うと便利です。
1246    
1247    
1248     =item *
1249    
1250     行を > ではじめると、
1251     引用文C<< <blockquote> >>が書けます。
1252     >の数が多いとインデントが深くなります(3レベルまで)。
1253    
1254     >文章
1255     >文章
1256     >>さらなる引用
1257     >文章
1258    
1259     =back
1260    
1261     =head1 更新履歴
1262    
1263     =over 4
1264    
1265     =item *
1266    
1267     2001年10月20日、Version 1.6.6。
1268    
1269     更新の衝突対策。
1270     元ページの簡単なチェックサムを取っておき、
1271     更新前にチェックサムを比較する。
1272     修正個所はdigestという文字列を検索すれば分かる。
1273     本来はMD5などでちゃんとやった方がいいのだけれど。
1274    
1275     衝突時に表示されるメッセージなどは「極悪」さんのページを参考にした。
1276    
1277     =item *
1278    
1279     2001年10月17日、Version 1.6.5。
1280    
1281     プレビュー画面で、更新ボタンを押したときに送信される
1282     メッセージの内容をinput要素のtype="hidden"を使って埋め込むのをやめる。
1283     代わりに、textarea要素を使う。
1284     再プレビュー用にmyspecial_を導入。でもきれいな対策ではない。
1285    
1286     =item *
1287    
1288     2001年8月30日、Version 1.6.4。
1289    
1290     URLでダイレクトにページ名を指定しても、
1291     $WikiNameと$BracketName以外のページを作れないようにした。
1292     (is_valid_nameとis_editable参照)。
1293    
1294     =item *
1295    
1296     2001年8月30日、Version 1.6.3。
1297    
1298     RecentChangesを編集・再編集不可とした。
1299     編集不可ページは@uneditableにページ名を入れる。
1300    
1301     =item *
1302    
1303     2001年2月25日、Version 1.6.1, 1.6.2。
1304    
1305     差分機能のバグ修正。
1306     do_previewで'>'が扱えないバグを修正
1307     (ユーザからの指摘)。
1308    
1309     =item *
1310    
1311     2001年2月22日、Version 1.6.0。
1312     差分機能を実装した。
1313    
1314     =item *
1315    
1316     2001年2月19日、Version 1.5.4。
1317     画像ファイルへのリンクは画像にしてみた。
1318    
1319     =item *
1320    
1321     2001年2月19日、Version 1.5.3。
1322     RecentChangesの中に削除したページがあるのをやめた。
1323     use strict;で引っかかる部分を少し整理(完全ではない)。
1324    
1325     =item *
1326    
1327     2001年2月16日、Version 1.5.2。
1328     textareaに表示およびプレビューする前に < や > を &lt; や &gt; に変換した
1329     (do_preview, editpage, print_preview_buttons)。
1330    
1331     =item *
1332    
1333     2000年12月27日、Version 1.5.1。
1334     プレビュー画面を整理した。
1335    
1336     =item *
1337    
1338     2000年12月22日、Version 1.5.0。
1339     全体的にずいぶん書き直した。
1340     一覧を別途作成するようにした(do_list)。
1341     書き込む前に確認画面を出すようにした(do_preview)。
1342     テキストの書き方を編集画面に入れた(do_edit, do_reedit)。
1343     WhatsNew→RecentChanges、TopPage→FrontPageに変更した。
1344    
1345     =item *
1346    
1347     2000年12月20日、Version 1.1.0。
1348     tieを利用して、dbmopenが使えない場合でも動作するように修正。
1349     利用者の1人である「極悪」さんから
1350     送っていただいたコードを元にしています。
1351    
1352     =item *
1353    
1354     2000年9月5日、Version 1.0.2。
1355     <body color=...> → <body bgcolor=...>
1356     利用者からの指摘による。感謝。
1357    
1358     =item *
1359    
1360     2000年8月6日、Version 1.0.1を公開。
1361     C MAGAZINE(ソフトバンクパブリッシング)
1362     2000年10月号連載記事向け公開版。
1363     [[ ]] の最後が「望」のようにシフトJISで
1364     0x5Dになる場合の回避を行なった。
1365    
1366     =item *
1367    
1368     2000年8月5日、Version 1.0.0を公開。
1369    
1370     =item *
1371    
1372     2000年7月23日、Version 0.82を公開。
1373     編集時のリンクミス。
1374     <textarea>の属性変更。
1375    
1376     =item *
1377    
1378     2000年7月22日、Version 0.81を公開。
1379     ロゴを組み込む。
1380    
1381     =item *
1382    
1383     2000年7月21日、Version 0.80を公開。
1384     PODをCGI中に書き込む。
1385    
1386     =item *
1387    
1388     2000年7月19日、Version 0.70を公開。
1389     '''イタリック'''や、--、---、>>、>>>などを実装。
1390    
1391     =item *
1392    
1393     2000年7月18日、Version 0.60を公開。
1394     *太字*を''太字''に変更
1395    
1396     =item *
1397    
1398     2000年7月17日、Version 0.50を公開。
1399    
1400     =item *
1401    
1402     2000年7月17日、さらにいろいろ追加する。
1403    
1404     =item *
1405    
1406     2000年7月16日、いろいろ追加。
1407    
1408     =item *
1409    
1410     2000年7月15日、公開。
1411    
1412     =back
1413    
1414     =head1 TODO
1415    
1416     - テキスト表示モード
1417     - Charsetを明示。
1418     - textarea中の閉じタグ対応
1419     - メニューの英語表記付記
1420     - プレビューのボタンで、mymsgをinputのvalueに入れているが、改行をそのままvalueにいれてはいけないのではないか。
1421     - 「再編集」の機能はブラウザの back で充分ではないか。プレビューはもっとシンプルに。
1422     - ページタイトル(Wikiname)が検索にかかるようにする。
1423     - InterWiki風の機能「URLを隠しつつ、リンクを張る」
1424    
1425     =head1 作者
1426    
1427     Copyright (C) 2000 by Hiroshi Yuki.
1428     結城浩 <hyuki@hyuki.com>
1429     http://www.hyuki.com/
1430     http://www.hyuki.com/yukiwiki/
1431    
1432     質問、意見、バグ報告は hyuki@hyuki.com にメールしてください。
1433    
1434     =head1 配布条件
1435    
1436     YukiWikiは、
1437     GNU General Public Licenseにて公開します。
1438    
1439     YukiWikiはフリーソフトです。
1440     ご自由にお使いください。
1441     自分好みのYukiWikiが作れるようにシンプルにしてあります。
1442    
1443     =head1 謝辞
1444    
1445     本家のWikiWikiを作ったCunningham & Cunningham, Inc.に
1446     感謝します。
1447    
1448     YukiWikiを楽しんで使ってくださる
1449     ネット上の方々に感謝します。
1450    
1451     YukiWikiのロゴをデザインしてくださった橋本礼奈さん
1452     http://city.hokkai.or.jp/~reina/
1453     に感謝します。
1454    
1455     tieを使った版の元になるコードを送ってくださった
1456     「極悪」さんに感謝します。
1457    
1458     =head1 参照リンク
1459    
1460     =over 4
1461    
1462     =item *
1463    
1464     YukiWikiホームページ
1465     http://www.hyuki.com/yukiwiki/
1466    
1467     =item *
1468    
1469     本家のWikiWiki
1470     http://c2.com/cgi/wiki?WikiWikiWeb
1471    
1472     =item *
1473    
1474     本家のWikiWikiの作者(Cunningham & Cunningham, Inc.)
1475     http://c2.com/
1476    
1477     =item *
1478    
1479     YukiWikiのロゴデザインをしてくださった橋本礼奈さんのページ
1480     http://city.hokkai.or.jp/~reina/
1481    
1482     =back
1483    
1484     =cut

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24