1 // PukiWiki - Yet another WikiWikiWeb clone.
4 // 2017 PukiWiki Development Team
5 // License: GPL v2 or (at your option) any later version
7 // PukiWiki search2 pluign - JavaScript client script
8 window.addEventListener && window.addEventListener('DOMContentLoaded', function() {
9 function enableSearch2() {
11 var maxResultLines = 20;
12 var minBlockLines = 5;
13 var minSearchWaitMilliseconds = 100;
14 function escapeHTML (s) {
15 if(typeof s !== 'string') {
18 return s.replace(/[&"<>]/g, function(m) {
27 function doSearch(searchText, session, startIndex) {
28 var url = './?cmd=search2&action=query';
30 url += '&q=' + encodeURIComponent(searchText);
32 url += '&start=' + startIndex;
34 ).then(function(response){
36 return response.json();
38 throw new Error(response.status + ': ' +
39 + response.statusText + ' on ' + url);
41 }).then(function(obj) {
42 showResult(obj, session, searchText);
43 })['catch'](function(err){
45 console.log('Error! Please check JavaScript console' + '\n' + JSON.stringify(err) + '|' + err);
48 function getMessageTemplate(idText, defaultText) {
49 var messageHolder = document.querySelector('#' + idText);
50 var messageTemplate = (messageHolder && messageHolder.value) || defaultText;
51 return messageTemplate;
53 function showResult(obj, session, searchText) {
54 var searchRegex = textToRegex(searchText);
55 var ul = document.querySelector('#result-list');
57 if (obj.start_index === 0) {
60 if (! session.scan_page_count) session.scan_page_count = 0;
61 if (! session.read_page_count) session.read_page_count = 0;
62 if (! session.hit_page_count) session.hit_page_count = 0;
63 session.scan_page_count += obj.scan_page_count;
64 session.read_page_count += obj.read_page_count;
65 session.hit_page_count += obj.results.length;
66 session.page_count = obj.page_count;
68 var msg = obj.message;
69 var notFoundMessageTemplate = getMessageTemplate('_plugin_search2_msg_result_notfound',
70 'No page which contains $1 has been found.');
71 var foundMessageTemplate = getMessageTemplate('_plugin_search2_msg_result_found',
72 'In the page <strong>$2</strong>, <strong>$3</strong> pages that contain all the terms $1 were found.');
73 var searchTextDecorated = findAndDecorateText(searchText, searchRegex);
74 if (searchTextDecorated === null) searchTextDecorated = escapeHTML(searchText);
75 var messageTemplate = foundMessageTemplate;
76 if (obj.search_done && session.hit_page_count === 0) {
77 messageTemplate = notFoundMessageTemplate;
79 msg = messageTemplate.replace(/\$1|\$2|\$3/g, function(m){
81 '$1': searchTextDecorated,
82 '$2': session.hit_page_count,
83 '$3': session.read_page_count
86 document.querySelector('#_plugin_search2_message').innerHTML = msg;
88 if (obj.search_done) {
91 var progress = ' (' + session.read_page_count + ' / ' +
92 session.scan_page_count + ' / ' + session.page_count + ')';
93 var e = document.querySelector('#_plugin_search2_msg_searching');
94 var msg = e && e.value || 'Searching...';
95 setSearchStatus(msg + progress);
97 var results = obj.results;
98 results.forEach(function(val, index) {
99 var fragment = document.createDocumentFragment();
100 var li = document.createElement('li');
102 var decoratedName = findAndDecorateText(val.name, searchRegex);
103 if (! decoratedName) {
104 decoratedName = escapeHTML(val.name);
106 li.innerHTML = '<a href="' + href + '">' + decoratedName + '</a>';
107 fragment.appendChild(li);
108 var summary = getSummary(val.body, searchRegex);
109 for (var i = 0; i < summary.length; i++) {
110 var pre = document.createElement('pre');
111 pre.innerHTML = summary[i].lines.join('\n');
112 fragment.appendChild(pre);
114 ul.appendChild(fragment);
116 if (!obj.search_done && obj.next_start_index) {
117 var waitE = document.querySelector('#_search2_search_wait_milliseconds');
118 var interval = minSearchWaitMilliseconds;
120 interval = parseInt(waitE.value);
122 interval = minSearchWaitMilliseconds;
124 if (interval < minSearchWaitMilliseconds) {
125 interval = minSearchWaitMilliseconds;
127 setTimeout(function(){
128 doSearch(searchText, session, obj.next_start_index);
132 function textToRegex(searchText) {
133 var regEscape = /[\\^$.*+?()[\]{}|]/g;
134 var s1 = searchText.replace(/^\s+|\s+$/g, '');
135 var sp = s1.split(/\s+/);
137 for (var i = 0; i < sp.length; i++) {
141 rText += '(' + sp[i].replace(regEscape, '\\$&') + ')';
143 return new RegExp(rText, 'ig');
145 function getTargetLines(body, searchRegex) {
146 var lines = body.split('\n');
149 var isInAuthorHeader = true;
150 var lastFoundLineIndex = -1 - aroundLines;
151 var lastAddedLineIndex = lastFoundLineIndex;
154 for (var index = 0, length = lines.length; index < length; index++) {
155 var line = lines[index];
156 if (isInAuthorHeader) {
157 // '#author line is not search target'
158 if (line.match(/^#author\(/)) {
159 // Remove this line from search target
161 } else if (line.match(/^#freeze(\W|$)/)) {
165 isInAuthorHeader = false;
168 var decorated = findAndDecorateText(line, searchRegex);
169 if (decorated === null) {
170 if (index < lastFoundLineIndex + aroundLines + 1) {
171 foundLines.push('' + (index + 1) + ':\t' + escapeHTML(lines[index]));
173 lastAddedLineIndex = index;
176 var startIndex = Math.max(Math.max(lastAddedLineIndex + 1, index - aroundLines), 0);
177 if (lastAddedLineIndex + 1 < startIndex) {
180 startIndex: startIndex,
181 foundLineIndex: index,
184 foundLines = block.lines;
187 if (lineCount >= maxResultLines) {
188 foundLines.push('...');
191 for (var i = startIndex; i < index; i++) {
192 foundLines.push('' + (i + 1) + ':\t' + escapeHTML(lines[i]));
195 foundLines.push('' + (index + 1) + ':\t' + decorated);
197 lastFoundLineIndex = lastAddedLineIndex = index;
201 //return foundLines.join('\n');
203 function findAndDecorateText(text, searchRegex) {
204 var isReplaced = false;
208 searchRegex.lastIndex = 0;
209 while ((m = searchRegex.exec(text)) !== null) {
211 var pre = text.substring(lastIndex, m.index);
212 decorated += escapeHTML(pre);
213 for (var i = 1; i < m.length; i++) {
215 decorated += '<strong class="word' + (i - 1) + '">' + escapeHTML(m[i]) + '</strong>'
218 lastIndex = searchRegex.lastIndex;
221 decorated += escapeHTML(text.substr(lastIndex));
226 function getSummary(bodyText, searchRegex) {
227 return getTargetLines(bodyText, searchRegex);
229 function hookSearch2(e) {
230 var form = document.querySelector('form');
231 if (form && form.q) {
233 if (q.value === '') {
238 function removeEncodeHint() {
239 var form = document.querySelector('form');
240 if (form && form.encode_hint && (typeof form.encode_hint.removeAttribute === 'function')) {
241 form.encode_hint.removeAttribute('name');
244 function kickFirstSearch() {
245 var searchText = document.querySelector('#_plugin_search2_searchtext');
246 if (!searchText) return;
247 if (searchText && searchText.value) {
248 var e = document.querySelector('#_plugin_search2_msg_searching');
249 var msg = e && e.value || 'Searching...';
250 setSearchStatus(msg);
251 doSearch(searchText.value, {}, 0);
254 function setSearchStatus(statusText) {
255 var statusObj = document.querySelector('#_plugin_search2_search_status');
257 statusObj.textContent = statusText;
260 function replaceSearchWithSearch2() {
261 var forms = document.querySelectorAll('form');
262 for (var i = 0; i < forms.length; i++) {
264 if (f.action.match(/cmd=search$/)) {
265 f.addEventListener('submit', function(e) {
266 var q = e.target.word.value;
267 var base = e.target && e.target.base && e.target.base.value;
268 var loc = document.location;
269 var url = loc.protocol + '//' + loc.host + loc.pathname +
271 (base ? '&base=' + encodeURIComponent(base) : '') +
272 '&q=' + encodeURIComponent(q);
274 setTimeout(function() {
282 function isEnabledFunctions() {
283 if (window.fetch && document.querySelector) {
288 if (! isEnabledFunctions()) return;
289 replaceSearchWithSearch2();