1jQuery(function ($) { 2 "use strict"; 3 4 /* =========================== 5 * UTILITIES 6 * =========================== */ 7 const isRTL = document.documentElement.lang.slice(0, 2) === "ar"; 8 const locale = isRTL ? "ar-AE" : "en-US"; 9 10 const i18n = $.extend({ 11 noTitle: "No title available", 12 noDesc: "No description available", 13 resultsFound: "Results Found", 14 resultsFoundPrefix: "", 15 servicesHeader: "Services", 16 contentHeader: "Content", 17 filesHeader: "Files", 18 noResults: "No results found. Please try a different term", 19 enterTerm: "Enter a search term above to see results", 20 startSug: "Start searching to see suggestions", 21 noSug: "No suggestions found", 22 micPerm: 23 'Permission Access to microphone denied, to use this feature please allow microphone access to this site, for help <a href="https://support.google.com/chrome/answer/2693767?co=GENIE.Platform%3DDesktop" target="_blank" title="Google Chrome Help">click here</a>', 24 micNoSpeech: 25 "No audio detected, please check your microphone settings and try again", 26 open: "Open", 27 share: "Share", 28 download: "Download", 29 fileText: "FILE", 30 documentText: "Document", 31 lastModified: "Last modified", 32 }, isRTL ? { 33 enterTerm: "أدخ٠عبارة Ø£Ø¹ÙØ§Ù ÙÙØªÙ عرض اÙÙØªØ§Ø¦Ø¬", 34 noSug: "ÙÙ ÙØªÙ Ø§ÙØ¹Ø«Ùر عÙÙ Ø§Ù Ø§ÙØªØ±Ø§ØØ§Øª", 35 filesHeader: "اÙÙ Ø³ØªÙØ¯Ø§Øª ", 36 contentHeader: "اÙÙ ØØªÙÙ", 37 servicesHeader: "Ø§ÙØ®Ø¯Ù ات", 38 startSug: "ابدأ Ø§ÙØ¨ØØ« ÙØ¹Ø±Ø¶ Ø§ÙØ§ÙØªØ±Ø§ØØ§Øª", 39 resultsFound: "ØªÙ Ø§ÙØ¹Ø«Ùر عÙÙ ", 40 resultsFoundPrefix: "ÙØªØ§Ø¦Ø¬", 41 noResults: "ÙÙ ÙØªÙ Ø§ÙØ¹Ø«Ùر عÙÙ Ø§Ù ÙØªØ§Ø¦Ø¬. ÙØ±Ø¬Ù تجربة Ù ØµØ·ÙØ ٠ختÙÙ", 42 micPerm: 43 '<a href="https://support.google.com/chrome/answer/2693767?co=GENIE.Platform%3DDesktop" target="_blank" title="Google Chrome Help">ØªÙ Ø±ÙØ¶ إذ٠اÙÙØµÙ٠إÙ٠اÙÙ ÙÙØ±ÙÙÙÙØ ÙØ§Ø³ØªØ®Ø¯Ø§Ù ÙØ°Ù اÙÙ ÙØ²Ø©Ø ÙØ±Ø¬Ù ØªÙØ¹Ù٠استخدا٠اÙÙ ÙÙØ±ÙÙÙÙ ÙÙÙØµÙ٠إÙÙ ÙØ°Ø§ اÙÙ ÙÙØ¹ Ø ÙÙ٠ساعدة اÙÙØ± ÙÙØ§</a>', 44 noTitle: "ÙØ§ ÙÙØ¬Ø¯ عÙÙØ§Ù ٠تاØ", 45 noDesc: "ÙØ§ ÙÙØ¬Ø¯ ÙØµÙ ٠تاØ", 46 open: "ÙØªØ", 47 share: "شارÙ", 48 download: "تØÙ ÙÙ", 49 fileText: "Ù ÙÙ", 50 documentText: "ÙØ«ÙÙØ©", 51 lastModified: "ØªØ§Ø±ÙØ® اخر تعدÙÙ", 52 micNoSpeech: 53 "ÙÙ ÙØªÙ Ø§ÙØªØ´Ø§Ù ØµÙØª Ø ÙØ±Ø¬Ù Ø§ÙØªØÙ٠٠٠إعدادات اÙÙ ÙÙØ±ÙÙÙÙ ÙØ¥Ø¹Ø§Ø¯Ø© اÙÙ ØØ§ÙÙØ©", 54 } : {}); 55 56 const serviceImageUrl = "https://www.dm.gov.ae/wp-content/uploads/2020/06/services_screen.jpg"; 57 const serviceScreenCaptureEnUrl = "https://www.dm.gov.ae/wp-content/uploads/2020/06/services_screen.jpg"; 58 const serviceScreenCaptureArUrl = "https://www.dm.gov.ae/wp-content/uploads/2020/06/services_screen.jpg"; 59 60 const esc = (s) => String(s ?? "") 61 .replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""); 62 63 const fmtDate = (ts) => { 64 try { return new Date(ts).toLocaleDateString(locale); } 65 catch { return "-"; } 66 }; 67 68 // Simple highlighter without raw regex injection 69 const highlight = (text, q) => { 70 if (!text || !q) return text || ""; 71 const norm = (v) => v.toString().toLowerCase(); 72 const t = text.toString(); 73 const idx = norm(t).indexOf(norm(q)); 74 if (idx === -1) return t; 75 return t.slice(0, idx) + "<em>" + t.slice(idx, idx + q.length) + "</em>" + t.slice(idx + q.length); 76 }; 77 78 const detectBrowser = () => { 79 const ua = navigator.userAgent; 80 if ((ua.indexOf("Opera") !== -1 && ua.indexOf("OPR") !== -1)) return "Opera"; 81 if (ua.indexOf("Chrome") !== -1) return "Chrome"; 82 if (ua.indexOf("Safari") !== -1) return "Safari"; 83 if (ua.indexOf("Firefox") !== -1) return "Firefox"; 84 if (ua.indexOf("MSIE") !== -1 || !!document.documentMode) return "IE"; 85 return "Unknown"; 86 }; 87 88 // tiny debounce 89 const debounce = (fn, ms=300) => { 90 let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn.apply(null,args), ms); }; 91 }; 92 93 /* =========================== 94 * CACHED SELECTORS 95 * =========================== */ 96 const $resultsPanel = $(".search-results-results"); 97 const $pagingPanel = $(".search-paging-pages"); 98 const $pagingInfo = $(".search-results-pageinfo"); 99 const $sugPanel = $(".search-results-suggestions-list"); 100 const $errorPanel = $(".search-error-panel"); 101 const $termEcho = $(".search-results-term-keyword"); 102 const $searchInfo = $(".search-results-info"); 103 const $searchCount = $(".search-results-count-no"); 104 const $searchInput = $("#search_page_input"); 105 106 const $filter = $("#search_filter_input"); 107 const $perPage = $("#search-paging-perpage-input"); 108 const $sort = $("#search_sort_input"); 109 110 let viewAll = false;
111 112 /* =========================== 113 * BASIC UI (existing small pieces) 114 * =========================== */ 115 $(".dubai-more-box").on("click", function () { 116 const cls = $(this).data("div"); 117 $(".dubai-more-box").not(this).removeClass("active"); 118 $(this).addClass("active"); 119 $(".dubaimore_content").hide().filter("." + cls).show(); 120 }); 121 122 // enable Fancybox when user tabs into right column 123 $(".search-results-right").on("focusin", function () { 124 $("a.ajaxFancyBox").fancybox({ padding: 0 }); 125 }); 126 127 if (isRTL) { 128 $('.rtl #search-top-input-input').attr("placeholder", "Ø§ÙØ¨ØØ« عÙ"); 129 $('.rtl .filterby').text("ØªØ±ØªÙØ¨ اÙÙØªØ§Ø¦Ø¬ :"); 130 $('.rtl .sortby').text("ØªØ±ØªÙØ¨ ØØ³Ø¨ :"); 131 $('.rtl .perpage').text("ÙÙÙ ØµÙØØ©"); 132 $('.rtl .showpaging').text("عرض"); 133 $('.rtl .search-filter-services-lbl').text("Ø§ÙØ®Ø¯Ù ات"); 134 $('.rtl .search-filter-content-lbl').text("اÙÙ ØØªÙÙ"); 135 $('.rtl .search-filter-documents-lbl').text("اÙÙ ÙÙØ§Øª"); 136 $('.rtl .search-filter-all-lbl').text("عرض اÙÙÙ"); 137 $('.rtl .searchsuggestionslabel').text("Ø§ÙØªØ±Ø§ØØ§Øª Ø§ÙØ¨ØØ« :"); 138 $('.rtl .search-sort-services-lbl').text("Ø§ÙØ£Ùد٠"); 139 $('.rtl .search-sort-content-lbl').text("Ø§ÙØ£ØØ¯Ø«"); 140 $('.rtl .search-results-term-text').text("ÙØªØ§Ø¦Ø¬ Ø§ÙØ¨ØØ« ع٠"); 141 $('.rtl .search-results-count-text').text("ØªÙ Ø§ÙØ¹Ø«Ùر عÙÙ:"); 142 $('.rtl .no-searchMsg').text("أدخ٠عبارة Ø£Ø¹ÙØ§Ù ÙÙØªÙ عرض اÙÙØªØ§Ø¦Ø¬"); 143 $('.rtl .search-results-suggestions-list li:nth-of-type(1)').text("ابدأ Ø§ÙØ¨ØØ« ÙØ¹Ø±Ø¶ Ø§ÙØ§ÙØªØ±Ø§ØØ§Øª"); 144 $('.rtl .search-header-area .search span').text("إجراء Ø¨ØØ«"); 145 $('.rtl .iconSerch').attr("href", "https://stgportal03.dm.gov.ae/search-dubai-municipality-arabic/?lang=ar"); 146 } 147 148 /* =========================== 149 * ADDSEARCH CLIENTS 150 * =========================== */ 151 const client = new AddSearchClient("b3b578f840aadc0f27cf47ff57bce2a6"); 152 client.setThrottleTime(500); 153 154 // Header search UIs (desktop + mobile) 155 const conf = { searchResultsPageUrl: "/search", collectAnalytics: true }; 156 157 const uiDesktop = new AddSearchUI(client, conf); 158 uiDesktop.searchField({ 159 containerId: "searchcontainer-header", 160 placeholder: isRTL ? "Ø§Ø¨ØØ«" : "Search", 161 autofocus: false 162 }); 163 164 const uiMobile = new AddSearchUI(client, conf); 165 uiMobile.searchField({ 166 containerId: "searchcontainer-headermobile", 167 placeholder: isRTL ? "Ø§Ø¨ØØ«" : "Search", 168 autofocus: false 169 }); 170 171 const acTpl = ` 172 <div class="addsearch-autocomplete" style="position:relative;"> 173 {{#gt searchResults.results.length 0}} 174 <ul> 175 {{#each ../searchResults.results}} 176 <li id="{{id}}" class="autocomplete-item search-item-suggestion" 177 data-title="{{title}}" data-url="{{url}}"> 178 <span class="main-image {{document_type}} {{#unless images.main}}noimage{{/unless}}" 179 {{#if images.main}} style="background-image:url({{images.main}})"{{/if}} 180 {{#if custom_fields.is_service}} style="background-image:url('/wp-content/uploads/2020/06/services_screen.jpg')"{{/if}}> 181 </span> 182 <div><strong>{{title}}</strong><br />{{{highlight}}}</div> 183 </li> 184 {{/each}} 185 </ul> 186 {{/gt}} 187 </div>`; 188 189 const acClientDesktop = new AddSearchClient("b3b578f840aadc0f27cf47ff57bce2a6"); 190 acClientDesktop.setPaging(1, 5, "relevance", "desc"); 191 uiDesktop.autocomplete({ 192 containerId: "autocomplete-header", 193 template: acTpl, 194 hideAutomatically: false, 195 infiniteScrollElement: document.querySelector("#scrollable-header"), 196 sources: [{ type: AddSearchUI.AUTOCOMPLETE_TYPE.SEARCH, client: acClientDesktop, collectSearchAnalytics: true, jsonKey: "results" }] 197 }); 198 uiDesktop.start(); 199 200 const acClientMobile = new AddSearchClient("b3b578f840aadc0f27cf47ff57bce2a6"); 201 acClientMobile.setPaging(1, 5, "relevance", "desc"); 202 acClientMobile.setThrottleTime(500); 203 uiMobile.autocomplete({ 204 containerId: "autocomplete-headermobile", 205 template: acTpl, 206 hideAutomatically: false, 207 infiniteScrollElement: document.querySelector("#scrollable-headermobile"), 208 sources: [{ type: AddSearchUI.AUTOCOMPLETE_TYPE.SEARCH, client: acClientMobile, collectSearchAn
208alytics: true, jsonKey: "results" }] 209 }); 210 uiMobile.start(); 211 212 // Header âSearchâ buttons & Enter handling 213 $(document).on("click", ".searchNewBtn", function () { 214 const v = $("#searchcontainer-header").find("input").val(); 215 localStorage.setItem("searchValue", v); 216 }); 217 $(document).on("keyup", "#searchcontainer-header input", function (e) { 218 if (e.keyCode === 13) { 219 const v = $("#searchcontainer-header").find("input").val(); 220 localStorage.setItem("searchValue", v); 221 window.location.href = $("body").hasClass("rtl") 222 ? "/search-dubai-municipality-arabic/?lang=ar" : "/search/"; 223 e.preventDefault(); 224 } 225 }); 226 $(document).on("click", "#scrollable-header .search-item-suggestion", function () { 227 client.sendStatsEvent("click", $("#searchcontainer-header").find("input").val(), 228 { documentId: $(this).attr("id"), position: 1 }); 229 window.location.href = $(this).data("url"); 230 }); 231 232 $(document).on("keyup", "#searchcontainer-headermobile input", function (e) { 233 if (e.keyCode === 13) { 234 const v = $("#searchcontainer-headermobile").find("input").val(); 235 localStorage.setItem("searchValue", v); 236 window.location.href = $("body").hasClass("rtl") 237 ? "/search-dubai-municipality-arabic/?lang=ar" : "/search/"; 238 e.preventDefault(); 239 } 240 }); 241 $(document).on("click", "#scrollable-headermobile .search-item-suggestion", function () { 242 client.sendStatsEvent("click", $("#searchcontainer-headermobile").find("input").val(), 243 { documentId: $(this).attr("id"), position: 1 }); 244 window.location.href = $(this).data("url"); 245 }); 246 247 /* =========================== 248 * RENDER HELPERS 249 * =========================== */ 250 const headerHTML = (label) => ` 251 <div class="search-result-header fa fa-angle-down"> 252 <span class="search-result-header-text">${esc(label)}</span> 253 </div>`; 254 255 const moreInfoHTML = (r) => { 256 if (r.category !== "Document") return ""; 257 const canShare = !!navigator.share; 258 return ` 259 <div class="search-result-document-more-info"> 260 <div class="more-info-text"> 261 <h3>${esc(r.titleNoEm || r.title || i18n.noTitle)}</h3> 262 <p class="more-info-file">${i18n.fileText}</p> 263 <p class="more-info-date">${i18n.lastModified}: <span>${esc(r.timestamp || "-")}</span></p> 264 </div> 265 <div class="more-info-actions"> 266 <a href="${esc(r.url)}" title="${i18n.open}" class="more-info-open"> 267 <i class="fas fa-external-link"></i>${i18n.open} 268 </a> 269 ${canShare ? ` 270 <a href="#" data-url="${esc(r.url)}" data-title="${esc(r.titleNoEm || r.title || "")}" 271 class="more-info-share" title="${i18n.share}"> 272 <i class="fas fa-share-alt"></i> ${i18n.share} 273 </a>` : ""} 274 <a href="${esc(r.url)}" download target="_blank" title="${i18n.download}" class="more-info-download"> 275 <i class="fas fa-download"></i> ${i18n.download} 276 </a> 277 </div> 278 </div>`; 279 }; 280 281 const imageHTML = (r) => { 282 if (!r.imageUrl) { 283 return `<div class="search-result-image-placeholder"><i class="far fa-image"></i></div>`; 284 } 285 return `<img src="${esc(r.imageUrl)}" title="${esc(r.titleNoEm || r.title || "")}" />`; 286 }; 287 288 const capturePreviewLink = (r) => { 289 if (!r.captureUrl) return ""; 290 const title = esc(r.titleNoEm || r.title || ""); 291 return ` 292 <a href="${esc(r.captureUrl)}" class="ajaxFancyBox" data-fancybox="${esc(r.id || "")}" 293 data-title="${title}" title="${title}" style="color:#800823;"> 294 <i class="fa fa-info-circle"></i> 295 </a>`; 296 }; 297 298 const resultHTML = (r) => ` 299 <div class="search-results-result search-result-${esc(r.category)} thin-bottom-border" 300 data-capture-url="${esc(r.captureUrl || "")}"> 301 <div class="search-result-result-info"> 302 <div class="search-result-title"> 303 <a href="${esc(r.url||"#")}" data-id="${esc(r.id||"")}" data-pos="${esc(r.pos||0)}" 304 class="search_result_link" title="${esc(r.titleNoEm||r.title||i18n.noTitle)}"> 305 <h2 class="search-results-result-title">${r.title || i18n.noTitle}</h2> 306 </a> 307 <p class="search-results-result-desc">${r.desc || i18n.noDesc}</p> 308 </div> 309 <div class="search-results-result-cat">${esc(r.displayCat || "")}</div> 310 </div> 311 </div>`; 312 313 /* =========================== 314 * SEARCH RENDERING 315 * =========================== */ 316 function renderResults(res) { 317 318 if (!res || !Array.isArray(res.hits)) { 319 $resultsPanel.html(""); 320 $pagingPanel.html(" "); 321 return; 322 } 323 324 const q = $searchInput.val(); 325 const docs = [], svcs = [], content = []; 326 let pos = 1; 327
328 res.hits.forEach((el) => { 329 const r = { 330 id: el.id || "", 331 pos: pos++, 332 imageUrl: "", 333 title: i18n.noTitle, 334 titleNoEm: i18n.noTitle, 335 desc: i18n.noDesc, 336 url: el.url || "#", 337 timestamp: el.ts ? fmtDate(el.ts) : "-", 338 captureUrl: "", 339 category: "", 340 displayCat: "" 341 }; 342 343 if (el.images) { 344 r.imageUrl = el.images.main || el.images.capture || ""; 345 r.captureUrl = el.images.capture || ""; 346 } 347 348 if (el.title) { 349 r.titleNoEm = el.title; 350 r.title = highlight(esc(el.title), q); 351 } else if (el.highlight) { 352 r.title = el.highlight; 353 r.titleNoEm = el.highlight.replace(/<[^>]+>/g, ""); 354 } 355 356 if (el.highlight) r.desc = el.highlight; 357 else if (el.meta_description) r.desc = el.meta_description; 358 359 // Categorize 360 if (el.custom_fields && el.custom_fields.is_service === "1") { 361 r.category = "Service"; 362 r.displayCat = i18n.servicesHeader; 363 r.imageUrl = serviceImageUrl; 364 r.captureUrl = isRTL ? serviceScreenCaptureArUrl : serviceScreenCaptureEnUrl; 365 if (el.custom_fields.service_desc) { 366 r.desc = el.custom_fields.service_desc.replace(/(<([^>]+)>)/gi, "") 367 .replace(/^(.{200}[^\s]*).*/, "$1"); 368 } 369 svcs.push(r); 370 /*} else if (el.categories && el.categories.length > 2 && el.categories[1] === "1xwp-content") {*/ 371 } else if (el.custom_fields && el.custom_fields.isDocument === "1") { 372 r.category = "Document"; 373 r.displayCat = i18n.documentText; 374 docs.push(r); 375 } else if (el.categories && el.categories.length > 2 && el.categories[1] === "1xwp-content") { 376 } else { 377 r.category = "Content"; 378 r.displayCat = i18n.contentHeader; 379 /*if (el.categories && el.categories.length > 1) { 380 r.displayCat = el.categories[1].substring(2).replace(/-/g, " "); 381 } else { 382 r.displayCat = i18n.contentHeader; 383 }*/ 384 content.push(r); 385 } 386 }); 387 388 let html = ""; 389 if (svcs.length) { 390 //headerHTML(i18n.servicesHeader) 391 html += '<div class="service_block">'; 392 svcs.forEach((r) => html += resultHTML(r)); 393 html += "</div>"; 394 } 395 if (content.length) { 396 html += '<div class="content_block">'; 397 content.forEach((r) => html += resultHTML(r)); 398 html += "</div>"; 399 } 400 if (docs.length) { 401 html += '<div class="document_block">'; 402 docs.forEach((r) => html += resultHTML(r)); 403 html += "</div>"; 404 } 405 406 $resultsPanel.html(html); 407 408 renderPaging(res); 409 410 // hoverintent document info 411 if (typeof hoverintent !== "undefined") { 412 $(".search-result-Document").each(function () { 413 const el = this; 414 hoverintent( 415 el, 416 function () { 417 const $popup = $(el).find(".search-result-document-more-info"); 418 const pos = $(el).position(); 419 $popup.css({ top: pos.top }).show(); 420 }, 421 function () { $(el).find(".search-result-document-more-info").hide(); } 422 ); 423 }); 424 } 425 426 // web share 427 $resultsPanel.off("click.share").on("click.share", ".more-info-share", function (e) { 428 e.preventDefault(); 429 if (navigator.share) { 430 navigator.share({ url: $(this).data("url"), title: $(this).data("title") }); 431 } 432 }); 433 434 $searchInfo.show(); 435 $searchCount.text((res.total_hits || 0).toLocaleString(locale)); 436 } 437 438 function renderPaging(res) { 439 $pagingPanel.html(""); 440 441 if (!res || !res.total_hits) { 442 $resultsPanel.html(`<p class="search-result-empty">${i18n.noResults}</p>`); 443 return; 444 } 445 446 // --- basic variables --- 447 let pageSize = parseInt($perPage.val(), 10) || 10; 448 if (viewAll) pageSize = 300; 449 450 const totalResults = res.total_hits || 0; 451 const totalPages = Math.max(1, Math.ceil(totalResults / pageSize)); 452 const currentPage = Math.min(Math.max(res.page || 1, 1), totalPages); 453 454 // --- page info --- 455 const startIndex = (currentPage - 1) * pageSize + 1; 456 const endIndex = Math.min(currentPage * pageSize, totalResults);
457 var pageInfoText = `Showing ${startIndex.toLocaleString(locale)} to ${endIndex.toLocaleString(locale)} of ${totalResults.toLocaleString(locale)} results`; 458 if(isRTL){ 459 pageInfoText = `عرض ${startIndex.toLocaleString(locale)} Ø¥ÙÙ ${endIndex.toLocaleString(locale)} ٠٠أص٠${totalResults.toLocaleString(locale)} ÙØªÙجة`; 460 461 } 462 463 464 $(".search-results-pageinfo-range").remove(); 465 $pagingInfo.html(`<div class="search-results-pageinfo-range">${pageInfoText}</div>`); 466 467 // --- helper for ellipses window --- 468 function makePageWindow(total, current) { 469 const s = new Set([1, 2, total - 1, total, current - 1, current, current + 1]); 470 const nums = [...s].filter(n => n >= 1 && n <= total).sort((a, b) => a - b); 471 const out = []; 472 for (let i = 0; i < nums.length; i++) { 473 if (i > 0 && nums[i] - nums[i - 1] > 1) out.push("â¦"); 474 out.push(nums[i]); 475 } 476 return out; 477 } 478 479 const windowPages = makePageWindow(totalPages, currentPage); 480 481 // --- build HTML --- 482 let html = `<div class="dm-pager">`; 483 484 // Prev arrow 485 html += ` 486 <button class="dm-pager-arrow button_default button_square mr-1" data-page="${currentPage - 1}" 487 ${currentPage === 1 ? "disabled" : ""} aria-label="Previous page"><i class="fa fa-arrow-left"></i></button>`; 488 489 // Numbered buttons
490 windowPages.forEach(p => { 491 if (p === "â¦") { 492 html += `<span class="dm-pager-ellipsis mr-1">â¦</span>`; 493 } else { 494 const isCurrent = Number(p) === currentPage; 495 html += `<button class="dm-pager-btn ${isCurrent ? "active" : ""} button_default button_square mr-1" data-page="${p}" 496 ${isCurrent ? 'aria-current="page"' : ""}>${p}</button>`; 497 } 498 }); 499 500 // Next arrow 501 html += ` 502 <button class="dm-pager-arrow button_default button_square mr-1" data-page="${currentPage + 1}" 503 ${currentPage === totalPages ? "disabled" : ""} aria-label="Next page"><i class="fa fa-arrow-right"></i></button>`; 504 505 html += `</div>`; // end .dm-pager 506 507 $pagingPanel.html(html); 508 509 // --- events --- 510 $pagingPanel.off("click.page").on("click.page", "[data-page]", function () { 511 const p = parseInt($(this).data("page"), 10); 512 if (!Number.isFinite(p) || p < 1 || p > totalPages || p === currentPage) return; 513 startSearch($searchInput.val(), p); 514 }); 515} 516 517 518 519 520 function renderSuggestions(sug) { 521 if (!sug || !Array.isArray(sug.suggestions) || !sug.suggestions.length) { 522 $sugPanel.html(`<li class="search-suggestion-empty">${i18n.noSug}</li>`); 523 return; 524 } 525 const html = sug.suggestions.map(s => `<li class="search-suggestion-result">${esc(s.value)}</li>`).join(""); 526 $sugPanel.html(html); 527 } 528 529 /* =========================== 530 * SEARCH FLOW 531 * =========================== */ 532 function applyFilters(page, pageSize) { 533 const isDoc = ($filter.val()=="Document"); 534 const isCont = ($filter.val()=="Content"); 535 const isSvc = ($filter.val()=="Services"); 536 const asc = ($sort.val()=="Oldest"); 537 const desc = ($sort.val()=="Latest"); 538 539 if (isDoc) { 540 client.addCustomFieldFilter("isDocument", "1"); 541 client.removeCustomFieldFilter("isContent"); 542 client.removeCustomFieldFilter("is_service"); 543 } else if (isCont) { 544 client.removeCustomFieldFilter("isDocument"); 545 client.removeCustomFieldFilter("is_service"); 546 client.addCustomFieldFilter("isContent", "1"); 547 } else if (isSvc) { 548 client.removeCustomFieldFilter("isDocument"); 549 client.removeCustomFieldFilter("isContent"); 550 client.addCustomFieldFilter("is_service", "1"); 551 } else { 552 client.removeCustomFieldFilter("isDocument"); 553 client.removeCustomFieldFilter("isContent"); 554 client.removeCustomFieldFilter("is_service"); 555 } 556 557 if (asc) client.setPaging(page, pageSize, "date", "asc"); 558 else if (desc)client.setPaging(page, pageSize, "date", "desc"); 559 else client.setPaging(page, pageSize, "relevance", "desc"); 560 561 client.setFuzzyMatch("retry"); 562 } 563 564 function startSearch(term, page) { 565 $errorPanel.html(" "); 566 const q = (term || "").trim(); 567 568 if (q.length <= 1) { 569 $resultsPanel.html(`<p class="search-result-empty">${i18n.enterTerm}</p>`); 570 $pagingPanel.html(" "); 571 $termEcho.text(""); 572 return; 573 } 574 575 localStorage.setItem("searchValue", q); 576 $(".search-paging-panel").show(); 577 578 let pageSize = parseInt($perPage.val(), 10) || 10; 579 if (viewAll) pageSize = 300; 580 581 applyFilters(page, pageSize); 582 583 client.search(q, renderResults); 584 client.suggestions(q, renderSuggestions); 585 client.sendStatsEvent("search", q, { numberOfResults: $searchCount.text() }); 586 587 // clickable suggestions 588 $sugPanel.off("click.pick").on("click.pick", ".search-suggestion-result", function () { 589 const text = $(this).text(); 590 $searchInput.val(text); 591 startSearch(text, 1); 592 }); 593 594 $termEcho.text(`"${q}"`); 595 } 596 597 // expose if needed 598 window.startSearch = startSearch; 599 600 // click tracking for result links 601 $(document).on("click", ".search_result_link", function () { 602 client.sendStatsEvent("click", $searchInput.val(), { 603 documentId: $(this).data("id"), 604 position: $(this).data("pos") 605 }); 606 }); 607 608 /* =========================== 609 * VOICE SEARCH 610 * =========================== */ 611 const browser = detectBrowser(); 612 613 // Show voice button when supported (Chrome webkitSpeechRecognition OR fallback to Azure) 614 if ("webkitSpeechRecognition" in window || ["Opera", "Firefox", "Safari"].includes(browser)) { 615 $(".search-voice").show(); 616 } 617 618 // Chrome: webkitSpeechRecognition 619 if ("webkitSpeechRecognition" in window) { 620 const rec = new webkitSpeechRecognition(); 621 rec.continuous = false;
622 rec.interimResults = false; 623 rec.lang = isRTL ? "ar-AE" : "en-US"; 624 625 let listening = false; 626 627 $(".search-voice").on("click", function () { 628 $errorPanel.html(""); 629 if (["Firefox","Opera","OPR","Safari"].includes(browser)) return; // handled by Azure below 630 631 if (listening) { 632 rec.abort(); 633 listening = false; 634 $(".search-voice").html('<i class="fal fa-microphone-alt"></i>'); 635 } else { 636 listening = true; 637 rec.start(); 638 $(".search-voice").html('<i class="fal fa-microphone-alt-slash"></i>'); 639 } 640 }); 641 642 rec.onresult = function (e) { 643 const path = window.location.href; 644 const said = (e.results?.[0]?.[0]?.transcript) || ""; 645 if (!said) return; 646 647 if (path.indexOf("/search") !== -1) { 648 startSearch(said, 1); 649 $searchInput.val(said); 650 } else { 651 localStorage.setItem("searchValue", said); 652 window.location.href = $("body").hasClass("rtl") 653 ? "/search-dubai-municipality-arabic/?lang=ar" : "/search"; 654 } 655 listening = false; 656 $(".search-voice").html('<i class="fal fa-microphone-alt"></i>'); 657 }; 658 659 rec.onnomatch = rec.onend = function () { 660 $(".search-voice").html('<i class="fal fa-microphone-alt"></i>'); 661 }; 662 663 rec.onerror = function (e) { 664 if (e.error === "not-allowed") { 665 $errorPanel.html('<span class="voice-permission-denied">' + i18n.micPerm + "</span>"); 666 } else if (e.error === "no-speech") { 667 $errorPanel.html('<span class="voice-permission-denied">' + i18n.micNoSpeech + "</span>"); 668 } 669 $(".search-voice").html('<i class="fal fa-microphone-alt"></i>'); 670 }; 671 } 672 673 // Fallback (Firefox/Opera/Safari): Azure Speech Translation (client-side demo; avoid hardcoding keys) 674 if (["Opera","Firefox","Safari"].includes(browser)) { 675 $(".search-voice").on("click", function () { 676 $errorPanel.html(""); 677 $(".search-voice").html('<i class="fal fa-microphone-alt-slash"></i>'); 678 679 const SpeechSDK = window.SpeechSDK; 680 if (!SpeechSDK) { 681 console.warn("Azure Speech SDK missing"); 682 $(".search-voice").html('<i class="fal fa-microphone-alt"></i>'); 683 return; 684 } 685 686 const speechConfig = SpeechSDK.SpeechTranslationConfig.fromSubscription("YOUR_AZURE_SPEECH_KEY", "centralindia"); 687 speechConfig.speechRecognitionLanguage = "en-US"; 688 const lang = "en"; 689 speechConfig.addTargetLanguage(lang); 690 691 const audioConfig = SpeechSDK.AudioConfig.fromDefaultMicrophoneInput(); 692 const recognizer = new SpeechSDK.TranslationRecognizer(speechConfig, audioConfig); 693 694 recognizer.recognizeOnceAsync(function (result) { 695 if (result.reason === SpeechSDK.ResultReason.TranslatedSpeech) { 696 let translation = result.translations.get(lang) || ""; 697 if (translation.endsWith(".")) translation = translation.slice(0, -1); 698 699 const path = window.location.href; 700 if (path.indexOf("/search") !== -1) { 701 if (translation) { 702 startSearch(translation, 1); 703 $searchInput.val(translation); 704 } 705 } else { 706 if (translation) { 707 localStorage.setItem("searchValue", translation); 708 window.location.href = $("body").hasClass("rtl") 709 ? "/search-dubai-municipality-arabic/?lang=ar" : "/search"; 710 } 711 } 712 } 713 $(".search-voice").html('<i class="fal fa-microphone-alt"></i>'); 714 recognizer.close(); 715 }, function (err) { 716 console.error(err); 717 $(".search-voice").html('<i class="fal fa-microphone-alt"></i>'); 718 recognizer.close(); 719 }); 720 }); 721 } 722 723 /* =========================== 724 * SEARCH PANEL CONTROLS 725 * =========================== */ 726 $("#search_filter_input, #search_sort_input").on("change", function () { 727 startSearch($searchInput.val(), 1); 728 }); 729 730 $/*(".search-open-search, .small-side-menu-container .search").on("click", function () { 731 $("body").toggleClass("dynamic-search-open"); 732 $(".search-results-info").hide(); 733 $("#dynamic-search-area").show(); 734 $searchInput.trigger("focus"); 735 });*/ 736 737 $("#search-top-close-btn").on("click", function () { 738 $(".search-results-results").html("<p>" + i18n.enterTerm + "</p>"); 739 $searchInput.val(""); 740 $errorPanel.html(" "); 741 $(".search-results-term-keyword").html(""); 742 $(".search-results-count-no").html("0"); 743 $(".search-results-info").hide(); 744 $(".search-filter").prop("checked", false); 745 $("#search-filter-all").prop("checked", true); 746 $pagingPanel.html(""); 747 $sugPanel.html("<li>" + i18n.startSug + "</li>"); 748 }); 749 750 // Typing â debounced search 751 $searchInput.on("input", debounce(function () { 752 viewAll = false;
753 startSearch($searchInput.val(), 1); // use the cached selector 754 }, 350)); 755 756 var passValue = localStorage.getItem('searchValue'); 757 setTimeout(function(){ 758 if(passValue !='' && passValue !=undefined && passValue !=null){ 759 $("#search_page_input").val(passValue); 760 startSearch(passValue, 1); // use the cached selector 761 } 762 }, 100); 763 764 $("#search-paging-perpage-input").on("change", function () { 765 startSearch($searchInput.val(), 1); 766 }); 767 768 // Stats on suggestion clicks (header/mobile areas already wired above) 769 $(document).on("click", ".search_result_link", function () { 770 client.sendStatsEvent("click", $searchInput.val(), { 771 documentId: $(this).data("id"), 772 position: $(this).data("pos") 773 }); 774 }); 775 776 /* =========================== 777 * CONTENT PAGE SPECIAL ANCHORS 778 * =========================== */ 779 (function wireSpecialAnchors() { 780 const curr = window.location.href; 781 782 // English page 783 if (curr === "https://dm.gov.ae/discover-dubai/saruq-al-hadid/") { 784 const pairs = [ 785 { sel: "a[href='/saruq-al-hadid/#The-Site']", id: "#The-Site" }, 786 { sel: "a[href='/saruq-al-hadid/#the-museum']", id: "#the-museum" }, 787 { sel: "a[href='/saruq-al-hadid/#publications']", id: "#publications" }, 788 ]; 789 pairs.forEach(({ sel, id }) => { 790 if ($(sel).length) $(sel).attr("href","javascript:void(0);") 791 .on("click", () => $("html, body").animate({ scrollTop: $(id).offset().top }, 1000)); 792 }); 793 } 794 795 // Arabic page 796 if (curr.indexOf("/%d8%a7%d9%83%d8%aa%d8%b4%d9%81-%d8%af%d8%a8%d9%8a/%d8%b3%d8%a7%d8%b1%d9%88%d9%82-%d8%a7%d9%84%d8%ad%d8%af%d9%8a%d8%af/?lang=ar") !== -1) { 797 const pairs = [ 798 { sel: "a[href='/سارÙÙ-Ø§ÙØØ¯ÙØ¯/?lang=ar#The-Site']", id: "#The-Site" }, 799 { sel: "a[href='/سارÙÙ-Ø§ÙØØ¯ÙØ¯/?lang=ar#the-museum']", id: "#the-museum" }, 800 { sel: "a[href='/سارÙÙ-Ø§ÙØØ¯ÙØ¯/?lang=ar#publications']", id: "#publications" }, 801 ]; 802 pairs.forEach(({ sel, id }) => { 803 if ($(sel).length) $(sel).attr("href","javascript:void(0);") 804 .on("click", () => $("html, body").animate({ scrollTop: $(id).offset().top }, 1000)); 805 }); 806 } 807 })(); 808 809});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.