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

Contents of /suikawiki/script/wiki.cgi

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.11 - (show annotations) (download)
Sun Mar 24 01:12:13 2002 UTC (22 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.10: +7 -2 lines
2002-03-24  wakaba <w@suika.fam.cx>

	* wiki.cgi (inline): 
	- Ignore LinkLikeThis style anchor.
	- Allow [[space including anchor]].
	(make_link): Likewise.  Gets rid of angle branckets
	before make anchor.

1 #!/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 # $Id: wiki.cgi,v 1.10 2002/03/24 01:09:03 wakaba Exp $
24 ##############################
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 my $dbname = './wikidata';
44 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 = '\[\[([^>\x09]+?\]?)\]\]';
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 <!--<input type="hidden" name="mycmd" value="preview">-->
237 <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 <input type="submit" name="myspecial_preview" value="確認">
241 <input type="submit" name="myspecial_write" value="確認せず変更">
242 </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 (
568 (?:&lt;(?:mailto|http|https|ftp|urn):[\x21-\x7E]*)&gt;
569 #| (?:$WikiName) # LocalLinkLikeThis
570 | (?:$BracketName) # [[日本語リンク]]
571 )
572 !
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} || $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; charset=${charset}
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 <div class="navigation">
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 </div>
664 <div class="update">
665 <a href="/gate/cvs/wakaba/wiki/" title="CVS Repository">
666 \$Revision: wiki.cgi,v 1.10 2002/03/24 01:09:03 wakaba Exp $
667 \$Date: $
668 </a>
669 </div>
670 </body></html>
671 EOD
672 }
673
674 # URLやページの名前からリンクを作る
675 sub make_link {
676 my $name = shift;
677 $name =~ s/^&lt;(.*)&gt;$/$1/;
678 $name =~ s/^\[\[(.*)\]\]$/$1/;
679 if ($name =~ /^(http|https|ftp).*?(\.png|\.jpeg|\.jpg)?$/) {
680 if ($2) {
681 return qq|<a href="$name"><img border="0" src="$name" /></a>|;
682 } else {
683 return qq|&lt;<a href="$name">$name</a>&gt;|;
684 }
685 } elsif ($name =~ /^mailto:(.*)/) {
686 my $address = $1;
687 return qq|&lt;<a href="$name">$address</a>&gt;|;
688 } elsif ($name =~ m#^urn:[0-9A-Za-z_:;/.-]+#) {
689 return qq|&lt;<a href="/uri-res/N2L?${name}">$name</a>&gt;|;
690 } else {
691 my $name2 = $name; $name2 =~ tr/\x20/-/;
692 if ($database{$name2}) {
693 my $percent_name = &encode_percent($name2);
694 return qq|<a href="$thisurl?mycmd=read;mypage=$percent_name" class="wiki">$name</a>|;
695 } elsif ($database{'[['.$name2.']]'}) {
696 my $percent_name = &encode_percent('[['.$name2.']]');
697 return qq|<a href="$thisurl?mycmd=read;mypage=$percent_name" class="wiki">$name</a>|;
698 } else {
699 my $percent_name = &encode_percent($name2);
700 return qq|<a href="$thisurl?mycmd=edit;mypage=$percent_name" class="wiki newpage">$name<span class="mark">?</span></a>|;
701 }
702 }
703 }
704
705 # %xx の形式にエンコードする
706 # これは、
707 # http://www.hyuki.com/yukiwiki/yukiwiki.cgi?mycmd=read&mypage=%3C%8C%8B%8F%E9%8D_%3E
708 # という形式のために使われる。
709 # '<結城浩>' → '%3C%8C%8B%8F%E9%8D_%3E'
710 sub encode_percent {
711 my $name = shift;
712 my $encoded = '';
713 foreach my $ch (split(//, $name)) {
714 if ($ch =~ /[A-Za-z0-9_]/) {
715 $encoded .= $ch;
716 } else {
717 $encoded .= '%' . sprintf("%02X", ord($ch));
718 }
719 }
720 return $encoded;
721 }
722
723 # テキスト本体をHTMLに変換する
724 sub convert_html {
725 my ($txt) = shift;
726 my (@txt) = split(/\n/, $txt);
727 foreach (@txt) {
728 chomp;
729 if (/^\*\*(.*)/) {
730 push(@result, splice(@saved), '<h3>' . &inline($1) . '</h3>');
731 } elsif (/^\*(.*)/) {
732 push(@result, splice(@saved), '<h2>' . &inline($1) . '</h2>');
733 } elsif (/^----/) {
734 push(@result, splice(@saved), '<hr>');
735 } elsif (/^(-{1,3})(.*)/) {
736 &back_push('ul', length($1));
737 push(@result, '<li>' . &inline($2) . '</li>');
738 } elsif (/^:([^:]+):(.*)/) {
739 &back_push('dl', 1);
740 push(@result, '<dt>' . &inline($1) . '</dt>', '<dd>' . &inline($2) . '</dd>');
741 } elsif (/^(>{1,3})(.*)/) {
742 &back_push('blockquote', length($1));
743 push(@result, &inline($2));
744 } elsif (/^\s*$/) {
745 push(@result, splice(@saved));
746 unshift(@saved, "</p>");
747 push(@result, "<p>");
748 } elsif (/^(\s+.*)$/) {
749 &back_push('pre', 1);
750 push(@result, &escape($1)); # Not &inline, but &escape
751 } else {
752 push(@result, &inline($_));
753 }
754 }
755 push(@result, splice(@saved));
756 return join("\n", @result);
757 }
758
759 # &back_push($tag, $count)
760 # $tagのタグを$levelレベルまで詰める。
761 sub back_push {
762 my ($tag, $level) = @_;
763 while (@saved > $level) {
764 push(@result, shift(@saved));
765 }
766 if ($saved[0] ne "</$tag>") {
767 push(@result, splice(@saved));
768 }
769 while (@saved < $level) {
770 unshift(@saved, "</$tag>");
771 push(@result, "<$tag>");
772 }
773 }
774
775 # 編集可能ページか?
776 sub is_editable {return 1;
777 ## TODO: ...
778 my ($pagename) = @_;
779 foreach (@uneditable) {
780 if ($pagename eq $_) {
781 return 0;
782 }
783 }
784 if (&is_valid_name($pagename)) {
785 return 1;
786 }
787 return 0;
788 }
789
790 # Validな名前か?
791 sub is_valid_name {
792 my ($pagename) = @_;
793 if ($pagename =~ /^$WikiName$/) {
794 return 1;
795 } elsif ($pagename =~ /^$BracketName$/) {
796 return 1;
797 } else {
798 return 0;
799 }
800 }
801
802 # 現在時刻の文字列を得る
803 sub get_current_datestr {
804 my (@wdays) = ( "日", "月", "火", "水", "木", "金", "土" );
805 my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time);
806 return sprintf("%4d-%02d-%02d (%s) %02d:%02d:%02d",
807 $year + 1900, $mon + 1, $mday, $wdays[$wday], $hour, $min, $sec);
808 }
809
810 # URL?SomePageや、
811 # URL?[[結城浩]]の形式だった場合、(not yet)
812 # 強制的にmycmdをreadにして$formの内容を設定する。
813 sub normalize_form {
814 foreach my $key (keys %form) {
815 if ($key =~ /^$WikiName$/) {
816 $form{mycmd} = 'read';
817 $form{mypage} = $1;
818 last;
819 } elsif ($key =~ /^$BracketName$/) {
820 $form{mycmd} = 'read';
821 $form{mypage} = $1;
822 last;
823 }
824 }
825 }
826
827 # 変換テストを行なうときのサンプル
828 sub print_sample {
829 my $txt = &convert_html(<<"EOD");
830 *大見出し1
831 **小見出し1-1
832 -項目1
833 -項目2
834 -項目3
835 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
836 段落1段落1段落1段落1段落1段落''強調''1段落1段落1段落1段落1段落1
837 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
838
839 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
840 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
841 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
842 **小見出し1-2
843 :用語1:いろいろ書いた解説文1と''強調単語''
844 段落1段落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 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
847 :用語2:いろいろ書いた解説文2
848 :用語3:いろいろ書いた解説文3
849 ----
850 *大見出し2
851 **小見出し2-1
852 &lt;http://suika.fam.cx/&gt;
853 **小見出し2-2
854
855 [[結城浩]]
856
857 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1
858 段落1段落1段落1段落'''強調'''1段落1段落1段落1段落1段落1段落1段落1
859 段落1段落1段落1段落'''''強い強調'''''1段落1段落1段落1段落1段落1段落1段落1段落1
860 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
861 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
862 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2
863
864 レベル0レベル0レベル0レベル0レベル0レベル0
865
866 >レベル1
867 >レベル1
868 >レベル1
869 >>レベル2
870 >>レベル2
871 >>レベル2
872 >>>レベル3
873 -はろ1
874 --はろ2
875 ろろろろ2
876 ---はろ3
877 --はろ2
878 ---はろ3
879 --はろ2
880 ---はろ3
881 >>>レベル3
882 >>>レベル3
883 >>>レベル3
884 EOD
885 print $txt;
886 exit;
887 }
888
889 sub diff_check {
890 traverse_sequences(
891 $msgrefA, $msgrefB,
892 {
893 MATCH => \&df_match,
894 DISCARD_A => \&df_delete,
895 DISCARD_B => \&df_add,
896 }
897 );
898 &diff_flush;
899 }
900
901 sub diff_flush {
902 $diff_text .= join('', map { "-$_\n" } splice(@diff_deleted));
903 $diff_text .= join('', map { "+$_\n" } splice(@diff_added));
904 }
905
906 sub df_match {
907 my ($a, $b) = @_;
908 &diff_flush;
909 $diff_text .= "=$msgrefA->[$a]\n";
910 }
911
912 sub df_delete {
913 my ($a, $b) = @_;
914 push(@diff_deleted, $msgrefA->[$a]);
915 }
916
917 sub df_add {
918 my ($a, $b) = @_;
919 push(@diff_added, $msgrefB->[$b]);
920 }
921
922 sub calc_message_digest { # You have to use MD5...
923 my $text = shift;
924 my @text = split(//, $text);
925 my $len = length($text);
926 my $checksum = 0;
927 foreach (@text) {
928 $checksum += ord($_);
929 $checksum = ($checksum * 2) % 65536 + (($checksum & 32768) ? 1 : 0); # 16bit rotate
930 }
931 return "$len:$checksum";
932 }
933
934 # Definition of YukiWikiDB
935 package YukiWikiDB;
936
937 my $debug = 1;
938
939 # Constructor
940 sub new {
941 return shift->TIEHASH(@_);
942 }
943
944 # tying
945 sub TIEHASH {
946 my ($class, $dbname) = @_;
947 my $self = {
948 dir => $dbname,
949 keys => [],
950 };
951 if (not -d $self->{dir}) {
952 if (!mkdir($self->{dir}, 0777)) {
953 print "mkdir(" . $self->{dir} . ") fail\n" if ($debug);
954 return undef;
955 }
956 }
957 return bless($self, $class);
958 }
959
960 # Store
961 sub STORE {
962 my ($self, $key, $val) = @_;
963 my $file = &make_filename($self, $key);
964 if (open(FILE,"> $file")) {
965 binmode(FILE);
966 print FILE $val;
967 close(FILE);
968 return $self->{$key} = $val;
969 } else {
970 print "$file create error.";
971 }
972 }
973
974 # Fetch
975 sub FETCH {
976 my ($self, $key) = @_;
977 my $file = &make_filename($self, $key);
978 if (open(FILE, $file)) {
979 local $/;
980 $self->{$key} = <FILE>;
981 close(FILE);
982 }
983 return $self->{$key};
984 }
985
986 # Exists
987 sub EXISTS {
988 my ($self, $key) = @_;
989 my $file = &make_filename($self, $key);
990 return -e($file);
991 }
992
993 # Delete
994 sub DELETE {
995 my ($self, $key) = @_;
996 my $file = &make_filename($self, $key);
997 unlink $file;
998 return delete $self->{$key};
999 }
1000
1001 sub FIRSTKEY {
1002 my ($self) = @_;
1003 opendir(DIR, $self->{dir}) or die $self->{dir};
1004 @{$self->{keys}} = grep /\.txt$/, readdir(DIR);
1005 foreach my $name (@{$self->{keys}}) {
1006 $name =~ s/\.txt$//;
1007 $name =~ s/[0-9A-F][0-9A-F]/pack("C", hex($&))/eg;
1008 }
1009 return shift @{$self->{keys}};
1010 }
1011
1012 sub NEXTKEY {
1013 my ($self) = @_;
1014 return shift @{$self->{keys}};
1015 }
1016
1017 sub make_filename {
1018 my ($self, $key) = @_;
1019 my $enkey = '';
1020 foreach my $ch (split(//, $key)) {
1021 $enkey .= sprintf("%02X", ord($ch));
1022 }
1023 return $self->{dir} . "/$enkey.txt";
1024 }
1025 __END__
1026
1027 =head1 NAME
1028
1029 YukiWiki - 自由にページを追加・削除・編集できるWebページ構築CGI
1030
1031 Copyright (C) 2000,2001 by Hiroshi Yuki.
1032 結城浩 <hyuki@hyuki.com>
1033 http://www.hyuki.com/
1034 http://www.hyuki.com/yukiwiki/
1035
1036 =head1 SYNOPSIS
1037
1038 http://www.hyuki.com/yukiwiki/yukiwiki.cgi
1039
1040 =head1 DESCRIPTION
1041
1042 YukiWiki(結城ウィキィ)は参加者が自由にページを追加・削除・編集できる
1043 不思議なWebページ群を作るCGIです。
1044 Webで動作する掲示板とちょっと似ていますが、
1045 Web掲示板が単にメッセージを追加するだけなのに対して、
1046 YukiWikiは、Webページ全体を自由に変更することができます。
1047
1048 YukiWikiは、Cunningham & CunninghamのWikiWikiWebの
1049 仕様を参考にして独自に作られました。
1050
1051 YukiWikiはPerlで書かれたCGIスクリプトとして実現されていますので、
1052 Perlが動作するWebサーバならば比較的容易に設置できます。
1053
1054 あとはdbmopenが使える環境ならば設置できます(Version 1.5.0以降ならdbmopenが使えなくても設置できます)。
1055
1056
1057 YukiWikiはフリーソフトです。
1058 ご自由にお使いください。
1059
1060 =head1 設置方法
1061
1062 =head2 入手
1063
1064 YukiWikiの最新版は、
1065 http://www.hyuki.com/yukiwiki/
1066 から入手できます。
1067
1068 =head2 ファイル一覧
1069
1070 readme.txt ドキュメント
1071 yukiwiki.cgi YukiWiki本体
1072 yukiwiki.gif ロゴ(カラー版)
1073 yukimono.gif ロゴ(モノクロ版)
1074 jcode.pl 漢字コードライブラリ
1075
1076 =head2 インストール
1077
1078 =over 4
1079
1080 =item 1.
1081
1082 アーカイブを解く。
1083
1084 =item 2.
1085
1086 yukiwiki.cgiのはじめの方にある設定を確認します。
1087 通常は何もしなくてよいが、
1088 はじめは$touchfileを''にした方がよいでしょう。
1089
1090 =item 3.
1091
1092 yukiwiki.cgiとjcode.plを同じところに設置します。
1093
1094 =item 4.
1095
1096 サイズ0のyukiwiki.dbというファイルを設置します。
1097 (Perlシステムによってはyukiwiki.pag, yukiwiki.dir)
1098
1099 =item 5.
1100
1101 yukiwiki.cgiにブラウザからアクセスします。
1102
1103 =back
1104
1105 =head2 パーミッション
1106
1107 ファイル パーミッション 転送モード
1108 yukiwiki.cgi 755 ASCII
1109 yukiwiki.gif 644 BINARY
1110 yukimono.gif 644 BINARY
1111 jcode.pl 644 ASCII
1112
1113 $dbmopen = 1; にした場合:
1114 yukiwiki.db 666 BINARY
1115 (yukiwiki.pag, yukiwiki.dirの場合もあり)
1116
1117 $dbmopen = 0; にした場合: (カレントディレクトリを777にしておく)
1118 . 777 (転送しない)
1119
1120 =head1 データのバックアップ方法
1121
1122 $dbmopen = 1;の場合は、
1123 データはすべてyukiwiki.db(.dir, .pag)に入る。
1124 これをバックアップすればよい。
1125
1126 $dbmopen = 0;の場合は、
1127 yukiwikiというディレクトリができるので、
1128 これ以下をバックアップすればよい。
1129
1130 =head1 新しいページの作り方
1131
1132 =over 4
1133
1134 =item 1.
1135
1136 まず、適当なページ(例えばFrontPage)を選び、
1137 ページの下にある「編集」リンクをたどります。
1138
1139 =item 2.
1140
1141 するとテキスト入力ができる状態になるので、
1142 そこにNewPageのような単語
1143 (大文字小文字混在している英文字列)
1144 を書いて「保存」します。
1145
1146 =item 3.
1147
1148 保存すると、FrontPageのページが書き換わり、
1149 あなたが書いたNewPageという文字列の後ろに ? というリンクが表示されます。
1150 この ? はそのページがまだ存在しないことを示す印です。
1151
1152 =item 4.
1153
1154 その ? をクリックすると新しいページNewPageができますので、
1155 あなたの好きな文章をその新しいページに書いて保存します。
1156
1157 =item 5.
1158
1159 NewPageページができるとFrontPageの ? は消えて、リンクとなります。
1160
1161 =back
1162
1163 =head1 テキスト整形のルール
1164
1165 =over 4
1166
1167 =item *
1168
1169 連続した複数行はフィルされて表示されます。
1170
1171 =item *
1172
1173 空行は段落C<< <p> >>の区切りとなります。
1174
1175 =item *
1176
1177 HTMLのタグは書けません。
1178
1179 =item *
1180
1181 B<''ボールド''>のようにシングルクォート二つではさむと、
1182 ボールドC<< <b> >>になります。
1183
1184 =item *
1185
1186 B<'''イタリック'''>のようにシングルクォート三つではさむと、
1187 イタリックC<< <i> >>になります。
1188
1189 =item *
1190
1191 B<---->のようにマイナス4つがあると、
1192 水平線C<< <hr> >>になります。
1193
1194 =item *
1195
1196 行をB<*>ではじめると、
1197 大見出しC<< <h2> >>になります。
1198
1199 =item *
1200
1201 行をB<**>ではじめると、
1202 小見出しC<< <h3> >>になります。
1203
1204 =item *
1205
1206 行をマイナス-ではじめると、
1207 箇条書きC<< <ul> >>になります。
1208 マイナスの数が増えるとレベルが下がります(3レベルまで)
1209
1210 -項目1
1211 --項目1-1
1212 --項目1-2
1213 -項目2
1214 -項目3
1215 --項目3-1
1216 ---項目3-1-1
1217 ---項目3-1-2
1218 --項目3-2
1219
1220 =item *
1221
1222 コロンを使うと、
1223 用語と解説文のリストC<< <dl> >>が書けます。
1224
1225 :用語1:いろいろ書いた解説文1
1226 :用語2:いろいろ書いた解説文2
1227 :用語3:いろいろ書いた解説文3
1228
1229 =item *
1230
1231 リンク
1232
1233 =over 4
1234
1235 =item *
1236
1237 LinkToSomePageやFrontPageのように、
1238 英単語の最初の一文字を大文字にしたものが
1239 二つ以上連続したものはYukiWikiのページ名となり、
1240 それが文章中に含まれるとリンクになります。
1241
1242 =item *
1243
1244 http://www.hyuki.com/ のようなURLは自動的にリンクになります。
1245
1246 =item *
1247
1248 二重の大かっこ[[ ]]でくくった文字列も、
1249 YukiWikiのページ名になります。
1250 大かっこの中にはスペースを含めてはいけません。
1251 日本語も使えます。
1252
1253 =back
1254
1255 =item *
1256
1257 行頭がスペースやタブで始まっていると、
1258 それは整形済みの段落C<< <pre> >>として扱われます。
1259 プログラムの表示などに使うと便利です。
1260
1261
1262 =item *
1263
1264 行を > ではじめると、
1265 引用文C<< <blockquote> >>が書けます。
1266 >の数が多いとインデントが深くなります(3レベルまで)。
1267
1268 >文章
1269 >文章
1270 >>さらなる引用
1271 >文章
1272
1273 =back
1274
1275 =head1 更新履歴
1276
1277 =over 4
1278
1279 =item *
1280
1281 2001年10月20日、Version 1.6.6。
1282
1283 更新の衝突対策。
1284 元ページの簡単なチェックサムを取っておき、
1285 更新前にチェックサムを比較する。
1286 修正個所はdigestという文字列を検索すれば分かる。
1287 本来はMD5などでちゃんとやった方がいいのだけれど。
1288
1289 衝突時に表示されるメッセージなどは「極悪」さんのページを参考にした。
1290
1291 =item *
1292
1293 2001年10月17日、Version 1.6.5。
1294
1295 プレビュー画面で、更新ボタンを押したときに送信される
1296 メッセージの内容をinput要素のtype="hidden"を使って埋め込むのをやめる。
1297 代わりに、textarea要素を使う。
1298 再プレビュー用にmyspecial_を導入。でもきれいな対策ではない。
1299
1300 =item *
1301
1302 2001年8月30日、Version 1.6.4。
1303
1304 URLでダイレクトにページ名を指定しても、
1305 $WikiNameと$BracketName以外のページを作れないようにした。
1306 (is_valid_nameとis_editable参照)。
1307
1308 =item *
1309
1310 2001年8月30日、Version 1.6.3。
1311
1312 RecentChangesを編集・再編集不可とした。
1313 編集不可ページは@uneditableにページ名を入れる。
1314
1315 =item *
1316
1317 2001年2月25日、Version 1.6.1, 1.6.2。
1318
1319 差分機能のバグ修正。
1320 do_previewで'>'が扱えないバグを修正
1321 (ユーザからの指摘)。
1322
1323 =item *
1324
1325 2001年2月22日、Version 1.6.0。
1326 差分機能を実装した。
1327
1328 =item *
1329
1330 2001年2月19日、Version 1.5.4。
1331 画像ファイルへのリンクは画像にしてみた。
1332
1333 =item *
1334
1335 2001年2月19日、Version 1.5.3。
1336 RecentChangesの中に削除したページがあるのをやめた。
1337 use strict;で引っかかる部分を少し整理(完全ではない)。
1338
1339 =item *
1340
1341 2001年2月16日、Version 1.5.2。
1342 textareaに表示およびプレビューする前に < や > を &lt; や &gt; に変換した
1343 (do_preview, editpage, print_preview_buttons)。
1344
1345 =item *
1346
1347 2000年12月27日、Version 1.5.1。
1348 プレビュー画面を整理した。
1349
1350 =item *
1351
1352 2000年12月22日、Version 1.5.0。
1353 全体的にずいぶん書き直した。
1354 一覧を別途作成するようにした(do_list)。
1355 書き込む前に確認画面を出すようにした(do_preview)。
1356 テキストの書き方を編集画面に入れた(do_edit, do_reedit)。
1357 WhatsNew→RecentChanges、TopPage→FrontPageに変更した。
1358
1359 =item *
1360
1361 2000年12月20日、Version 1.1.0。
1362 tieを利用して、dbmopenが使えない場合でも動作するように修正。
1363 利用者の1人である「極悪」さんから
1364 送っていただいたコードを元にしています。
1365
1366 =item *
1367
1368 2000年9月5日、Version 1.0.2。
1369 <body color=...> → <body bgcolor=...>
1370 利用者からの指摘による。感謝。
1371
1372 =item *
1373
1374 2000年8月6日、Version 1.0.1を公開。
1375 C MAGAZINE(ソフトバンクパブリッシング)
1376 2000年10月号連載記事向け公開版。
1377 [[ ]] の最後が「望」のようにシフトJISで
1378 0x5Dになる場合の回避を行なった。
1379
1380 =item *
1381
1382 2000年8月5日、Version 1.0.0を公開。
1383
1384 =item *
1385
1386 2000年7月23日、Version 0.82を公開。
1387 編集時のリンクミス。
1388 <textarea>の属性変更。
1389
1390 =item *
1391
1392 2000年7月22日、Version 0.81を公開。
1393 ロゴを組み込む。
1394
1395 =item *
1396
1397 2000年7月21日、Version 0.80を公開。
1398 PODをCGI中に書き込む。
1399
1400 =item *
1401
1402 2000年7月19日、Version 0.70を公開。
1403 '''イタリック'''や、--、---、>>、>>>などを実装。
1404
1405 =item *
1406
1407 2000年7月18日、Version 0.60を公開。
1408 *太字*を''太字''に変更
1409
1410 =item *
1411
1412 2000年7月17日、Version 0.50を公開。
1413
1414 =item *
1415
1416 2000年7月17日、さらにいろいろ追加する。
1417
1418 =item *
1419
1420 2000年7月16日、いろいろ追加。
1421
1422 =item *
1423
1424 2000年7月15日、公開。
1425
1426 =back
1427
1428 =head1 TODO
1429
1430 - テキスト表示モード
1431 - Charsetを明示。
1432 - textarea中の閉じタグ対応
1433 - メニューの英語表記付記
1434 - プレビューのボタンで、mymsgをinputのvalueに入れているが、改行をそのままvalueにいれてはいけないのではないか。
1435 - 「再編集」の機能はブラウザの back で充分ではないか。プレビューはもっとシンプルに。
1436 - ページタイトル(Wikiname)が検索にかかるようにする。
1437 - InterWiki風の機能「URLを隠しつつ、リンクを張る」
1438
1439 =head1 作者
1440
1441 Copyright (C) 2000 by Hiroshi Yuki.
1442 結城浩 <hyuki@hyuki.com>
1443 http://www.hyuki.com/
1444 http://www.hyuki.com/yukiwiki/
1445
1446 質問、意見、バグ報告は hyuki@hyuki.com にメールしてください。
1447
1448 =head1 配布条件
1449
1450 YukiWikiは、
1451 GNU General Public Licenseにて公開します。
1452
1453 YukiWikiはフリーソフトです。
1454 ご自由にお使いください。
1455 自分好みのYukiWikiが作れるようにシンプルにしてあります。
1456
1457 =head1 謝辞
1458
1459 本家のWikiWikiを作ったCunningham & Cunningham, Inc.に
1460 感謝します。
1461
1462 YukiWikiを楽しんで使ってくださる
1463 ネット上の方々に感謝します。
1464
1465 YukiWikiのロゴをデザインしてくださった橋本礼奈さん
1466 http://city.hokkai.or.jp/~reina/
1467 に感謝します。
1468
1469 tieを使った版の元になるコードを送ってくださった
1470 「極悪」さんに感謝します。
1471
1472 =head1 参照リンク
1473
1474 =over 4
1475
1476 =item *
1477
1478 YukiWikiホームページ
1479 http://www.hyuki.com/yukiwiki/
1480
1481 =item *
1482
1483 本家のWikiWiki
1484 http://c2.com/cgi/wiki?WikiWikiWeb
1485
1486 =item *
1487
1488 本家のWikiWikiの作者(Cunningham & Cunningham, Inc.)
1489 http://c2.com/
1490
1491 =item *
1492
1493 YukiWikiのロゴデザインをしてくださった橋本礼奈さんのページ
1494 http://city.hokkai.or.jp/~reina/
1495
1496 =back
1497
1498 =cut

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24