gitea源码

search.js 52KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566
  1. /*!
  2. * # Fomantic-UI - Search
  3. * http://github.com/fomantic/Fomantic-UI/
  4. *
  5. *
  6. * Released under the MIT license
  7. * http://opensource.org/licenses/MIT
  8. *
  9. */
  10. ;(function ($, window, document, undefined) {
  11. 'use strict';
  12. $.isFunction = $.isFunction || function(obj) {
  13. return typeof obj === "function" && typeof obj.nodeType !== "number";
  14. };
  15. window = (typeof window != 'undefined' && window.Math == Math)
  16. ? window
  17. : (typeof self != 'undefined' && self.Math == Math)
  18. ? self
  19. : Function('return this')()
  20. ;
  21. $.fn.search = function(parameters) {
  22. var
  23. $allModules = $(this),
  24. moduleSelector = $allModules.selector || '',
  25. time = new Date().getTime(),
  26. performance = [],
  27. query = arguments[0],
  28. methodInvoked = (typeof query == 'string'),
  29. queryArguments = [].slice.call(arguments, 1),
  30. returnedValue
  31. ;
  32. $(this)
  33. .each(function() {
  34. var
  35. settings = ( $.isPlainObject(parameters) )
  36. ? $.extend(true, {}, $.fn.search.settings, parameters)
  37. : $.extend({}, $.fn.search.settings),
  38. className = settings.className,
  39. metadata = settings.metadata,
  40. regExp = settings.regExp,
  41. fields = settings.fields,
  42. selector = settings.selector,
  43. error = settings.error,
  44. namespace = settings.namespace,
  45. eventNamespace = '.' + namespace,
  46. moduleNamespace = namespace + '-module',
  47. $module = $(this),
  48. $prompt = $module.find(selector.prompt),
  49. $searchButton = $module.find(selector.searchButton),
  50. $results = $module.find(selector.results),
  51. $result = $module.find(selector.result),
  52. $category = $module.find(selector.category),
  53. element = this,
  54. instance = $module.data(moduleNamespace),
  55. disabledBubbled = false,
  56. resultsDismissed = false,
  57. module
  58. ;
  59. module = {
  60. initialize: function() {
  61. module.verbose('Initializing module');
  62. module.get.settings();
  63. module.determine.searchFields();
  64. module.bind.events();
  65. module.set.type();
  66. module.create.results();
  67. module.instantiate();
  68. },
  69. instantiate: function() {
  70. module.verbose('Storing instance of module', module);
  71. instance = module;
  72. $module
  73. .data(moduleNamespace, module)
  74. ;
  75. },
  76. destroy: function() {
  77. module.verbose('Destroying instance');
  78. $module
  79. .off(eventNamespace)
  80. .removeData(moduleNamespace)
  81. ;
  82. },
  83. refresh: function() {
  84. module.debug('Refreshing selector cache');
  85. $prompt = $module.find(selector.prompt);
  86. $searchButton = $module.find(selector.searchButton);
  87. $category = $module.find(selector.category);
  88. $results = $module.find(selector.results);
  89. $result = $module.find(selector.result);
  90. },
  91. refreshResults: function() {
  92. $results = $module.find(selector.results);
  93. $result = $module.find(selector.result);
  94. },
  95. bind: {
  96. events: function() {
  97. module.verbose('Binding events to search');
  98. if(settings.automatic) {
  99. $module
  100. .on(module.get.inputEvent() + eventNamespace, selector.prompt, module.event.input)
  101. ;
  102. $prompt
  103. .attr('autocomplete', 'off')
  104. ;
  105. }
  106. $module
  107. // prompt
  108. .on('focus' + eventNamespace, selector.prompt, module.event.focus)
  109. .on('blur' + eventNamespace, selector.prompt, module.event.blur)
  110. .on('keydown' + eventNamespace, selector.prompt, module.handleKeyboard)
  111. // search button
  112. .on('click' + eventNamespace, selector.searchButton, module.query)
  113. // results
  114. .on('mousedown' + eventNamespace, selector.results, module.event.result.mousedown)
  115. .on('mouseup' + eventNamespace, selector.results, module.event.result.mouseup)
  116. .on('click' + eventNamespace, selector.result, module.event.result.click)
  117. ;
  118. }
  119. },
  120. determine: {
  121. searchFields: function() {
  122. // this makes sure $.extend does not add specified search fields to default fields
  123. // this is the only setting which should not extend defaults
  124. if(parameters && parameters.searchFields !== undefined) {
  125. settings.searchFields = parameters.searchFields;
  126. }
  127. }
  128. },
  129. event: {
  130. input: function() {
  131. if(settings.searchDelay) {
  132. clearTimeout(module.timer);
  133. module.timer = setTimeout(function() {
  134. if(module.is.focused()) {
  135. module.query();
  136. }
  137. }, settings.searchDelay);
  138. }
  139. else {
  140. module.query();
  141. }
  142. },
  143. focus: function() {
  144. module.set.focus();
  145. if(settings.searchOnFocus && module.has.minimumCharacters() ) {
  146. module.query(function() {
  147. if(module.can.show() ) {
  148. module.showResults();
  149. }
  150. });
  151. }
  152. },
  153. blur: function(event) {
  154. var
  155. pageLostFocus = (document.activeElement === this),
  156. callback = function() {
  157. module.cancel.query();
  158. module.remove.focus();
  159. module.timer = setTimeout(module.hideResults, settings.hideDelay);
  160. }
  161. ;
  162. if(pageLostFocus) {
  163. return;
  164. }
  165. resultsDismissed = false;
  166. if(module.resultsClicked) {
  167. module.debug('Determining if user action caused search to close');
  168. $module
  169. .one('click.close' + eventNamespace, selector.results, function(event) {
  170. if(module.is.inMessage(event) || disabledBubbled) {
  171. $prompt.focus();
  172. return;
  173. }
  174. disabledBubbled = false;
  175. if( !module.is.animating() && !module.is.hidden()) {
  176. callback();
  177. }
  178. })
  179. ;
  180. }
  181. else {
  182. module.debug('Input blurred without user action, closing results');
  183. callback();
  184. }
  185. },
  186. result: {
  187. mousedown: function() {
  188. module.resultsClicked = true;
  189. },
  190. mouseup: function() {
  191. module.resultsClicked = false;
  192. },
  193. click: function(event) {
  194. module.debug('Search result selected');
  195. var
  196. $result = $(this),
  197. $title = $result.find(selector.title).eq(0),
  198. $link = $result.is('a[href]')
  199. ? $result
  200. : $result.find('a[href]').eq(0),
  201. href = $link.attr('href') || false,
  202. target = $link.attr('target') || false,
  203. // title is used for result lookup
  204. value = ($title.length > 0)
  205. ? $title.text()
  206. : false,
  207. results = module.get.results(),
  208. result = $result.data(metadata.result) || module.get.result(value, results)
  209. ;
  210. if(value) {
  211. module.set.value(value);
  212. }
  213. if( $.isFunction(settings.onSelect) ) {
  214. if(settings.onSelect.call(element, result, results) === false) {
  215. module.debug('Custom onSelect callback cancelled default select action');
  216. disabledBubbled = true;
  217. return;
  218. }
  219. }
  220. module.hideResults();
  221. if(href) {
  222. event.preventDefault();
  223. module.verbose('Opening search link found in result', $link);
  224. if(target == '_blank' || event.ctrlKey) {
  225. window.open(href);
  226. }
  227. else {
  228. window.location.href = (href);
  229. }
  230. }
  231. }
  232. }
  233. },
  234. ensureVisible: function ensureVisible($el) {
  235. var elTop, elBottom, resultsScrollTop, resultsHeight;
  236. elTop = $el.position().top;
  237. elBottom = elTop + $el.outerHeight(true);
  238. resultsScrollTop = $results.scrollTop();
  239. resultsHeight = $results.height()
  240. parseInt($results.css('paddingTop'), 0) +
  241. parseInt($results.css('paddingBottom'), 0);
  242. if (elTop < 0) {
  243. $results.scrollTop(resultsScrollTop + elTop);
  244. }
  245. else if (resultsHeight < elBottom) {
  246. $results.scrollTop(resultsScrollTop + (elBottom - resultsHeight));
  247. }
  248. },
  249. handleKeyboard: function(event) {
  250. var
  251. // force selector refresh
  252. $result = $module.find(selector.result),
  253. $category = $module.find(selector.category),
  254. $activeResult = $result.filter('.' + className.active),
  255. currentIndex = $result.index( $activeResult ),
  256. resultSize = $result.length,
  257. hasActiveResult = $activeResult.length > 0,
  258. keyCode = event.which,
  259. keys = {
  260. backspace : 8,
  261. enter : 13,
  262. escape : 27,
  263. upArrow : 38,
  264. downArrow : 40
  265. },
  266. newIndex
  267. ;
  268. // search shortcuts
  269. if(keyCode == keys.escape) {
  270. module.verbose('Escape key pressed, blurring search field');
  271. module.hideResults();
  272. resultsDismissed = true;
  273. }
  274. if( module.is.visible() ) {
  275. if(keyCode == keys.enter) {
  276. module.verbose('Enter key pressed, selecting active result');
  277. if( $result.filter('.' + className.active).length > 0 ) {
  278. module.event.result.click.call($result.filter('.' + className.active), event);
  279. event.preventDefault();
  280. return false;
  281. }
  282. }
  283. else if(keyCode == keys.upArrow && hasActiveResult) {
  284. module.verbose('Up key pressed, changing active result');
  285. newIndex = (currentIndex - 1 < 0)
  286. ? currentIndex
  287. : currentIndex - 1
  288. ;
  289. $category
  290. .removeClass(className.active)
  291. ;
  292. $result
  293. .removeClass(className.active)
  294. .eq(newIndex)
  295. .addClass(className.active)
  296. .closest($category)
  297. .addClass(className.active)
  298. ;
  299. module.ensureVisible($result.eq(newIndex));
  300. event.preventDefault();
  301. }
  302. else if(keyCode == keys.downArrow) {
  303. module.verbose('Down key pressed, changing active result');
  304. newIndex = (currentIndex + 1 >= resultSize)
  305. ? currentIndex
  306. : currentIndex + 1
  307. ;
  308. $category
  309. .removeClass(className.active)
  310. ;
  311. $result
  312. .removeClass(className.active)
  313. .eq(newIndex)
  314. .addClass(className.active)
  315. .closest($category)
  316. .addClass(className.active)
  317. ;
  318. module.ensureVisible($result.eq(newIndex));
  319. event.preventDefault();
  320. }
  321. }
  322. else {
  323. // query shortcuts
  324. if(keyCode == keys.enter) {
  325. module.verbose('Enter key pressed, executing query');
  326. module.query();
  327. module.set.buttonPressed();
  328. $prompt.one('keyup', module.remove.buttonFocus);
  329. }
  330. }
  331. },
  332. setup: {
  333. api: function(searchTerm, callback) {
  334. var
  335. apiSettings = {
  336. debug : settings.debug,
  337. on : false,
  338. cache : settings.cache,
  339. action : 'search',
  340. urlData : {
  341. query : searchTerm
  342. },
  343. onSuccess : function(response) {
  344. module.parse.response.call(element, response, searchTerm);
  345. callback();
  346. },
  347. onFailure : function() {
  348. module.displayMessage(error.serverError);
  349. callback();
  350. },
  351. onAbort : function(response) {
  352. },
  353. onError : module.error
  354. }
  355. ;
  356. $.extend(true, apiSettings, settings.apiSettings);
  357. module.verbose('Setting up API request', apiSettings);
  358. $module.api(apiSettings);
  359. }
  360. },
  361. can: {
  362. useAPI: function() {
  363. return $.fn.api !== undefined;
  364. },
  365. show: function() {
  366. return module.is.focused() && !module.is.visible() && !module.is.empty();
  367. },
  368. transition: function() {
  369. return settings.transition && $.fn.transition !== undefined && $module.transition('is supported');
  370. }
  371. },
  372. is: {
  373. animating: function() {
  374. return $results.hasClass(className.animating);
  375. },
  376. hidden: function() {
  377. return $results.hasClass(className.hidden);
  378. },
  379. inMessage: function(event) {
  380. if(!event.target) {
  381. return;
  382. }
  383. var
  384. $target = $(event.target),
  385. isInDOM = $.contains(document.documentElement, event.target)
  386. ;
  387. return (isInDOM && $target.closest(selector.message).length > 0);
  388. },
  389. empty: function() {
  390. return ($results.html() === '');
  391. },
  392. visible: function() {
  393. return ($results.filter(':visible').length > 0);
  394. },
  395. focused: function() {
  396. return ($prompt.filter(':focus').length > 0);
  397. }
  398. },
  399. get: {
  400. settings: function() {
  401. if($.isPlainObject(parameters) && parameters.searchFullText) {
  402. settings.fullTextSearch = parameters.searchFullText;
  403. module.error(settings.error.oldSearchSyntax, element);
  404. }
  405. if (settings.ignoreDiacritics && !String.prototype.normalize) {
  406. settings.ignoreDiacritics = false;
  407. module.error(error.noNormalize, element);
  408. }
  409. },
  410. inputEvent: function() {
  411. var
  412. prompt = $prompt[0],
  413. inputEvent = (prompt !== undefined && prompt.oninput !== undefined)
  414. ? 'input'
  415. : (prompt !== undefined && prompt.onpropertychange !== undefined)
  416. ? 'propertychange'
  417. : 'keyup'
  418. ;
  419. return inputEvent;
  420. },
  421. value: function() {
  422. return $prompt.val();
  423. },
  424. results: function() {
  425. var
  426. results = $module.data(metadata.results)
  427. ;
  428. return results;
  429. },
  430. result: function(value, results) {
  431. var
  432. result = false
  433. ;
  434. value = (value !== undefined)
  435. ? value
  436. : module.get.value()
  437. ;
  438. results = (results !== undefined)
  439. ? results
  440. : module.get.results()
  441. ;
  442. if(settings.type === 'category') {
  443. module.debug('Finding result that matches', value);
  444. $.each(results, function(index, category) {
  445. if(Array.isArray(category.results)) {
  446. result = module.search.object(value, category.results)[0];
  447. // don't continue searching if a result is found
  448. if(result) {
  449. return false;
  450. }
  451. }
  452. });
  453. }
  454. else {
  455. module.debug('Finding result in results object', value);
  456. result = module.search.object(value, results)[0];
  457. }
  458. return result || false;
  459. },
  460. },
  461. select: {
  462. firstResult: function() {
  463. module.verbose('Selecting first result');
  464. $result.first().addClass(className.active);
  465. }
  466. },
  467. set: {
  468. focus: function() {
  469. $module.addClass(className.focus);
  470. },
  471. loading: function() {
  472. $module.addClass(className.loading);
  473. },
  474. value: function(value) {
  475. module.verbose('Setting search input value', value);
  476. $prompt
  477. .val(value)
  478. ;
  479. },
  480. type: function(type) {
  481. type = type || settings.type;
  482. if(settings.type == 'category') {
  483. $module.addClass(settings.type);
  484. }
  485. },
  486. buttonPressed: function() {
  487. $searchButton.addClass(className.pressed);
  488. }
  489. },
  490. remove: {
  491. loading: function() {
  492. $module.removeClass(className.loading);
  493. },
  494. focus: function() {
  495. $module.removeClass(className.focus);
  496. },
  497. buttonPressed: function() {
  498. $searchButton.removeClass(className.pressed);
  499. },
  500. diacritics: function(text) {
  501. return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text;
  502. }
  503. },
  504. query: function(callback) {
  505. callback = $.isFunction(callback)
  506. ? callback
  507. : function(){}
  508. ;
  509. var
  510. searchTerm = module.get.value(),
  511. cache = module.read.cache(searchTerm)
  512. ;
  513. callback = callback || function() {};
  514. if( module.has.minimumCharacters() ) {
  515. if(cache) {
  516. module.debug('Reading result from cache', searchTerm);
  517. module.save.results(cache.results);
  518. module.addResults(cache.html);
  519. module.inject.id(cache.results);
  520. callback();
  521. }
  522. else {
  523. module.debug('Querying for', searchTerm);
  524. if($.isPlainObject(settings.source) || Array.isArray(settings.source)) {
  525. module.search.local(searchTerm);
  526. callback();
  527. }
  528. else if( module.can.useAPI() ) {
  529. module.search.remote(searchTerm, callback);
  530. }
  531. else {
  532. module.error(error.source);
  533. callback();
  534. }
  535. }
  536. settings.onSearchQuery.call(element, searchTerm);
  537. }
  538. else {
  539. module.hideResults();
  540. }
  541. },
  542. search: {
  543. local: function(searchTerm) {
  544. var
  545. results = module.search.object(searchTerm, settings.source),
  546. searchHTML
  547. ;
  548. module.set.loading();
  549. module.save.results(results);
  550. module.debug('Returned full local search results', results);
  551. if(settings.maxResults > 0) {
  552. module.debug('Using specified max results', results);
  553. results = results.slice(0, settings.maxResults);
  554. }
  555. if(settings.type == 'category') {
  556. results = module.create.categoryResults(results);
  557. }
  558. searchHTML = module.generateResults({
  559. results: results
  560. });
  561. module.remove.loading();
  562. module.addResults(searchHTML);
  563. module.inject.id(results);
  564. module.write.cache(searchTerm, {
  565. html : searchHTML,
  566. results : results
  567. });
  568. },
  569. remote: function(searchTerm, callback) {
  570. callback = $.isFunction(callback)
  571. ? callback
  572. : function(){}
  573. ;
  574. if($module.api('is loading')) {
  575. $module.api('abort');
  576. }
  577. module.setup.api(searchTerm, callback);
  578. $module
  579. .api('query')
  580. ;
  581. },
  582. object: function(searchTerm, source, searchFields) {
  583. searchTerm = module.remove.diacritics(String(searchTerm));
  584. var
  585. results = [],
  586. exactResults = [],
  587. fuzzyResults = [],
  588. searchExp = searchTerm.replace(regExp.escape, '\\$&'),
  589. matchRegExp = new RegExp(regExp.beginsWith + searchExp, 'i'),
  590. // avoid duplicates when pushing results
  591. addResult = function(array, result) {
  592. var
  593. notResult = ($.inArray(result, results) == -1),
  594. notFuzzyResult = ($.inArray(result, fuzzyResults) == -1),
  595. notExactResults = ($.inArray(result, exactResults) == -1)
  596. ;
  597. if(notResult && notFuzzyResult && notExactResults) {
  598. array.push(result);
  599. }
  600. }
  601. ;
  602. source = source || settings.source;
  603. searchFields = (searchFields !== undefined)
  604. ? searchFields
  605. : settings.searchFields
  606. ;
  607. // search fields should be array to loop correctly
  608. if(!Array.isArray(searchFields)) {
  609. searchFields = [searchFields];
  610. }
  611. // exit conditions if no source
  612. if(source === undefined || source === false) {
  613. module.error(error.source);
  614. return [];
  615. }
  616. // iterate through search fields looking for matches
  617. $.each(searchFields, function(index, field) {
  618. $.each(source, function(label, content) {
  619. var
  620. fieldExists = (typeof content[field] == 'string') || (typeof content[field] == 'number')
  621. ;
  622. if(fieldExists) {
  623. var text;
  624. if (typeof content[field] === 'string'){
  625. text = module.remove.diacritics(content[field]);
  626. } else {
  627. text = content[field].toString();
  628. }
  629. if( text.search(matchRegExp) !== -1) {
  630. // content starts with value (first in results)
  631. addResult(results, content);
  632. }
  633. else if(settings.fullTextSearch === 'exact' && module.exactSearch(searchTerm, text) ) {
  634. // content fuzzy matches (last in results)
  635. addResult(exactResults, content);
  636. }
  637. else if(settings.fullTextSearch == true && module.fuzzySearch(searchTerm, text) ) {
  638. // content fuzzy matches (last in results)
  639. addResult(fuzzyResults, content);
  640. }
  641. }
  642. });
  643. });
  644. $.merge(exactResults, fuzzyResults);
  645. $.merge(results, exactResults);
  646. return results;
  647. }
  648. },
  649. exactSearch: function (query, term) {
  650. query = query.toLowerCase();
  651. term = term.toLowerCase();
  652. return term.indexOf(query) > -1;
  653. },
  654. fuzzySearch: function(query, term) {
  655. var
  656. termLength = term.length,
  657. queryLength = query.length
  658. ;
  659. if(typeof query !== 'string') {
  660. return false;
  661. }
  662. query = query.toLowerCase();
  663. term = term.toLowerCase();
  664. if(queryLength > termLength) {
  665. return false;
  666. }
  667. if(queryLength === termLength) {
  668. return (query === term);
  669. }
  670. search: for (var characterIndex = 0, nextCharacterIndex = 0; characterIndex < queryLength; characterIndex++) {
  671. var
  672. queryCharacter = query.charCodeAt(characterIndex)
  673. ;
  674. while(nextCharacterIndex < termLength) {
  675. if(term.charCodeAt(nextCharacterIndex++) === queryCharacter) {
  676. continue search;
  677. }
  678. }
  679. return false;
  680. }
  681. return true;
  682. },
  683. parse: {
  684. response: function(response, searchTerm) {
  685. if(Array.isArray(response)){
  686. var o={};
  687. o[fields.results]=response;
  688. response = o;
  689. }
  690. var
  691. searchHTML = module.generateResults(response)
  692. ;
  693. module.verbose('Parsing server response', response);
  694. if(response !== undefined) {
  695. if(searchTerm !== undefined && response[fields.results] !== undefined) {
  696. module.addResults(searchHTML);
  697. module.inject.id(response[fields.results]);
  698. module.write.cache(searchTerm, {
  699. html : searchHTML,
  700. results : response[fields.results]
  701. });
  702. module.save.results(response[fields.results]);
  703. }
  704. }
  705. }
  706. },
  707. cancel: {
  708. query: function() {
  709. if( module.can.useAPI() ) {
  710. $module.api('abort');
  711. }
  712. }
  713. },
  714. has: {
  715. minimumCharacters: function() {
  716. var
  717. searchTerm = module.get.value(),
  718. numCharacters = searchTerm.length
  719. ;
  720. return (numCharacters >= settings.minCharacters);
  721. },
  722. results: function() {
  723. if($results.length === 0) {
  724. return false;
  725. }
  726. var
  727. html = $results.html()
  728. ;
  729. return html != '';
  730. }
  731. },
  732. clear: {
  733. cache: function(value) {
  734. var
  735. cache = $module.data(metadata.cache)
  736. ;
  737. if(!value) {
  738. module.debug('Clearing cache', value);
  739. $module.removeData(metadata.cache);
  740. }
  741. else if(value && cache && cache[value]) {
  742. module.debug('Removing value from cache', value);
  743. delete cache[value];
  744. $module.data(metadata.cache, cache);
  745. }
  746. }
  747. },
  748. read: {
  749. cache: function(name) {
  750. var
  751. cache = $module.data(metadata.cache)
  752. ;
  753. if(settings.cache) {
  754. module.verbose('Checking cache for generated html for query', name);
  755. return (typeof cache == 'object') && (cache[name] !== undefined)
  756. ? cache[name]
  757. : false
  758. ;
  759. }
  760. return false;
  761. }
  762. },
  763. create: {
  764. categoryResults: function(results) {
  765. var
  766. categoryResults = {}
  767. ;
  768. $.each(results, function(index, result) {
  769. if(!result.category) {
  770. return;
  771. }
  772. if(categoryResults[result.category] === undefined) {
  773. module.verbose('Creating new category of results', result.category);
  774. categoryResults[result.category] = {
  775. name : result.category,
  776. results : [result]
  777. };
  778. }
  779. else {
  780. categoryResults[result.category].results.push(result);
  781. }
  782. });
  783. return categoryResults;
  784. },
  785. id: function(resultIndex, categoryIndex) {
  786. var
  787. resultID = (resultIndex + 1), // not zero indexed
  788. letterID,
  789. id
  790. ;
  791. if(categoryIndex !== undefined) {
  792. // start char code for "A"
  793. letterID = String.fromCharCode(97 + categoryIndex);
  794. id = letterID + resultID;
  795. module.verbose('Creating category result id', id);
  796. }
  797. else {
  798. id = resultID;
  799. module.verbose('Creating result id', id);
  800. }
  801. return id;
  802. },
  803. results: function() {
  804. if($results.length === 0) {
  805. $results = $('<div />')
  806. .addClass(className.results)
  807. .appendTo($module)
  808. ;
  809. }
  810. }
  811. },
  812. inject: {
  813. result: function(result, resultIndex, categoryIndex) {
  814. module.verbose('Injecting result into results');
  815. var
  816. $selectedResult = (categoryIndex !== undefined)
  817. ? $results
  818. .children().eq(categoryIndex)
  819. .children(selector.results)
  820. .first()
  821. .children(selector.result)
  822. .eq(resultIndex)
  823. : $results
  824. .children(selector.result).eq(resultIndex)
  825. ;
  826. module.verbose('Injecting results metadata', $selectedResult);
  827. $selectedResult
  828. .data(metadata.result, result)
  829. ;
  830. },
  831. id: function(results) {
  832. module.debug('Injecting unique ids into results');
  833. var
  834. // since results may be object, we must use counters
  835. categoryIndex = 0,
  836. resultIndex = 0
  837. ;
  838. if(settings.type === 'category') {
  839. // iterate through each category result
  840. $.each(results, function(index, category) {
  841. if(category.results.length > 0){
  842. resultIndex = 0;
  843. $.each(category.results, function(index, result) {
  844. if(result.id === undefined) {
  845. result.id = module.create.id(resultIndex, categoryIndex);
  846. }
  847. module.inject.result(result, resultIndex, categoryIndex);
  848. resultIndex++;
  849. });
  850. categoryIndex++;
  851. }
  852. });
  853. }
  854. else {
  855. // top level
  856. $.each(results, function(index, result) {
  857. if(result.id === undefined) {
  858. result.id = module.create.id(resultIndex);
  859. }
  860. module.inject.result(result, resultIndex);
  861. resultIndex++;
  862. });
  863. }
  864. return results;
  865. }
  866. },
  867. save: {
  868. results: function(results) {
  869. module.verbose('Saving current search results to metadata', results);
  870. $module.data(metadata.results, results);
  871. }
  872. },
  873. write: {
  874. cache: function(name, value) {
  875. var
  876. cache = ($module.data(metadata.cache) !== undefined)
  877. ? $module.data(metadata.cache)
  878. : {}
  879. ;
  880. if(settings.cache) {
  881. module.verbose('Writing generated html to cache', name, value);
  882. cache[name] = value;
  883. $module
  884. .data(metadata.cache, cache)
  885. ;
  886. }
  887. }
  888. },
  889. addResults: function(html) {
  890. if( $.isFunction(settings.onResultsAdd) ) {
  891. if( settings.onResultsAdd.call($results, html) === false ) {
  892. module.debug('onResultsAdd callback cancelled default action');
  893. return false;
  894. }
  895. }
  896. if(html) {
  897. $results
  898. .html(html)
  899. ;
  900. module.refreshResults();
  901. if(settings.selectFirstResult) {
  902. module.select.firstResult();
  903. }
  904. module.showResults();
  905. }
  906. else {
  907. module.hideResults(function() {
  908. $results.empty();
  909. });
  910. }
  911. },
  912. showResults: function(callback) {
  913. callback = $.isFunction(callback)
  914. ? callback
  915. : function(){}
  916. ;
  917. if(resultsDismissed) {
  918. return;
  919. }
  920. if(!module.is.visible() && module.has.results()) {
  921. if( module.can.transition() ) {
  922. module.debug('Showing results with css animations');
  923. $results
  924. .transition({
  925. animation : settings.transition + ' in',
  926. debug : settings.debug,
  927. verbose : settings.verbose,
  928. duration : settings.duration,
  929. onShow : function() {
  930. var $firstResult = $module.find(selector.result).eq(0);
  931. if($firstResult.length > 0) {
  932. module.ensureVisible($firstResult);
  933. }
  934. },
  935. onComplete : function() {
  936. callback();
  937. },
  938. queue : true
  939. })
  940. ;
  941. }
  942. else {
  943. module.debug('Showing results with javascript');
  944. $results
  945. .stop()
  946. .fadeIn(settings.duration, settings.easing)
  947. ;
  948. }
  949. settings.onResultsOpen.call($results);
  950. }
  951. },
  952. hideResults: function(callback) {
  953. callback = $.isFunction(callback)
  954. ? callback
  955. : function(){}
  956. ;
  957. if( module.is.visible() ) {
  958. if( module.can.transition() ) {
  959. module.debug('Hiding results with css animations');
  960. $results
  961. .transition({
  962. animation : settings.transition + ' out',
  963. debug : settings.debug,
  964. verbose : settings.verbose,
  965. duration : settings.duration,
  966. onComplete : function() {
  967. callback();
  968. },
  969. queue : true
  970. })
  971. ;
  972. }
  973. else {
  974. module.debug('Hiding results with javascript');
  975. $results
  976. .stop()
  977. .fadeOut(settings.duration, settings.easing)
  978. ;
  979. }
  980. settings.onResultsClose.call($results);
  981. }
  982. },
  983. generateResults: function(response) {
  984. module.debug('Generating html from response', response);
  985. var
  986. template = settings.templates[settings.type],
  987. isProperObject = ($.isPlainObject(response[fields.results]) && !$.isEmptyObject(response[fields.results])),
  988. isProperArray = (Array.isArray(response[fields.results]) && response[fields.results].length > 0),
  989. html = ''
  990. ;
  991. if(isProperObject || isProperArray ) {
  992. if(settings.maxResults > 0) {
  993. if(isProperObject) {
  994. if(settings.type == 'standard') {
  995. module.error(error.maxResults);
  996. }
  997. }
  998. else {
  999. response[fields.results] = response[fields.results].slice(0, settings.maxResults);
  1000. }
  1001. }
  1002. if($.isFunction(template)) {
  1003. html = template(response, fields, settings.preserveHTML);
  1004. }
  1005. else {
  1006. module.error(error.noTemplate, false);
  1007. }
  1008. }
  1009. else if(settings.showNoResults) {
  1010. html = module.displayMessage(error.noResults, 'empty', error.noResultsHeader);
  1011. }
  1012. settings.onResults.call(element, response);
  1013. return html;
  1014. },
  1015. displayMessage: function(text, type, header) {
  1016. type = type || 'standard';
  1017. module.debug('Displaying message', text, type, header);
  1018. module.addResults( settings.templates.message(text, type, header) );
  1019. return settings.templates.message(text, type, header);
  1020. },
  1021. setting: function(name, value) {
  1022. if( $.isPlainObject(name) ) {
  1023. $.extend(true, settings, name);
  1024. }
  1025. else if(value !== undefined) {
  1026. settings[name] = value;
  1027. }
  1028. else {
  1029. return settings[name];
  1030. }
  1031. },
  1032. internal: function(name, value) {
  1033. if( $.isPlainObject(name) ) {
  1034. $.extend(true, module, name);
  1035. }
  1036. else if(value !== undefined) {
  1037. module[name] = value;
  1038. }
  1039. else {
  1040. return module[name];
  1041. }
  1042. },
  1043. debug: function() {
  1044. if(!settings.silent && settings.debug) {
  1045. if(settings.performance) {
  1046. module.performance.log(arguments);
  1047. }
  1048. else {
  1049. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1050. module.debug.apply(console, arguments);
  1051. }
  1052. }
  1053. },
  1054. verbose: function() {
  1055. if(!settings.silent && settings.verbose && settings.debug) {
  1056. if(settings.performance) {
  1057. module.performance.log(arguments);
  1058. }
  1059. else {
  1060. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  1061. module.verbose.apply(console, arguments);
  1062. }
  1063. }
  1064. },
  1065. error: function() {
  1066. if(!settings.silent) {
  1067. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  1068. module.error.apply(console, arguments);
  1069. }
  1070. },
  1071. performance: {
  1072. log: function(message) {
  1073. var
  1074. currentTime,
  1075. executionTime,
  1076. previousTime
  1077. ;
  1078. if(settings.performance) {
  1079. currentTime = new Date().getTime();
  1080. previousTime = time || currentTime;
  1081. executionTime = currentTime - previousTime;
  1082. time = currentTime;
  1083. performance.push({
  1084. 'Name' : message[0],
  1085. 'Arguments' : [].slice.call(message, 1) || '',
  1086. 'Element' : element,
  1087. 'Execution Time' : executionTime
  1088. });
  1089. }
  1090. clearTimeout(module.performance.timer);
  1091. module.performance.timer = setTimeout(module.performance.display, 500);
  1092. },
  1093. display: function() {
  1094. var
  1095. title = settings.name + ':',
  1096. totalTime = 0
  1097. ;
  1098. time = false;
  1099. clearTimeout(module.performance.timer);
  1100. $.each(performance, function(index, data) {
  1101. totalTime += data['Execution Time'];
  1102. });
  1103. title += ' ' + totalTime + 'ms';
  1104. if(moduleSelector) {
  1105. title += ' \'' + moduleSelector + '\'';
  1106. }
  1107. if($allModules.length > 1) {
  1108. title += ' ' + '(' + $allModules.length + ')';
  1109. }
  1110. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  1111. console.groupCollapsed(title);
  1112. if(console.table) {
  1113. console.table(performance);
  1114. }
  1115. else {
  1116. $.each(performance, function(index, data) {
  1117. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  1118. });
  1119. }
  1120. console.groupEnd();
  1121. }
  1122. performance = [];
  1123. }
  1124. },
  1125. invoke: function(query, passedArguments, context) {
  1126. var
  1127. object = instance,
  1128. maxDepth,
  1129. found,
  1130. response
  1131. ;
  1132. passedArguments = passedArguments || queryArguments;
  1133. context = element || context;
  1134. if(typeof query == 'string' && object !== undefined) {
  1135. query = query.split(/[\. ]/);
  1136. maxDepth = query.length - 1;
  1137. $.each(query, function(depth, value) {
  1138. var camelCaseValue = (depth != maxDepth)
  1139. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  1140. : query
  1141. ;
  1142. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  1143. object = object[camelCaseValue];
  1144. }
  1145. else if( object[camelCaseValue] !== undefined ) {
  1146. found = object[camelCaseValue];
  1147. return false;
  1148. }
  1149. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  1150. object = object[value];
  1151. }
  1152. else if( object[value] !== undefined ) {
  1153. found = object[value];
  1154. return false;
  1155. }
  1156. else {
  1157. return false;
  1158. }
  1159. });
  1160. }
  1161. if( $.isFunction( found ) ) {
  1162. response = found.apply(context, passedArguments);
  1163. }
  1164. else if(found !== undefined) {
  1165. response = found;
  1166. }
  1167. if(Array.isArray(returnedValue)) {
  1168. returnedValue.push(response);
  1169. }
  1170. else if(returnedValue !== undefined) {
  1171. returnedValue = [returnedValue, response];
  1172. }
  1173. else if(response !== undefined) {
  1174. returnedValue = response;
  1175. }
  1176. return found;
  1177. }
  1178. };
  1179. if(methodInvoked) {
  1180. if(instance === undefined) {
  1181. module.initialize();
  1182. }
  1183. module.invoke(query);
  1184. }
  1185. else {
  1186. if(instance !== undefined) {
  1187. instance.invoke('destroy');
  1188. }
  1189. module.initialize();
  1190. }
  1191. })
  1192. ;
  1193. return (returnedValue !== undefined)
  1194. ? returnedValue
  1195. : this
  1196. ;
  1197. };
  1198. $.fn.search.settings = {
  1199. name : 'Search',
  1200. namespace : 'search',
  1201. silent : false,
  1202. debug : false,
  1203. verbose : false,
  1204. performance : true,
  1205. // template to use (specified in settings.templates)
  1206. type : 'standard',
  1207. // minimum characters required to search
  1208. minCharacters : 1,
  1209. // whether to select first result after searching automatically
  1210. selectFirstResult : false,
  1211. // API config
  1212. apiSettings : false,
  1213. // object to search
  1214. source : false,
  1215. // Whether search should query current term on focus
  1216. searchOnFocus : true,
  1217. // fields to search
  1218. searchFields : [
  1219. 'id',
  1220. 'title',
  1221. 'description'
  1222. ],
  1223. // field to display in standard results template
  1224. displayField : '',
  1225. // search anywhere in value (set to 'exact' to require exact matches
  1226. fullTextSearch : 'exact',
  1227. // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...)
  1228. ignoreDiacritics : false,
  1229. // whether to add events to prompt automatically
  1230. automatic : true,
  1231. // delay before hiding menu after blur
  1232. hideDelay : 0,
  1233. // delay before searching
  1234. searchDelay : 200,
  1235. // maximum results returned from search
  1236. maxResults : 7,
  1237. // whether to store lookups in local cache
  1238. cache : true,
  1239. // whether no results errors should be shown
  1240. showNoResults : true,
  1241. // preserve possible html of resultset values
  1242. preserveHTML : true,
  1243. // transition settings
  1244. transition : 'scale',
  1245. duration : 200,
  1246. easing : 'easeOutExpo',
  1247. // callbacks
  1248. onSelect : false,
  1249. onResultsAdd : false,
  1250. onSearchQuery : function(query){},
  1251. onResults : function(response){},
  1252. onResultsOpen : function(){},
  1253. onResultsClose : function(){},
  1254. className: {
  1255. animating : 'animating',
  1256. active : 'active',
  1257. empty : 'empty',
  1258. focus : 'focus',
  1259. hidden : 'hidden',
  1260. loading : 'loading',
  1261. results : 'results',
  1262. pressed : 'down'
  1263. },
  1264. error : {
  1265. source : 'Cannot search. No source used, and Semantic API module was not included',
  1266. noResultsHeader : 'No Results',
  1267. noResults : 'Your search returned no results',
  1268. logging : 'Error in debug logging, exiting.',
  1269. noEndpoint : 'No search endpoint was specified',
  1270. noTemplate : 'A valid template name was not specified.',
  1271. oldSearchSyntax : 'searchFullText setting has been renamed fullTextSearch for consistency, please adjust your settings.',
  1272. serverError : 'There was an issue querying the server.',
  1273. maxResults : 'Results must be an array to use maxResults setting',
  1274. method : 'The method you called is not defined.',
  1275. noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.'
  1276. },
  1277. metadata: {
  1278. cache : 'cache',
  1279. results : 'results',
  1280. result : 'result'
  1281. },
  1282. regExp: {
  1283. escape : /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,
  1284. beginsWith : '(?:\s|^)'
  1285. },
  1286. // maps api response attributes to internal representation
  1287. fields: {
  1288. categories : 'results', // array of categories (category view)
  1289. categoryName : 'name', // name of category (category view)
  1290. categoryResults : 'results', // array of results (category view)
  1291. description : 'description', // result description
  1292. image : 'image', // result image
  1293. price : 'price', // result price
  1294. results : 'results', // array of results (standard)
  1295. title : 'title', // result title
  1296. url : 'url', // result url
  1297. action : 'action', // "view more" object name
  1298. actionText : 'text', // "view more" text
  1299. actionURL : 'url' // "view more" url
  1300. },
  1301. selector : {
  1302. prompt : '.prompt',
  1303. searchButton : '.search.button',
  1304. results : '.results',
  1305. message : '.results > .message',
  1306. category : '.category',
  1307. result : '.result',
  1308. title : '.title, .name'
  1309. },
  1310. templates: {
  1311. escape: function(string, preserveHTML) {
  1312. if (preserveHTML){
  1313. return string;
  1314. }
  1315. var
  1316. badChars = /[<>"'`]/g,
  1317. shouldEscape = /[&<>"'`]/,
  1318. escape = {
  1319. "<": "&lt;",
  1320. ">": "&gt;",
  1321. '"': "&quot;",
  1322. "'": "&#x27;",
  1323. "`": "&#x60;"
  1324. },
  1325. escapedChar = function(chr) {
  1326. return escape[chr];
  1327. }
  1328. ;
  1329. if(shouldEscape.test(string)) {
  1330. string = string.replace(/&(?![a-z0-9#]{1,6};)/, "&amp;");
  1331. return string.replace(badChars, escapedChar);
  1332. }
  1333. return string;
  1334. },
  1335. message: function(message, type, header) {
  1336. var
  1337. html = ''
  1338. ;
  1339. if(message !== undefined && type !== undefined) {
  1340. html += ''
  1341. + '<div class="message ' + type + '">'
  1342. ;
  1343. if(header) {
  1344. html += ''
  1345. + '<div class="header">' + header + '</div>'
  1346. ;
  1347. }
  1348. html += ' <div class="description">' + message + '</div>';
  1349. html += '</div>';
  1350. }
  1351. return html;
  1352. },
  1353. category: function(response, fields, preserveHTML) {
  1354. var
  1355. html = '',
  1356. escape = $.fn.search.settings.templates.escape
  1357. ;
  1358. if(response[fields.categoryResults] !== undefined) {
  1359. // each category
  1360. $.each(response[fields.categoryResults], function(index, category) {
  1361. if(category[fields.results] !== undefined && category.results.length > 0) {
  1362. html += '<div class="category">';
  1363. if(category[fields.categoryName] !== undefined) {
  1364. html += '<div class="name">' + escape(category[fields.categoryName], preserveHTML) + '</div>';
  1365. }
  1366. // each item inside category
  1367. html += '<div class="results">';
  1368. $.each(category.results, function(index, result) {
  1369. if(result[fields.url]) {
  1370. html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">';
  1371. }
  1372. else {
  1373. html += '<a class="result">';
  1374. }
  1375. if(result[fields.image] !== undefined) {
  1376. html += ''
  1377. + '<div class="image">'
  1378. + ' <img src="' + result[fields.image].replace(/"/g,"") + '">'
  1379. + '</div>'
  1380. ;
  1381. }
  1382. html += '<div class="content">';
  1383. if(result[fields.price] !== undefined) {
  1384. html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>';
  1385. }
  1386. if(result[fields.title] !== undefined) {
  1387. html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>';
  1388. }
  1389. if(result[fields.description] !== undefined) {
  1390. html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>';
  1391. }
  1392. html += ''
  1393. + '</div>'
  1394. ;
  1395. html += '</a>';
  1396. });
  1397. html += '</div>';
  1398. html += ''
  1399. + '</div>'
  1400. ;
  1401. }
  1402. });
  1403. if(response[fields.action]) {
  1404. if(fields.actionURL === false) {
  1405. html += ''
  1406. + '<div class="action">'
  1407. + escape(response[fields.action][fields.actionText], preserveHTML)
  1408. + '</div>';
  1409. } else {
  1410. html += ''
  1411. + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">'
  1412. + escape(response[fields.action][fields.actionText], preserveHTML)
  1413. + '</a>';
  1414. }
  1415. }
  1416. return html;
  1417. }
  1418. return false;
  1419. },
  1420. standard: function(response, fields, preserveHTML) {
  1421. var
  1422. html = '',
  1423. escape = $.fn.search.settings.templates.escape
  1424. ;
  1425. if(response[fields.results] !== undefined) {
  1426. // each result
  1427. $.each(response[fields.results], function(index, result) {
  1428. if(result[fields.url]) {
  1429. html += '<a class="result" href="' + result[fields.url].replace(/"/g,"") + '">';
  1430. }
  1431. else {
  1432. html += '<a class="result">';
  1433. }
  1434. if(result[fields.image] !== undefined) {
  1435. html += ''
  1436. + '<div class="image">'
  1437. + ' <img src="' + result[fields.image].replace(/"/g,"") + '">'
  1438. + '</div>'
  1439. ;
  1440. }
  1441. html += '<div class="content">';
  1442. if(result[fields.price] !== undefined) {
  1443. html += '<div class="price">' + escape(result[fields.price], preserveHTML) + '</div>';
  1444. }
  1445. if(result[fields.title] !== undefined) {
  1446. html += '<div class="title">' + escape(result[fields.title], preserveHTML) + '</div>';
  1447. }
  1448. if(result[fields.description] !== undefined) {
  1449. html += '<div class="description">' + escape(result[fields.description], preserveHTML) + '</div>';
  1450. }
  1451. html += ''
  1452. + '</div>'
  1453. ;
  1454. html += '</a>';
  1455. });
  1456. if(response[fields.action]) {
  1457. if(fields.actionURL === false) {
  1458. html += ''
  1459. + '<div class="action">'
  1460. + escape(response[fields.action][fields.actionText], preserveHTML)
  1461. + '</div>';
  1462. } else {
  1463. html += ''
  1464. + '<a href="' + response[fields.action][fields.actionURL].replace(/"/g,"") + '" class="action">'
  1465. + escape(response[fields.action][fields.actionText], preserveHTML)
  1466. + '</a>';
  1467. }
  1468. }
  1469. return html;
  1470. }
  1471. return false;
  1472. }
  1473. }
  1474. };
  1475. })( jQuery, window, document );