//
// Search2 plugin - Show detail result using JavaScript
-define('PLUGIN_SEARCH2_MAX_LENGTH', 80);
-define('PLUGIN_SEARCH2_MAX_BASE', 16); // #search(1,2,3,...,15,16)
+define('PLUGIN_SEARCH2_MAX_BASE', 16); // #search(1,2,3,...,15,16)
define('PLUGIN_SEARCH2_RESULT_RECORD_LIMIT', 500);
define('PLUGIN_SEARCH2_RESULT_RECORD_LIMIT_START', 100);
define('PLUGIN_SEARCH2_SEARCH_WAIT_MILLISECONDS', 1000);
+define('PLUGIN_SEARCH2_SEARCH_MAX_RESULTS', 1000);
// Show a search box on a page
function plugin_search2_convert()
function plugin_search2_action()
{
- global $vars, $_title_search, $_title_result;
+ global $vars, $_title_search, $_title_result, $_msg_searching;
$action = isset($vars['action']) ? $vars['action'] : '';
$base = isset($vars['base']) ? $vars['base'] : '';
$bases[] = $base;
}
if ($action === '') {
- $q = isset($vars['q']) ? $vars['q'] : '';
+ $q = trim(isset($vars['q']) ? $vars['q'] : '');
+ $offset_s = isset($vars['offset']) ? $vars['offset'] : '';
+ $offset = pkwk_ctype_digit($offset_s) ? intval($offset_s) : 0;
+ $prev_offset_s = isset($vars['prev_offset']) ? $vars['prev_offset'] : '';
if ($q === '') {
return array('msg' => $_title_search,
- 'body' => plugin_search2_search_form($q, '', $bases));
+ 'body' => "<br>" . $_msg_searching . "\n" .
+ plugin_search2_search_form($q, $bases, $offset));
} else {
$msg = str_replace('$1', htmlsc($q), $_title_result);
return array('msg' => $msg,
- 'body' => plugin_search2_search_form($q, '', $bases));
+ 'body' => plugin_search2_search_form($q, $bases, $offset, $prev_offset_s));
}
} else if ($action === 'query') {
- $text = isset($vars['q']) ? $vars['q'] : '';
+ $q = isset($vars['q']) ? $vars['q'] : '';
+ $search_start_time = isset($vars['search_start_time']) ?
+ $vars['search_start_time'] : null;
+ $modified_since = (int)(isset($vars['modified_since']) ?
+ $vars['modified_since'] : '0');
header('Content-Type: application/json; charset=UTF-8');
- plugin_search2_do_search($text, $base, $start_index);
+ plugin_search2_do_search($q, $base, $start_index,
+ $search_start_time, $modified_since);
exit;
}
}
-function plugin_search2_do_search($query_text, $base, $start_index)
+function plugin_search2_get_base_url($search_text)
+{
+ global $vars;
+ $params = array();
+ if (!defined('PKWK_UTF8_ENABLE')) {
+ $params[] = 'encode_hint=' . rawurlencode($vars['encode_hint']);
+ }
+ $params[] = 'cmd=search2';
+ if (isset($vars['encode_hint']) && $vars['encode_hint']) {
+ $params[] = 'encode_hint=' . rawurlencode($vars['encode_hint']);
+ }
+ if ($search_text) {
+ $params[] = 'q=' . plugin_search2_urlencode_searchtext($search_text);
+ }
+ if (isset($vars['base']) && $vars['base']) {
+ $params[] = 'base=' . rawurlencode($vars['base']);
+ }
+ $url = get_base_uri() . '?' . join('&', $params);
+ return $url;
+}
+
+function plugin_search2_urlencode_searchtext($search_text)
+{
+ $s2 = preg_replace('#^\s+|\s+$#', '', $search_text);
+ if (!$s2) return '';
+ $sp = preg_split('#\s+#', $s2);
+ $list = array();
+ for ($i = 0; $i < count($sp); $i++) {
+ $list[] = rawurlencode($sp[$i]);
+ }
+ return join('+', $list);
+}
+
+function plugin_search2_do_search($query_text, $base, $start_index,
+ $search_start_time, $modified_since)
{
global $whatsnew, $non_list, $search_non_list;
global $_msg_andresult, $_msg_orresult, $_msg_notfoundresult;
- global $search_auth;
+ global $search_auth, $auth_user;
$result_record_limit = $start_index === 0 ?
PLUGIN_SEARCH2_RESULT_RECORD_LIMIT_START : PLUGIN_SEARCH2_RESULT_RECORD_LIMIT;
$b_type_and = true; // AND:TRUE OR:FALSE
$key_candidates = preg_split('/\s+/', $query_text, -1, PREG_SPLIT_NO_EMPTY);
- for ($i = count($key_candidates) - 1; $i >= 0; $i--) {
+ for ($i = count($key_candidates) - 2; $i >= 1; $i--) {
if ($key_candidates[$i] === 'OR') {
$b_type_and = false;
unset($key_candidates[$i]);
foreach ($keys as $key=>$value)
$keys[$key] = '/' . $value . '/S';
- $pages = get_existpages();
+ if ($modified_since > 0) {
+ // Recent search
+ $recent_files = get_recent_files();
+ $modified_loc = $modified_since - LOCALZONE;
+ $pages = array();
+ foreach ($recent_files as $p => $time) {
+ if ($time >= $modified_loc) {
+ $pages[] = $p;
+ }
+ }
+ if ($base != '') {
+ $pages = preg_grep('/^' . preg_quote($base, '/') . '/S', $pages);
+ }
+ $page_names = $pages;
+ } else {
+ // Normal search
+ $pages = get_existpages();
- // Avoid
- if ($base != '') {
- $pages = preg_grep('/^' . preg_quote($base, '/') . '/S', $pages);
+ // Avoid
+ if ($base != '') {
+ $pages = preg_grep('/^' . preg_quote($base, '/') . '/S', $pages);
+ }
+ if (! $search_non_list) {
+ $pages = array_diff($pages, preg_grep('/' . $non_list . '/S', $pages));
+ }
+ $pages = array_flip($pages);
+ unset($pages[$whatsnew]);
+ $page_names = array_keys($pages);
}
- if (! $search_non_list) {
- $pages = array_diff($pages, preg_grep('/' . $non_list . '/S', $pages));
+ natsort($page_names);
+ // Cache collabolate
+ if (is_null($search_start_time)) {
+ // Don't use client cache
+ $search_start_time = UTIME + LOCALZONE;
}
- natsort($pages);
- $pages = array_flip($pages);
- unset($pages[$whatsnew]);
- $page_names = array_keys($pages);
-
$found_pages = array();
$readable_page_index = -1;
$scan_page_index = -1;
if ($saved_scan_start_index === -1) {
$saved_scan_start_index = $scan_page_index;
}
- // Search for page name and contents
- $body = get_source($page, TRUE, TRUE, TRUE);
- $target = $page . "\n" . remove_author_header($body);
- foreach ($keys as $key) {
- $b_match = preg_match($key, $target);
- if ($b_type_and xor $b_match) break; // OR
+ if (count($keys) > 0) {
+ // Search for page name and contents
+ $body = get_source($page, TRUE, TRUE, TRUE);
+ $target = $page . "\n" . remove_author_header($body);
+ foreach ($keys as $key) {
+ $b_match = preg_match($key, $target);
+ if ($b_type_and xor $b_match) break; // OR
+ }
+ } else {
+ // No search target. get_source($page) is meaningless.
+ // $b_match is always false.
}
if ($b_match) {
// Found!
- $filemtime = null;
$author_info = get_author_info($body);
- if ($author_info === false || $pagename_only) {
- $updated_at = get_date_atom(filemtime(get_filename($page)));
+ if ($author_info) {
+ $updated_at = get_update_datetime_from_author($author_info);
+ $updated_time = strtotime($updated_at);
+ } else {
+ $updated_time = filemtime(get_filename($page));
+ $updated_at = get_date_atom($updated_time);
}
if ($pagename_only) {
// The user cannot read this page body
$found_pages[] = array('name' => (string)$page,
'url' => get_page_uri($page), 'updated_at' => $updated_at,
+ 'updated_time' => $updated_time,
'body' => '', 'pagename_only' => 1);
} else {
$found_pages[] = array('name' => (string)$page,
'url' => get_page_uri($page), 'updated_at' => $updated_at,
+ 'updated_time' => $updated_time,
'body' => (string)$body);
}
}
'last_read_page_name' => $last_read_page_name,
'next_start_index' => $readable_page_index + 1,
'search_done' => $search_done,
+ 'search_start_time' => $search_start_time,
+ 'auth_user' => $auth_user,
'results' => $found_pages);
$obj = $result_obj;
if (!defined('PKWK_UTF8_ENABLE')) {
print(json_encode($obj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
-function plugin_search2_search_form($s_word = '', $type = '', $bases = array())
+function plugin_search2_search_form($search_text = '', $bases = array(),
+ $offset, $prev_offset_s = null)
{
global $_btn_search;
global $_search_pages, $_search_all;
global $_msg_andresult, $_msg_orresult, $_msg_notfoundresult;
- global $_search_detail, $_search_searching;
+ global $_search_detail, $_search_searching, $_search_showing_result;
global $_msg_unsupported_webbrowser, $_msg_use_alternative_link;
+ global $_msg_more_results, $_msg_prev_results, $_msg_general_error;
+ global $auth_user;
+ static $search2_form_total_count = 0;
+ $search2_form_total_count++;
$script = get_base_uri();
- $h_search_text = htmlsc($s_word);
+ $h_search_text = htmlsc($search_text);
$base_option = '';
if (!empty($bases)) {
$_search2_search_wait_milliseconds = PLUGIN_SEARCH2_SEARCH_WAIT_MILLISECONDS;
$result_page_panel =<<<EOD
<input type="checkbox" id="_plugin_search2_detail" checked><label for="_plugin_search2_detail">$_search_detail</label>
-<input type="hidden" id="_plugin_search2_msg_searching" value="$_search_searching">
-<input type="hidden" id="_plugin_search2_msg_result_notfound" value="$_search2_result_notfound">
-<input type="hidden" id="_plugin_search2_msg_result_found" value="$_search2_result_found">
-<input type="hidden" id="_search2_search_wait_milliseconds" value="$_search2_search_wait_milliseconds">
+<ul id="_plugin_search2_result-list">
+</ul>
EOD;
- if ($h_search_text == '') {
+ if ($h_search_text == '' || $search2_form_total_count > 1) {
$result_page_panel = '';
}
<form action="$script" method="GET" class="_plugin_search2_form">
<div>
<input type="hidden" name="cmd" value="search2">
- <input type="search" name="q" value="$h_search_text" size="30">
+ <input type="search" name="q" value="$h_search_text" data-original-q="$h_search_text" size="40">
<input type="submit" value="$_btn_search">
</div>
$base_option
</div>
EOD;
+ $h_auth_user = htmlsc($auth_user);
+ $h_base_url = htmlsc(plugin_search2_get_base_url($search_text));
+ $h_msg_more_results = htmlsc($_msg_more_results);
+ $h_msg_prev_results = htmlsc($_msg_prev_results);
+ $max_results = PLUGIN_SEARCH2_SEARCH_MAX_RESULTS;
+ $prev_offset = pkwk_ctype_digit($prev_offset_s) ? $prev_offset_s : '';
+ $search_props =<<<EOD
+<div style="display:none;">
+ <input type="hidden" id="_plugin_search2_auth_user" value="$h_auth_user">
+ <input type="hidden" id="_plugin_search2_base_url" value="$h_base_url">
+ <input type="hidden" id="_plugin_search2_msg_searching" value="$_search_searching">
+ <input type="hidden" id="_plugin_search2_msg_showing_result" value="$_search_showing_result">
+ <input type="hidden" id="_plugin_search2_msg_result_notfound" value="$_search2_result_notfound">
+ <input type="hidden" id="_plugin_search2_msg_result_found" value="$_search2_result_found">
+ <input type="hidden" id="_plugin_search2_msg_more_results" value="$h_msg_more_results">
+ <input type="hidden" id="_plugin_search2_msg_prev_results" value="$h_msg_prev_results">
+ <input type="hidden" id="_plugin_search2_search_wait_milliseconds" value="$_search2_search_wait_milliseconds">
+ <input type="hidden" id="_plugin_search2_max_results" value="$max_results">
+ <input type="hidden" id="_plugin_search2_offset" value="$offset">
+ <input type="hidden" id="_plugin_search2_prev_offset" value="$prev_offset">
+ <input type="hidden" id="_plugin_search2_msg_error" value="$_msg_general_error">
+</div>
+EOD;
+ if ($search2_form_total_count > 1) {
+ $search_props = '';
+ }
return <<<EOD
<noscript>
<p class="_plugin_search2_nosupport_message" style="display:none;">
$_msg_unsupported_webbrowser $alt_msg
</p>
+$search_props
$form
<div class="_plugin_search2_search_status"></div>
<div class="_plugin_search2_message"></div>
$result_page_panel
-<ul id="result-list">
-</ul>
$second_form
EOD;
}
// License: GPL v2 or (at your option) any later version
//
// PukiWiki search2 pluign - JavaScript client script
-window.addEventListener && window.addEventListener('DOMContentLoaded', function() {
+window.addEventListener && window.addEventListener('DOMContentLoaded', function() { // eslint-disable-line no-unused-expressions
+ 'use strict';
function enableSearch2() {
var aroundLines = 2;
var maxResultLines = 20;
- var minBlockLines = 5;
- var minSearchWaitMilliseconds = 100;
+ var defaultSearchWaitMilliseconds = 100;
+ var defaultMaxResults = 1000;
var kanaMap = null;
- function escapeHTML (s) {
- if(typeof s !== 'string') {
- s = '' + s;
+ var searchProps = {};
+ /**
+ * Escape HTML special charactors
+ *
+ * @param {string} s
+ */
+ function escapeHTML(s) {
+ if (typeof s !== 'string') {
+ return '' + s;
}
return s.replace(/[&"<>]/g, function(m) {
return {
}[m];
});
}
- function doSearch(searchText, session, startIndex) {
- var url = './?cmd=search2&action=query';
- var props = getSiteProps();
- url += '&encode_hint=' + encodeURIComponent('\u3077');
- if (searchText) {
- url += '&q=' + encodeURIComponent(searchText);
- }
- if (session.base) {
- url += '&base=' + encodeURIComponent(session.base);
+ /**
+ * @param {string} idText
+ * @param {number} defaultValue
+ * @type number
+ */
+ function getIntById(idText, defaultValue) {
+ var value = defaultValue;
+ try {
+ var element = document.getElementById(idText);
+ if (element) {
+ value = parseInt(element.value, 10);
+ if (isNaN(value)) { // eslint-disable-line no-restricted-globals
+ value = defaultValue;
+ }
+ }
+ } catch (e) {
+ value = defaultValue;
}
- url += '&start=' + startIndex;
- fetch (url
- ).then(function(response){
- if (response.ok) {
- return response.json();
- } else {
- throw new Error(response.status + ': ' +
- + response.statusText + ' on ' + url);
+ return value;
+ }
+ /**
+ * @param {string} idText
+ * @param {string} defaultValue
+ * @type string
+ */
+ function getTextById(idText, defaultValue) {
+ var value = defaultValue;
+ try {
+ var element = document.getElementById(idText);
+ if (element.value) {
+ value = element.value;
}
- }).then(function(obj) {
- showResult(obj, session, searchText);
- })['catch'](function(err){
- console.log(err);
- console.log('Error! Please check JavaScript console' + '\n' + JSON.stringify(err) + '|' + err);
- });
+ } catch (e) {
+ value = defaultValue;
+ }
+ return value;
}
- function getMessageTemplate(idText, defaultText) {
- var messageHolder = document.querySelector('#' + idText);
- var messageTemplate = (messageHolder && messageHolder.value) || defaultText;
- return messageTemplate;
+ function prepareSearchProps() {
+ var p = {};
+ p.errorMsg = getTextById('_plugin_search2_msg_error',
+ 'An error occurred while processing.');
+ p.searchingMsg = getTextById('_plugin_search2_msg_searching',
+ 'Searching...');
+ p.showingResultMsg = getTextById('_plugin_search2_msg_showing_result',
+ 'Showing search results');
+ p.prevOffset = getTextById('_plugin_search2_prev_offset', '');
+ var baseUrlDefault = document.location.pathname + document.location.search;
+ baseUrlDefault = baseUrlDefault.replace(/&offset=\d+/, '');
+ p.baseUrl = getTextById('_plugin_search2_base_url', baseUrlDefault);
+ p.msgPrevResultsTemplate = getTextById('_plugin_search2_msg_prev_results', 'Previous $1 pages');
+ p.msgMoreResultsTemplate = getTextById('_plugin_search2_msg_more_results', 'Next $1 pages');
+ p.user = getTextById('_plugin_search2_auth_user', '');
+ p.showingResultMsg = getTextById('_plugin_search2_msg_showing_result', 'Showing search results');
+ p.notFoundMessageTemplate = getTextById('_plugin_search2_msg_result_notfound',
+ 'No page which contains $1 has been found.');
+ p.foundMessageTemplate = getTextById('_plugin_search2_msg_result_found',
+ 'In the page <strong>$2</strong>, <strong>$3</strong> pages that contain all the terms $1 were found.');
+ p.maxResults = getIntById('_plugin_search2_max_results', defaultMaxResults);
+ p.searchInterval = getIntById('_plugin_search2_search_wait_milliseconds', defaultSearchWaitMilliseconds);
+ p.offset = getIntById('_plugin_search2_offset', 0);
+ searchProps = p;
}
- function getAuthorInfo(text) {
-
+ function getSiteProps() {
+ var empty = {};
+ var propsDiv = document.getElementById('pukiwiki-site-properties');
+ if (!propsDiv) return empty;
+ var jsonE = propsDiv.querySelector('div[data-key="site-props"]');
+ if (!jsonE) return empty;
+ var props = JSON.parse(jsonE.getAttribute('data-value'));
+ return props || empty;
}
- function getPassage(now, dateText) {
- if (! dateText) {
- return '';
- }
- var units = [{u: 'm', max: 60}, {u: 'h', max: 24}, {u: 'd', max: 1}];
- var d = new Date();
- d.setTime(Date.parse(dateText));
- var t = (now.getTime() - d.getTime()) / (1000 * 60); // minutes
- var unit = units[0].u, card = units[0].max;
- for (var i = 0; i < units.length; i++) {
- unit = units[i].u, card = units[i].max;
- if (t < card) break;
- t = t / card;
+ /**
+ * @param {NodeList} nodeList
+ * @param {function(Node, number): void} func
+ */
+ function forEach(nodeList, func) {
+ if (nodeList.forEach) {
+ nodeList.forEach(func);
+ } else {
+ for (var i = 0, n = nodeList.length; i < n; i++) {
+ func(nodeList[i], i);
+ }
}
- return '(' + Math.floor(t) + unit + ')';
}
- function removeSearchOperators(searchText) {
- var sp = searchText.split(/\s+/);
- if (sp.length <= 1) {
- return searchText;
- }
- var hasOr = false;
- for (var i = sp.length - 1; i >= 0; i--) {
- if (sp[i] === 'OR') {
- hasOr = true;
- sp.splice(i, 1);
+ /**
+ * @param {string} text
+ * @param {RegExp} searchRegex
+ */
+ function findAndDecorateText(text, searchRegex) {
+ var isReplaced = false;
+ var lastIndex = 0;
+ var m;
+ var decorated = '';
+ if (!searchRegex) return null;
+ searchRegex.lastIndex = 0;
+ while ((m = searchRegex.exec(text)) !== null) {
+ if (m[0] === '') {
+ // Fail-safe
+ console.log('Invalid searchRegex ' + searchRegex);
+ return null;
+ }
+ isReplaced = true;
+ var pre = text.substring(lastIndex, m.index);
+ decorated += escapeHTML(pre);
+ for (var i = 1; i < m.length; i++) {
+ if (m[i]) {
+ decorated += '<strong class="word' + (i - 1) + '">' + escapeHTML(m[i]) + '</strong>';
+ }
}
+ lastIndex = searchRegex.lastIndex;
}
- return sp.join(' ');
- }
- function showResult(obj, session, searchText) {
- var searchRegex = textToRegex(removeSearchOperators(searchText));
- var ul = document.querySelector('#result-list');
- if (!ul) return;
- if (obj.start_index === 0) {
- ul.innerHTML = '';
+ if (isReplaced) {
+ decorated += escapeHTML(text.substr(lastIndex));
+ return decorated;
}
- if (! session.scan_page_count) session.scan_page_count = 0;
- if (! session.read_page_count) session.read_page_count = 0;
- if (! session.hit_page_count) session.hit_page_count = 0;
- var prevHitPageCount = session.hit_page_count;
- session.scan_page_count += obj.scan_page_count;
- session.read_page_count += obj.read_page_count;
- session.hit_page_count += obj.results.length;
- session.page_count = obj.page_count;
- if (prevHitPageCount === 0 && session.hit_page_count > 0) {
- var div = document.querySelector('._plugin_search2_second_form');
- if (div) {
- div.style.display = 'block';
- }
- }
- var msg = obj.message;
- var notFoundMessageTemplate = getMessageTemplate('_plugin_search2_msg_result_notfound',
- 'No page which contains $1 has been found.');
- var foundMessageTemplate = getMessageTemplate('_plugin_search2_msg_result_found',
- 'In the page <strong>$2</strong>, <strong>$3</strong> pages that contain all the terms $1 were found.');
+ return null;
+ }
+ /**
+ * @param {Object} session
+ * @param {string} searchText
+ * @param {RegExp} searchRegex
+ * @param {boolean} nowSearching
+ */
+ function getSearchResultMessage(session, searchText, searchRegex, nowSearching) {
var searchTextDecorated = findAndDecorateText(searchText, searchRegex);
if (searchTextDecorated === null) searchTextDecorated = escapeHTML(searchText);
- var messageTemplate = foundMessageTemplate;
- if (obj.search_done && session.hit_page_count === 0) {
- messageTemplate = notFoundMessageTemplate;
+ var messageTemplate = searchProps.foundMessageTemplate;
+ if (!nowSearching && session.hitPageCount === 0) {
+ messageTemplate = searchProps.notFoundMessageTemplate;
}
- msg = messageTemplate.replace(/\$1|\$2|\$3/g, function(m){
+ var msg = messageTemplate.replace(/\$1|\$2|\$3/g, function(m) {
return {
- '$1': searchTextDecorated,
- '$2': session.hit_page_count,
- '$3': session.read_page_count
+ $1: searchTextDecorated,
+ $2: session.hitPageCount,
+ $3: session.readPageCount
}[m];
});
- setSearchMessage(msg);
- var progress = ' (read:' + session.read_page_count + ', scanned:' +
- session.scan_page_count + ', all:' + session.page_count + ')';
- var e = document.querySelector('#_plugin_search2_msg_searching');
- var msg = e && e.value || 'Searching...';
- setSearchStatus(msg + progress);
- if (obj.search_done) {
- setTimeout(function(){
- setSearchStatus('');
- }, 5000);
+ return msg;
+ }
+ /**
+ * @param {Object} session
+ */
+ function getSearchProgress(session) {
+ var progress = '(read:' + session.readPageCount + ', scan:' +
+ session.scanPageCount + ', all:' + session.pageCount;
+ if (session.offset) {
+ progress += ', offset: ' + session.offset;
}
- var results = obj.results;
- var now = new Date();
- results.forEach(function(val, index) {
- var fragment = document.createDocumentFragment();
- var li = document.createElement('li');
- var hash = '#q=' + encodeSearchTextForHash(searchText);
- var href = val.url + hash;
- var decoratedName = findAndDecorateText(val.name, searchRegex);
- if (! decoratedName) {
- decoratedName = escapeHTML(val.name);
- }
- var author = getAuthorHeader(val.body);
- var updatedAt = '';
- if (author) {
- updatedAt = getUpdateTimeFromAuthorInfo(author);
- } else {
- updatedAt = val.updated_at;
- }
- var liHtml = '<a href="' + escapeHTML(href) + '">' + decoratedName + '</a> ' +
- getPassage(now, updatedAt);
- li.innerHTML = liHtml;
- var a = li.querySelector('a');
- if (a && a.hash) {
- if (a.hash !== hash) {
- // Some browser execute encodeHTML(hash) automatically. Support them.
- a.href = val.url + '#encq=' + encodeSearchTextForHash(searchText);
- }
- }
- fragment.appendChild(li);
- var div = document.createElement('div');
- div.classList.add('search-result-detail');
- var head = document.createElement('div');
- head.classList.add('search-result-page-summary');
- head.innerHTML = escapeHTML(getBodySummary(val.body));
- div.appendChild(head);
- var summary = getSummary(val.body, searchRegex);
- for (var i = 0; i < summary.length; i++) {
- var pre = document.createElement('pre');
- pre.innerHTML = summary[i].lines.join('\n');
- div.appendChild(pre);
- }
- fragment.appendChild(div);
- ul.appendChild(fragment);
- });
- if (!obj.search_done && obj.next_start_index) {
- var waitE = document.querySelector('#_search2_search_wait_milliseconds');
- var interval = minSearchWaitMilliseconds;
- try {
- interval = parseInt(waitE.value);
- } catch (e) {
- interval = minSearchWaitMilliseconds;
- }
- if (interval < minSearchWaitMilliseconds) {
- interval = minSearchWaitMilliseconds;
+ progress += ')';
+ return progress;
+ }
+ /**
+ * @param {Object} session
+ * @param {number} maxResults
+ */
+ function getOffsetLinks(session, maxResults) {
+ var baseUrl = searchProps.baseUrl;
+ var links = [];
+ if ('prevOffset' in session) {
+ var prevResultUrl = baseUrl;
+ if (session.prevOffset > 0) {
+ prevResultUrl += '&offset=' + session.prevOffset;
}
- setTimeout(function(){
- doSearch(searchText, session, obj.next_start_index);
- }, interval);
+ var msgPrev = searchProps.msgPrevResultsTemplate.replace(/\$1/, maxResults);
+ var prevResultHtml = '<a href="' + prevResultUrl + '">' + msgPrev + '</a>';
+ links.push(prevResultHtml);
}
+ if ('nextOffset' in session) {
+ var nextResultUrl = baseUrl + '&offset=' + session.nextOffset +
+ '&prev_offset=' + session.offset;
+ var msgMore = searchProps.msgMoreResultsTemplate.replace(/\$1/, maxResults);
+ var moreResultHtml = '<a href="' + nextResultUrl + '">' + msgMore + '</a>';
+ links.push(moreResultHtml);
+ }
+ if (links.length > 0) {
+ return links.join(' ');
+ }
+ return '';
}
function prepareKanaMap() {
if (kanaMap !== null) return;
var dakuten = '\uFF9E';
var maru = '\uFF9F';
var map = {};
- for (var c = 0xFF61; c <=0xFF9F; c++) {
+ for (var c = 0xFF61; c <= 0xFF9F; c++) {
var han = String.fromCharCode(c);
var zen = han.normalize('NFKC');
map[zen] = han;
var hanDaku = han + dakuten;
var zenDaku = hanDaku.normalize('NFKC');
if (zenDaku.length === 1) { // +Handaku-ten OK
- map[zenDaku] = hanDaku;
+ map[zenDaku] = hanDaku;
}
var hanMaru = han + maru;
var zenMaru = hanMaru.normalize('NFKC');
if (zenMaru.length === 1) { // +Maru OK
- map[zenMaru] = hanMaru;
+ map[zenMaru] = hanMaru;
}
}
kanaMap = map;
}
+ /**
+ * @param {searchText} searchText
+ * @type RegExp
+ */
function textToRegex(searchText) {
if (!searchText) return null;
- var regEscape = /[\\^$.*+?()[\]{}|]/g;
// 1:Symbol 2:Katakana 3:Hiragana
var regRep = /([\\^$.*+?()[\]{}|])|([\u30a1-\u30f6])|([\u3041-\u3096])/g;
+ var replacementFunc = function(m, m1, m2, m3) {
+ if (m1) {
+ // Symbol - escape with prior backslach
+ return '\\' + m1;
+ } else if (m2) {
+ // Katakana
+ var r = '(?:' + String.fromCharCode(m2.charCodeAt(0) - 0x60) +
+ '|' + m2;
+ if (kanaMap[m2]) {
+ r += '|' + kanaMap[m2];
+ }
+ r += ')';
+ return r;
+ } else if (m3) {
+ // Hiragana
+ var katakana = String.fromCharCode(m3.charCodeAt(0) + 0x60);
+ var r2 = '(?:' + m3 + '|' + katakana;
+ if (kanaMap[katakana]) {
+ r2 += '|' + kanaMap[katakana];
+ }
+ r2 += ')';
+ return r2;
+ }
+ return m;
+ };
var s1 = searchText.replace(/^\s+|\s+$/g, '');
+ if (!s1) return null;
var sp = s1.split(/\s+/);
var rText = '';
prepareKanaMap();
for (var i = 0; i < sp.length; i++) {
if (rText !== '') {
- rText += '|'
+ rText += '|';
}
var s = sp[i];
if (s.normalize) {
s = s.normalize('NFKC');
}
- var s2 = s.replace(regRep, function(m, m1, m2, m3){
- if (m1) {
- // Symbol - escape with prior backslach
- return '\\' + m1;
- } else if (m2) {
- // Katakana
- var r = '(?:' + String.fromCharCode(m2.charCodeAt(0) - 0x60) +
- '|' + m2;
- if (kanaMap[m2]) {
- r += '|' + kanaMap[m2];
- }
- r += ')';
- return r;
- } else if (m3) {
- // Hiragana
- var katakana = String.fromCharCode(m3.charCodeAt(0) + 0x60);
- var r = '(?:' + m3 + '|' + katakana;
- if (kanaMap[katakana]) {
- r += '|' + kanaMap[katakana];
- }
- r += ')';
- return r;
- }
- return m;
- });
+ var s2 = s.replace(regRep, replacementFunc);
rText += '(' + s2 + ')';
}
return new RegExp(rText, 'ig');
}
- function getAuthorHeader(body) {
- var start = 0;
- var pos;
- while ((pos = body.indexOf('\n', start)) >= 0) {
- var line = body.substring(start, pos);
- if (line.match(/^#author\(/, line)) {
- return line;
- } else if (line.match(/^#freeze(\W|$)/, line)) {
- // Found #freeze still in header
+ /**
+ * @param {string} statusText
+ */
+ function setSearchStatus(statusText) {
+ var statusList = document.querySelectorAll('._plugin_search2_search_status');
+ forEach(statusList, function(statusObj) {
+ statusObj.textContent = statusText;
+ });
+ }
+ /**
+ * @param {string} msgHTML
+ */
+ function setSearchMessage(msgHTML) {
+ var objList = document.querySelectorAll('._plugin_search2_message');
+ forEach(objList, function(obj) {
+ obj.innerHTML = msgHTML;
+ });
+ }
+ function showSecondSearchForm() {
+ // Show second search form
+ var div = document.querySelector('._plugin_search2_second_form');
+ if (div) {
+ div.style.display = 'block';
+ }
+ }
+ /**
+ * @param {Element} form
+ * @type string
+ */
+ function getSearchBase(form) {
+ var f = form || document.querySelector('._plugin_search2_form');
+ var base = '';
+ forEach(f.querySelectorAll('input[name="base"]'), function(radio) {
+ if (radio.checked) base = radio.value;
+ });
+ return base;
+ }
+ /**
+ * Decorate found block (for pre innerHTML)
+ *
+ * @param {Object} block
+ * @param {RegExp} searchRegex
+ */
+ function decorateFoundBlock(block, searchRegex) {
+ var lines = [];
+ for (var j = 0; j < block.lines.length; j++) {
+ var line = block.lines[j];
+ var decorated = findAndDecorateText(line, searchRegex);
+ if (decorated === null) {
+ lines.push('' + (block.startIndex + j + 1) + ':\t' + escapeHTML(line));
} else {
- // other line, #author not found
- return null;
+ lines.push('' + (block.startIndex + j + 1) + ':\t' + decorated);
}
- start = pos + 1;
}
- return null;
- }
- function getUpdateTimeFromAuthorInfo(authorInfo) {
- var m = authorInfo.match(/^#author\("([^;"]+)(;[^;"]+)?/);
- if (m) {
- return m[1];
+ if (block.beyondLimit) {
+ lines.push('...');
}
- return '';
+ return lines.join('\n');
}
- function getTargetLines(body, searchRegex) {
+ /**
+ * @param {string} body
+ * @param {RegExp} searchRegex
+ */
+ function getSummaryInfo(body, searchRegex) {
var lines = body.split('\n');
- var found = [];
var foundLines = [];
var isInAuthorHeader = true;
var lastFoundLineIndex = -1 - aroundLines;
var lastAddedLineIndex = lastFoundLineIndex;
var blocks = [];
var lineCount = 0;
+ var currentBlock = null;
for (var index = 0, length = lines.length; index < length; index++) {
var line = lines[index];
if (isInAuthorHeader) {
isInAuthorHeader = false;
}
}
- var decorated = findAndDecorateText(line, searchRegex);
- if (decorated === null) {
+ var match = line.match(searchRegex);
+ if (!match) {
if (index < lastFoundLineIndex + aroundLines + 1) {
- foundLines.push('' + (index + 1) + ':\t' + escapeHTML(lines[index]));
+ foundLines.push(lines[index]);
lineCount++;
lastAddedLineIndex = index;
}
foundLineIndex: index,
lines: []
};
+ currentBlock = block;
foundLines = block.lines;
blocks.push(block);
}
if (lineCount >= maxResultLines) {
- foundLines.push('...');
+ currentBlock.beyondLimit = true;
return blocks;
}
for (var i = startIndex; i < index; i++) {
- foundLines.push('' + (i + 1) + ':\t' + escapeHTML(lines[i]));
+ foundLines.push(lines[i]);
lineCount++;
}
- foundLines.push('' + (index + 1) + ':\t' + decorated);
+ foundLines.push(line);
lineCount++;
lastFoundLineIndex = lastAddedLineIndex = index;
}
}
return blocks;
}
- function getSummary(bodyText, searchRegex) {
- return getTargetLines(bodyText, searchRegex);
+ /**
+ * @param {Date} now
+ * @param {string} dateText
+ */
+ function getPassage(now, dateText) {
+ if (!dateText) {
+ return '';
+ }
+ var units = [{u: 'm', max: 60}, {u: 'h', max: 24}, {u: 'd', max: 1}];
+ var d = new Date();
+ d.setTime(Date.parse(dateText));
+ var t = (now.getTime() - d.getTime()) / (1000 * 60); // minutes
+ var unit = units[0].u; var card = units[0].max;
+ for (var i = 0; i < units.length; i++) {
+ unit = units[i].u; card = units[i].max;
+ if (t < card) break;
+ t = t / card;
+ }
+ return '(' + Math.floor(t) + unit + ')';
}
- function hookSearch2(e) {
- var form = document.querySelector('form');
- if (form && form.q) {
- var q = form.q;
- if (q.value === '') {
- q.focus();
+ /**
+ * @param {string} searchText
+ */
+ function removeSearchOperators(searchText) {
+ var sp = searchText.split(/\s+/);
+ if (sp.length <= 1) {
+ return searchText;
+ }
+ for (var i = sp.length - 2; i >= 1; i--) {
+ if (sp[i] === 'OR') {
+ sp.splice(i, 1);
}
}
+ return sp.join(' ');
+ }
+ /**
+ * @param {string} pathname
+ */
+ function getSearchCacheKeyBase(pathname) {
+ return 'path.' + pathname + '.search2.';
+ }
+ /**
+ * @param {string} pathname
+ */
+ function getSearchCacheKeyDateBase(pathname) {
+ var now = new Date();
+ var dateKey = now.getFullYear() + '_0' + (now.getMonth() + 1) + '_0' + now.getDate();
+ dateKey = dateKey.replace(/_\d?(\d\d)/g, '$1');
+ return getSearchCacheKeyBase(pathname) + dateKey + '.';
+ }
+ /**
+ * @param {string} pathname
+ * @param {string} searchText
+ * @param {number} offset
+ */
+ function getSearchCacheKey(pathname, searchText, offset) {
+ return getSearchCacheKeyDateBase(pathname) + 'offset=' + offset +
+ '.' + searchText;
+ }
+ /**
+ * @param {string} pathname
+ * @param {string} searchText
+ */
+ function clearSingleCache(pathname, searchText) {
+ if (!window.localStorage) return;
+ var removeTargets = [];
+ var keyBase = getSearchCacheKeyDateBase(pathname);
+ for (var i = 0, n = localStorage.length; i < n; i++) {
+ var key = localStorage.key(i);
+ if (key.substr(0, keyBase.length) === keyBase) {
+ // Search result Cache
+ var subKey = key.substr(keyBase.length);
+ var m = subKey.match(/^offset=\d+\.(.+)$/);
+ if (m && m[1] === searchText) {
+ removeTargets.push(key);
+ }
+ }
+ }
+ removeTargets.forEach(function(target) {
+ localStorage.removeItem(target);
+ });
}
+ /**
+ * @param {string} body
+ */
function getBodySummary(body) {
var lines = body.split('\n');
var isInAuthorHeader = true;
var summary = [];
- var lineCount = 0;
for (var index = 0, length = lines.length; index < length; index++) {
var line = lines[index];
if (isInAuthorHeader) {
if (line.match(/^#\w+/)) continue; // Block-type plugin
if (line.match(/^\/\//)) continue; // Comment
if (line.substr(0, 1) === '*') {
- line = line.replace(/\s*\[\#\w+\]$/, ''); // Remove anchor
+ line = line.replace(/\s*\[#\w+\]$/, ''); // Remove anchor
}
summary.push(line);
if (summary.length >= 10) {
}
return summary.join(' ').substring(0, 150);
}
+ /**
+ * @param {string} q searchText
+ */
+ function encodeSearchText(q) {
+ var sp = q.split(/\s+/);
+ for (var i = 0; i < sp.length; i++) {
+ sp[i] = encodeURIComponent(sp[i]);
+ }
+ return sp.join('+');
+ }
+ /**
+ * @param {string} q searchText
+ */
+ function encodeSearchTextForHash(q) {
+ var sp = q.split(/\s+/);
+ return sp.join('+');
+ }
+ function getSearchTextInLocationHash() {
+ var hash = document.location.hash;
+ if (!hash) return '';
+ var q = '';
+ if (hash.substr(0, 3) === '#q=') {
+ q = hash.substr(3).replace(/\+/g, ' ');
+ } else {
+ return '';
+ }
+ var decodedQ = decodeURIComponent(q);
+ if (q !== decodedQ) {
+ q = decodedQ + ' OR ' + q;
+ }
+ return q;
+ }
+ function colorSearchTextInBody() {
+ var searchText = getSearchTextInLocationHash();
+ if (!searchText) return;
+ var searchRegex = textToRegex(removeSearchOperators(searchText));
+ if (!searchRegex) return;
+ var ignoreTags = ['INPUT', 'TEXTAREA', 'BUTTON',
+ 'SCRIPT', 'FRAME', 'IFRAME'];
+ /**
+ * @param {Element} element
+ */
+ function colorSearchText(element) {
+ var decorated = findAndDecorateText(element.nodeValue, searchRegex);
+ if (decorated) {
+ var span = document.createElement('span');
+ span.innerHTML = decorated;
+ element.parentNode.replaceChild(span, element);
+ }
+ }
+ /**
+ * @param {Element} element
+ */
+ function walkElement(element) {
+ var e = element.firstChild;
+ while (e) {
+ if (e.nodeType === 3 && e.nodeValue &&
+ e.nodeValue.length >= 2 && /\S/.test(e.nodeValue)) {
+ var next = e.nextSibling;
+ colorSearchText(e, searchRegex);
+ e = next;
+ } else {
+ if (e.nodeType === 1 && ignoreTags.indexOf(e.tagName) === -1) {
+ walkElement(e);
+ }
+ e = e.nextSibling;
+ }
+ }
+ }
+ var target = document.getElementById('body');
+ walkElement(target);
+ }
+ /**
+ * @param {Array<Object>} newResults
+ * @param {Element} ul
+ */
+ function removePastResults(newResults, ul) {
+ var removedCount = 0;
+ var nodes = ul.childNodes;
+ for (var i = nodes.length - 1; i >= 0; i--) {
+ var node = nodes[i];
+ if (node.tagName !== 'LI' && node.tagName !== 'DIV') continue;
+ var nodePagename = node.getAttribute('data-pagename');
+ var isRemoveTarget = false;
+ for (var j = 0, n = newResults.length; j < n; j++) {
+ var r = newResults[j];
+ if (r.name === nodePagename) {
+ isRemoveTarget = true;
+ break;
+ }
+ }
+ if (isRemoveTarget) {
+ if (node.tagName === 'LI') {
+ removedCount++;
+ }
+ ul.removeChild(node);
+ }
+ }
+ return removedCount;
+ }
+ /**
+ * @param {Array<Object>} results
+ * @param {string} searchText
+ * @param {RegExp} searchRegex
+ * @param {Element} parentElement
+ * @param {boolean} insertTop
+ */
+ function addSearchResult(results, searchText, searchRegex, parentElement, insertTop) {
+ var now = new Date();
+ var parentFragment = document.createDocumentFragment();
+ results.forEach(function(val) {
+ var fragment = document.createDocumentFragment();
+ var li = document.createElement('li');
+ var hash = '#q=' + encodeSearchTextForHash(searchText);
+ var href = val.url + hash;
+ var decoratedName = findAndDecorateText(val.name, searchRegex);
+ if (!decoratedName) {
+ decoratedName = escapeHTML(val.name);
+ }
+ var updatedAt = val.updatedAt;
+ var liHtml = '<a href="' + escapeHTML(href) + '">' + decoratedName + '</a> ' +
+ getPassage(now, updatedAt);
+ li.innerHTML = liHtml;
+ li.setAttribute('data-pagename', val.name);
+ fragment.appendChild(li);
+ var div = document.createElement('div');
+ div.classList.add('search-result-detail');
+ var head = document.createElement('div');
+ head.classList.add('search-result-page-summary');
+ head.innerHTML = escapeHTML(val.bodySummary);
+ div.appendChild(head);
+ var summaryInfo = val.hitSummary;
+ for (var i = 0; i < summaryInfo.length; i++) {
+ var pre = document.createElement('pre');
+ pre.innerHTML = decorateFoundBlock(summaryInfo[i], searchRegex);
+ div.appendChild(pre);
+ }
+ div.setAttribute('data-pagename', val.name);
+ fragment.appendChild(div);
+ parentFragment.appendChild(fragment);
+ });
+ if (insertTop && parentElement.firstChild) {
+ parentElement.insertBefore(parentFragment, parentElement.firstChild);
+ } else {
+ parentElement.appendChild(parentFragment);
+ }
+ }
+ /**
+ * @param {Object} obj
+ * @param {Object} session
+ * @param {string} searchText
+ * @param {number} prevTimestamp
+ */
+ function showResult(obj, session, searchText, prevTimestamp) {
+ var props = getSiteProps();
+ var searchRegex = textToRegex(removeSearchOperators(searchText));
+ var ul = document.querySelector('#_plugin_search2_result-list');
+ if (!ul) return;
+ if (obj.start_index === 0 && !prevTimestamp) {
+ ul.innerHTML = '';
+ }
+ var searchDone = obj.search_done;
+ if (!session.scanPageCount) session.scanPageCount = 0;
+ if (!session.readPageCount) session.readPageCount = 0;
+ if (!session.hitPageCount) session.hitPageCount = 0;
+ var prevHitPageCount = session.hitPageCount;
+ session.hitPageCount += obj.results.length;
+ if (!prevTimestamp) {
+ session.scanPageCount += obj.scan_page_count;
+ session.readPageCount += obj.read_page_count;
+ session.pageCount = obj.page_count;
+ }
+ session.searchStartTime = obj.search_start_time;
+ session.authUser = obj.auth_user;
+ if (prevHitPageCount === 0 && session.hitPageCount > 0) {
+ showSecondSearchForm();
+ }
+ var results = obj.results;
+ var cachedResults = [];
+ results.forEach(function(val) {
+ var cache = {};
+ cache.name = val.name;
+ cache.url = val.url;
+ cache.updatedAt = val.updated_at;
+ cache.updatedTime = val.updated_time;
+ cache.bodySummary = getBodySummary(val.body);
+ cache.hitSummary = getSummaryInfo(val.body, searchRegex);
+ cachedResults.push(cache);
+ });
+ if (prevTimestamp) {
+ var removedCount = removePastResults(cachedResults, ul);
+ session.hitPageCount -= removedCount;
+ }
+ var msg = getSearchResultMessage(session, searchText, searchRegex, !searchDone);
+ setSearchMessage(msg);
+ if (prevTimestamp) {
+ setSearchStatus(searchProps.searchingMsg);
+ } else {
+ setSearchStatus(searchProps.searchingMsg + ' ' +
+ getSearchProgress(session));
+ }
+ if (searchDone) {
+ var singlePageResult = session.offset === 0 && !session.nextOffset;
+ var progress = getSearchProgress(session);
+ setTimeout(function() {
+ if (singlePageResult) {
+ setSearchStatus('');
+ } else {
+ setSearchStatus(searchProps.showingResultMsg + ' ' + progress);
+ }
+ }, 2000);
+ }
+ if (session.results) {
+ if (prevTimestamp) {
+ var newResult = [].concat(cachedResults);
+ Array.prototype.push.apply(newResult, session.results);
+ session.results = newResult;
+ } else {
+ Array.prototype.push.apply(session.results, cachedResults);
+ }
+ } else {
+ session.results = cachedResults;
+ }
+ addSearchResult(cachedResults, searchText, searchRegex, ul, prevTimestamp);
+ var maxResults = searchProps.maxResults;
+ if (searchDone) {
+ session.searchText = searchText;
+ var prevOffset = searchProps.prevOffset;
+ if (prevOffset) {
+ session.prevOffset = parseInt(prevOffset, 10);
+ }
+ var json = JSON.stringify(session);
+ var cacheKey = getSearchCacheKey(props.base_uri_pathname, searchText, session.offset);
+ if (window.localStorage) {
+ localStorage[cacheKey] = json;
+ }
+ if ('prevOffset' in session || 'nextOffset' in session) {
+ setSearchMessage(msg + ' ' + getOffsetLinks(session, maxResults));
+ }
+ }
+ if (!searchDone && obj.next_start_index) {
+ if (session.results.length >= maxResults) {
+ // Save results
+ session.nextOffset = obj.next_start_index;
+ var prevOffset2 = searchProps.prevOffset;
+ if (prevOffset2) {
+ session.prevOffset = parseInt(prevOffset2, 10);
+ }
+ var key = getSearchCacheKey(props.base_uri_pathname, searchText, session.offset);
+ localStorage[key] = JSON.stringify(session);
+ // Stop API calling
+ setSearchMessage(msg + ' ' + getOffsetLinks(session, maxResults));
+ setSearchStatus(searchProps.showingResultMsg + ' ' +
+ getSearchProgress(session));
+ } else {
+ setTimeout(function() {
+ doSearch(searchText, // eslint-disable-line no-use-before-define
+ session, obj.next_start_index,
+ obj.search_start_time);
+ }, searchProps.searchInterval);
+ }
+ }
+ }
+ /**
+ * @param {string} searchText
+ * @param {string} base
+ * @param {number} offset
+ */
+ function showCachedResult(searchText, base, offset) {
+ var props = getSiteProps();
+ var searchRegex = textToRegex(removeSearchOperators(searchText));
+ var ul = document.querySelector('#_plugin_search2_result-list');
+ if (!ul) return null;
+ var searchCacheKey = getSearchCacheKey(props.base_uri_pathname, searchText, offset);
+ var cache1 = localStorage[searchCacheKey];
+ if (!cache1) {
+ return null;
+ }
+ var session = JSON.parse(cache1);
+ if (!session) return null;
+ if (base !== session.base) {
+ return null;
+ }
+ var user = searchProps.user;
+ if (user !== session.authUser) {
+ return null;
+ }
+ if (session.hitPageCount > 0) {
+ showSecondSearchForm();
+ }
+ var msg = getSearchResultMessage(session, searchText, searchRegex, false);
+ setSearchMessage(msg);
+ addSearchResult(session.results, searchText, searchRegex, ul);
+ var maxResults = searchProps.maxResults;
+ if ('prevOffset' in session || 'nextOffset' in session) {
+ var moreResultHtml = getOffsetLinks(session, maxResults);
+ setSearchMessage(msg + ' ' + moreResultHtml);
+ var progress = getSearchProgress(session);
+ setSearchStatus(searchProps.showingResultMsg + ' ' + progress);
+ } else {
+ setSearchStatus('');
+ }
+ return session;
+ }
+ function removeCachedResults() {
+ var props = getSiteProps();
+ if (!props || !props.base_uri_pathname) return;
+ var keyPrefix = getSearchCacheKeyDateBase(props.base_uri_pathname);
+ var keyBase = getSearchCacheKeyBase(props.base_uri_pathname);
+ var removeTargets = [];
+ for (var i = 0, n = localStorage.length; i < n; i++) {
+ var key = localStorage.key(i);
+ if (key.substr(0, keyBase.length) === keyBase) {
+ // Search result Cache
+ if (key.substr(0, keyPrefix.length) !== keyPrefix) {
+ removeTargets.push(key);
+ }
+ }
+ }
+ removeTargets.forEach(function(target) {
+ localStorage.removeItem(target);
+ });
+ }
+ /**
+ * @param {string} searchText
+ * @param {object} session
+ * @param {number} startIndex
+ * @param {number} searchStartTime
+ * @param {number} prevTimestamp
+ */
+ function doSearch(searchText, session, startIndex, searchStartTime, prevTimestamp) {
+ var url = './?cmd=search2&action=query';
+ url += '&encode_hint=' + encodeURIComponent('\u3077');
+ if (searchText) {
+ url += '&q=' + encodeURIComponent(searchText);
+ }
+ if (session.base) {
+ url += '&base=' + encodeURIComponent(session.base);
+ }
+ if (prevTimestamp) {
+ url += '&modified_since=' + prevTimestamp;
+ } else {
+ url += '&start=' + startIndex;
+ if (searchStartTime) {
+ url += '&search_start_time=' + encodeURIComponent(searchStartTime);
+ }
+ if (!('offset' in session)) {
+ session.offset = startIndex;
+ }
+ }
+ fetch(url, {credentials: 'same-origin'}
+ ).then(function(response) {
+ if (response.ok) {
+ return response.json();
+ }
+ throw new Error(response.status + ': ' +
+ response.statusText + ' on ' + url);
+ }).then(function(obj) {
+ showResult(obj, session, searchText, prevTimestamp);
+ })['catch'](function(err) { // eslint-disable-line dot-notation
+ if (window.console && console.log) {
+ console.log(err);
+ console.log('Error! Please check JavaScript console\n' + JSON.stringify(err) + '|' + err);
+ }
+ setSearchStatus(searchProps.errorMsg);
+ });
+ }
+ function hookSearch2() {
+ var form = document.querySelector('form');
+ if (form && form.q) {
+ var q = form.q;
+ if (q.value === '') {
+ q.focus();
+ }
+ }
+ }
function removeEncodeHint() {
// Remove 'encode_hint' if site charset is UTF-8
var props = getSiteProps();
if (!props.is_utf8) return;
var forms = document.querySelectorAll('form');
- forEach(forms, function(form){
+ forEach(forms, function(form) {
if (form.cmd && form.cmd.value === 'search2') {
if (form.encode_hint && (typeof form.encode_hint.removeAttribute === 'function')) {
form.encode_hint.removeAttribute('name');
var searchText = form && form.q;
if (!searchText) return;
if (searchText && searchText.value) {
- var e = document.querySelector('#_plugin_search2_msg_searching');
- var msg = e && e.value || 'Searching...';
- setSearchStatus(msg);
- var base = '';
- forEach(form.querySelectorAll('input[name="base"]'), function(radio){
- if (radio.checked) base = radio.value;
- });
- doSearch(searchText.value, {base: base}, 0);
- }
- }
- function setSearchStatus(statusText) {
- var statusList = document.querySelectorAll('._plugin_search2_search_status');
- forEach(statusList, function(statusObj){
- statusObj.textContent = statusText;
- });
- }
- function setSearchMessage(msgHTML) {
- var objList = document.querySelectorAll('._plugin_search2_message');
- forEach(objList, function(obj){
- obj.innerHTML = msgHTML;
- });
- }
- function forEach(nodeList, func) {
- if (nodeList.forEach) {
- nodeList.forEach(func);
- } else {
- for (var i = 0, n = nodeList.length; i < n; i++) {
- func(nodeList[i], i);
+ var offset = searchProps.offset;
+ var base = getSearchBase(form);
+ var prevSession = showCachedResult(searchText.value, base, offset);
+ if (prevSession) {
+ // Display Cache results, then search only modified pages
+ if (!('offset' in prevSession) || prevSession.offset === 0) {
+ doSearch(searchText.value, prevSession, offset, null,
+ prevSession.searchStartTime);
+ } else {
+ // Show search results
+ }
+ } else {
+ doSearch(searchText.value, {base: base, offset: offset}, offset, null);
}
+ removeCachedResults();
}
}
function replaceSearchWithSearch2() {
- forEach(document.querySelectorAll('form'), function(f){
+ forEach(document.querySelectorAll('form'), function(f) {
+ function onAndRadioClick() {
+ var sp = removeSearchOperators(f.word.value).split(/\s+/);
+ var newText = sp.join(' ');
+ if (f.word.value !== newText) {
+ f.word.value = newText;
+ }
+ }
+ function onOrRadioClick() {
+ var sp = removeSearchOperators(f.word.value).split(/\s+/);
+ var newText = sp.join(' OR ');
+ if (f.word.value !== newText) {
+ f.word.value = newText;
+ }
+ }
if (f.action.match(/cmd=search$/)) {
f.addEventListener('submit', function(e) {
var q = e.target.word.value;
var base = '';
- forEach(f.querySelectorAll('input[name="base"]'), function(radio){
+ forEach(f.querySelectorAll('input[name="base"]'), function(radio) {
if (radio.checked) base = radio.value;
});
var props = getSiteProps();
(base ? '&base=' + encodeURIComponent(base) : '');
e.preventDefault();
setTimeout(function() {
- location.href = url;
+ window.location.href = url;
}, 1);
return false;
});
var radios = f.querySelectorAll('input[type="radio"][name="type"]');
- forEach(radios, function(radio){
+ forEach(radios, function(radio) {
if (radio.value === 'AND') {
radio.addEventListener('click', onAndRadioClick);
} else if (radio.value === 'OR') {
radio.addEventListener('click', onOrRadioClick);
}
});
- function onAndRadioClick(e) {
- var sp = removeSearchOperators(f.word.value).split(/\s+/);
- var newText = sp.join(' ');
- if (f.word.value !== newText) {
- f.word.value = newText;
- }
- }
- function onOrRadioClick(e) {
- var sp = removeSearchOperators(f.word.value).split(/\s+/);
- var newText = sp.join(' OR ');
- if (f.word.value !== newText) {
- f.word.value = newText;
+ } else if (f.cmd && f.cmd.value === 'search2') {
+ f.addEventListener('submit', function() {
+ var newSearchText = f.q.value;
+ var prevSearchText = f.q.getAttribute('data-original-q');
+ if (newSearchText === prevSearchText) {
+ // Clear resultCache to search same text again
+ var props = getSiteProps();
+ clearSingleCache(props.base_uri_pathname, prevSearchText);
}
- }
+ });
}
});
}
- function encodeSearchText(q) {
- var sp = q.split(/\s+/);
- for (var i = 0; i < sp.length; i++) {
- sp[i] = encodeURIComponent(sp[i]);
- }
- return sp.join('+');
- }
- function encodeSearchTextForHash(q) {
- var sp = q.split(/\s+/);
- return sp.join('+');
- }
- function findAndDecorateText(text, searchRegex) {
- var isReplaced = false;
- var lastIndex = 0;
- var m;
- var decorated = '';
- searchRegex.lastIndex = 0;
- while ((m = searchRegex.exec(text)) !== null) {
- isReplaced = true;
- var pre = text.substring(lastIndex, m.index);
- decorated += escapeHTML(pre);
- for (var i = 1; i < m.length; i++) {
- if (m[i]) {
- decorated += '<strong class="word' + (i - 1) + '">' + escapeHTML(m[i]) + '</strong>'
- }
- }
- lastIndex = searchRegex.lastIndex;
- }
- if (isReplaced) {
- decorated += escapeHTML(text.substr(lastIndex));
- return decorated;
- }
- return null;
- }
- function getSearchTextInLocationHash() {
- // TODO Cross browser
- var hash = location.hash;
- if (!hash) return '';
- var q = '';
- if (hash.substr(0, 3) === '#q=') {
- q = hash.substr(3).replace(/\+/g, ' ');
- } else if (hash.substr(0, 6) === '#encq=') {
- q = decodeURIComponent(hash.substr(6).replace(/\+/g, ' '));
- }
- return q;
- }
- function colorSearchTextInBody() {
- var searchText = getSearchTextInLocationHash();
- if (!searchText) return;
- var searchRegex = textToRegex(removeSearchOperators(searchText));
- var headReText = '([\\s\\b]|^)';
- var tailReText = '\\b';
- var ignoreTags = ['INPUT', 'TEXTAREA', 'BUTTON',
- 'SCRIPT', 'FRAME', 'IFRAME'];
- function colorSearchText(element, searchRegex) {
- var decorated = findAndDecorateText(element.nodeValue, searchRegex);
- if (decorated) {
- var span = document.createElement('span');
- span.innerHTML = decorated;
- element.parentNode.replaceChild(span, element);
- }
- }
- function walkElement(element) {
- var e = element.firstChild;
- while (e) {
- if (e.nodeType == 3 && e.nodeValue &&
- e.nodeValue.length >= 2 && /\S/.test(e.nodeValue)) {
- var next = e.nextSibling;
- colorSearchText(e, searchRegex);
- e = next;
- } else {
- if (e.nodeType == 1 && ignoreTags.indexOf(e.tagName) == -1) {
- walkElement(e);
- }
- e = e.nextSibling;
- }
- }
- }
- var target = document.getElementById('body');
- walkElement(target);
- }
function showNoSupportMessage() {
var pList = document.getElementsByClassName('_plugin_search2_nosupport_message');
for (var i = 0; i < pList.length; i++) {
if (props.json_enabled) return true;
return false;
}
- function getSiteProps() {
- var empty = {};
- var propsDiv = document.getElementById('pukiwiki-site-properties');
- if (!propsDiv) return empty;
- var jsonE = propsDiv.querySelector('div[data-key="site-props"]');
- if (!jsonE) return emptry;
- var props = JSON.parse(jsonE.getAttribute('data-value'));
- return props || empty;
- }
+ prepareSearchProps();
colorSearchTextInBody();
- if (! isEnabledFetchFunctions()) {
+ if (!isEnabledFetchFunctions()) {
showNoSupportMessage();
return;
}
- if (! isEnableServerFunctions()) return;
+ if (!isEnableServerFunctions()) return;
replaceSearchWithSearch2();
hookSearch2();
removeEncodeHint();