1function normalizePath(path) { 2return (path || '').replace(/^\/+|\/+$/g, ''); 3} 4 5// Basic sanitizer for URL-derived values (query and facet-like params). 6// This is a defensive layer to prevent HTML/JS injection via query string. 7// We strip angle brackets which are required to start HTML tags; normal 8// job searches and facet values should not rely on them. 9function sanitizeQueryParam(value) { 10 if (value == null) return ''; 11 try { 12 return String(value).replace(/[<>]/g, ''); 13 } catch (e) { 14 return ''; 15 } 16} 17 18function buildPathWithBase(relativePath) { 19const base = ROUTE_BASE_PATH.replace(/\/+$/g, ''); 20const rel = (relativePath || '').replace(/^\/+/, ''); 21return rel ? base + '/' + rel : base || '/'; 22} 23 24const REFINEMENT_ROUTE_KEYS = [ 25 ['category', 'category'], 26 ['location_filters', 'location'], 27 ['occupation', 'occupation'], 28 ['seniority', 'experience'], 29 ['job_type', 'employment-type'], 30 ['lang', 'lang'], 31 ['skills', 'skills'], 32]; 33const POSTED_ROUTE_VALUES = ['last-24h', 'last-72h', 'last-7-days', 'last-30-days', 'last-90-days']; 34 35function getMultiValueParam(qs, name) { 36 const vals = qs.getAll(name); 37 const raw = vals.length > 1 ? vals.join(',') : (qs.get(name) || ''); 38 return sanitizeQueryParam(raw); 39} 40 41function mapRefinementListToRoute(rl) { 42 const result = {}; 43 REFINEMENT_ROUTE_KEYS.forEach(([refinementKey, routeKey]) => { 44 if (rl[refinementKey] && rl[refinementKey].length > 0) { 45 result[routeKey] = rl[refinementKey].join(','); 46 } 47 }); 48 return result; 49} 50 51function normalizePresetRefinement(value) { 52 if (value == null) return value; 53 const v = String(value).trim(); 54 if (!v) return ''; 55 return v; 56} 57 58function normalizePostedValue(value) { 59 const v = sanitizeQueryParam(value || '').trim().toLowerCase(); 60 return POSTED_ROUTE_VALUES.indexOf(v) >= 0 ? v : ''; 61} 62 63function getServerPresetFilters() { 64 if (window.serverPresetFilters) return window.serverPresetFilters; 65 try { 66 const el = document.getElementById('js_variables'); 67 if (!el) return null; 68 const raw = el.getAttribute('data-preset-filters'); 69 if (!raw) return null; 70 const parsed = JSON.parse(raw); 71 window.serverPresetFilters = parsed; 72 return parsed; 73 } catch (e) { 74 return null; 75 } 76} 77 78function mergeIndexUiStateWithPreset(indexUiState, preset) { 79 if (!preset || typeof preset !== 'object') return indexUiState; 80 const merged = Object.assign({}, indexUiState); 81 try { 82 if (!merged.query && preset.query) { 83 merged.query = String(preset.query); 84 } 85 } catch (e) { 86 } 87 const ensureRefinementList = () => { 88 if (!merged.refinementList || typeof merged.refinementList !== 'object') { 89 merged.refinementList = {}; 90 } 91 }; 92 if (preset.refinementList && typeof preset.refinementList === 'object') { 93 ensureRefinementList(); 94 merged.refinementList = Object.assign({}, merged.refinementList); 95 Object.keys(preset.refinementList).forEach((attr) => { 96 const rawVals = Array.isArray(preset.refinementList[attr]) ? preset.refinementList[attr] : [preset.refinementList[attr]]; 97 const presetVals = rawVals.map((val) => normalizePresetRefinement(val)); 98 const currentVals = merged.refinementList && Array.isArray(merged.refinementList[attr]) ? merged.refinementList[attr] : []; 99 if (!currentVals.length) { 100 if (presetVals.length) merged.refinementList[attr] = presetVals.slice(); 101 } else { 102 const set = new Set(currentVals); 103 presetVals.forEach(v => set.add(v)); 104 merged.refinementList[attr] = Array.from(set); 105 } 106 }); 107 } 108 if (!window.initialSkillsOperator && preset.skillsOperator) { 109 window.initialSkillsOperator = String(preset.skillsOperator).toLowerCase() === 'and' ? 'and' : 'or'; 110 } 111 const presetPosted = normalizePostedValue(preset.posted); 112 if (!merged.posted && presetPosted) { 113 merged.posted = presetPosted; 114 } 115 116 // Ensure numeric salary presets are reflected in the InstantSearch UI state. 117 // The backend uses `numericRefinements` for SSR, while the UI widgets read `uiState.range.max_salary`. 118 if (preset.range && preset.range.max_salary) { 119 merged.range = Object.assign({}, merged.range || {}, { max_salary: String(preset.range.max_salary) }); 120 } else if (preset.numericRefinements && preset.numericRefinements.max_salary) { 121 const nr = preset.numericRefinements.max_salary; 122 if (nr && typeof nr === 'object' && nr['>=']) { 123 const min = parseInt(String(nr['>=']), 10); 124 if (!Number.isNaN(min) && min > 0) { 125 merged.range = Object.assign({}, merged.range || {}, { max_salary: String(min) + ':' }); 126 } 127 } 128 } 129 return merged; 130} 131 132 133function routeToState() { 134 const filters = {}; 135 136 try { 137 const qs = new URLSearchParams(window.location.search || ''); 138 const rawQ = qs.get('query') || qs.get('q'); 139 const q = sanitizeQueryParam(rawQ); 140 const routeRefinements = {};
141 REFINEMENT_ROUTE_KEYS.forEach(([refinementKey, routeKey]) => { 142 routeRefinements[refinementKey] = getMultiValueParam(qs, routeKey); 143 }); 144 const skillsOp = (qs.get('skillsOperator') || qs.get('skills_op') || '').toLowerCase(); 145 const minSalaryRaw = qs.get('min_salary') || qs.get('salary_min') || qs.get('max_salary') || ''; 146 const minSalaryStr = sanitizeQueryParam(minSalaryRaw); 147 const postedValue = normalizePostedValue(qs.get('posted') || ''); 148 149 if (q) filters.query = q; 150 REFINEMENT_ROUTE_KEYS.forEach(([refinementKey, routeKey]) => { 151 const rawValue = routeRefinements[refinementKey]; 152 if (!rawValue) return; 153 const values = rawValue.split(',').filter(Boolean); 154 if (routeKey === 'category' || routeKey === 'occupation') { 155 if (values.length) filters[refinementKey] = values; 156 return; 157 } 158 filters[refinementKey] = values; 159 }); 160 if (skillsOp === 'and' || skillsOp === 'or') { 161 window.initialSkillsOperator = skillsOp; 162 filters.skillsOperator = skillsOp; 163 } else if (!window.initialSkillsOperator) { 164 window.initialSkillsOperator = 'or'; 165 } 166 if (minSalaryStr) { 167 const parsed = parseInt(minSalaryStr, 10); 168 if (!Number.isNaN(parsed)) { 169 filters.min_salary = String(parsed); 170 } 171 } 172 if (postedValue) { 173 filters.posted = postedValue; 174 } 175 } catch (e) { 176 } 177 178 const indexUiState = {}; 179 180 const refinements = {}; 181 REFINEMENT_ROUTE_KEYS.forEach(([refinementKey]) => { 182 if (filters[refinementKey]) { 183 refinements[refinementKey] = Array.isArray(filters[refinementKey]) 184 ? filters[refinementKey] 185 : [filters[refinementKey]]; 186 } 187 }); 188 if (Object.keys(refinements).length) { 189 indexUiState.refinementList = refinements; 190 } 191 192 if (filters.query) { 193 indexUiState.query = filters.query; 194 } 195 196 if (filters.skillsOperator) { 197 indexUiState.skillsOperator = filters.skillsOperator; 198 } 199 if (filters.min_salary) { 200 const min = String(filters.min_salary); 201 indexUiState.range = Object.assign({}, indexUiState.range, { max_salary: min + ':' }); 202 } 203 if (filters.posted) { 204 indexUiState.posted = filters.posted; 205 } 206 207 const presets = getServerPresetFilters(); 208 try { 209 const baselineFromPresets = mergeIndexUiStateWithPreset({}, presets || {}); 210 window.serverPresetUiStateBaseline = baselineFromPresets; 211 window.serverPresetFlatRouteStateBaseline = buildRouteStateFromIndexUiState(baselineFromPresets); 212 window.serverPresetSkillsOperatorBaseline = baselineFromPresets.skillsOperator; 213 } catch (e) { 214 window.serverPresetUiStateBaseline = undefined; 215 window.serverPresetFlatRouteStateBaseline = undefined; 216 window.serverPresetSkillsOperatorBaseline = undefined; 217 } 218 const mergedUiState = mergeIndexUiStateWithPreset(indexUiState, presets); 219 220 try { 221 const currentPathNormalized = normalizePath(window.location.pathname); 222 const hasNoRefinements = !(mergedUiState.refinementList && Object.keys(mergedUiState.refinementList).length); 223 if (currentPathNormalized === '' && hasNoRefinements) { 224 window.onVanity = true; 225 window.vanityPath = '/'; 226 window.presetCanonicalPath = buildPathFromRouteState().replace(/\/+$/, ''); 227 } 228 } catch (e) { 229 } 230 231 if (presets) { 232 window.onVanity = true; 233 window.vanityPath = window.location.pathname || '/'; 234 try { 235 window.presetCanonicalPath = buildPathFromRouteState().replace(/\/+$/, ''); 236 } catch (e) { 237 } 238 } 239 240 return { 241 remotive_unlimited: mergedUiState, 242 }; 243} 244 245 246function stateToRoute(uiState) { 247 const indexUiState = uiState.remotive_unlimited || {}; 248 249 const routeState = {}; 250 251 const rl = indexUiState.refinementList || {}; 252 Object.assign(routeState, mapRefinementListToRoute(rl)); 253 if (indexUiState.skillsOperator && (indexUiState.skillsOperator === 'and' || indexUiState.skillsOperator === 'or')) { 254 routeState.skillsOperator = indexUiState.skillsOperator; 255 } else if (typeof window.skillsOperator === 'string' && (window.skillsOperator === 'and' || window.skillsOperator === 'or')) { 256 routeState.skillsOperator = window.skillsOperator; 257 } 258 if (indexUiState.query) { 259 routeState.query = String(indexUiState.query); 260 } 261 if (indexUiState.range && indexUiState.range.max_salary) { 262 try { 263 const rangeStr = String(indexUiState.range.max_salary); 264 const minPart = rangeStr.split(':')[0]; 265 const parsed = parseInt(minPart, 10); 266 if (!Number.isNaN(parsed) && parsed > 0) { 267 routeState.min_salary = String(parsed); 268 } 269 } catch (e) {} 270 } 271 if (indexUiState.posted) { 272 const postedValue = normalizePostedValue(indexUiState.posted); 273 if (postedValue) { 274 routeState.posted = postedValue; 275 } 276 } 277 try { 278 window.lastFullRouteStateForVanity = Object.assign({}, routeState); 279 window.currentRouteMatchesPresetBaseline = isCurrentRouteStatePresetBaseline( 280 window.lastFullRouteStateForVanity 281 ); 282 } catch (e) { 283 window.lastFullRouteStateForVanity = Object.assign({}, routeState); 284 window.currentRouteMatchesPresetBaseline = false;
285 } 286 return routeState; 287} 288 289function trimRouteStateAgainstBaseline(routeState) { 290 try { 291 const baseline = window.serverPresetUiStateBaseline || {}; 292 const baselineRL = (baseline && baseline.refinementList) || {}; 293 const trimmed = {}; 294 const toArray = (v) => Array.isArray(v) ? v.slice() : (v ? [String(v)] : []); 295 const diffCsv = (currentCsv, baselineArr) => { 296 const currentArr = toArray(currentCsv && String(currentCsv).split(',').filter(Boolean)); 297 const baseSet = new Set(toArray(baselineArr)); 298 const extras = currentArr.filter(v => !baseSet.has(v)); 299 return extras.join(','); 300 }; 301 302 REFINEMENT_ROUTE_KEYS.forEach(([refinementKey, routeKey]) => { 303 if (routeState[routeKey]) { 304 const d = diffCsv(routeState[routeKey], baselineRL[refinementKey]); 305 if (d) trimmed[routeKey] = d; 306 } 307 }); 308 309 if (routeState.query) { 310 const safeQuery = sanitizeQueryParam(routeState.query); 311 const baseQuery = (baseline && baseline.query) ? String(baseline.query) : ''; 312 if (String(safeQuery) !== baseQuery) trimmed.query = String(safeQuery); 313 } 314 315 if (routeState.min_salary) { 316 let baseMin = ''; 317 try { 318 if (baseline && baseline.range && baseline.range.max_salary) { 319 baseMin = String(baseline.range.max_salary).split(':')[0] || ''; 320 } 321 } catch (e) { baseMin = ''; } 322 if (String(routeState.min_salary) !== String(baseMin)) { 323 trimmed.min_salary = String(routeState.min_salary); 324 } 325 } 326 if (routeState.posted) { 327 const currentPosted = normalizePostedValue(routeState.posted); 328 const basePosted = normalizePostedValue(baseline.posted || ''); 329 if (currentPosted && currentPosted !== basePosted) { 330 trimmed.posted = currentPosted; 331 } 332 } 333 334 try { 335 const currOp = (window.skillsOperator || 'or'); 336 const baseOp = (window.serverPresetSkillsOperatorBaseline || 'or'); 337 if (currOp !== baseOp) { 338 trimmed.skillsOperator = currOp; 339 } 340 } catch (e) {} 341 342 return trimmed; 343 } catch (e) { 344 return routeState || {}; 345 } 346} 347 348function buildPathFromRouteState() { 349 return buildPathWithBase(''); 350} 351 352function buildSearchFromRouteState(routeState) { 353 try { 354 const sp = new URLSearchParams(); 355 const searchableKeys = [ 356 'query', 357 'category', 358 'location', 359 'occupation', 360 'experience', 361 'employment-type', 362 'skills', 363 'lang', 364 'min_salary', 365 'posted', 366 ]; 367 searchableKeys.forEach((key) => { 368 if (routeState && routeState[key]) { 369 sp.set(key, sanitizeQueryParam(routeState[key])); 370 } 371 }); 372 if (routeState && routeState.skillsOperator) { 373 const op = normalizeRouteStateOperator(routeState.skillsOperator); 374 const baselineOp = normalizeRouteStateOperator( 375 window.serverPresetSkillsOperatorBaseline || window.initialSkillsOperator || 'or' 376 ); 377 if (op && op !== baselineOp) { 378 sp.set('skillsOperator', op); 379 } 380 } 381 const s = sp.toString(); 382 return s ? ('?' + s) : ''; 383 } catch (e) { 384 return ''; 385 } 386} 387 388function buildRouteStateFromIndexUiState(indexUiState) { 389 const flat = {}; 390 const rl = indexUiState.refinementList || {}; 391 const routeRefinements = mapRefinementListToRoute(rl); 392 delete routeRefinements.lang; 393 Object.assign(flat, routeRefinements); 394 const op = (indexUiState.skillsOperator || window.initialSkillsOperator || window.skillsOperator || 'or'); 395 if (op) flat.skillsOperator = op; 396 const postedValue = normalizePostedValue(indexUiState.posted); 397 if (postedValue) flat.posted = postedValue; 398 return flat; 399} 400 401function parseRouteStateFromURL() { 402 const routeState = {}; 403 404 try { 405 const qs = new URLSearchParams(window.location.search || ''); 406 const rawQ = qs.get('query') || qs.get('q'); 407 const q = sanitizeQueryParam(rawQ); 408 if (q) routeState.query = q; 409 REFINEMENT_ROUTE_KEYS.forEach(([, routeKey]) => { 410 const value = getMultiValueParam(qs, routeKey); 411 if (value) routeState[routeKey] = value; 412 }); 413 const skillsOp = (qs.get('skillsOperator') || qs.get('skills_op') || '').toLowerCase(); 414 if (skillsOp === 'and' || skillsOp === 'or') routeState.skillsOperator = skillsOp; 415 const minSalary = qs.get('min_salary') || qs.get('salary_min') || qs.get('max_salary'); 416 if (minSalary) routeState.min_salary = sanitizeQueryParam(minSalary); 417 const posted = normalizePostedValue(qs.get('posted') || ''); 418 if (posted) routeState.posted = posted; 419 } catch (e) { 420 } 421 422 return routeState; 423} 424 425function toSortedCsvArray(value) { 426 if (value == null) return []; 427 return String(value) 428 .split(',') 429 .map((part) => sanitizeQueryParam(part).trim()) 430 .filter(Boolean) 431 .sort(); 432} 433 434function normalizeRouteStateOperator(value) { 435 const op = String(value || '').toLowerCase(); 436 return (op === 'and' || op === 'or') ? op : ''; 437} 438 439function parseMinSalaryFromRange(rangeValue) { 440 try { 441 const minPart = String(rangeValue || '').split(':')[0]; 442 const parsed = parseInt(minPart, 10); 443 if (!Number.isNaN(parsed) && parsed > 0) { 444 return String(parsed); 445 } 446 } catch (e) {} 447 return ''; 448} 449 450function getPresetBaselineRouteState() { 451 if (window.serverPresetComparableRouteState) { 452 return window.serverPresetComparableRouteState; 453 } 454 try { 455 const baselineUi = window.serverPresetUiStateBaseline || {}; 456 const baseline = buildRouteStateFromIndexUiState(baselineUi); 457 const baselineRL = (baselineUi && baselineUi.refinementList) || {}; 458 459 if (baselineUi.query) baseline.query = sanitizeQueryParam(baselineUi.query); 460 if (baselineRL.lang && baselineRL.lang.length > 0) baseline.lang = baselineRL.lang.join(','); 461 if (baselineUi.range && baselineUi.range.max_salary) { 462 const minSalary = parseMinSalaryFromRange(baselineUi.range.max_salary); 463 if (minSalary) baseline.min_salary = minSalary; 464 } 465 const posted = normalizePostedValue(baselineUi.posted || ''); 466 if (posted) baseline.posted = posted; 467 const op = normalizeRouteStateOperator( 468 baselineUi.skillsOperator || window.serverPresetSkillsOperatorBaseline 469 ); 470 if (op) baseline.skillsOperator = op; 471 472 window.serverPresetComparableRouteState = baseline; 473 return baseline; 474 } catch (e) { 475 return null; 476 } 477} 478 479function isCurrentRouteStatePresetBaseline(routeState) { 480 const baseline = getPresetBaselineRouteState(); 481 if (!baseline) return false;
482 483 const csvKeys = [ 484 'category', 485 'location', 486 'occupation', 487 'experience', 488 'employment-type', 489 'skills', 490 'lang', 491 ]; 492 493 for (let i = 0; i < csvKeys.length; i++) { 494 const key = csvKeys[i]; 495 const currentVals = toSortedCsvArray(routeState && routeState[key]); 496 const baselineVals = toSortedCsvArray(baseline[key]); 497 if (currentVals.join(',') !== baselineVals.join(',')) { 498 return false; 499 } 500 } 501 502 const currentQuery = sanitizeQueryParam(routeState && routeState.query); 503 const baselineQuery = sanitizeQueryParam(baseline.query || ''); 504 if (String(currentQuery || '') !== String(baselineQuery || '')) { 505 return false; 506 } 507 508 const currentMinSalary = sanitizeQueryParam(routeState && routeState.min_salary); 509 const baselineMinSalary = sanitizeQueryParam(baseline.min_salary || ''); 510 if (String(currentMinSalary || '') !== String(baselineMinSalary || '')) { 511 return false; 512 } 513 514 const currentPosted = normalizePostedValue(routeState && routeState.posted); 515 const baselinePosted = normalizePostedValue(baseline.posted || ''); 516 if (currentPosted !== baselinePosted) { 517 return false; 518 } 519 520 const currentOp = normalizeRouteStateOperator(routeState && routeState.skillsOperator); 521 const baselineOp = normalizeRouteStateOperator(baseline.skillsOperator); 522 if (currentOp !== baselineOp) { 523 return false; 524 } 525 526 return true; 527} 528 529function shouldKeepVanityPath(indexRouteState, canonicalTrim) { 530 if (!(window.onVanity && window.presetCanonicalPath)) { 531 return false; 532 } 533 if (canonicalTrim !== window.presetCanonicalPath) { 534 return false; 535 } 536 const vanityTrim = (window.vanityPath || '').replace(/\/+$/, ''); 537 const isRootVanity = vanityTrim === ''; 538 if (window.lastFullRouteStateForVanity && typeof window.lastFullRouteStateForVanity === 'object') { 539 const fullState = window.lastFullRouteStateForVanity; 540 const hasMeaningfulFilter = !!( 541 fullState.category || 542 fullState.location || 543 fullState.occupation || 544 fullState.experience || 545 fullState['employment-type'] || 546 fullState.skills || 547 fullState.lang || 548 fullState.query || 549 fullState.min_salary || 550 fullState.posted 551 ); 552 if (!hasMeaningfulFilter) { 553 return isRootVanity; 554 } 555 return isCurrentRouteStatePresetBaseline(window.lastFullRouteStateForVanity); 556 } 557 if (isRootVanity) { 558 return true; 559 } 560 if (typeof window.currentRouteMatchesPresetBaseline === 'boolean') { 561 return window.currentRouteMatchesPresetBaseline; 562 } 563 return isCurrentRouteStatePresetBaseline(indexRouteState); 564} 565 566const customRouter = { 567 read() { 568 return { 569 remotive_unlimited: parseRouteStateFromURL(), 570 }; 571 }, 572 573 write(routeState) { 574 const indexRouteState = routeState.remotive_unlimited || {}; 575 const canonicalPath = buildPathFromRouteState(); 576 const trimmedRouteState = trimRouteStateAgainstBaseline(indexRouteState); 577 const searchStr = buildSearchFromRouteState(indexRouteState); 578 const vanitySearchStr = buildSearchFromRouteState(trimmedRouteState); 579 const canonicalTrim = canonicalPath.replace(/\/+$/, ''); 580 581 if (!window.onVanity) { 582 const isEmptyState = Object.keys(indexRouteState).length === 0; 583 if (isEmptyState) { 584 const currentTrim = window.location.pathname.replace(/\/+$/, ''); 585 if (currentTrim !== '' || (window.location.search || '') !== '') { 586 window.history.pushState({}, '', '/' + (searchStr || '')); 587 } 588 return; 589 } 590 } 591 592 if (window.onVanity && window.presetCanonicalPath) { 593 const currentTrim = window.location.pathname.replace(/\/+$/, ''); 594 const currentSearch = window.location.search || ''; 595 const vanityTrim = (window.vanityPath || '').replace(/\/+$/, ''); 596 const keepVanityPath = shouldKeepVanityPath(indexRouteState, canonicalTrim); 597 if (keepVanityPath) { 598 if ((currentTrim !== vanityTrim && vanityTrim) || currentSearch !== (vanitySearchStr || '')) { 599 window.history.pushState({}, '', (vanityTrim || '/') + (vanitySearchStr || '')); 600 } 601 return; 602 } else {
603 if (currentTrim !== canonicalTrim || currentSearch !== (searchStr || '')) { 604 window.history.pushState({}, '', (canonicalPath || (ROUTE_BASE_PATH || '/')) + (searchStr || '')); 605 } 606 return; 607 } 608 } 609 610 const currentPath = window.location.pathname.replace(/\/+$/, ''); 611 const currentSearch = window.location.search || ''; 612 const nextPath = canonicalTrim; 613 if (currentPath !== nextPath || currentSearch !== (searchStr || '')) { 614 window.history.pushState({}, '', (canonicalPath || (ROUTE_BASE_PATH || '/')) + (searchStr || '')); 615 } 616 }, 617 618 createURL(routeState) { 619 const indexRouteState = routeState.remotive_unlimited || {}; 620 const canonicalPath = buildPathFromRouteState(); 621 const trimmedRouteState = trimRouteStateAgainstBaseline(indexRouteState); 622 const searchStr = buildSearchFromRouteState(indexRouteState); 623 const vanitySearchStr = buildSearchFromRouteState(trimmedRouteState); 624 const canonicalTrim = canonicalPath.replace(/\/+$/, ''); 625 626 if (!window.onVanity) { 627 const isEmptyState = Object.keys(indexRouteState).length === 0; 628 if (isEmptyState) { 629 return '/' + (searchStr || ''); 630 } 631 } 632 633 if (window.onVanity && window.presetCanonicalPath) { 634 const vanityPath = window.vanityPath || (ROUTE_BASE_PATH || '/'); 635 const keepVanityPath = shouldKeepVanityPath(indexRouteState, canonicalTrim); 636 if (keepVanityPath) { 637 return (vanityPath || '/') + (vanitySearchStr || ''); 638 } else { 639 return (canonicalPath || (ROUTE_BASE_PATH || '/')) + (searchStr || ''); 640 } 641 } 642 643 return (canonicalPath || (ROUTE_BASE_PATH || '/')) + (searchStr || ''); 644 }, 645 646 onUpdate(callback) { 647 window.addEventListener('popstate', callback); 648 }, 649}; 650 651const routing = { 652 router: customRouter, 653 stateMapping: { 654 stateToRoute(uiState) { 655 return { 656 remotive_unlimited: stateToRoute(uiState), 657 }; 658 }, 659 routeToState(routeState) { 660 return routeToState(); 661 }, 662 }, 663}; 664
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.