リビジョン | 15e6dda2512403bb7738c34bccd906842d0b6a14 (tree) |
---|---|
日時 | 2017-09-30 15:31:10 |
作者 | umorigu <umorigu@gmai...> |
コミッター | umorigu |
BugTrack/692 Show page contents in search result - search2 plugin
* Show page contents by client-side JavaScript
* Add new "search2" plugin with "skin/search2.js" JavaScript
* Toggle switch to show details or not
* Supoort both UTF-8 and EUC-JP encodings
* OR Search with "OR"-combined keywords (ex: "A OR B")
* Always show passage
* Color search texts in details view
* Color search texts in each text-found page
* Web browser requirement: HTML5 Fetch API (You can use Polyfill library)
* Server requirement: PHP5.4+ (can handle JSON)
@@ -2,7 +2,7 @@ | ||
2 | 2 | // PukiWiki - Yet another WikiWikiWeb clone. |
3 | 3 | // en.lng.php |
4 | 4 | // Copyright |
5 | -// 2002-2016 PukiWiki Development Team | |
5 | +// 2002-2017 PukiWiki Development Team | |
6 | 6 | // 2001-2002 Originally written by yu-ji |
7 | 7 | // License: GPL v2 or (at your option) any later version |
8 | 8 | // |
@@ -53,6 +53,8 @@ $_msg_help = 'View Text Formatting Rules'; | ||
53 | 53 | $_msg_week = array('Sun','Mon','Tue','Wed','Thu','Fri','Sat'); |
54 | 54 | $_msg_content_back_to_top = '<div class="jumpmenu"><a href="#navigator">↑</a></div>'; |
55 | 55 | $_msg_word = 'These search terms have been highlighted:'; |
56 | +$_msg_unsupported_webbrowser = 'This function doesn\'t support your current Web browser.'; | |
57 | +$_msg_use_alternative_link = 'Please go to the following link destination: $1'; | |
56 | 58 | |
57 | 59 | /////////////////////////////////////// |
58 | 60 | // Symbols |
@@ -367,6 +369,8 @@ $_btn_and = 'AND'; | ||
367 | 369 | $_btn_or = 'OR'; |
368 | 370 | $_search_pages = 'Search for page starts from $1'; |
369 | 371 | $_search_all = 'Search for all pages'; |
372 | +$_search_searching = 'Searching...'; | |
373 | +$_search_detail = 'Show details'; | |
370 | 374 | |
371 | 375 | /////////////////////////////////////// |
372 | 376 | // source.inc.php |
@@ -2,7 +2,7 @@ | ||
2 | 2 | // PukiWiki - Yet another WikiWikiWeb clone. |
3 | 3 | // ja.lng.php |
4 | 4 | // Copyright |
5 | -// 2002-2016 PukiWiki Development Team | |
5 | +// 2002-2017 PukiWiki Development Team | |
6 | 6 | // 2001-2002 Originally written by yu-ji |
7 | 7 | // License: GPL v2 or (at your option) any later version |
8 | 8 | // |
@@ -55,6 +55,8 @@ $_msg_help = 'テキスト整形のルールを表示する'; | ||
55 | 55 | $_msg_week = array('日','月','火','水','木','金','土'); |
56 | 56 | $_msg_content_back_to_top = '<div class="jumpmenu"><a href="#navigator">↑</a></div>'; |
57 | 57 | $_msg_word = 'これらのキーワードがハイライトされています:'; |
58 | +$_msg_unsupported_webbrowser = 'この機能はお使いのWebブラウザには対応していません。'; | |
59 | +$_msg_use_alternative_link = 'リンク先の機能をご利用ください: $1'; | |
58 | 60 | |
59 | 61 | /////////////////////////////////////// |
60 | 62 | // Symbols |
@@ -114,7 +116,7 @@ $_LANG['skin']['rename'] = '名前変更'; // Rename a page (and related) | ||
114 | 116 | $_LANG['skin']['rss'] = '最終更新のRSS'; // RSS of RecentChanges |
115 | 117 | $_LANG['skin']['rss10'] = & $_LANG['skin']['rss']; |
116 | 118 | $_LANG['skin']['rss20'] = & $_LANG['skin']['rss']; |
117 | -$_LANG['skin']['search'] = '単語検索'; | |
119 | +$_LANG['skin']['search'] = '検索'; | |
118 | 120 | $_LANG['skin']['top'] = 'トップ'; // Top page |
119 | 121 | $_LANG['skin']['unfreeze'] = '凍結解除'; |
120 | 122 | $_LANG['skin']['upload'] = '添付'; // Attach a file |
@@ -361,7 +363,7 @@ $_rename_messages = array( | ||
361 | 363 | |
362 | 364 | /////////////////////////////////////// |
363 | 365 | // search.inc.php |
364 | -$_title_search = '単語検索'; | |
366 | +$_title_search = '検索'; | |
365 | 367 | $_title_result = '$1 の検索結果'; |
366 | 368 | $_msg_searching = '全てのページから単語を検索します。大文字小文字の区別はありません。'; |
367 | 369 | $_btn_search = '検索'; |
@@ -369,6 +371,8 @@ $_btn_and = 'AND検索'; | ||
369 | 371 | $_btn_or = 'OR検索'; |
370 | 372 | $_search_pages = '$1 から始まるページを検索'; |
371 | 373 | $_search_all = '全てのページを検索'; |
374 | +$_search_searching = '検索中...'; | |
375 | +$_search_detail = '詳細表示'; | |
372 | 376 | |
373 | 377 | /////////////////////////////////////// |
374 | 378 | // source.inc.php |
@@ -15,9 +15,16 @@ define('PKWK_MAXSHOW_CACHE', 'recent.dat'); | ||
15 | 15 | // AutoLink |
16 | 16 | define('PKWK_AUTOLINK_REGEX_CACHE', 'autolink.dat'); |
17 | 17 | |
18 | -// Get source(wiki text) data of the page | |
19 | -// Returns FALSE if error occurerd | |
20 | -function get_source($page = NULL, $lock = TRUE, $join = FALSE) | |
18 | +/** | |
19 | + * Get source(wiki text) data of the page | |
20 | + * | |
21 | + * @param $page page name | |
22 | + * @param $lock lock | |
23 | + * @param $join true: return string, false: return array of string | |
24 | + * @param $raw true: return file content as-is | |
25 | + * @return FALSE if error occurerd | |
26 | + */ | |
27 | +function get_source($page = NULL, $lock = TRUE, $join = FALSE, $raw = FALSE) | |
21 | 28 | { |
22 | 29 | //$result = NULL; // File is not found |
23 | 30 | $result = $join ? '' : array(); |
@@ -44,6 +51,9 @@ function get_source($page = NULL, $lock = TRUE, $join = FALSE) | ||
44 | 51 | } else { |
45 | 52 | $result = fread($fp, $size); |
46 | 53 | if ($result !== FALSE) { |
54 | + if ($raw) { | |
55 | + return $result; | |
56 | + } | |
47 | 57 | // Removing Carriage-Return |
48 | 58 | $result = str_replace("\r", '', $result); |
49 | 59 | } |
@@ -204,16 +214,54 @@ function remove_author_info($wikitext) | ||
204 | 214 | return preg_replace('/^\s*#author\([^\n]*(\n|$)/m', '', $wikitext); |
205 | 215 | } |
206 | 216 | |
207 | -function remove_author_lines($lines) | |
217 | +/** | |
218 | + * Remove author line from wikitext | |
219 | + */ | |
220 | +function remove_author_header($wikitext) | |
208 | 221 | { |
209 | - $author_head = '#author('; | |
210 | - $len = strlen($author_head); | |
211 | - for ($i = 0; $i < 5; $i++) { | |
212 | - if (substr($lines[$i], 0, $len) === $author_head) { | |
213 | - unset($lines[$i]); | |
222 | + $start = 0; | |
223 | + while (($pos = strpos($wikitext, "\n", $start)) != false) { | |
224 | + $line = substr($wikitext, $start, $pos); | |
225 | + $m = null; | |
226 | + if (preg_match('/^#author\(/', $line, $m)) { | |
227 | + // fond #author line, Remove this line only | |
228 | + if ($start === 0) { | |
229 | + return substr($wikitext, $pos + 1); | |
230 | + } else { | |
231 | + return substr($wikitext, 0, $start - 1) . | |
232 | + substr($wikitext, $pos + 1); | |
233 | + } | |
234 | + } else if (preg_match('/^#freeze(\W|$)/', $line, $m)) { | |
235 | + // Found #freeze still in header | |
236 | + } else { | |
237 | + // other line, #author not found | |
238 | + return $wikitext; | |
239 | + } | |
240 | + $start = $pos + 1; | |
241 | + } | |
242 | + return $wikitext; | |
243 | +} | |
244 | + | |
245 | +/** | |
246 | + * Get author info from wikitext | |
247 | + */ | |
248 | +function get_author_info($wikitext) | |
249 | +{ | |
250 | + $start = 0; | |
251 | + while (($pos = strpos($wikitext, "\n", $start)) != false) { | |
252 | + $line = substr($wikitext, $start, $pos); | |
253 | + $m = null; | |
254 | + if (preg_match('/^#author\(/', $line, $m)) { | |
255 | + return $line; | |
256 | + } else if (preg_match('/^#freeze(\W|$)/', $line, $m)) { | |
257 | + // Found #freeze still in header | |
258 | + } else { | |
259 | + // other line, #author not found | |
260 | + return false; | |
214 | 261 | } |
262 | + $start = $pos + 1; | |
215 | 263 | } |
216 | - return $lines; | |
264 | + return false; | |
217 | 265 | } |
218 | 266 | |
219 | 267 | function get_date_atom($timestamp) |
@@ -346,8 +346,8 @@ function do_search($word, $type = 'AND', $non_format = FALSE, $base = '') | ||
346 | 346 | |
347 | 347 | // Search for page contents |
348 | 348 | foreach ($keys as $key) { |
349 | - $lines = remove_author_lines(get_source($page, TRUE, FALSE)); | |
350 | - $b_match = preg_match($key, join('', $lines)); | |
349 | + $body = get_source($page, TRUE, TRUE, TRUE); | |
350 | + $b_match = preg_match($key, remove_author_header($body)); | |
351 | 351 | if ($b_type xor $b_match) break; // OR |
352 | 352 | } |
353 | 353 | if ($b_match) continue; |
@@ -206,10 +206,28 @@ function get_html_scripting_data() | ||
206 | 206 | if (!isset($ticket_link_sites) || !is_array($ticket_link_sites)) { |
207 | 207 | return ''; |
208 | 208 | } |
209 | + $is_utf8 = (bool)defined('PKWK_UTF8_ENABLE'); | |
209 | 210 | // Require: PHP 5.4+ |
210 | - if (!defined('JSON_UNESCAPED_UNICODE')) { | |
211 | - return ''; | |
212 | - }; | |
211 | + $json_enabled = defined('JSON_UNESCAPED_UNICODE'); | |
212 | + if (!$json_enabled) { | |
213 | + $empty_data = <<<EOS | |
214 | +<div id="pukiwiki-site-properties" style="display:none;"> | |
215 | +</div> | |
216 | +EOS; | |
217 | + return $empty_data; | |
218 | + } | |
219 | + // Site basic Properties | |
220 | + $props = array( | |
221 | + 'is_utf8' => $is_utf8, | |
222 | + 'json_enabled' => $json_enabled, | |
223 | + 'base_uri_pathname' => get_base_uri(PKWK_URI_ROOT), | |
224 | + 'base_uri_absolute' => get_base_uri(PKWK_URI_ABSOLUTE) | |
225 | + ); | |
226 | + $props_json = htmlsc(json_encode($props, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); | |
227 | + $site_props = <<<EOS | |
228 | +<div data-key="site-props" data-value="$props_json"></div> | |
229 | +EOS; | |
230 | + // AutoTicketLink | |
213 | 231 | $text = ''; |
214 | 232 | foreach ($ticket_link_sites as $s) { |
215 | 233 | if (!preg_match('/^([a-zA-Z0-9]+)([\.\-][a-zA-Z0-9]+)*$/', $s['key'])) { |
@@ -221,11 +239,15 @@ function get_html_scripting_data() | ||
221 | 239 | EOS; |
222 | 240 | $text .= "\n"; |
223 | 241 | } |
224 | - $data = <<<EOS | |
225 | -<div id="pukiwiki-site-properties" style="display:none;"> | |
242 | + $ticketlink_data = <<<EOS | |
226 | 243 | <div class="ticketlink-def"> |
227 | 244 | $text |
228 | 245 | </div> |
246 | +EOS; | |
247 | + $data = <<<EOS | |
248 | +<div id="pukiwiki-site-properties" style="display:none;"> | |
249 | +$site_props | |
250 | +$ticketlink_data | |
229 | 251 | </div> |
230 | 252 | EOS; |
231 | 253 | return $data; |
@@ -266,8 +266,8 @@ function links_do_search_page($word) | ||
266 | 266 | $b_match = FALSE; |
267 | 267 | // Search for page contents |
268 | 268 | foreach ($keys as $key) { |
269 | - $lines = remove_author_lines(get_source($page, TRUE, FALSE)); | |
270 | - $b_match = preg_match($key, join('', $lines)); | |
269 | + $body = get_source($page, TRUE, TRUE, TRUE); | |
270 | + $b_match = preg_match($key, remove_author_header($body)); | |
271 | 271 | if (! $b_match) break; // OR |
272 | 272 | } |
273 | 273 | if ($b_match) continue; |
@@ -0,0 +1,222 @@ | ||
1 | +<?php | |
2 | +// PukiWiki - Yet another WikiWikiWeb clone. | |
3 | +// search2.inc.php | |
4 | +// Copyright 2017 PukiWiki Development Team | |
5 | +// License: GPL v2 or (at your option) any later version | |
6 | +// | |
7 | +// Search2 plugin - Show detail result using JavaScript | |
8 | + | |
9 | +define('PLUGIN_SEARCH2_MAX_LENGTH', 80); | |
10 | +define('PLUGIN_SEARCH2_MAX_BASE', 16); // #search(1,2,3,...,15,16) | |
11 | + | |
12 | +// Show a search box on a page | |
13 | +function plugin_search2_convert() | |
14 | +{ | |
15 | + $args = func_get_args(); | |
16 | + return plugin_search_search_form('', '', $args); | |
17 | +} | |
18 | + | |
19 | +function plugin_search2_action() | |
20 | +{ | |
21 | + global $vars, $_title_search, $_title_result; | |
22 | + | |
23 | + $action = isset($vars['action']) ? $vars['action'] : ''; | |
24 | + $base = isset($vars['base']) ? $vars['base'] : ''; | |
25 | + $bases = array(); | |
26 | + if ($base !== '') { | |
27 | + $bases[] = $base; | |
28 | + } | |
29 | + if ($action === '') { | |
30 | + $q = isset($vars['q']) ? $vars['q'] : ''; | |
31 | + if ($q === '') { | |
32 | + return array('msg' => $_title_search, | |
33 | + 'body' => plugin_search2_search_form($q, '', $bases)); | |
34 | + } else { | |
35 | + $msg = str_replace('$1', htmlsc($q), $_title_result); | |
36 | + return array('msg' => $msg, | |
37 | + 'body' => plugin_search2_search_form($q, '', $bases)); | |
38 | + } | |
39 | + } else if ($action === 'query') { | |
40 | + $text = isset($vars['q']) ? $vars['q'] : ''; | |
41 | + header('Content-Type: application/json; charset=UTF-8'); | |
42 | + plugin_search2_do_search($text, $base); | |
43 | + exit; | |
44 | + } | |
45 | +} | |
46 | + | |
47 | +function plugin_search2_do_search($query_text, $base) | |
48 | +{ | |
49 | + global $whatsnew, $non_list, $search_non_list; | |
50 | + global $_msg_andresult, $_msg_orresult, $_msg_notfoundresult; | |
51 | + global $search_auth; | |
52 | + | |
53 | + $retval = array(); | |
54 | + | |
55 | + $b_type_and = true; // AND:TRUE OR:FALSE | |
56 | + $key_candidates = preg_split('/\s+/', $query_text, -1, PREG_SPLIT_NO_EMPTY); | |
57 | + for ($i = count($key_candidates) - 1; $i >= 0; $i--) { | |
58 | + if ($key_candidates[$i] === 'OR') { | |
59 | + $b_type_and = false; | |
60 | + unset($key_candidates[$i]); | |
61 | + } | |
62 | + } | |
63 | + $key_candidates = array_merge($key_candidates); | |
64 | + $keys = get_search_words($key_candidates); | |
65 | + foreach ($keys as $key=>$value) | |
66 | + $keys[$key] = '/' . $value . '/S'; | |
67 | + | |
68 | + $pages = get_existpages(); | |
69 | + | |
70 | + // Avoid | |
71 | + if ($base != '') { | |
72 | + $pages = preg_grep('/^' . preg_quote($base, '/') . '/S', $pages); | |
73 | + } | |
74 | + if (! $search_non_list) { | |
75 | + $pages = array_diff($pages, preg_grep('/' . $non_list . '/S', $pages)); | |
76 | + } | |
77 | + natsort($pages); | |
78 | + $pages = array_flip($pages); | |
79 | + unset($pages[$whatsnew]); | |
80 | + $page_names = array_keys($pages); | |
81 | + | |
82 | + $found_pages = array(); | |
83 | + $readable_page_index = -1; | |
84 | + $scan_page_index = -1; | |
85 | + $last_read_page_name = null; | |
86 | + foreach ($page_names as $page) { | |
87 | + $b_match = FALSE; | |
88 | + $pagename_only = false; | |
89 | + $scan_page_index++; | |
90 | + if (! is_page_readable($page)) { | |
91 | + if ($search_auth) { | |
92 | + // $search_auth - 1: User can know page names that contain search text if the page is readable | |
93 | + continue; | |
94 | + } | |
95 | + // $search_auth - 0: All users can know page names that conntain search text | |
96 | + $pagename_only = true; | |
97 | + } | |
98 | + $readable_page_index++; | |
99 | + // Search for page name and contents | |
100 | + $body = get_source($page, TRUE, TRUE, TRUE); | |
101 | + $target = $page . "\n" . remove_author_header($body); | |
102 | + foreach ($keys as $key) { | |
103 | + $b_match = preg_match($key, $target); | |
104 | + if ($b_type_and xor $b_match) break; // OR | |
105 | + } | |
106 | + if ($b_match) { | |
107 | + // Found! | |
108 | + $filemtime = null; | |
109 | + $author_info = get_author_info($body); | |
110 | + if ($author_info === false || $pagename_only) { | |
111 | + $updated_at = get_date_atom(filemtime(get_filename($page))); | |
112 | + } | |
113 | + if ($pagename_only) { | |
114 | + // The user cannot read this page body | |
115 | + $found_pages[] = array('name' => (string)$page, | |
116 | + 'url' => get_page_uri($page), 'updated_at' => $updated_at, | |
117 | + 'body' => '', 'pagename_only' => 1); | |
118 | + } else { | |
119 | + $found_pages[] = array('name' => (string)$page, | |
120 | + 'url' => get_page_uri($page), 'updated_at' => $updated_at, | |
121 | + 'body' => (string)$body); | |
122 | + } | |
123 | + } | |
124 | + $last_read_page_name = $page; | |
125 | + } | |
126 | + $message = str_replace('$1', htmlsc($query_text), str_replace('$2', count($found_pages), | |
127 | + str_replace('$3', count($page_names), $b_type_and ? $_msg_andresult : $_msg_orresult))); | |
128 | + $search_done = (boolean)($scan_page_index + 1 === count($page_names)); | |
129 | + $result_obj = array( | |
130 | + 'message' => $message, | |
131 | + 'q' => $query_text, | |
132 | + 'read_page_count' => $readable_page_index + 1, | |
133 | + 'scan_page_count' => $scan_page_index + 1, | |
134 | + 'page_count' => count($page_names), | |
135 | + 'last_read_page_name' => $last_read_page_name, | |
136 | + 'search_done' => $search_done, | |
137 | + 'results' => $found_pages); | |
138 | + $obj = $result_obj; | |
139 | + if (!defined('PKWK_UTF8_ENABLE')) { | |
140 | + if (SOURCE_ENCODING === 'EUC-JP') { | |
141 | + mb_convert_variables('UTF-8', 'CP51932', $obj); | |
142 | + } else { | |
143 | + mb_convert_variables('UTF-8', SOURCE_ENCODING, $obj); | |
144 | + } | |
145 | + } | |
146 | + print(json_encode($obj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); | |
147 | +} | |
148 | + | |
149 | +function plugin_search2_search_form($s_word = '', $type = '', $bases = array()) | |
150 | +{ | |
151 | + global $_btn_search; | |
152 | + global $_search_pages, $_search_all; | |
153 | + global $_msg_andresult, $_msg_orresult, $_msg_notfoundresult; | |
154 | + global $_search_detail, $_search_searching; | |
155 | + global $_msg_unsupported_webbrowser, $_msg_use_alternative_link; | |
156 | + | |
157 | + $script = get_base_uri(); | |
158 | + $h_search_text = htmlsc($s_word); | |
159 | + | |
160 | + $base_option = ''; | |
161 | + if (!empty($bases)) { | |
162 | + $base_msg = ''; | |
163 | + $_num = 0; | |
164 | + $check = ' checked'; | |
165 | + foreach($bases as $base) { | |
166 | + ++$_num; | |
167 | + if (PLUGIN_SEARCH2_MAX_BASE < $_num) break; | |
168 | + $s_base = htmlsc($base); | |
169 | + $base_str = '<strong>' . $s_base . '</strong>'; | |
170 | + $base_label = str_replace('$1', $base_str, $_search_pages); | |
171 | + $base_msg .=<<<EOD | |
172 | + <div> | |
173 | + <label> | |
174 | + <input type="radio" name="base" value="$s_base" $check> $base_label | |
175 | + </label> | |
176 | + </div> | |
177 | +EOD; | |
178 | + $check = ''; | |
179 | + } | |
180 | + $base_msg .=<<<EOD | |
181 | +<label><input type="radio" name="base" value=""> $_search_all</label> | |
182 | +EOD; | |
183 | + $base_option = '<div class="small">' . $base_msg . '</div>'; | |
184 | + } | |
185 | + $_search2_result_notfound = htmlsc($_msg_notfoundresult); | |
186 | + $_search2_result_found = htmlsc($_msg_andresult); | |
187 | + $_search2_search_wait_milliseconds = PLUGIN_SEARCH2_SEARCH_WAIT_MILLISECONDS; | |
188 | + $result_page_panel =<<<EOD | |
189 | +<div id="_plugin_search2_search_status"></div> | |
190 | +<div id="_plugin_search2_message"></div> | |
191 | +<input type="checkbox" id="_plugin_search2_detail" checked><label for="_plugin_search2_detail">$_search_detail</label> | |
192 | +<input type="hidden" id="_plugin_search2_msg_searching" value="$_search_searching"> | |
193 | +<input type="hidden" id="_plugin_search2_msg_result_notfound" value="$_search2_result_notfound"> | |
194 | +<input type="hidden" id="_plugin_search2_msg_result_found" value="$_search2_result_found"> | |
195 | +<input type="hidden" id="_search2_search_wait_milliseconds" value="$_search2_search_wait_milliseconds"> | |
196 | +EOD; | |
197 | + if ($h_search_text == '') { | |
198 | + $result_page_panel = ''; | |
199 | + } | |
200 | + | |
201 | + $plain_search_link = '<a href="' . $script . '?cmd=search' . '">' . htmlsc($_btn_search) . '</a>'; | |
202 | + $alt_msg = str_replace('$1', $plain_search_link, $_msg_use_alternative_link); | |
203 | + return <<<EOD | |
204 | +<noscript> | |
205 | + <p>$_msg_unsupported_webbrowser $alt_msg</p> | |
206 | +</noscript> | |
207 | +<p class="_plugin_search2_nosupport_message" style="display:none;"> | |
208 | + $_msg_unsupported_webbrowser $alt_msg | |
209 | +</p> | |
210 | +<form action="$script" method="GET" class="_plugin_search2_form"> | |
211 | + <div> | |
212 | + <input type="hidden" name="cmd" value="search2"> | |
213 | + <input type="search" name="q" value="$h_search_text" size="30"> | |
214 | + <input type="submit" value="$_btn_search"> | |
215 | + </div> | |
216 | +$base_option | |
217 | +</form> | |
218 | +$result_page_panel | |
219 | +<ul id="result-list"> | |
220 | +</ul> | |
221 | +EOD; | |
222 | +} |
@@ -160,7 +160,7 @@ window.addEventListener && window.addEventListener('DOMContentLoaded', function( | ||
160 | 160 | } |
161 | 161 | var siteNodes = defRoot.querySelectorAll('.ticketlink-site'); |
162 | 162 | Array.prototype.forEach.call(siteNodes, function (e) { |
163 | - var siteInfoText = e.dataset && e.dataset.site; | |
163 | + var siteInfoText = e.getAttribute('data-site'); | |
164 | 164 | if (!siteInfoText) return; |
165 | 165 | var info = textToSiteInfo(siteInfoText); |
166 | 166 | if (info) { |
@@ -641,6 +641,21 @@ tr.bugtrack_state_undef td { | ||
641 | 641 | background-color: #ff3333; |
642 | 642 | } |
643 | 643 | |
644 | +/* search2.inc.php */ | |
645 | +input#_plugin_search2_detail:checked ~ ul > div.search-result-detail { | |
646 | + display: block; | |
647 | +} | |
648 | +input#_plugin_search2_detail ~ ul > div.search-result-detail { | |
649 | + display: none; | |
650 | +} | |
651 | +.search-result-page-summary { | |
652 | + font-size: 70%; | |
653 | + color: gray; | |
654 | + overflow: hidden; | |
655 | + text-overflow: ellipsis; | |
656 | + white-space: nowrap; | |
657 | +} | |
658 | + | |
644 | 659 | @media print { |
645 | 660 | a:link, |
646 | 661 | a:visited { |
@@ -69,6 +69,7 @@ header('Content-Type: text/html; charset=' . CONTENT_CHARSET); | ||
69 | 69 | <link rel="stylesheet" type="text/css" href="<?php echo SKIN_DIR ?>pukiwiki.css" /> |
70 | 70 | <link rel="alternate" type="application/rss+xml" title="RSS" href="<?php echo $link['rss'] ?>" /><?php // RSS auto-discovery ?> |
71 | 71 | <script type="text/javascript" src="skin/main.js" defer></script> |
72 | + <script type="text/javascript" src="skin/search2.js" defer></script> | |
72 | 73 | |
73 | 74 | <?php echo $head_tag ?> |
74 | 75 | </head> |
@@ -0,0 +1,585 @@ | ||
1 | +// PukiWiki - Yet another WikiWikiWeb clone. | |
2 | +// search2.js | |
3 | +// Copyright | |
4 | +// 2017 PukiWiki Development Team | |
5 | +// License: GPL v2 or (at your option) any later version | |
6 | +// | |
7 | +// PukiWiki search2 pluign - JavaScript client script | |
8 | +window.addEventListener && window.addEventListener('DOMContentLoaded', function() { | |
9 | + function enableSearch2() { | |
10 | + var aroundLines = 2; | |
11 | + var maxResultLines = 20; | |
12 | + var minBlockLines = 5; | |
13 | + var minSearchWaitMilliseconds = 100; | |
14 | + var kanaMap = null; | |
15 | + function escapeHTML (s) { | |
16 | + if(typeof s !== 'string') { | |
17 | + s = '' + s; | |
18 | + } | |
19 | + return s.replace(/[&"<>]/g, function(m) { | |
20 | + return { | |
21 | + '&': '&', | |
22 | + '"': '"', | |
23 | + '<': '<', | |
24 | + '>': '>' | |
25 | + }[m]; | |
26 | + }); | |
27 | + } | |
28 | + function doSearch(searchText, session, startIndex) { | |
29 | + var url = './?cmd=search2&action=query'; | |
30 | + var props = getSiteProps(); | |
31 | + url += '&encode_hint=' + encodeURIComponent('\u3077'); | |
32 | + if (searchText) { | |
33 | + url += '&q=' + encodeURIComponent(searchText); | |
34 | + } | |
35 | + if (session.base) { | |
36 | + url += '&base=' + encodeURIComponent(session.base); | |
37 | + } | |
38 | + url += '&start=' + startIndex; | |
39 | + fetch (url | |
40 | + ).then(function(response){ | |
41 | + if (response.ok) { | |
42 | + return response.json(); | |
43 | + } else { | |
44 | + throw new Error(response.status + ': ' + | |
45 | + + response.statusText + ' on ' + url); | |
46 | + } | |
47 | + }).then(function(obj) { | |
48 | + showResult(obj, session, searchText); | |
49 | + })['catch'](function(err){ | |
50 | + console.log(err); | |
51 | + console.log('Error! Please check JavaScript console' + '\n' + JSON.stringify(err) + '|' + err); | |
52 | + }); | |
53 | + } | |
54 | + function getMessageTemplate(idText, defaultText) { | |
55 | + var messageHolder = document.querySelector('#' + idText); | |
56 | + var messageTemplate = (messageHolder && messageHolder.value) || defaultText; | |
57 | + return messageTemplate; | |
58 | + } | |
59 | + function getAuthorInfo(text) { | |
60 | + | |
61 | + } | |
62 | + function getPassage(now, dateText) { | |
63 | + if (! dateText) { | |
64 | + return ''; | |
65 | + } | |
66 | + var units = [{u: 'm', max: 60}, {u: 'h', max: 24}, {u: 'd', max: 1}]; | |
67 | + var d = new Date(); | |
68 | + d.setTime(Date.parse(dateText)); | |
69 | + var t = (now.getTime() - d.getTime()) / (1000 * 60); // minutes | |
70 | + var unit = units[0].u, card = units[0].max; | |
71 | + for (var i = 0; i < units.length; i++) { | |
72 | + unit = units[i].u, card = units[i].max; | |
73 | + if (t < card) break; | |
74 | + t = t / card; | |
75 | + } | |
76 | + return '(' + Math.floor(t) + unit + ')'; | |
77 | + } | |
78 | + function removeSearchOperators(searchText) { | |
79 | + var sp = searchText.split(/\s+/); | |
80 | + if (sp.length <= 1) { | |
81 | + return searchText; | |
82 | + } | |
83 | + var hasOr = false; | |
84 | + for (var i = sp.length - 1; i >= 0; i--) { | |
85 | + if (sp[i] === 'OR') { | |
86 | + hasOr = true; | |
87 | + sp.splice(i, 1); | |
88 | + } | |
89 | + } | |
90 | + return sp.join(' '); | |
91 | + } | |
92 | + function showResult(obj, session, searchText) { | |
93 | + var searchRegex = textToRegex(removeSearchOperators(searchText)); | |
94 | + var ul = document.querySelector('#result-list'); | |
95 | + if (!ul) return; | |
96 | + ul.innerHTML = ''; | |
97 | + if (! session.scan_page_count) session.scan_page_count = 0; | |
98 | + if (! session.read_page_count) session.read_page_count = 0; | |
99 | + if (! session.hit_page_count) session.hit_page_count = 0; | |
100 | + session.scan_page_count += obj.scan_page_count; | |
101 | + session.read_page_count += obj.read_page_count; | |
102 | + session.hit_page_count += obj.results.length; | |
103 | + session.page_count = obj.page_count; | |
104 | + | |
105 | + var msg = obj.message; | |
106 | + var notFoundMessageTemplate = getMessageTemplate('_plugin_search2_msg_result_notfound', | |
107 | + 'No page which contains $1 has been found.'); | |
108 | + var foundMessageTemplate = getMessageTemplate('_plugin_search2_msg_result_found', | |
109 | + 'In the page <strong>$2</strong>, <strong>$3</strong> pages that contain all the terms $1 were found.'); | |
110 | + var searchTextDecorated = findAndDecorateText(searchText, searchRegex); | |
111 | + if (searchTextDecorated === null) searchTextDecorated = escapeHTML(searchText); | |
112 | + var messageTemplate = foundMessageTemplate; | |
113 | + if (session.hit_page_count === 0) { | |
114 | + messageTemplate = notFoundMessageTemplate; | |
115 | + } | |
116 | + msg = messageTemplate.replace(/\$1|\$2|\$3/g, function(m){ | |
117 | + return { | |
118 | + '$1': searchTextDecorated, | |
119 | + '$2': session.hit_page_count, | |
120 | + '$3': session.read_page_count | |
121 | + }[m]; | |
122 | + }); | |
123 | + document.querySelector('#_plugin_search2_message').innerHTML = msg; | |
124 | + | |
125 | + setSearchStatus(''); | |
126 | + var results = obj.results; | |
127 | + var now = new Date(); | |
128 | + results.forEach(function(val, index) { | |
129 | + var fragment = document.createDocumentFragment(); | |
130 | + var li = document.createElement('li'); | |
131 | + var hash = '#q=' + encodeSearchTextForHash(searchText); | |
132 | + var href = val.url + hash; | |
133 | + var decoratedName = findAndDecorateText(val.name, searchRegex); | |
134 | + if (! decoratedName) { | |
135 | + decoratedName = escapeHTML(val.name); | |
136 | + } | |
137 | + var author = getAuthorHeader(val.body); | |
138 | + var updatedAt = ''; | |
139 | + if (author) { | |
140 | + updatedAt = getUpdateTimeFromAuthorInfo(author); | |
141 | + } else { | |
142 | + updatedAt = val.updated_at; | |
143 | + } | |
144 | + var liHtml = '<a href="' + escapeHTML(href) + '">' + decoratedName + '</a> ' + | |
145 | + getPassage(now, updatedAt); | |
146 | + li.innerHTML = liHtml; | |
147 | + var a = li.querySelector('a'); | |
148 | + if (a && a.hash) { | |
149 | + if (a.hash !== hash) { | |
150 | + // Some browser execute encodeHTML(hash) automatically. Support them. | |
151 | + a.href = val.url + '#encq=' + encodeSearchTextForHash(searchText); | |
152 | + } | |
153 | + } | |
154 | + fragment.appendChild(li); | |
155 | + var div = document.createElement('div'); | |
156 | + div.classList.add('search-result-detail'); | |
157 | + var head = document.createElement('div'); | |
158 | + head.classList.add('search-result-page-summary'); | |
159 | + head.innerHTML = escapeHTML(getBodySummary(val.body)); | |
160 | + div.appendChild(head); | |
161 | + var summary = getSummary(val.body, searchRegex); | |
162 | + for (var i = 0; i < summary.length; i++) { | |
163 | + var pre = document.createElement('pre'); | |
164 | + pre.innerHTML = summary[i].lines.join('\n'); | |
165 | + div.appendChild(pre); | |
166 | + } | |
167 | + fragment.appendChild(div); | |
168 | + ul.appendChild(fragment); | |
169 | + }); | |
170 | + } | |
171 | + function prepareKanaMap() { | |
172 | + if (kanaMap !== null) return; | |
173 | + if (!String.prototype.normalize) { | |
174 | + kanaMap = {}; | |
175 | + return; | |
176 | + } | |
177 | + var dakuten = '\uFF9E'; | |
178 | + var maru = '\uFF9F'; | |
179 | + var map = {}; | |
180 | + for (var c = 0xFF61; c <=0xFF9F; c++) { | |
181 | + var han = String.fromCharCode(c); | |
182 | + var zen = han.normalize('NFKC'); | |
183 | + map[zen] = han; | |
184 | + var hanDaku = han + dakuten; | |
185 | + var zenDaku = hanDaku.normalize('NFKC'); | |
186 | + if (zenDaku.length === 1) { // +Handaku-ten OK | |
187 | + map[zenDaku] = hanDaku; | |
188 | + } | |
189 | + var hanMaru = han + maru; | |
190 | + var zenMaru = hanMaru.normalize('NFKC'); | |
191 | + if (zenMaru.length === 1) { // +Maru OK | |
192 | + map[zenMaru] = hanMaru; | |
193 | + } | |
194 | + } | |
195 | + kanaMap = map; | |
196 | + } | |
197 | + function textToRegex(searchText) { | |
198 | + if (!searchText) return null; | |
199 | + var regEscape = /[\\^$.*+?()[\]{}|]/g; | |
200 | + // 1:Symbol 2:Katakana 3:Hiragana | |
201 | + var regRep = /([\\^$.*+?()[\]{}|])|([\u30a1-\u30f6])|([\u3041-\u3096])/g; | |
202 | + var s1 = searchText.replace(/^\s+|\s+$/g, ''); | |
203 | + var sp = s1.split(/\s+/); | |
204 | + var rText = ''; | |
205 | + prepareKanaMap(); | |
206 | + for (var i = 0; i < sp.length; i++) { | |
207 | + if (rText !== '') { | |
208 | + rText += '|' | |
209 | + } | |
210 | + var s = sp[i]; | |
211 | + if (s.normalize) { | |
212 | + s = s.normalize('NFKC'); | |
213 | + } | |
214 | + var s2 = s.replace(regRep, function(m, m1, m2, m3){ | |
215 | + if (m1) { | |
216 | + // Symbol - escape with prior backslach | |
217 | + return '\\' + m1; | |
218 | + } else if (m2) { | |
219 | + // Katakana | |
220 | + var r = '(?:' + String.fromCharCode(m2.charCodeAt(0) - 0x60) + | |
221 | + '|' + m2; | |
222 | + if (kanaMap[m2]) { | |
223 | + r += '|' + kanaMap[m2]; | |
224 | + } | |
225 | + r += ')'; | |
226 | + return r; | |
227 | + } else if (m3) { | |
228 | + // Hiragana | |
229 | + var katakana = String.fromCharCode(m3.charCodeAt(0) + 0x60); | |
230 | + var r = '(?:' + m3 + '|' + katakana; | |
231 | + if (kanaMap[katakana]) { | |
232 | + r += '|' + kanaMap[katakana]; | |
233 | + } | |
234 | + r += ')'; | |
235 | + return r; | |
236 | + } | |
237 | + return m; | |
238 | + }); | |
239 | + rText += '(' + s2 + ')'; | |
240 | + } | |
241 | + return new RegExp(rText, 'ig'); | |
242 | + } | |
243 | + function getAuthorHeader(body) { | |
244 | + var start = 0; | |
245 | + var pos; | |
246 | + while ((pos = body.indexOf('\n', start)) >= 0) { | |
247 | + var line = body.substring(start, pos); | |
248 | + if (line.match(/^#author\(/, line)) { | |
249 | + return line; | |
250 | + } else if (line.match(/^#freeze(\W|$)/, line)) { | |
251 | + // Found #freeze still in header | |
252 | + } else { | |
253 | + // other line, #author not found | |
254 | + return null; | |
255 | + } | |
256 | + start = pos + 1; | |
257 | + } | |
258 | + return null; | |
259 | + } | |
260 | + function getUpdateTimeFromAuthorInfo(authorInfo) { | |
261 | + var m = authorInfo.match(/^#author\("([^;"]+)(;[^;"]+)?/); | |
262 | + if (m) { | |
263 | + return m[1]; | |
264 | + } | |
265 | + return ''; | |
266 | + } | |
267 | + function getTargetLines(body, searchRegex) { | |
268 | + var lines = body.split('\n'); | |
269 | + var found = []; | |
270 | + var foundLines = []; | |
271 | + var isInAuthorHeader = true; | |
272 | + var lastFoundLineIndex = -1 - aroundLines; | |
273 | + var lastAddedLineIndex = lastFoundLineIndex; | |
274 | + var blocks = []; | |
275 | + var lineCount = 0; | |
276 | + for (var index = 0, length = lines.length; index < length; index++) { | |
277 | + var line = lines[index]; | |
278 | + if (isInAuthorHeader) { | |
279 | + // '#author line is not search target' | |
280 | + if (line.match(/^#author\(/)) { | |
281 | + // Remove this line from search target | |
282 | + continue; | |
283 | + } else if (line.match(/^#freeze(\W|$)/)) { | |
284 | + // Still in header | |
285 | + } else { | |
286 | + // Already in body | |
287 | + isInAuthorHeader = false; | |
288 | + } | |
289 | + } | |
290 | + var decorated = findAndDecorateText(line, searchRegex); | |
291 | + if (decorated === null) { | |
292 | + if (index < lastFoundLineIndex + aroundLines + 1) { | |
293 | + foundLines.push('' + (index + 1) + ':\t' + escapeHTML(lines[index])); | |
294 | + lineCount++; | |
295 | + lastAddedLineIndex = index; | |
296 | + } | |
297 | + } else { | |
298 | + var startIndex = Math.max(Math.max(lastAddedLineIndex + 1, index - aroundLines), 0); | |
299 | + if (lastAddedLineIndex + 1 < startIndex) { | |
300 | + // Newly found! | |
301 | + var block = { | |
302 | + startIndex: startIndex, | |
303 | + foundLineIndex: index, | |
304 | + lines: [] | |
305 | + }; | |
306 | + foundLines = block.lines; | |
307 | + blocks.push(block); | |
308 | + } | |
309 | + if (lineCount >= maxResultLines) { | |
310 | + foundLines.push('...'); | |
311 | + return blocks; | |
312 | + } | |
313 | + for (var i = startIndex; i < index; i++) { | |
314 | + foundLines.push('' + (i + 1) + ':\t' + escapeHTML(lines[i])); | |
315 | + lineCount++; | |
316 | + } | |
317 | + foundLines.push('' + (index + 1) + ':\t' + decorated); | |
318 | + lineCount++; | |
319 | + lastFoundLineIndex = lastAddedLineIndex = index; | |
320 | + } | |
321 | + } | |
322 | + return blocks; | |
323 | + } | |
324 | + function getSummary(bodyText, searchRegex) { | |
325 | + return getTargetLines(bodyText, searchRegex); | |
326 | + } | |
327 | + function hookSearch2(e) { | |
328 | + var form = document.querySelector('form'); | |
329 | + if (form && form.q) { | |
330 | + var q = form.q; | |
331 | + if (q.value === '') { | |
332 | + q.focus(); | |
333 | + } | |
334 | + } | |
335 | + } | |
336 | + function getBodySummary(body) { | |
337 | + var lines = body.split('\n'); | |
338 | + var isInAuthorHeader = true; | |
339 | + var summary = []; | |
340 | + var lineCount = 0; | |
341 | + for (var index = 0, length = lines.length; index < length; index++) { | |
342 | + var line = lines[index]; | |
343 | + if (isInAuthorHeader) { | |
344 | + // '#author line is not search target' | |
345 | + if (line.match(/^#author\(/)) { | |
346 | + // Remove this line from search target | |
347 | + continue; | |
348 | + } else if (line.match(/^#freeze(\W|$)/)) { | |
349 | + continue; | |
350 | + // Still in header | |
351 | + } else { | |
352 | + // Already in body | |
353 | + isInAuthorHeader = false; | |
354 | + } | |
355 | + } | |
356 | + line = line.replace(/^\s+|\s+$/g, ''); | |
357 | + if (line.length === 0) continue; // Empty line | |
358 | + if (line.match(/^#\w+/)) continue; // Block-type plugin | |
359 | + if (line.match(/^\/\//)) continue; // Comment | |
360 | + if (line.substr(0, 1) === '*') { | |
361 | + line = line.replace(/\s*\[\#\w+\]$/, ''); // Remove anchor | |
362 | + } | |
363 | + summary.push(line); | |
364 | + if (summary.length >= 10) { | |
365 | + continue; | |
366 | + } | |
367 | + } | |
368 | + return summary.join(' ').substring(0, 150); | |
369 | + } | |
370 | + function removeEncodeHint() { | |
371 | + // Remove 'encode_hint' if site charset is UTF-8 | |
372 | + var props = getSiteProps(); | |
373 | + if (!props.is_utf8) return; | |
374 | + var forms = document.querySelectorAll('form'); | |
375 | + forEach(forms, function(form){ | |
376 | + if (form.cmd && form.cmd.value === 'search2') { | |
377 | + if (form.encode_hint && (typeof form.encode_hint.removeAttribute === 'function')) { | |
378 | + form.encode_hint.removeAttribute('name'); | |
379 | + } | |
380 | + } | |
381 | + }); | |
382 | + } | |
383 | + function kickFirstSearch() { | |
384 | + var form = document.querySelector('._plugin_search2_form'); | |
385 | + var searchText = form && form.q; | |
386 | + if (!searchText) return; | |
387 | + if (searchText && searchText.value) { | |
388 | + var e = document.querySelector('#_plugin_search2_msg_searching'); | |
389 | + var msg = e && e.value || 'Searching...'; | |
390 | + setSearchStatus(msg); | |
391 | + var base = ''; | |
392 | + forEach(form.querySelectorAll('input[name="base"]'), function(radio){ | |
393 | + if (radio.checked) base = radio.value; | |
394 | + }); | |
395 | + doSearch(searchText.value, {base: base}, 0); | |
396 | + } | |
397 | + } | |
398 | + function setSearchStatus(statusText) { | |
399 | + var statusObj = document.querySelector('#_plugin_search2_search_status'); | |
400 | + if (statusObj) { | |
401 | + statusObj.textContent = statusText; | |
402 | + } | |
403 | + } | |
404 | + function forEach(nodeList, func) { | |
405 | + if (nodeList.forEach) { | |
406 | + nodeList.forEach(func); | |
407 | + } else { | |
408 | + for (var i = 0, n = nodeList.length; i < n; i++) { | |
409 | + func(nodeList[i], i); | |
410 | + } | |
411 | + } | |
412 | + } | |
413 | + function replaceSearchWithSearch2() { | |
414 | + forEach(document.querySelectorAll('form'), function(f){ | |
415 | + if (f.action.match(/cmd=search$/)) { | |
416 | + f.addEventListener('submit', function(e) { | |
417 | + var q = e.target.word.value; | |
418 | + var base = ''; | |
419 | + forEach(f.querySelectorAll('input[name="base"]'), function(radio){ | |
420 | + if (radio.checked) base = radio.value; | |
421 | + }); | |
422 | + var props = getSiteProps(); | |
423 | + var loc = document.location; | |
424 | + var baseUri = loc.protocol + '//' + loc.host + loc.pathname; | |
425 | + if (props.base_uri_pathname) { | |
426 | + baseUri = props.base_uri_pathname; | |
427 | + } | |
428 | + var url = baseUri + '?' + | |
429 | + (props.is_utf8 ? '' : 'encode_hint=' + | |
430 | + encodeURIComponent('\u3077') + '&') + | |
431 | + 'cmd=search2' + | |
432 | + '&q=' + encodeSearchText(q) + | |
433 | + (base ? '&base=' + encodeURIComponent(base) : ''); | |
434 | + e.preventDefault(); | |
435 | + setTimeout(function() { | |
436 | + location.href = url; | |
437 | + }, 1); | |
438 | + return false; | |
439 | + }); | |
440 | + var radios = f.querySelectorAll('input[type="radio"][name="type"]'); | |
441 | + forEach(radios, function(radio){ | |
442 | + if (radio.value === 'AND') { | |
443 | + radio.addEventListener('click', onAndRadioClick); | |
444 | + } else if (radio.value === 'OR') { | |
445 | + radio.addEventListener('click', onOrRadioClick); | |
446 | + } | |
447 | + }); | |
448 | + function onAndRadioClick(e) { | |
449 | + var sp = removeSearchOperators(f.word.value).split(/\s+/); | |
450 | + var newText = sp.join(' '); | |
451 | + if (f.word.value !== newText) { | |
452 | + f.word.value = newText; | |
453 | + } | |
454 | + } | |
455 | + function onOrRadioClick(e) { | |
456 | + var sp = removeSearchOperators(f.word.value).split(/\s+/); | |
457 | + var newText = sp.join(' OR '); | |
458 | + if (f.word.value !== newText) { | |
459 | + f.word.value = newText; | |
460 | + } | |
461 | + } | |
462 | + } | |
463 | + }); | |
464 | + } | |
465 | + function encodeSearchText(q) { | |
466 | + var sp = q.split(/\s+/); | |
467 | + for (var i = 0; i < sp.length; i++) { | |
468 | + sp[i] = encodeURIComponent(sp[i]); | |
469 | + } | |
470 | + return sp.join('+'); | |
471 | + } | |
472 | + function encodeSearchTextForHash(q) { | |
473 | + var sp = q.split(/\s+/); | |
474 | + return sp.join('+'); | |
475 | + } | |
476 | + function findAndDecorateText(text, searchRegex) { | |
477 | + var isReplaced = false; | |
478 | + var lastIndex = 0; | |
479 | + var m; | |
480 | + var decorated = ''; | |
481 | + searchRegex.lastIndex = 0; | |
482 | + while ((m = searchRegex.exec(text)) !== null) { | |
483 | + isReplaced = true; | |
484 | + var pre = text.substring(lastIndex, m.index); | |
485 | + decorated += escapeHTML(pre); | |
486 | + for (var i = 1; i < m.length; i++) { | |
487 | + if (m[i]) { | |
488 | + decorated += '<strong class="word' + (i - 1) + '">' + escapeHTML(m[i]) + '</strong>' | |
489 | + } | |
490 | + } | |
491 | + lastIndex = searchRegex.lastIndex; | |
492 | + } | |
493 | + if (isReplaced) { | |
494 | + decorated += escapeHTML(text.substr(lastIndex)); | |
495 | + return decorated; | |
496 | + } | |
497 | + return null; | |
498 | + } | |
499 | + function getSearchTextInLocationHash() { | |
500 | + // TODO Cross browser | |
501 | + var hash = location.hash; | |
502 | + if (!hash) return ''; | |
503 | + var q = ''; | |
504 | + if (hash.substr(0, 3) === '#q=') { | |
505 | + q = hash.substr(3).replace(/\+/g, ' '); | |
506 | + } else if (hash.substr(0, 6) === '#encq=') { | |
507 | + q = decodeURIComponent(hash.substr(6).replace(/\+/g, ' ')); | |
508 | + } | |
509 | + return q; | |
510 | + } | |
511 | + function colorSearchTextInBody() { | |
512 | + var searchText = getSearchTextInLocationHash(); | |
513 | + if (!searchText) return; | |
514 | + var searchRegex = textToRegex(removeSearchOperators(searchText)); | |
515 | + var headReText = '([\\s\\b]|^)'; | |
516 | + var tailReText = '\\b'; | |
517 | + var ignoreTags = ['INPUT', 'TEXTAREA', 'BUTTON', | |
518 | + 'SCRIPT', 'FRAME', 'IFRAME']; | |
519 | + function colorSearchText(element, searchRegex) { | |
520 | + var decorated = findAndDecorateText(element.nodeValue, searchRegex); | |
521 | + if (decorated) { | |
522 | + var span = document.createElement('span'); | |
523 | + span.innerHTML = decorated; | |
524 | + element.parentNode.replaceChild(span, element); | |
525 | + } | |
526 | + } | |
527 | + function walkElement(element) { | |
528 | + var e = element.firstChild; | |
529 | + while (e) { | |
530 | + if (e.nodeType == 3 && e.nodeValue && | |
531 | + e.nodeValue.length >= 2 && /\S/.test(e.nodeValue)) { | |
532 | + var next = e.nextSibling; | |
533 | + colorSearchText(e, searchRegex); | |
534 | + e = next; | |
535 | + } else { | |
536 | + if (e.nodeType == 1 && ignoreTags.indexOf(e.tagName) == -1) { | |
537 | + walkElement(e); | |
538 | + } | |
539 | + e = e.nextSibling; | |
540 | + } | |
541 | + } | |
542 | + } | |
543 | + var target = document.getElementById('body'); | |
544 | + walkElement(target); | |
545 | + } | |
546 | + function showNoSupportMessage() { | |
547 | + var pList = document.getElementsByClassName('_plugin_search2_nosupport_message'); | |
548 | + for (var i = 0; i < pList.length; i++) { | |
549 | + var p = pList[i]; | |
550 | + p.style.display = 'block'; | |
551 | + } | |
552 | + } | |
553 | + function isEnabledFetchFunctions() { | |
554 | + if (window.fetch && document.querySelector && window.JSON) { | |
555 | + return true; | |
556 | + } | |
557 | + return false; | |
558 | + } | |
559 | + function isEnableServerFunctions() { | |
560 | + var props = getSiteProps(); | |
561 | + if (props.json_enabled) return true; | |
562 | + return false; | |
563 | + } | |
564 | + function getSiteProps() { | |
565 | + var empty = {}; | |
566 | + var propsDiv = document.getElementById('pukiwiki-site-properties'); | |
567 | + if (!propsDiv) return empty; | |
568 | + var jsonE = propsDiv.querySelector('div[data-key="site-props"]'); | |
569 | + if (!jsonE) return emptry; | |
570 | + var props = JSON.parse(jsonE.getAttribute('data-value')); | |
571 | + return props || empty; | |
572 | + } | |
573 | + colorSearchTextInBody(); | |
574 | + if (! isEnabledFetchFunctions()) { | |
575 | + showNoSupportMessage(); | |
576 | + return; | |
577 | + } | |
578 | + if (! isEnableServerFunctions()) return; | |
579 | + replaceSearchWithSearch2(); | |
580 | + hookSearch2(); | |
581 | + removeEncodeHint(); | |
582 | + kickFirstSearch(); | |
583 | + } | |
584 | + enableSearch2(); | |
585 | +}); |
@@ -517,6 +517,21 @@ tr.bugtrack_state_undef td { | ||
517 | 517 | background-color: #ff3333; |
518 | 518 | } |
519 | 519 | |
520 | +/* search2.inc.php */ | |
521 | +input#_plugin_search2_detail:checked ~ ul > div.search-result-detail { | |
522 | + display: block; | |
523 | +} | |
524 | +input#_plugin_search2_detail ~ ul > div.search-result-detail { | |
525 | + display: none; | |
526 | +} | |
527 | +.search-result-page-summary { | |
528 | + font-size: 70%; | |
529 | + color: gray; | |
530 | + overflow: hidden; | |
531 | + text-overflow: ellipsis; | |
532 | + white-space: nowrap; | |
533 | +} | |
534 | + | |
520 | 535 | @media print { |
521 | 536 | img#logo, |
522 | 537 | div#navigator, |