{"version":3,"file":"index.min.js","sources":["../../runes/js/search_core.js","../../runes/js/index_layout.js","../../runes/js/index_scripts.js","../../runes/js/index_multiselect.js","../../runes/js/index_map.js","../../runes/js/index_query_builder.js","../../runes/js/index_search.js","../../runes/js/index_view_model.js","../../runes/js/index_import_utils.js","../../runes/js/index.js"],"sourcesContent":["/**\n * Shared search helpers used by both the main index page and the EDA page.\n */\n\n/**\n * Checks if a given word is a personal name.\n * @param {string} word - The word to check.\n * @returns {boolean} - True if the word is a personal name, false otherwise.\n */\nexport function isPersonalName(word) {\n  return word.startsWith('\"') || word.startsWith('&quot;')\n    || word.includes('/\"') || word.includes('/&quot;');\n}\n\n/**\n * Normalizes whitespace in a string by replacing all whitespace characters\n * with regular spaces.\n *\n * @param {*} value - The value to normalize\n * @returns {string} The normalized string\n */\nexport function normalizeWhitespace(value) {\n  return String(value).replace(/\\s/g, ' ');\n}\n\n/**\n * Prepares a string for comparison by normalizing whitespace and optionally lowercasing.\n * @param {*} value - The value to prepare\n * @param {boolean} ignoreCase - Whether to lowercase the string\n * @returns {string} The prepared string\n */\nexport function prepareForComparison(value, ignoreCase) {\n  const normalizedValue = normalizeWhitespace(value);\n  return ignoreCase ? normalizedValue.toLowerCase() : normalizedValue;\n}\n\n/**\n * Strips editorial special symbols from a string so that searches can match\n * text regardless of annotation markers.\n *\n * Handles both literal characters and HTML-entity forms.\n *\n * @param {string} str - The string to strip\n * @returns {string} The string with special symbols removed\n */\nexport function stripSpecialSymbols(str) {\n  return String(str)\n    .replace(/&quot;/g, '')\n    .replace(/&lt;/g, '')\n    .replace(/&gt;/g, '')\n    .replace(/[\"<>|[\\](){}^\\u00b4?+:\\u00d7\\u00b7\\u00a4'÷¶]/g, '');\n}\n\n/**\n * Returns a word search function based on the specified search mode.\n * @param {string} searchMode - The search mode ('exact', 'beginsWith', 'endsWith', 'regex', 'includes', 'contains'). 'contains' is an alias for 'includes'.\n * @param {Object} options - Additional options\n * @param {boolean} [options.ignoreCase=false] - Whether to ignore case when searching\n * @param {boolean} [options.includeSpecialSymbols=false] - Whether to keep editorial symbols in comparisons\n * @returns {Function} A search function that takes (word, query) parameters\n * @throws {Error} When an invalid regex pattern is provided in regex mode\n */\nexport function getWordSearchFunction(searchMode, options = {}) {\n  const { ignoreCase = false, includeSpecialSymbols = false } = options;\n  const normalizedSearchMode = searchMode === 'contains' ? 'includes' : searchMode;\n\n  const prepareString = (str) => {\n    let preparedValue = prepareForComparison(str, ignoreCase);\n    if (!includeSpecialSymbols) {\n      preparedValue = stripSpecialSymbols(preparedValue);\n    }\n    return preparedValue;\n  };\n\n  const prepareWord = (word) => {\n    if (word.includes('&#x2F;') || word.includes('/')) {\n      return word.split(/&#x2F;|\\//).map(name => {\n        return name.trim().replace(/^(&quot;|\")/, '');\n      });\n    }\n\n    return [word];\n  };\n\n  switch (normalizedSearchMode) {\n    case 'exact':\n      return (word, query) => {\n        const words = prepareWord(word);\n        return words.some(oneWord => prepareString(oneWord) === prepareString(query));\n      };\n\n    case 'beginsWith':\n      return (word, query) => {\n        const words = prepareWord(word);\n        return words.some(oneWord => prepareString(oneWord).startsWith(prepareString(query)));\n      };\n\n    case 'endsWith':\n      return (word, query) => {\n        const words = prepareWord(word);\n        return words.some(oneWord => prepareString(oneWord).endsWith(prepareString(query)));\n      };\n\n    case 'regex':\n      return (word, query) => {\n        try {\n          const flags = ignoreCase ? 'i' : '';\n          const regex = new RegExp(query, flags);\n          return regex.test(word);\n        } catch (error) {\n          throw new Error(`Invalid regex pattern: ${query}`);\n        }\n      };\n\n    case 'includes':\n    default:\n      return (word, query) => {\n        const words = prepareWord(word);\n        return words.some(oneWord => prepareString(oneWord).includes(prepareString(query)));\n      };\n  }\n}\n\n/**\n * Splits a phrase query into tokens by whitespace.\n * Whitespace is first normalized to single spaces, then the string is\n * trimmed and split on runs of whitespace.\n *\n * @param {string} query - The query string to tokenize\n * @returns {string[]} Array of tokens. Empty array if query is empty/whitespace only.\n */\nexport function splitPhraseTokens(query) {\n  if (query === null || query === undefined) {\n    return [];\n  }\n  const normalized = normalizeWhitespace(query).trim();\n  if (normalized === '') {\n    return [];\n  }\n  return normalized.split(/\\s+/);\n}\n\n/**\n * Returns a phrase (multi-word) matcher for the specified search mode.\n *\n * The returned function accepts `(docWords, query)` where `docWords` is the\n * array of words in the inscription (e.g. `entry.normalisation_norse_words`)\n * and `query` is the phrase to search for. It returns an array of matched\n * windows; each window is an array of word indices `[i, i+1, …, i+N-1]`.\n *\n * Per-operator semantics (N = number of tokens in the phrase, N ≥ 2):\n *   - exact: every token must exactly equal the corresponding consecutive\n *     doc word.\n *   - includes / contains: every token must appear as a substring of the\n *     corresponding consecutive doc word. This mirrors single-word\n *     `contains` (which is `String.includes`) and is consistent across\n *     every position, so e.g. \"es satt\" matches \"es sattr\" and \"e sat\"\n *     matches \"es sattr\".\n *   - beginsWith: tokens 0..N-2 must equal doc words exactly; the last\n *     doc word must start with the last token.\n *   - endsWith: tokens 1..N-1 must equal doc words exactly; the first\n *     doc word must end with the first token.\n *\n * Windows may appear anywhere in the doc (not anchored to start/end).\n *\n * @param {string} searchMode - One of 'exact', 'beginsWith', 'endsWith',\n *                              'includes', 'contains'.\n * @param {Object} options - Additional options\n * @param {boolean} [options.ignoreCase=false]\n * @param {boolean} [options.includeSpecialSymbols=false]\n * @returns {Function} (docWords, query) => number[][]\n */\nexport function getPhraseMatchFunction(searchMode, options = {}) {\n  const { ignoreCase = false, includeSpecialSymbols = false } = options;\n  const normalizedSearchMode = searchMode === 'contains' ? 'includes' : searchMode;\n\n  const prepareString = (str) => {\n    let preparedValue = prepareForComparison(str, ignoreCase);\n    if (!includeSpecialSymbols) {\n      preparedValue = stripSpecialSymbols(preparedValue);\n    }\n    return preparedValue;\n  };\n\n  // Reduce a doc word to its comparable alternatives (handles personal-name\n  // annotations like \"/X\" splitting into multiple candidates).\n  const prepareDocWordVariants = (word) => {\n    if (word.includes('&#x2F;') || word.includes('/')) {\n      return word.split(/&#x2F;|\\//).map(name => {\n        return prepareString(name.trim().replace(/^(&quot;|\")/, ''));\n      });\n    }\n    return [prepareString(word)];\n  };\n\n  const docWordEquals = (docWord, tokenPrepared) => {\n    return prepareDocWordVariants(docWord).some(v => v === tokenPrepared);\n  };\n\n  const docWordStartsWith = (docWord, tokenPrepared) => {\n    return prepareDocWordVariants(docWord).some(v => v.startsWith(tokenPrepared));\n  };\n\n  const docWordEndsWith = (docWord, tokenPrepared) => {\n    return prepareDocWordVariants(docWord).some(v => v.endsWith(tokenPrepared));\n  };\n\n  const docWordIncludes = (docWord, tokenPrepared) => {\n    return prepareDocWordVariants(docWord).some(v => v.includes(tokenPrepared));\n  };\n\n  // Decide the comparison to apply at position `j` of an N-token window.\n  const compareAt = (docWord, token, j, n) => {\n    const isFirst = j === 0;\n    const isLast = j === n - 1;\n    switch (normalizedSearchMode) {\n      case 'includes':\n        // Every token is a substring of the corresponding doc word.\n        return docWordIncludes(docWord, token);\n      case 'beginsWith':\n        return isLast ? docWordStartsWith(docWord, token) : docWordEquals(docWord, token);\n      case 'endsWith':\n        return isFirst ? docWordEndsWith(docWord, token) : docWordEquals(docWord, token);\n      case 'exact':\n      default:\n        return docWordEquals(docWord, token);\n    }\n  };\n\n  return (docWords, query) => {\n    const tokens = splitPhraseTokens(query).map(prepareString);\n    const windows = [];\n    const n = tokens.length;\n    if (\n      n === 0 ||\n      tokens.some(token => token.length === 0) ||\n      !Array.isArray(docWords) ||\n      docWords.length < n\n    ) {\n      return windows;\n    }\n\n    for (let i = 0; i <= docWords.length - n; i++) {\n      let matches = true;\n      for (let j = 0; j < n; j++) {\n        if (!compareAt(docWords[i + j], tokens[j], j, n)) {\n          matches = false;\n          break;\n        }\n      }\n      if (matches) {\n        const window = [];\n        for (let j = 0; j < n; j++) window.push(i + j);\n        windows.push(window);\n      }\n    }\n\n    return windows;\n  };\n}\n\n/**\n * Returns field mapping for supported directional word searches.\n * @param {string} searchDirection - Direction identifier\n * @returns {{normalisationField: string, fromField: string, toField: string}|null}\n */\nexport function getSearchDirectionConfig(searchDirection) {\n  switch (searchDirection) {\n    case 'norseToTransliteration':\n      return {\n        normalisationField: 'normalisation_norse',\n        fromField: 'normalisation_norse',\n        toField: 'transliteration',\n      };\n    case 'scandinavianToTransliteration':\n      return {\n        normalisationField: 'normalisation_scandinavian',\n        fromField: 'normalisation_scandinavian',\n        toField: 'transliteration',\n      };\n    case 'transliterationToNorse':\n      return {\n        normalisationField: 'normalisation_norse',\n        fromField: 'transliteration',\n        toField: 'normalisation_norse',\n      };\n    case 'transliterationToScandinavian':\n      return {\n        normalisationField: 'normalisation_scandinavian',\n        fromField: 'transliteration',\n        toField: 'normalisation_scandinavian',\n      };\n    default:\n      return null;\n  }\n}\n","export const COMPACT_DB_LAYOUT_MEDIA_QUERY =\n  '(max-width: 767.98px), (max-width: 1366px) and (any-pointer: coarse)';\n\nconst TABLET_MAX_WIDTH = 1366;\n\nfunction getViewportWidth(windowObject) {\n  const documentWidth = windowObject.document\n    && windowObject.document.documentElement\n    ? windowObject.document.documentElement.clientWidth\n    : 0;\n  return Math.max(documentWidth || 0, windowObject.innerWidth || 0);\n}\n\nfunction isTouchTabletFallback(windowObject, navigatorObject) {\n  const viewportWidth = getViewportWidth(windowObject);\n  if (!viewportWidth || viewportWidth > TABLET_MAX_WIDTH) {\n    return false;\n  }\n\n  const userAgent = String(navigatorObject.userAgent || '');\n  const platform = String(navigatorObject.platform || '');\n  const hasTouch = Number(navigatorObject.maxTouchPoints || 0) > 0\n    || 'ontouchstart' in windowObject;\n  const isIPadOS = /iPad/i.test(userAgent)\n    || (platform === 'MacIntel' && Number(navigatorObject.maxTouchPoints || 0) > 1);\n  const isAndroidTablet = /Android|Silk|Kindle/i.test(userAgent);\n\n  return hasTouch && (isIPadOS || isAndroidTablet);\n}\n\nexport function isCompactDbLayout(\n  windowObject = typeof window !== 'undefined' ? window : null,\n  navigatorObject = typeof navigator !== 'undefined' ? navigator : {}\n) {\n  if (!windowObject) {\n    return false;\n  }\n\n  try {\n    if (typeof windowObject.matchMedia === 'function'\n      && windowObject.matchMedia(COMPACT_DB_LAYOUT_MEDIA_QUERY).matches) {\n      return true;\n    }\n    return isTouchTabletFallback(windowObject, navigatorObject);\n  } catch (error) {\n    return false;\n  }\n}\n\nexport function syncCompactDbLayoutClass() {\n  if (typeof document === 'undefined' || !document.documentElement) {\n    return false;\n  }\n  const compact = isCompactDbLayout();\n  document.documentElement.classList.toggle('db-compact-layout', compact);\n  return compact;\n}\n\nexport function initCompactDbLayout() {\n  const compact = syncCompactDbLayoutClass();\n  if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {\n    return compact;\n  }\n\n  window.addEventListener('resize', syncCompactDbLayoutClass);\n  window.addEventListener('orientationchange', syncCompactDbLayoutClass);\n  return compact;\n}\n","import { isPersonalName } from './search_core.js';\nimport { isCompactDbLayout } from './index_layout.js';\n\nexport { isPersonalName } from './search_core.js';\n\nexport const schemaFieldsInfo = [\n  {\n    schemaName: 'signature_text',\n    text: {\n      'en': 'Inscription ID'\n    },\n  },\n  {\n    schemaName: 'full_address',\n    text: {\n      'en': 'Any location',\n    },\n  },\n  {\n    schemaName: 'found_location',\n    text: {\n      'en': 'Found location',\n    },\n  },\n  {\n    schemaName: 'parish',\n    text: {\n      'en': 'Parish',\n    },\n  },\n  {\n    schemaName: 'district',\n    text: {\n      'en': 'District',\n    },\n  },\n  {\n    schemaName: 'municipality',\n    text: {\n      'en': 'Municipality',\n    },\n  },\n  {\n    schemaName: 'current_location',\n    text: {\n      'en': 'Current location',\n    },\n  },\n  {\n    schemaName: 'original_site',\n    text: {\n      en: 'Original site',\n    },\n  },\n  {\n    schemaName: 'coordination',\n    text: {\n      en: 'Coordinates',\n    },\n  },\n  {\n    schemaName: 'parish_code',\n    text: {\n      en: 'Parish code',\n    }\n  },\n  {\n    schemaName: 'rune_type',\n    text: {\n      en: 'Rune type',\n    }\n  },\n  {\n    schemaName: 'dating',\n    text: {\n      en: 'Dating period (U, V, or M)',\n    }\n  },\n  {\n    schemaName: 'year_from',\n    text: {\n      en: 'Year from',\n    },\n  },\n  {\n    schemaName: 'year_to',\n    text: {\n      en: 'Year to',\n    },\n  },\n  {\n    schemaName: 'style',\n    text: {\n      en: 'Style',\n    }\n  },\n  {\n    schemaName: 'carver',\n    text: {\n      en: 'Carver',\n    }\n  },\n  {\n    schemaName: 'material',\n    text: {\n      en: 'Material',\n    },\n  },\n  {\n    schemaName: 'material_type',\n    text: {\n      en: 'Material type',\n    },\n  },\n  {\n    schemaName: 'objectInfo',\n    text: {\n      en: 'Object information',\n    },\n  },\n  {\n    schemaName: 'references_combined',\n    text: {\n      en: 'References',\n    },\n  },\n  {\n    schemaName: 'additional',\n    text: {\n      en: 'Other information',\n    },\n  },\n  {\n    schemaName: 'normalisation_norse',\n    text: {\n      en: 'Normalization to Old West Norse',\n    },\n    css: 'normalization',\n    highlight: true,\n  },\n  {\n    schemaName: 'normalisation_scandinavian',\n    text: {\n      en: 'Normalisation to Old Scandinavian',\n    },\n    css: 'normalization',\n    highlight: true,\n  },\n  {\n    schemaName: 'english_translation',\n    text: {\n      en: 'Translation to English',\n    },\n    highlight: true,\n  },\n  {\n    schemaName: 'swedish_translation',\n    text: {\n      en: 'Translation to Swedish',\n    },\n    highlight: true,\n  },\n  {\n    schemaName: 'transliteration',\n    text: {\n      en: 'Transliterated runic text',\n    },\n    css: 'transliteration',\n    highlight: true,\n  },\n  {\n    schemaName: 'num_crosses',\n    text: {\n      en: 'Number of crosses',\n    },\n  },\n  {\n    schemaName: 'cross_form',\n    text: {\n      en: 'Cross form',\n    },\n  },\n  {\n    schemaName: 'images',\n    text: {\n      en: 'Images',\n    }\n  },\n  {\n    schemaName: 'lost',\n    text: {\n      en: 'Is Lost?',\n    }\n  }\n];\n\nconst allButNames = /[\\$\\[\\]\\{\\}\\(\\)\\?<>\\^`´\\|¬°·:×¤\\+÷']|¶+,\\./g;\nconst punctuation = /[\\.,;]/g;\n\n// Two tables below are true for DB 20240910. Assembled manually.\nconst normalizationWordsToSkip = {\n  'U 166': [3, 7],\n  'Sm 1': [24, 28],\n  'U 637': [10, 12],\n  'U 1036': [14, 16],\n  'M 11': [16, 35],\n  'G 280': [5, 7],\n  'GR 56': [8],\n  'N KJ72': [22],\n  'N B625': [69],\n  'N B311': [4],\n  'N B265': [25],\n  'N B259': [1],\n  'N B255': [20],\n  'N B27': [3],\n  'N A197': [3],\n  'N A5': [1],\n  'N A144': [1],\n  'N 603': [5],\n  'N 362': [2],\n  'N 208': [8],\n  'N 171': [13],\n  'N 124': [2],\n  'N 114': [3],\n  'N 26': [8],\n  'N 19': [12],\n  'DR NOR2004;7': [18],\n  'DR NOR1999;21': [47],\n  \"G 306\": [3],\n  \"G 129\": [11],\n  \"G 119\": [24],\n  \"Gs 8\": [7],\n  \"Vs 27\": [10],\n  \"Vs 22\": [6],\n  \"Vs 3\": [15],\n  \"U THS10;58\": [15],\n  \"U NOR1998;25\": [19],\n  \"U Fv1976;104\": [3],\n  \"U Fv1953;263\": [18],\n  \"U Fv1948;168\": [16],\n  \"U 1168\": [6],\n  \"U 1162\": [11],\n  \"U 1158\": [10],\n  \"U 1132\": [13],\n  \"U 1111\": [11],\n  \"U 1097\": [3],\n  \"U 1060\": [5],\n  \"U 1042\": [10],\n  \"U 1005\": [12],\n  \"U 968\": [3],\n  \"U 952\": [16],\n  \"U 917\": [3],\n  \"U 896\": [10],\n  \"U 805\": [40],\n  \"U 662\": [1],\n  \"U 618\": [12],\n  \"U 545\": [24],\n  \"U 459\": [11],\n  \"U 423\": [11],\n  \"U 351\": [14],\n  \"Vg 219\": [13],\n  \"Vg 67\": [25],\n  \"Sö Fv1986;218\": [8],\n  \"Sö 344\": [3],\n  \"Sö 320\": [5],\n  \"Sö 256\": [10],\n  \"Sö 205\": [3],\n  \"Sö 130\": [20],\n  \"Sö 109\": [13],\n  \"Sö 105\": [5],\n  \"Sö 82\": [9],\n  \"Sö 46\": [19],\n  \"Sö 21\": [6],\n  \"Ög 180\": [4],\n\n};\nconst transliterationWordsToSkip = {\n  \"IS IR;166\": [12],\n  'N 157': [4],\n  \"DR NOR2002;7\": [9],\n  \"G 325\": [8],\n  \"Nä 20\": [6],\n  \"U Sl115\": [1],\n  \"U Fv1992;156\": [7],\n  \"U Fv1986;84\": [1, 14],\n  \"U 775\": [13],\n  \"U 558\": [6],\n  \"U 431\": [12],\n  \"U 29\": [29],\n  \"Sö 196\": [34],\n  \"Ög 51\": [4],\n};\n\n\nlet gRenderInProgress = false;\nlet gMainDisplayScrollTargetId = null;\nconst NORMALISATION_COLUMNS = new Set(['normalisation_norse', 'normalisation_scandinavian']);\nconst MAX_VISIBLE_IMAGE_LINKS = 10;\n\nexport function setMainDisplayScrollTarget(signatureId) {\n  const parsedId = parseInt(signatureId, 10);\n  gMainDisplayScrollTargetId = Number.isNaN(parsedId) ? null : parsedId;\n}\n\nfunction scrollMainDisplayToSignature(selectedSignatureIds) {\n  const mainDisplay = document.getElementById('mainDisplay');\n  if (!mainDisplay) return;\n\n  let targetId = gMainDisplayScrollTargetId;\n  if (targetId === null && Array.isArray(selectedSignatureIds) && selectedSignatureIds.length > 0) {\n    const fallbackId = parseInt(selectedSignatureIds[selectedSignatureIds.length - 1], 10);\n    if (!Number.isNaN(fallbackId)) {\n      targetId = fallbackId;\n    }\n  }\n  if (targetId === null) return;\n\n  const targetArticle = mainDisplay.querySelector(`article[rundata-db-id=\"${targetId}\"]`);\n  if (!targetArticle) {\n    gMainDisplayScrollTargetId = null;\n    return;\n  }\n\n  const top = Math.max(0, targetArticle.offsetTop - 4);\n  mainDisplay.scrollTo({ top, behavior: 'auto' });\n  gMainDisplayScrollTargetId = null;\n}\n\nfunction concealPersonalNameMarkersForDisplay(columnName, htmlValue) {\n  if (!NORMALISATION_COLUMNS.has(columnName)) {\n    return htmlValue;\n  }\n  return htmlValue.replace(/&quot;/g, '');\n}\n\n/**\n * Retrieves a human-readable name for a schema field in the specified language.\n *\n * @param {string} schemaName - The machine name of the schema field.\n * @param {string} [lang='en'] - The language code to get the translation for (defaults to 'en').\n * @returns {string} The translated human-readable name if available, otherwise returns the schemaName.\n */\nexport function getHumanName(schemaName, lang = 'en') {\n  const field = schemaFieldsInfo.find(field => field.schemaName === schemaName);\n  if (!field) {\n    return schemaName; // Return the schemaName as is if not found\n  }\n  return field.text[lang] || schemaName; // Return the human name or schemaName if translation is missing\n}\n\n// Extracts word boundaries from inscription texts. These texts\n// have multiple separators (whitespaces, punctuation) that often\n// go together, i.e. space,punctuation,space\n// returns array of objects. Each object:\n// {start: num, end: num, text: string, isPersonal: 0|1}\n// Arguments:\n//   str - search string\nfunction getWordBoundaries(str, sourceIsEscaped = false) {\n  // regex used to detect word boundaries\n  const wordBoundariesSource = \"((\\\\s+|^(·|:|×|¤|'|\\\\+|÷|¶))(·|:|×|¤|'|\\\\+|÷|¶){0,1}\\\\s*)\";\n  // the reason it is written so complex via | is because we can do html escaping on that string\n  const punctuationSource = \"(·|:|×|¤|'|\\\\||\\\\+|÷|¶|\\\\(|\\\\)|\\\\[|\\\\])+\"\n  //const punctuationSource = \"([·|:×¤'+÷¶()|[\\]\\\\])+\";\n  const reg = new RegExp(sourceIsEscaped ? escapeHtml(wordBoundariesSource) : wordBoundariesSource, 'g');\n  // regex used to check if detected word is pure punctuation\n  const purePunctuation = new RegExp(sourceIsEscaped ? escapeHtml(punctuationSource) : punctuationSource);\n\n  const words = [];\n  let wordBegin = 0;\n\n  function processWord(wordBegin, wordEnd) {\n    const wordText = str.slice(wordBegin, wordEnd);\n\n    // Skip double sides character\n    if (str.slice(wordBegin, wordBegin + 2) === '¶¶') {\n      return null;\n    }\n    // skip {÷\n    if (str.slice(wordBegin, wordBegin+2) === \"{÷\") {\n      return null;\n    }\n\n    // Check if the word is pure punctuation\n    const punctuationCheck = purePunctuation.exec(wordText);\n    if (punctuationCheck && punctuationCheck[0].length === wordText.length) {\n      return null;\n    }\n\n    // skip ^\n    if (wordText == \"^\") {\n      return null;\n    }\n\n\n    const oneWord = {\n      start: wordBegin,\n      end: wordEnd,\n      text: wordText,\n      isPersonal: 0,\n    };\n\n    // Check if the word is personal\n    oneWord.isPersonal = isPersonalName(wordText) ? 1 : 0;\n\n    return oneWord;\n  }\n\n  let arr;\n  while ((arr = reg.exec(str)) !== null) {\n    if (wordBegin === arr.index) {\n      wordBegin += arr[0].length;\n      continue;\n    }\n    const oneWord = processWord(wordBegin, arr.index);\n    if (oneWord) {\n      words.push(oneWord);\n    }\n    wordBegin = arr.index + arr[0].length;\n  }\n\n  // Process any remaining text after the last regex match\n  if (wordBegin < str.length) {\n    const oneWord = processWord(wordBegin, str.length);\n    if (oneWord) {\n      words.push(oneWord);\n    }\n  }\n\n  return words;\n}\n\n/**\n * Convert a database object to a key map.\n * @param {Object} db - The database object that can be used to run queries.\n * @returns {Object} - The key map object. Key - signature ID, value - all signature info.\n */\nexport function convertDbToKeyMap(db) {\n  let content = db.exec(\"SELECT all_data.*, signatures_with_aliases.aliases FROM all_data LEFT JOIN signatures_with_aliases ON all_data.signature_id = signatures_with_aliases.signature_id\");\n  const columns = content[0].columns;\n  const allRows = content[0].values;\n  const signatureIdColumn = columns.indexOf(\"signature_id\");\n  const numCrossesColumn = columns.indexOf(\"num_crosses\");\n  const latitudeColumn = columns.indexOf('latitude');\n  const longitudeColumn = columns.indexOf('longitude');\n  const presentLatitudeColumn = columns.indexOf('present_latitude');\n  const presentLongitudeColumn = columns.indexOf('present_longitude');\n  const metaColumn = columns.indexOf('id');\n  const wordColumns = ['transliteration', 'normalisation_norse', 'normalisation_scandinavian'];\n  const crossFormByMetaId = {};\n\n  // Textual cross-form data (used by \"Cross form\" display field).\n  // This view is expected to exist in normal DB builds; if not, we keep it empty.\n  try {\n    const crossFormContent = db.exec(\"SELECT meta_id, crosses_textual FROM meta_with_crosses_textual\");\n    if (crossFormContent.length > 0) {\n      crossFormContent[0].values.forEach(([metaId, crossesTextual]) => {\n        crossFormByMetaId[metaId] = crossesTextual || \"\";\n      });\n    }\n  } catch (e) {\n    // no-op: leave crossFormByMetaId empty\n  }\n\n  const allDbImages = fetchAllImages(db);\n  let numDiffers = 0;\n\n  const dbArray = allRows.map(row => {\n    let objSignature = {};\n    const metaId = row[metaColumn];\n    for (let j = 0; j < row.length; j++) {\n      const columnName = columns[j];\n\n      objSignature[columnName] = row[j];\n      // handle NULL values\n      if (row[j] === null) {\n        objSignature[columnName] = \"\";\n      }\n\n      /////////////////////////////////////\n      // pre-split words and store arrays\n      if (wordColumns.includes(columnName)) {\n        objSignature[`${columnName}_html`] = escapeHtml(row[j]);\n        objSignature[`${columnName}_word_boundaries`] = getWordBoundaries(objSignature[`${columnName}_html`], true);\n      }\n    }\n\n    // Populate the textual cross form field so \"Cross form\" can render content.\n    objSignature['cross_form'] = crossFormByMetaId[metaId] || \"\";\n\n    // Build coordination text for main display.\n    const hasOriginalCoordinates = objSignature.latitude && objSignature.longitude\n      && parseFloat(objSignature.latitude) !== 0 && parseFloat(objSignature.longitude) !== 0;\n    const hasCurrentCoordinates = objSignature.present_latitude && objSignature.present_longitude\n      && parseFloat(objSignature.present_latitude) !== 0 && parseFloat(objSignature.present_longitude) !== 0;\n    const coordinationParts = [];\n    if (hasOriginalCoordinates) {\n      coordinationParts.push(`Oldest known latitude and longitude: ${objSignature.latitude}, ${objSignature.longitude}`);\n    }\n    if (hasCurrentCoordinates) {\n      coordinationParts.push(`Current latitude and longitude: ${objSignature.present_latitude}, ${objSignature.present_longitude}`);\n    }\n    objSignature['coordination'] = coordinationParts.join(' | ');\n\n    /////////////////////////////////////////\n    // Handle exceptions for word searches\n\n    // special treatment for \"Sö 145\"\n    if (objSignature.signature_text === \"Sö 145\") {\n      // treat `ata i` as a single word\n      objSignature['transliteration_html'] = escapeHtml(objSignature.transliteration.replace(\": ata i ::\", \": ata_i ::\"));\n      objSignature['transliteration_word_boundaries'] = getWordBoundaries(objSignature['transliteration_html'], true);\n      // replace it back\n      objSignature['transliteration_word_boundaries'][22].text = 'ata i';\n    }\n    if (normalizationWordsToSkip.hasOwnProperty(objSignature.signature_text)) {\n      const wordsToSkip = normalizationWordsToSkip[objSignature.signature_text];\n      objSignature.normalisation_norse_word_boundaries = objSignature.normalisation_norse_word_boundaries.filter((_, i) => !wordsToSkip.includes(i));\n      objSignature.normalisation_scandinavian_word_boundaries = objSignature.normalisation_scandinavian_word_boundaries.filter((_, i) => !wordsToSkip.includes(i));\n    }\n    if (transliterationWordsToSkip.hasOwnProperty(objSignature.signature_text)) {\n      const wordsToSkip = transliterationWordsToSkip[objSignature.signature_text];\n      objSignature.transliteration_word_boundaries = objSignature.transliteration_word_boundaries.filter((_, i) => !wordsToSkip.includes(i));\n    }\n    // End of exception handling\n    /////////////////////////////////////\n\n    // Adjust small mismatches\n    if (objSignature.transliteration_word_boundaries.length == 0 && objSignature.normalisation_norse_word_boundaries.length == 1) {\n      if (objSignature.normalisation_norse_word_boundaries[0].text == '...') {\n        // reset the normalisation_norse_word_boundaries\n        objSignature.normalisation_norse_word_boundaries = [];\n        objSignature.normalisation_scandinavian_word_boundaries = [];\n      }\n    }\n\n    wordColumns.forEach(name => {\n      // `${name}_word_boundaries` is a list of objects with keys: start, end, text\n      // create a new array out of it with only the text\n      objSignature[`${name}_words`] = objSignature[`${name}_word_boundaries`].map(boundary => {\n        return boundary.text;\n      });\n    });\n    numDiffers += objSignature.transliteration_word_boundaries.length !== objSignature.normalisation_norse_word_boundaries.length ? 1 : 0;\n\n    objSignature[\"signature_display\"] = prepareSignumForDisplay(objSignature);\n    if (objSignature[\"aliases\"]) {\n      objSignature[\"signature_header\"] = objSignature[\"signature_text\"] + \" (\" + objSignature[\"aliases\"] + \")\";\n    } else {\n      objSignature[\"signature_header\"] = objSignature[\"signature_text\"];\n    }\n\n    /////////////////////////////////////\n    // fill in images\n    if (allDbImages.hasOwnProperty(metaId)) {\n      const imagesMarkup = makeImagesMarkup(allDbImages[metaId]);\n      objSignature[\"directImages\"] = imagesMarkup.directImages;\n      objSignature[\"indirectImages\"] = imagesMarkup.indirectImages;\n    } else {\n      objSignature[\"directImages\"] = \"\";\n      objSignature[\"indirectImages\"] = \"\";\n    }\n\n    /////////////////////////////////////\n    // fill in crosses\n    let numCrosses = row[numCrossesColumn];\n    if (numCrosses > 0) {\n      objSignature['crosses'] = crossesForMeta(db, row[metaColumn]);\n    } else {\n      objSignature['crosses'] = [];\n    }\n\n    return objSignature;\n  });\n  const dbAsJson = new Map();\n  dbArray.forEach(inscription => {\n    dbAsJson.set(inscription.id, inscription);\n  });\n\n  console.log(`Number of signatures with different word boundaries (it should be 0): ${numDiffers}`);\n\n  return dbAsJson;\n}\n\nexport function prepareSignumForDisplay({signature_text, lost, new_reading}) {\n  let additional = \"\";\n  if (lost) {\n    additional += \"†\"\n  }\n  if (new_reading) {\n    additional += \"$\"\n  }\n  if (additional.length > 0) {\n    additional = \" \" + additional;\n  }\n\n  return signature_text + additional;\n}\n\nfunction resolveUrlOrBlob(urlOrBlob) {\n  if (urlOrBlob.startsWith('http://') || urlOrBlob.startsWith('https://')) {\n    return urlOrBlob;\n  }\n  const blobBase = (typeof window !== \"undefined\" && window.BLOB_BASE_URL)\n    ? window.BLOB_BASE_URL\n    : \"\";\n  if (blobBase) {\n    const normalizedBlobPath = (typeof urlOrBlob.normalize === \"function\")\n      ? urlOrBlob.normalize(\"NFD\")\n      : urlOrBlob;\n    const encodedBlobPath = normalizedBlobPath\n      .split('/')\n      .map(part => encodeURIComponent(part))\n      .join('/');\n    return blobBase.replace(/\\/$/, '') + '/' + encodedBlobPath;\n  }\n  return urlOrBlob;\n}\n\nexport function fetchAllImages(db) {\n  let content = [];\n  try {\n    content = db.exec(\"SELECT meta_id, link_url, direct_url, info FROM runes_imagelink\");\n  } catch (e) {\n    // Backwards compatibility with DB snapshots where `info` column is absent.\n    content = db.exec(\"SELECT meta_id, link_url, direct_url FROM runes_imagelink\");\n  }\n  if (!content || content.length === 0) {\n    return {};\n  }\n  const allRows = content[0].values;\n  const hasInfoColumn = content[0].columns.includes(\"info\");\n\n  let allImages = {};\n\n  for (let i = 0; i < allRows.length; i++) {\n    const row = allRows[i];\n    const indirect = row[1] || \"\";\n    const direct = row[2] || \"\";\n    const info = hasInfoColumn ? (row[3] || \"\") : \"\";\n\n    const metaId = row[0];\n    if (!allImages.hasOwnProperty(metaId)) {\n      allImages[metaId] = {links: []};\n    }\n    allImages[metaId].links.push({indirect, direct, info});\n  }\n  return allImages;\n}\n\nexport function makeImagesMarkup(signatureImageLinks) {\n  let directImages = \"\";\n  let indirectImages = \"\";\n  const isMobileViewport = isCompactDbLayout();\n\n  const escapeHtml = function (value) {\n    return String(value)\n      .replaceAll(\"&\", \"&amp;\")\n      .replaceAll(\"<\", \"&lt;\")\n      .replaceAll(\">\", \"&gt;\")\n      .replaceAll('\"', \"&quot;\");\n  };\n\n  const imageSourceLabel = function (url) {\n    try {\n      const hostname = (new URL(url)).hostname.replace(/^www\\./, \"\");\n      return hostname || url;\n    } catch {\n      return url;\n    }\n  };\n\n  const galleryLinks = isMobileViewport\n    ? signatureImageLinks.links\n    : signatureImageLinks.links.slice(0, MAX_VISIBLE_IMAGE_LINKS);\n  const hiddenLinks = isMobileViewport\n    ? []\n    : signatureImageLinks.links.slice(MAX_VISIBLE_IMAGE_LINKS);\n\n  directImages = '<div class=\"rundata-image-gallery\">';\n  galleryLinks.map(function (v) {\n    const indirectUrl = escapeHtml(v.indirect);\n    const directUrl = escapeHtml(v.direct);\n    const sourceLabel = escapeHtml(imageSourceLabel(v.indirect));\n    const infoText = (v.info || \"\").toString().trim();\n    const caption = infoText.length > 0\n      ? escapeHtml(infoText)\n      : `source: <a href=\"${indirectUrl}\" contentEditable=\"false\" target=\"_blank\">${sourceLabel}</a>`;\n    const imageContent = (!isMobileViewport || directUrl)\n      ? `<img src=\"${directUrl}\" class=\"rundata-image-avatar\" alt=\"Inscription image\">`\n      : '<div class=\"rundata-image-fallback\">Open image link</div>';\n    directImages += `<figure class=\"rundata-image-item\"><a href=\"${indirectUrl}\" contentEditable=\"false\" target=\"_blank\" class=\"rundata-image-link\">${imageContent}</a><figcaption class=\"rundata-image-source\">${caption}</figcaption></figure>`;\n  });\n  directImages += '</div>';\n\n  if (!isMobileViewport && hiddenLinks.length > 0) {\n    indirectImages = '<details class=\"rundata-more-image-links\"><summary><span class=\"text-primary text-decoration-underline\">Find more image links here</span></summary><ul>';\n    hiddenLinks.map(function (v) {\n      const indirectUrl = escapeHtml(v.indirect);\n      indirectImages += `<li><a href=\"${indirectUrl}\" contentEditable=\"false\" target=\"_blank\">${indirectUrl}</a></li>`;\n    });\n    indirectImages += '</ul></details>';\n  }\n\n  return {directImages, indirectImages};\n}\n\n/* Find all crosses related to particular metaId.\n * Returned variable is a multidimensional array. Dimensions:\n * 1. First dimension contains individual crosses.\n * 2. Second dimension denotes cross form group. There could only be 8 groups.\n * 3. Third dimension contains objects with 2 data fields (name, isCertain). Each object represents a particular cross form in a group.\n *    Note that some groups can be empty.\n */\nexport function crossesForMeta(db, metaId) {\n  // we do not check that this meta contains any crosses. This should be done in parent call\n  //let cc = db.exec(\"SELECT id FROM crosses WHERE meta_id = '\" + metaId + \"'\");\n  const contents = db.exec(\"SELECT cross_id, cross_forms.id, cross_forms.name, cross_forms.group_id, cross_definitions.is_certain FROM cross_definitions INNER JOIN cross_forms ON (cross_definitions.form_id = cross_forms.id) WHERE cross_id IN (SELECT id FROM crosses WHERE meta_id = '\" + metaId + \"') ORDER BY cross_id\");\n  if (!contents)\n    return [];\n\n  let lastCrossId = -1;\n  let crosses = [];\n\n  for (let i = 0; i < contents[0].values.length; i++) {\n    let crossId = parseInt(contents[0].values[i][0], 10);\n    if (crossId != lastCrossId) {\n      lastCrossId = crossId;\n      crosses.push(Array.apply(null, Array(8)).map(function() {return [];}));\n    }\n\n    let formName = contents[0].values[i][2];\n    let groupId = contents[0].values[i][3];\n    let isCertain = contents[0].values[i][4];\n\n    crosses[crosses.length - 1][parseInt(groupId, 10)].push({name: formName, isCertain: isCertain});\n  }\n  return crosses;\n}\n\n/**\n * Convert list/map of inscriptions to tree nodes suitable for jsTree.\n *\n * @param {*} inscriptions - Either a list of objects or a map. If map is provided, it will be converted to a list of map values.\n */\nexport function inscriptions2treeNodes(inscriptions) {\n  const values = inscriptions instanceof Map ? Array.from(inscriptions.values()) : inscriptions;\n\n  return values.map(inscription => {\n    return {\n      id: inscription.id.toString(),\n      text: inscription.signature_display,\n      normalizedText: inscription.signature_text.normalize(\"NFD\").replace(/[\\u0300-\\u036f]/g, \"\"),\n      icon: false,\n      signature: inscription.signature_text,\n    };\n  });\n}\n\nexport function selectFirstSignature() {\n  let root = $('#jstree').jstree(true).get_node('#');\n  $('#jstree').jstree(true).select_node(root.children[0]);\n}\n\n/**\n * Selects the inscription in jsTree that corresponds to the map marker that was clicked.\n * This is called by the marker click handler in initMap (index_map.js).\n * Selecting a node triggers the 'changed.jstree' event which calls displaySignatureInfo().\n * @param {string} _signature - Kept for API compatibility with index_map.js call site; not used here\n * @param {number|string} inscriptionId - The DB id of the inscription to select\n */\nexport function scrollToInscription(_signature, inscriptionId) {\n  const tree = $('#jstree').jstree(true);\n  tree.deselect_all();\n  tree.select_node(String(inscriptionId));\n  const container = $('#jstree')[0];\n  const el = document.getElementById(`${inscriptionId}_anchor`);\n  if (el && container) {\n    const containerRect = container.getBoundingClientRect();\n    const elRect = el.getBoundingClientRect();\n    container.scrollTop = container.scrollTop + elRect.top - containerRect.top;\n  }\n}\n\n/**\n * Convert list of inscriptions to a list of select options in html format.\n */\nexport function inscriptions2Select(container, dbMap) {\n  // clear container\n  container.innerHTML = \"\";\n\n  dbMap.forEach((inscription, id) => {\n    const option = document.createElement(\"option\");\n    option.value = id;\n    option.text = inscription.signature_display;\n\n    container.appendChild(option);\n  });\n}\n\n/**\n * Show tooltip message on a DOM element.\n *\n * @param {object} el DOM element, e.g. document.getElementById(\"myBtn\")\n * @param {string} message Tooltip message\n */\nfunction setTooltip(el, message) {\n  const that = $(el);\n  that.attr('data-bs-title', message).tooltip('show');\n  // do not care about multiple calls to setTimeout\n  setTimeout(function() {\n    that.tooltip('hide');\n  }, 900);\n}\n\nexport function initClipboardButtons() {\n  /**\n   * There is a collection of buttons that represent various tricky symbols, like umlauts.\n   * Clicking on them should copy the relevant symbol to the clipboard.\n   */\n  const clipboardButtons = document.querySelectorAll('.clip');\n  clipboardButtons.forEach(button => {\n    button.addEventListener('click', function() {\n      const symbol = button.innerText;\n      navigator.clipboard.writeText(symbol).then(function() {\n        setTooltip(button, 'Copied to clipboard!');\n      }).catch(function(err) {\n        setTooltip(button, 'Failed to copy!');\n      });\n    });\n  });\n}\n\nexport function arraysEqual(a, b) {\n  if (a === b) return true;\n  if (a == null || b == null) return false;\n  if (a.length != b.length) return false;\n\n  for (var i = 0; i < a.length; ++i) {\n    if (a[i] !== b[i]) return false;\n  }\n  return true;\n}\n\nexport function displaySignatureInfo() {\n  const indices = $('#jstree').jstree(true).get_selected();\n  if (indices.length == 0) {\n    return;\n  }\n\n  if (gRenderInProgress) {\n    gRenderInProgress = false;\n    setTimeout(displaySignatureInfo, 300);\n    return;\n  }\n  document.getElementById('mainDisplay').innerHTML = \"<img src='https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/themes/default-dark/throbber.gif'><br>\";\n  gRenderInProgress = true;\n  setTimeout(renderSignatures, 100);\n}\n\nfunction renderSignatures() {\n  if (!gRenderInProgress)\n    return;\n\n  try {\n    const selectedSignatureIds = $('#jstree').jstree(true).get_selected();\n    // When a search is active, render all active search hits in the main window.\n    // This ensures users can inspect the full result set without selecting each ID.\n    const selectedSignatures = (gViewModel && gViewModel.searchResults !== null)\n      ? gViewModel.getActiveInscriptions()\n      : gViewModel.getInscriptions(selectedSignatureIds);\n\n    const html = inscriptions2markup(selectedSignatures);\n    document.getElementById('mainDisplay').innerHTML = html.join('');\n    scrollMainDisplayToSignature(selectedSignatureIds);\n  } catch (e) {\n    console.error(`Error rendering signatures: ${e}`);\n  } finally {\n    gRenderInProgress = false;\n  }\n}\n\nfunction escapeHtml(string) {\n  const entityMap = {\n    '&': '&amp;',\n    '<': '&lt;',\n    '>': '&gt;',\n    '\"': '&quot;',\n    \"'\": '&#39;',\n    '/': '&#x2F;',\n    '`': '&#x60;',\n    '=': '&#x3D;'\n  };\n\n  return String(string).replace(/[&<>\"'`=\\/]/g, function (s) {\n    return entityMap[s];\n  });\n}\n\n/**\n * Replace specific raw URLs inside a plain-text reference with labelled\n * anchors, preserving surrounding citation text.\n *\n * Current mappings:\n * - http://fornvannen.se/... -> \"Fornvännen PDF\"\n * - http://kulturarvsdata.se/raa/samla/html/<number> -> \"ATA Rapport\"\n *\n * Returns null when no matching URL is found.\n */\nfunction renderSpecialReferenceLinks(refText) {\n  const raw = String(refText || '');\n  const urlRules = [\n    {\n      regex: /http:\\/\\/fornvannen\\.se\\/[^\\s;|]+/gi,\n      label: 'Fornvännen PDF',\n    },\n    {\n      regex: /http:\\/\\/kulturarvsdata\\.se\\/raa\\/samla\\/html\\/\\d+/gi,\n      label: 'ATA Rapport',\n    },\n  ];\n  const matches = [];\n  urlRules.forEach(rule => {\n    for (const match of raw.matchAll(rule.regex)) {\n      const start = match.index ?? -1;\n      if (start < 0) {\n        continue;\n      }\n      matches.push({\n        start,\n        end: start + match[0].length,\n        fullUrl: match[0],\n        label: rule.label,\n      });\n    }\n  });\n\n  if (matches.length === 0) {\n    return null;\n  }\n  matches.sort((a, b) => (a.start - b.start) || (b.end - a.end));\n\n  let result = '';\n  let lastIndex = 0;\n  for (const match of matches) {\n    if (match.start < lastIndex) {\n      // Skip overlaps, keeping the earliest non-overlapping match.\n      continue;\n    }\n    const fullUrl = match.fullUrl;\n    const cleanUrl = fullUrl.replace(/[),.;]+$/, '');\n    const trailing = fullUrl.slice(cleanUrl.length);\n\n    result += escapeHtml(raw.slice(lastIndex, match.start));\n    result += `<a href=\"${escapeHtml(cleanUrl)}\" target=\"_blank\" contenteditable=\"false\">${escapeHtml(match.label)}</a>`;\n    result += escapeHtml(trailing);\n    lastIndex = match.end;\n  }\n\n  result += escapeHtml(raw.slice(lastIndex));\n  return result;\n}\n\n/**\n * Highlight whole words in a string given their pre-computed character\n * boundaries. Used for word-aligned fields (transliteration and the two\n * normalisations). The boundaries are sorted in-place before rendering.\n *\n * @param {string} str - The (already HTML-escaped) string to render.\n * @param {Array<{start: number, end: number, text: string}>} wordBoundaries\n * @returns {string} The string with matched words wrapped in highlight spans.\n */\nexport function highlightWordsFromWordBoundaries(str, wordBoundaries) {\n  // Sort the indices to ensure they are processed in the correct order\n  wordBoundaries.sort((a, b) => a.start - b.start);\n\n  let highlightedStr = '';\n  let lastIndex = 0;\n\n  wordBoundaries.forEach(({start, end, text}) => {\n    // Append the part of the string before the current word\n    highlightedStr += str.slice(lastIndex, start);\n    // Wrap the word in a <span> tag and append it\n    highlightedStr += `<span class=\"highlight\">${str.slice(start, end)}</span>`;\n    // Update the lastIndex to the end of the current word\n    lastIndex = end;\n  });\n\n  // Append the remaining part of the string after the last word\n  highlightedStr += str.slice(lastIndex);\n\n  return highlightedStr;\n}\n\n/**\n * Highlight substring ranges in a raw (un-escaped) string, producing an\n * HTML-safe string with `<span class=\"highlight\">...</span>` around each\n * matched range. Non-highlight text is HTML-escaped via `escapeHtml`;\n * highlight content is also escaped before being wrapped.\n *\n * @param {string} rawStr - Original (un-escaped) field value.\n * @param {Array<[number, number]>} ranges - Array of [start, end) pairs into `rawStr`.\n * @returns {string} HTML-safe string with highlights applied.\n */\nexport function highlightSubstringRanges(rawStr, ranges) {\n  const str = rawStr == null ? '' : String(rawStr);\n  if (!Array.isArray(ranges) || ranges.length === 0) {\n    return escapeHtml(str);\n  }\n  // Sort and merge overlapping/adjacent ranges.\n  const sorted = ranges\n    .map(([s, e]) => [Math.max(0, s|0), Math.min(str.length, e|0)])\n    .filter(([s, e]) => e > s)\n    .sort((a, b) => a[0] - b[0]);\n  const merged = [];\n  for (const [s, e] of sorted) {\n    if (merged.length && s <= merged[merged.length - 1][1]) {\n      merged[merged.length - 1][1] = Math.max(merged[merged.length - 1][1], e);\n    } else {\n      merged.push([s, e]);\n    }\n  }\n\n  let out = '';\n  let last = 0;\n  for (const [s, e] of merged) {\n    if (s > last) out += escapeHtml(str.slice(last, s));\n    out += `<span class=\"highlight\">${escapeHtml(str.slice(s, e))}</span>`;\n    last = e;\n  }\n  if (last < str.length) out += escapeHtml(str.slice(last));\n  return out;\n}\n\nexport function inscriptions2markup(inscriptions) {\n  // array of selected inscription IDs\n  //const selectedSignatureIds = $('#jstree').jstree(true).get_selected();\n  const lang = 'en';\n  const paragraphSymbol = '§';\n  const sidesHeader = \"Sides or/and reading variants:\"\n\n  const showHeaders = $('#chkDisplayHeaders').is(\":checked\");\n  const userSelectedFields =  getUserSelectedFields();\n  const isMobileViewport = isCompactDbLayout();\n  let markupData = [];\n  for (let i = 0; i < inscriptions.length; i++) {\n\n    const inscriptionData = inscriptions[i];\n    const signatureId = inscriptionData.id;\n    const signatureName = inscriptionData.signature_text;\n\n    let paragraph = `<article signature=\"${signatureName}\" id=\"${signatureName}\" rundata-db-id=\"${signatureId}\" class=\"inscription-section\">`;\n    for (let j = 0; j < userSelectedFields.length; j++) {\n      const field = userSelectedFields[j];\n      const columnName = field.schemaName == \"signature_text\" ? \"signature_header\" : field.schemaName;\n      const humanName = field.text[lang];\n      const cssStyle = field.css || \"\";\n      const shouldHighlight = field.highlight || false;\n\n      let columnData = \"\";\n      if (columnName in inscriptionData) {\n        columnData = inscriptionData[columnName].toString();\n      }\n      const trimmedColumnData = String(columnData || '').trim();\n      if (isMobileViewport) {\n        if (columnName === 'lost' && (trimmedColumnData === '' || trimmedColumnData === '0' || trimmedColumnData.toLowerCase() === 'false')) {\n          continue;\n        }\n        if (columnName === 'num_crosses' && (trimmedColumnData === '' || trimmedColumnData === '0')) {\n          continue;\n        }\n        if (columnName === 'rune_type' && trimmedColumnData === '') {\n          continue;\n        }\n        if (columnName === 'cross_form' && trimmedColumnData === '' && inscriptionData['num_crosses'] == 0) {\n          continue;\n        }\n      }\n      if (showHeaders) {\n        paragraph += `<h4>${humanName}</h4>`;\n      } else if (paragraph.length > 0 && columnData !== \"\") {\n        // only add if there is some content to show and this is not the first field. Otherwise, we do not want to have empty lines in the beginning of the inscription info.\n        if (paragraph.slice(-7) === \"</span>\") {\n          paragraph += \"<br>\";\n        }\n      }\n\n      // Smiley is a special symbol: word substitute when word is not present\n      // We do not need to show it.\n      columnData = columnData.replace(/ ☺ /g, ' ');\n\n      if (columnName === 'images') {\n        if (!showHeaders && (inscriptionData['directImages'].length == 0 && inscriptionData['indirectImages'].length == 0)) {\n          // early stop if no headers and no images\n          continue;\n        }\n        if (inscriptionData['directImages'].length > 0) {\n          paragraph += inscriptionData['directImages'];\n          if (inscriptionData['indirectImages'].length > 0) {\n            // add image links as they have not been added yet\n            paragraph += '<br>' + inscriptionData['indirectImages'];\n          }\n          continue;\n        }\n        if (inscriptionData['indirectImages'].length > 0) {\n          paragraph += inscriptionData['indirectImages'];\n          continue;\n        }\n        if (showHeaders) {\n          paragraph += '<i>No images.</i>';\n        }\n\n        continue;\n      }\n\n      if (columnName === \"cross_form\") {\n        const hasCrossText = !!(columnData && columnData.trim() !== '');\n        if (!hasCrossText && inscriptionData['num_crosses'] == 0) {\n          if (showHeaders) {\n            paragraph += '<i>No crosses.</i>';\n          }\n          continue;\n        }\n\n        if (hasCrossText) {\n          paragraph += `<span class=\"${cssStyle}\">${escapeHtml(columnData)}</span>`;\n          paragraph += '<br>';\n        }\n\n        paragraph += '<table class=\"crosses\" border=\"1\">';\n        paragraph += '<thead><tr>';\n        paragraph += '<th>A</th>';\n        paragraph += '<th>B</th>';\n        paragraph += '<th>C</th>';\n        paragraph += '<th>D</th>';\n        paragraph += '<th>E</th>';\n        paragraph += '<th>F</th>';\n        paragraph += '<th>G</th>';\n        paragraph += '</th></thead>';\n        paragraph += '<tbody>';\n\n        const allCrosses = inscriptionData['crosses'];\n        for (let k = 0; k < allCrosses.length; k++) {\n          if (allCrosses[k][0].length > 0) {\n            // this is a cross from an undefined group\n            paragraph += '<tr><td colspan=\"7\">' + allCrosses[k][0][0].name + '</td></tr>';\n            continue;\n          }\n          paragraph += '<tr>';\n          // we have 8 groups in total, 0 being free-text and not a real group\n          for (let gr = 1; gr < 8; gr++) {\n            if (allCrosses[k][gr].length == 0) {\n              paragraph += '<td><span class=\"null\">&#8709;</span></td>';\n              continue;\n            }\n            paragraph += '<td>';\n            paragraph += allCrosses[k][gr].map(function (v, i) {\n              let res = '<img src=\"' + getCrossUrl(v.name) + '\" alt=\"'+v.name+'\" title=\"'+v.name+'\" width=\"32\" height=\"32\">';\n              if (v.isCerain == 0) {\n                res += '?';\n              }\n              return res;\n            }).join(', ');\n            paragraph += '</td>';\n          }\n        }\n        paragraph += '</tbody></table>';\n        continue;\n      }\n\n      if (columnName === \"coordination\") {\n        const lines = columnData\n          .split('|')\n          .map(item => item.trim())\n          .filter(item => item.length > 0);\n\n        if (lines.length === 0) {\n          if (showHeaders) {\n            paragraph += '<i>Absent, not in the database.</i>';\n          }\n          continue;\n        }\n\n        paragraph += `<span class=\"${cssStyle}\">${lines.map(line => escapeHtml(line)).join('<br>')}</span>`;\n        continue;\n      }\n\n      if ((columnData == '' || columnData == null) && showHeaders) {\n        paragraph += '<i>Absent, not in the database.</i>';\n        continue;\n      }\n\n      if (columnName == \"references_combined\") {\n        if (columnData && columnData.trim() !== '') {\n          const refs = columnData.split('|').map(r => r.trim()).filter(r => r.length > 0);\n          if (refs.length > 0) {\n            paragraph += `<ul class=\"${cssStyle}\">`;\n            refs.forEach(ref => {\n              const sep = ref.indexOf(':::');\n              if (sep !== -1) {\n                // Encoded link: \"<label>:::<url-or-blob-filename>\"\n                const label = ref.substring(0, sep);\n                const urlOrBlob = ref.substring(sep + 3);\n                const url = resolveUrlOrBlob(urlOrBlob);\n                paragraph += `<li><a href=\"${url}\" target=\"_blank\" contenteditable=\"false\">${escapeHtml(label)}</a></li>`;\n              } else {\n                const specialLinksMarkup = renderSpecialReferenceLinks(ref);\n                if (specialLinksMarkup !== null) {\n                  paragraph += `<li>${specialLinksMarkup}</li>`;\n                  return;\n                }\n                if (ref.includes('https://')) {\n                // Legacy unencoded URL (no label stored)\n                  paragraph += `<li><a href=\"${ref}\" target=\"_blank\" contenteditable=\"false\">${ref}</a></li>`;\n                } else {\n                  paragraph += `<li>${escapeHtml(ref)}</li>`;\n                }\n              }\n            });\n            paragraph += '</ul>';\n          } else if (showHeaders) {\n            paragraph += '<i>Absent, not in the database.</i>';\n          }\n        } else if (showHeaders) {\n          paragraph += '<i>Absent, not in the database.</i>';\n        }\n        continue;\n      }\n\n      const fieldRanges = inscriptionData.matchDetails\n        && inscriptionData.matchDetails.fieldRanges\n        && inscriptionData.matchDetails.fieldRanges[columnName];\n      if (shouldHighlight && Array.isArray(fieldRanges) && fieldRanges.length > 0) {\n        // Highlight ranges in the raw string, then escape non-highlight segments.\n        columnData = highlightSubstringRanges(columnData, fieldRanges);\n      } else {\n        columnData = escapeHtml(columnData);\n\n        if (shouldHighlight && inscriptionData.hasOwnProperty('matchDetails') && inscriptionData.matchDetails !== null && inscriptionData.matchDetails.hasOwnProperty('wordIndices')) {\n          const entryWordBoundaries = inscriptionData[`${columnName}_word_boundaries`];\n          // Word-indices highlighting only applies to columns that were pre-split into\n          // word boundaries (transliteration / normalisation_*). Translation columns\n          // are also `highlight: true` but have no `_word_boundaries`, so skip them\n          // to avoid TypeError on undefined.filter (see issue: normalization search\n          // crash \"can't access property 'filter', e is undefined\").\n          if (Array.isArray(entryWordBoundaries)) {\n            const matchedWords = inscriptionData.matchDetails.wordIndices;\n            const matchedWordBoundaries = entryWordBoundaries.filter((_, i) => matchedWords.includes(i));\n            columnData = highlightWordsFromWordBoundaries(columnData, matchedWordBoundaries);\n          }\n        }\n      }\n\n      // Keep personal-name markers in DB/search data, but hide them in\n      // rendered normalisation text shown in the main window.\n      columnData = concealPersonalNameMarkersForDisplay(columnName, columnData);\n\n      if (columnData.indexOf(paragraphSymbol) !== -1) {\n        const parts = columnData.split(paragraphSymbol);\n        paragraph += sidesHeader + \"<ul>\";\n        parts.forEach(part => {\n          if (!part.trim()) {\n            return;\n          }\n\n          paragraph += `<li><span class=\"${cssStyle}\">${paragraphSymbol} ${part}</span></li>`;\n        });\n        paragraph += \"</ul>\";\n      } else {\n        paragraph += `<span class=\"${cssStyle}\">${columnData}</span>`;\n      }\n    }\n    paragraph += '</article>';\n    markupData.push(paragraph);\n  }\n  return markupData;\n}\n\n/**\n * Handle message from export worker\n */\nexport function onExportWorkerMessage(e) {\n  gExportInProgress = false;\n\n  if (e.data.error) {\n    onExportError({message: e.data.message});\n    return;\n  }\n\n  try {\n    // Create a download for the XLSX file\n    if (e.data.buffer) {\n      // Convert ArrayBuffer to Blob with the correct MIME type\n      const blob = new Blob([e.data.buffer], {\n        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\n      });\n\n      // Create a download link and trigger the download\n      const link = document.createElement('a');\n      link.href = URL.createObjectURL(blob);\n      link.download = 'rundata-net_results.xlsx';\n      document.body.appendChild(link);\n      link.click();\n      document.body.removeChild(link);\n\n      // Close the modal after successful export\n      closeResultsIoModal();\n    }\n  } catch (error) {\n    onExportError(error);\n  } finally {\n    hideLoading();\n  }\n}\n\n/**\n * Handle export errors\n */\nexport function onExportError(error) {\n  console.error('Export error:', error);\n  hideLoading();\n  gExportInProgress = false;\n\n  // Display error to user\n  showAlert(\"Export error: \" + (error.message || 'Unknown error'));\n}\n\nexport function showAlert(message) {\n  const alertObj = document.getElementById('alertObj');\n  alertObj.textContent = message;\n  alertObj.style.display = 'block';\n\n  // Hide the alert after 5 seconds\n  setTimeout(() => {\n    alertObj.style.display = 'none';\n  }, 5000);\n}\n\n/**\n * Helper function to show loading indicator\n * @param {string} [description] - Optional loading description text\n */\nexport function showLoading(description) {\n  const defaultDescription = \"It can take up to a minute.\";\n  const txtLoadingDescription = document.getElementById('txtLoadingDescription');\n\n  if (txtLoadingDescription) {\n    txtLoadingDescription.textContent = description || defaultDescription;\n  }\n  $('#loading').show();\n}\n\n/**\n * Helper function to hide loading indicator\n */\nexport function hideLoading() {\n  $('#loading').hide();\n}\n\nexport function closeResultsIoModal() {\n  $('#modalResultsIo').modal('hide');\n}\n","import { isCompactDbLayout } from './index_layout.js';\n\n// Key in the local storage under which users display options are saved.\n// display options are information which is displayed per inscription.\nconst gUserSelectedDisplayKey = \"userSelectedDisplay\";\nconst gShowHeadersKey = \"showHeaders\";\nconst gMobileDisplayDefaultsVersionKey = \"mobileDisplayDefaultsVersion\";\nconst MOBILE_DISPLAY_DEFAULTS_VERSION = \"20260603-mobile-property-order\";\nconst REQUIRED_DISPLAY_VALUES = ['coordination'];\nconst DEFAULT_SELECTED_DISPLAY_VALUES = [\n  'signature_text', 'transliteration', 'normalisation_scandinavian', 'normalisation_norse',\n  'english_translation', 'swedish_translation', 'found_location', 'parish', 'municipality', 'district', 'current_location',\n  'original_site', 'coordination', 'images', 'rune_type', 'carver', 'num_crosses', 'cross_form', 'dating', 'style',\n  'material_type', 'material', 'objectInfo', 'references_combined', 'additional'\n];\nconst MOBILE_DEFAULT_SELECTED_DISPLAY_VALUES = [\n  'signature_text',\n  'images',\n  'objectInfo',\n  'lost',\n  'dating',\n  'transliteration',\n  'normalisation_norse',\n  'normalisation_scandinavian',\n  'english_translation',\n  'swedish_translation',\n  'found_location',\n  'parish',\n  'district',\n  'municipality',\n  'coordination',\n  'carver',\n  'style',\n  'rune_type',\n  'num_crosses',\n  'cross_form',\n  'additional',\n  'references_combined',\n];\nconst DISPLAY_FIELD_GROUPS = [\n  {\n    title: 'Inscription',\n    fields: ['signature_text', 'lost', 'images'],\n  },\n  {\n    title: 'Runic Texts',\n    fields: ['transliteration', 'normalisation_scandinavian', 'normalisation_norse', 'english_translation', 'swedish_translation'],\n  },\n  {\n    title: 'Location',\n    fields: ['full_address', 'found_location', 'parish', 'municipality', 'district', 'current_location', 'original_site', 'coordination', 'parish_code'],\n  },\n  {\n    title: 'Dating',\n    fields: ['dating', 'year_from', 'year_to', 'style'],\n  },\n  {\n    title: 'Design and Object',\n    fields: ['rune_type', 'carver', 'num_crosses', 'cross_form', 'material_type', 'material', 'objectInfo'],\n  },\n  {\n    title: 'References and Other',\n    fields: ['references_combined', 'additional'],\n  },\n];\n\nfunction normalizeSelectedValues(selectedValues) {\n  const normalized = Array.isArray(selectedValues) ? [...selectedValues] : [...DEFAULT_SELECTED_DISPLAY_VALUES];\n  REQUIRED_DISPLAY_VALUES.forEach(requiredValue => {\n    if (!normalized.includes(requiredValue)) {\n      normalized.push(requiredValue);\n    }\n  });\n  return normalized;\n}\n\nfunction applyMobileDefaultMigration(selectedValues, storage) {\n  const normalized = normalizeSelectedValues(selectedValues);\n  if (!shouldUseMobileDefaults() || !storage) {\n    return normalized;\n  }\n  if (storage.getItem(gMobileDisplayDefaultsVersionKey) === MOBILE_DISPLAY_DEFAULTS_VERSION) {\n    return normalized;\n  }\n\n  const selectedSet = new Set(normalized);\n  const orderedDefaults = MOBILE_DEFAULT_SELECTED_DISPLAY_VALUES.filter(value => selectedSet.has(value) || !normalized.includes(value));\n  const remainingSelected = normalized.filter(value => !orderedDefaults.includes(value));\n  const migratedValues = [...orderedDefaults, ...remainingSelected];\n  storage.setItem(gMobileDisplayDefaultsVersionKey, MOBILE_DISPLAY_DEFAULTS_VERSION);\n  storage.setItem(gUserSelectedDisplayKey, JSON.stringify(migratedValues));\n  return migratedValues;\n}\n\nfunction shouldUseMobileDefaults() {\n  return isCompactDbLayout();\n}\n\nfunction isMobileDisplayOptionsUi() {\n  return shouldUseMobileDefaults();\n}\n\nfunction getDefaultSelectedDisplayValues() {\n  return shouldUseMobileDefaults()\n    ? MOBILE_DEFAULT_SELECTED_DISPLAY_VALUES\n    : DEFAULT_SELECTED_DISPLAY_VALUES;\n}\n\nfunction storageAvailable(type) {\n  let storage;\n  try {\n    storage = window[type];\n    const x = '__storage_test__';\n    storage.setItem(x, x);\n    storage.removeItem(x);\n    return true;\n  }\n  catch(e) {\n    return e instanceof DOMException && (\n      // everything except Firefox\n      e.code === 22 ||\n      // Firefox\n      e.code === 1014 ||\n      // test name field too, because code might not be present\n      // everything except Firefox\n      e.name === 'QuotaExceededError' ||\n      // Firefox\n      e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&\n      // acknowledge QuotaExceededError only if there's something already stored\n      (storage && storage.length !== 0);\n  }\n}\n\nexport function getUserSelectedDisplay() {\n  if (!storageAvailable('localStorage')) {\n    const fallbackDefaults = getDefaultSelectedDisplayValues();\n    return normalizeSelectedValues(fallbackDefaults);\n  }\n\n  try {\n    const storage = window['localStorage'];\n    if (storage.getItem(gUserSelectedDisplayKey)) {\n      return normalizeSelectedValues(JSON.parse(storage.getItem(gUserSelectedDisplayKey)));\n    }\n  } catch (e) {\n    console.error('Error while reading user selected display from local storage:', e);\n  }\n\n  const fallbackDefaults = getDefaultSelectedDisplayValues();\n  return normalizeSelectedValues(fallbackDefaults);\n}\n\nexport function saveUserSelectedDisplay(selectedValues = null) {\n  if (!storageAvailable('localStorage')) {\n    return;\n  }\n  const storage = window['localStorage'];\n\n  //var selectedValues = $('#multiselect_to option').map((index, el) => $(el).val()).toArray();\n  // ensure it is an array and encode it as json string, because local storage can work with string only.\n  const selectedValuesArray = JSON.stringify(selectedValues ? [].concat(selectedValues) : []);\n\n  storage.setItem(gUserSelectedDisplayKey, selectedValuesArray);\n}\n\nexport function getUserSelectedFields() {\n  const selectedValues = getUserSelectedDisplay();\n  return selectedValues\n    .map(value => schemaFieldsInfo.find(prop => prop.schemaName === value))\n    .filter(Boolean);\n}\n\n\nfunction getSchemaField(schemaName) {\n  return schemaFieldsInfo.find(prop => prop.schemaName === schemaName);\n}\n\nfunction getChecklistSelectedValues() {\n  return $('#displayOptionsChecklist input[data-display-field]:checked')\n    .map((index, el) => $(el).attr('data-display-field'))\n    .toArray();\n}\n\nfunction renderDisplayOptionRow(field, selectedSet) {\n  const isRequired = REQUIRED_DISPLAY_VALUES.includes(field.schemaName);\n  const isChecked = isRequired || selectedSet.has(field.schemaName);\n  const disabledAttr = isRequired ? 'disabled' : '';\n  const requiredHint = isRequired ? ' <span class=\"text-muted\">(always shown)</span>' : '';\n  return `\n    <div class=\"display-options-mobile-row\" data-display-option-row=\"${field.schemaName}\">\n      <div class=\"form-check mb-0 flex-grow-1\">\n        <input class=\"form-check-input\" type=\"checkbox\" data-display-field=\"${field.schemaName}\" id=\"displayField_${field.schemaName}\" ${isChecked ? 'checked' : ''} ${disabledAttr}>\n        <label class=\"form-check-label\" for=\"displayField_${field.schemaName}\">${field.text['en']}${requiredHint}</label>\n      </div>\n      <div class=\"display-options-mobile-order\" aria-label=\"Move ${field.text['en']}\">\n        <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" data-display-move=\"up\" data-display-field=\"${field.schemaName}\" aria-label=\"Move ${field.text['en']} up\">\n          <i class=\"bi bi-chevron-up\" aria-hidden=\"true\"></i>\n        </button>\n        <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" data-display-move=\"down\" data-display-field=\"${field.schemaName}\" aria-label=\"Move ${field.text['en']} down\">\n          <i class=\"bi bi-chevron-down\" aria-hidden=\"true\"></i>\n        </button>\n      </div>\n    </div>\n  `;\n}\n\nfunction updateMobileDisplayOrderButtons() {\n  const rows = Array.from(document.querySelectorAll('#displayOptionsChecklist [data-display-option-row]'));\n  rows.forEach((row, index) => {\n    const upButton = row.querySelector('[data-display-move=\"up\"]');\n    const downButton = row.querySelector('[data-display-move=\"down\"]');\n    if (upButton) {\n      upButton.disabled = index === 0;\n    }\n    if (downButton) {\n      downButton.disabled = index === rows.length - 1;\n    }\n  });\n}\n\nfunction renderMobileDisplayOptionsChecklist(selectedValues) {\n  const selectedSet = new Set(normalizeSelectedValues(selectedValues));\n  const $checklist = $('#displayOptionsChecklist');\n  $checklist.empty();\n\n  const orderedFieldNames = [\n    ...normalizeSelectedValues(selectedValues),\n    ...schemaFieldsInfo\n      .map(field => field.schemaName)\n      .filter(schemaName => !selectedSet.has(schemaName)),\n  ];\n  const usedFields = new Set();\n  orderedFieldNames.forEach(schemaName => {\n    if (usedFields.has(schemaName)) {\n      return;\n    }\n    const field = getSchemaField(schemaName);\n    if (!field) {\n      return;\n    }\n    usedFields.add(schemaName);\n    $checklist.append(renderDisplayOptionRow(field, selectedSet));\n  });\n  updateMobileDisplayOrderButtons();\n}\n\nfunction renderGroupedDisplayOptionsChecklist(selectedValues) {\n  const selectedSet = new Set(normalizeSelectedValues(selectedValues));\n  const usedFields = new Set();\n  const $checklist = $('#displayOptionsChecklist');\n  $checklist.empty();\n\n  DISPLAY_FIELD_GROUPS.forEach(group => {\n    const groupFields = group.fields\n      .map(name => getSchemaField(name))\n      .filter(Boolean);\n    if (groupFields.length === 0) {\n      return;\n    }\n    const $group = $('<div class=\"display-options-group\"></div>');\n    $group.append(`<h6 class=\"display-options-group-title\">${group.title}</h6>`);\n\n    groupFields.forEach(field => {\n      usedFields.add(field.schemaName);\n      const isRequired = REQUIRED_DISPLAY_VALUES.includes(field.schemaName);\n      const isChecked = isRequired || selectedSet.has(field.schemaName);\n      const disabledAttr = isRequired ? 'disabled' : '';\n      const requiredHint = isRequired ? ' <span class=\"text-muted\">(always shown)</span>' : '';\n      const rowHtml = `\n        <div class=\"form-check mb-1\">\n          <input class=\"form-check-input\" type=\"checkbox\" data-display-field=\"${field.schemaName}\" id=\"displayField_${field.schemaName}\" ${isChecked ? 'checked' : ''} ${disabledAttr}>\n          <label class=\"form-check-label\" for=\"displayField_${field.schemaName}\">${field.text['en']}${requiredHint}</label>\n        </div>\n      `;\n      $group.append(rowHtml);\n    });\n\n    $checklist.append($group);\n  });\n\n  const remainingFields = schemaFieldsInfo.filter(field => !usedFields.has(field.schemaName));\n  if (remainingFields.length > 0) {\n    const $otherGroup = $('<div class=\"display-options-group\"></div>');\n    $otherGroup.append('<h6 class=\"display-options-group-title\">Other</h6>');\n    remainingFields.forEach(field => {\n      const isRequired = REQUIRED_DISPLAY_VALUES.includes(field.schemaName);\n      const isChecked = isRequired || selectedSet.has(field.schemaName);\n      const disabledAttr = isRequired ? 'disabled' : '';\n      const requiredHint = isRequired ? ' <span class=\"text-muted\">(always shown)</span>' : '';\n      const rowHtml = `\n        <div class=\"form-check mb-1\">\n          <input class=\"form-check-input\" type=\"checkbox\" data-display-field=\"${field.schemaName}\" id=\"displayField_${field.schemaName}\" ${isChecked ? 'checked' : ''} ${disabledAttr}>\n          <label class=\"form-check-label\" for=\"displayField_${field.schemaName}\">${field.text['en']}${requiredHint}</label>\n        </div>\n      `;\n      $otherGroup.append(rowHtml);\n    });\n    $checklist.append($otherGroup);\n  }\n}\n\nfunction renderDisplayOptionsChecklist(selectedValues) {\n  if (isMobileDisplayOptionsUi()) {\n    renderMobileDisplayOptionsChecklist(selectedValues);\n    return;\n  }\n  renderGroupedDisplayOptionsChecklist(selectedValues);\n}\n\nfunction setDesktopMultiselectOptions(selectedValues) {\n  let sortValue = 0;\n  $('#multiselect_to').empty();\n  $('#multiselect').empty();\n\n  // Populate the list of already selected display options\n  selectedValues.forEach(value => {\n    const schemaField = schemaFieldsInfo.find(prop => prop.schemaName === value);\n    if (schemaField) {\n      $('#multiselect_to').append($('<option>', {\n        value: schemaField.schemaName,\n        text : schemaField.text['en'],\n        sortValue: sortValue++,\n      }));\n    }\n  });\n\n  // Populate the list of available display options\n  schemaFieldsInfo.forEach(schemaField => {\n    if (selectedValues.indexOf(schemaField.schemaName) === -1) {\n      $('#multiselect').append($('<option>', {\n        value: schemaField.schemaName,\n        text : schemaField.text['en'],\n        sortValue: sortValue++,\n      }));\n    }\n  });\n}\n\nfunction getCurrentSelectedValues() {\n  if (isMobileDisplayOptionsUi()) {\n    return getChecklistSelectedValues();\n  }\n  return $('#multiselect_to option').map((index, el) => $(el).val()).toArray();\n}\n\nfunction setMultiselectOptions(selectedValues, showHeaders) {\n  setDesktopMultiselectOptions(selectedValues);\n  renderDisplayOptionsChecklist(selectedValues);\n\n  if (typeof showHeaders !== 'boolean') {\n    showHeaders = showHeaders === 'true';\n  }\n  $('#chkDisplayHeaders').prop('checked', showHeaders);\n}\n\nfunction applyDisplayFormatChanges({ closeDialog = false } = {}) {\n  const alertObj = $('#formatDialogAlertObj');\n  const selectedValues = getCurrentSelectedValues();\n\n  if (selectedValues === null || selectedValues.length == 0) {\n    alertObj.html('Nothing is selected for display! Please select at least one property.');\n    alertObj.show();\n    return false;\n  }\n  alertObj.hide();\n\n  const lastShowHeaders = localStorage.getItem(gShowHeadersKey);\n  const lastUserSelectedValues = JSON.parse(localStorage.getItem(gUserSelectedDisplayKey) || '[]');\n  const showHeaders = $('#chkDisplayHeaders').is(\":checked\");\n  if (!arraysEqual(lastUserSelectedValues, selectedValues) || lastShowHeaders != String(showHeaders)) {\n    saveUserSelectedDisplay(selectedValues);\n    localStorage.setItem(gShowHeadersKey, showHeaders);\n\n    // display signature info\n    $('#multiselect, #displayOptionsChecklist').trigger('displayUpdated', {message: 'hello'});\n  }\n\n  if (closeDialog) {\n    document.getElementById('btnDismissDisplayFormat').click();\n  }\n  return true;\n}\n\nfunction applyMobileDisplayFormatImmediately() {\n  if (!isMobileDisplayOptionsUi()) {\n    return;\n  }\n  applyDisplayFormatChanges({ closeDialog: false });\n}\n\nfunction moveMobileDisplayOption(event) {\n  const button = event.target.closest('[data-display-move]');\n  if (!button || !isMobileDisplayOptionsUi()) {\n    return;\n  }\n\n  const row = button.closest('[data-display-option-row]');\n  if (!row) {\n    return;\n  }\n\n  if (button.dataset.displayMove === 'up' && row.previousElementSibling) {\n    row.parentElement.insertBefore(row, row.previousElementSibling);\n  } else if (button.dataset.displayMove === 'down' && row.nextElementSibling) {\n    row.parentElement.insertBefore(row.nextElementSibling, row);\n  }\n\n  updateMobileDisplayOrderButtons();\n  applyMobileDisplayFormatImmediately();\n}\n\n/**\n * Preserve the sorting after user interacts with the display options.\n * Reassigns sortValue attributes so the current order is maintained\n * when the multiselect sort function runs after adding/removing items.\n */\nexport function resortDisplayOptions() {\n  let newSort = 0;\n  $('#multiselect_to option').each(function() { $(this).attr('sortValue', newSort++); });\n  $('#multiselect option').each(function() { $(this).attr('sortValue', newSort++); });\n}\n\nexport function initMultiselect() {\n  const storage = window['localStorage'];\n  const savedSelected = storage.getItem(gUserSelectedDisplayKey);\n  const defaultValues = getDefaultSelectedDisplayValues();\n  const selectedValues = savedSelected\n    ? applyMobileDefaultMigration(JSON.parse(savedSelected), storage)\n    : normalizeSelectedValues(defaultValues);\n  if (!savedSelected && shouldUseMobileDefaults()) {\n    storage.setItem(gMobileDisplayDefaultsVersionKey, MOBILE_DISPLAY_DEFAULTS_VERSION);\n  }\n  const savedShowHeaders = storage.getItem(gShowHeadersKey);\n  const showHeaders = savedShowHeaders ? savedShowHeaders === 'true' : true;\n\n  setMultiselectOptions(selectedValues, showHeaders);\n\n  $('#formatDialogAlertObj').hide();\n\n  $('#multiselect').multiselect({\n    keepRenderingSortRight: false,\n    skipInitSortRight: false,\n    sort: {\n      left: function (a, b) {\n        const aValue = parseInt($(a).attr('sortValue'));\n        const bValue = parseInt($(b).attr('sortValue'));\n\n        return aValue > bValue ? 1 : -1;\n      },\n      right: function (a, b) {\n        const aValue = parseInt($(a).attr('sortValue'));\n        const bValue = parseInt($(b).attr('sortValue'));\n\n        return aValue > bValue ? 1 : -1;\n      }\n    },\n    afterMoveUp: () => resortDisplayOptions(),\n    afterMoveDown: () => resortDisplayOptions(),\n    afterMoveToRight: () => resortDisplayOptions(),\n    afterMoveToLeft: () => resortDisplayOptions(),\n  });\n\n  document.getElementById('btnApplyDisplayFormat').addEventListener('click', onDisplayFormatClicked);\n  document.getElementById('displayOptionsChecklist').addEventListener('change', applyMobileDisplayFormatImmediately);\n  document.getElementById('displayOptionsChecklist').addEventListener('click', moveMobileDisplayOption);\n  document.getElementById('chkDisplayHeaders').addEventListener('change', applyMobileDisplayFormatImmediately);\n  document.getElementById('btnDismissDisplayFormat').addEventListener('click', () => {\n    // revert the changes\n    const savedShowHeaders = localStorage.getItem(gShowHeadersKey);\n    const showHeaders = savedShowHeaders ? savedShowHeaders === 'true' : true;\n    const savedSelected = localStorage.getItem(gUserSelectedDisplayKey);\n    const selectedValues = normalizeSelectedValues(savedSelected ? JSON.parse(savedSelected) : getDefaultSelectedDisplayValues());\n    setMultiselectOptions(selectedValues, showHeaders);\n  });\n\n  const formatDialogEl = document.getElementById('divFormatDialog')\n  formatDialogEl.addEventListener('shown.bs.modal', event => {\n    $('#formatDialogAlertObj').hide();\n\n    // preserve current display options in a local storage, so that we may compare it later to detect user edit\n    const lastShowHeaders = $('#chkDisplayHeaders').is(\":checked\");\n    const userSelectedDisplay = getCurrentSelectedValues();\n    saveUserSelectedDisplay(userSelectedDisplay);\n    localStorage.setItem(gShowHeadersKey, lastShowHeaders);\n  });\n}\n\nfunction onDisplayFormatClicked(e) {\n  e.preventDefault();\n  applyDisplayFormatChanges({ closeDialog: true });\n}\n","import { isCompactDbLayout } from './index_layout.js';\n\n/*\n This script assumes that you reference leaflet.js and leaflet.css in your HTML file.\n It allows relies on defined `isSafari` function.\n*/\n\nconst MOBILE_PAIR_SPIDERFY_MAX_DISTANCE_METERS = 100;\nconst MOBILE_SHARED_COORDINATE_MAX_MARKERS = 10;\n\nexport function shouldSpiderfyNearbyPair(cluster, map, maxDistanceMeters = MOBILE_PAIR_SPIDERFY_MAX_DISTANCE_METERS) {\n  if (!cluster || typeof cluster.getAllChildMarkers !== 'function'\n    || !map || typeof map.distance !== 'function') {\n    return false;\n  }\n\n  if (typeof cluster.getChildCount === 'function' && cluster.getChildCount() !== 2) {\n    return false;\n  }\n\n  const childMarkers = cluster.getAllChildMarkers();\n  if (childMarkers.length !== 2) {\n    return false;\n  }\n\n  return map.distance(\n    childMarkers[0].getLatLng(),\n    childMarkers[1].getLatLng()\n  ) <= maxDistanceMeters;\n}\n\nexport function shouldSpiderfySharedCoordinates(cluster, maxMarkers = MOBILE_SHARED_COORDINATE_MAX_MARKERS) {\n  if (!cluster || typeof cluster.getChildCount !== 'function'\n    || typeof cluster.getAllChildMarkers !== 'function') {\n    return false;\n  }\n\n  const childCount = cluster.getChildCount();\n  if (childCount < 2 || childCount > maxMarkers) {\n    return false;\n  }\n\n  const childMarkers = cluster.getAllChildMarkers();\n  if (childMarkers.length !== childCount) {\n    return false;\n  }\n\n  const firstLatLng = childMarkers[0].getLatLng();\n  return childMarkers.every(marker => {\n    const latLng = marker.getLatLng();\n    return latLng.lat === firstLatLng.lat && latLng.lng === firstLatLng.lng;\n  });\n}\n\nexport function handleMobileClusterClick(event, map) {\n  const cluster = event && event.layer;\n  if (!cluster) {\n    return;\n  }\n\n  if (shouldSpiderfyNearbyPair(cluster, map) || shouldSpiderfySharedCoordinates(cluster)) {\n    cluster.spiderfy();\n    return;\n  }\n\n  const isAtMaxZoom = typeof map.getZoom === 'function'\n    && typeof map.getMaxZoom === 'function'\n    && map.getZoom() >= map.getMaxZoom();\n  if (isAtMaxZoom && typeof cluster.spiderfy === 'function') {\n    cluster.spiderfy();\n  } else if (typeof cluster.zoomToBounds === 'function') {\n    cluster.zoomToBounds();\n  }\n}\n\nexport function getMarkerClusterOptions(isMobile) {\n  const options = {\n    showCoverageOnHover: true,\n    chunkedLoading: true,\n    maxClusterRadius: 60,\n  };\n  if (isMobile) {\n    // Handle small nearby pairs ourselves so they separate immediately on tap.\n    options.zoomToBoundsOnClick = false;\n    options.spiderfyOnMaxZoom = false;\n  }\n  return options;\n}\n\nexport function getSpiderfiedTooltipDirection(marker, cluster, map) {\n  if (!marker || !cluster || !map\n    || typeof marker.getLatLng !== 'function'\n    || typeof cluster.getLatLng !== 'function'\n    || typeof map.latLngToLayerPoint !== 'function') {\n    return 'auto';\n  }\n\n  const markerPoint = map.latLngToLayerPoint(marker.getLatLng());\n  const clusterPoint = map.latLngToLayerPoint(cluster.getLatLng());\n  const deltaX = markerPoint.x - clusterPoint.x;\n  const deltaY = markerPoint.y - clusterPoint.y;\n\n  if (Math.abs(deltaX) >= Math.abs(deltaY)) {\n    return deltaX >= 0 ? 'right' : 'left';\n  }\n  return deltaY >= 0 ? 'bottom' : 'top';\n}\n\nfunction getSpiderfyEventMarkers(event) {\n  if (event && Array.isArray(event.markers)) {\n    return event.markers;\n  }\n  if (event && event.cluster && typeof event.cluster.getAllChildMarkers === 'function') {\n    return event.cluster.getAllChildMarkers();\n  }\n  return [];\n}\n\nexport function positionSpiderfiedTooltips(event, map) {\n  const cluster = event && event.cluster;\n  if (!cluster) {\n    return;\n  }\n\n  getSpiderfyEventMarkers(event).forEach(marker => {\n    const tooltip = typeof marker.getTooltip === 'function' ? marker.getTooltip() : null;\n    if (!tooltip || !tooltip.options) {\n      return;\n    }\n    if (typeof tooltip._rundataOriginalDirection === 'undefined') {\n      tooltip._rundataOriginalDirection = tooltip.options.direction || 'auto';\n    }\n    tooltip.options.direction = getSpiderfiedTooltipDirection(marker, cluster, map);\n    if (typeof tooltip.update === 'function') {\n      tooltip.update();\n    }\n  });\n}\n\nexport function resetSpiderfiedTooltips(event) {\n  getSpiderfyEventMarkers(event).forEach(marker => {\n    const tooltip = typeof marker.getTooltip === 'function' ? marker.getTooltip() : null;\n    if (!tooltip || !tooltip.options || typeof tooltip._rundataOriginalDirection === 'undefined') {\n      return;\n    }\n    tooltip.options.direction = tooltip._rundataOriginalDirection;\n    delete tooltip._rundataOriginalDirection;\n    if (typeof tooltip.update === 'function') {\n      tooltip.update();\n    }\n  });\n}\n\n// Initialize the map on the user-provided div with a given center and zoom level\n// Default center is [56.607512, 16.439838] and default zoom is 8.\nexport function initMap(divId, center = [56.607512, 16.439838], zoom = 8) {\n  const isMobile = isMobileDevice();\n  const map = L.map(divId, {\n    fullscreenControl: true,\n    // Use pseudo-fullscreen only on mobile to avoid desktop behavior changes.\n    fullscreenControlOptions: isMobile ? {\n      forcePseudoFullscreen: true,\n      pseudoFullscreen: true,\n    } : {},\n  }).setView(center, zoom);\n  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n    maxZoom: 19,\n    attribution: '© <a href=\"https://openstreetmap.org\">OpenStreetMap</a> contributors',\n    referrerPolicy: 'origin',\n  }).addTo(map);\n\n  // add location control to global name space for testing only\n  // on a production site, omit the \"lc = \"!\n  L.control.locate({\n    locateOptions: {\n      enableHighAccuracy: true,\n      timeout: 12000,\n      maximumAge: 60000,\n    },\n    strings: {\n      title: \"My location\",\n    }\n  })\n  .addTo(map);\n\n  map.on('locationerror', function(event) {\n    const details = getGeoLocationErrorDetails(event);\n    const message = `Geolocation error: ${details}`;\n    if (typeof showAlert === 'function') {\n      showAlert(message);\n    } else {\n      alert(message);\n    }\n  });\n\n  const markers = L.markerClusterGroup(getMarkerClusterOptions(isMobile));\n  markers.on('click', function (e) {\n    scrollToInscription(e.layer.options.signature, e.layer.options.id);\n  });\n  if (isMobile) {\n    markers.on('clusterclick', function(event) {\n      handleMobileClusterClick(event, map);\n    });\n    markers.on('spiderfied', function(event) {\n      positionSpiderfiedTooltips(event, map);\n    });\n    markers.on('unspiderfied', resetSpiderfiedTooltips);\n  }\n  markers.addTo(map);\n\n  return {map, markers};\n}\n\nfunction getGeoLocationErrorDetails(event) {\n  const code = event && typeof event.code === 'number' ? event.code : null;\n  const browserMessage = event && event.message ? String(event.message) : '';\n\n  if (code === 1) {\n    return 'permission denied. Allow location access for this site in browser settings and reload.';\n  }\n  if (code === 2) {\n    return 'position unavailable. Check GPS/network and try again.';\n  }\n  if (code === 3) {\n    return 'timeout. Move to better coverage and try again.';\n  }\n\n  if (browserMessage) {\n    return browserMessage;\n  }\n  return 'unknown issue. Check site permission and connection, then try again.';\n}\n\nexport function onHideMapClicked(mapContainerId, menuItemId) {\n  const mapContainerJquery = `#${mapContainerId}`;\n  const menuItemJquery = `#${menuItemId}`;\n\n  $(mapContainerJquery).toggle();\n  if ($(mapContainerJquery).is(\":visible\")) {\n    $(menuItemJquery).html('Hide map');\n  } else {\n    $(menuItemJquery).html('Show map');\n  }\n}\n\nfunction isMobileDevice() {\n  return isCompactDbLayout();\n}\n\nfunction getGeoIntentURL(lat, lng) {\n  // Use Google Maps universal directions URL so mobile users (including iPhone)\n  // can open navigation consistently.\n  return `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}&travelmode=driving`;\n}\n\nfunction selectInscriptionForMobileInfo(inscriptionId) {\n  if (typeof window === 'undefined') {\n    return false;\n  }\n\n  if (typeof window.scrollToInscription === 'function') {\n    window.scrollToInscription('', inscriptionId);\n    return true;\n  }\n\n  const jquery = window.jQuery || window.$;\n  if (typeof jquery !== 'function') {\n    return false;\n  }\n  const tree = jquery('#jstree').jstree(true);\n  if (!tree) {\n    return false;\n  }\n  tree.deselect_all();\n  tree.select_node(String(inscriptionId));\n  return true;\n}\n\nexport function openMobileInscriptionInfo(inscriptionId) {\n  if (!isMobileDevice() || !selectInscriptionForMobileInfo(inscriptionId)) {\n    return false;\n  }\n\n  const infoButton = typeof document !== 'undefined'\n    ? document.getElementById('mobilePaneInfo')\n    : null;\n  if (infoButton && typeof infoButton.click === 'function') {\n    infoButton.click();\n  }\n  return false;\n}\n\nfunction isLostInscription(inscriptionData) {\n  const value = inscriptionData && inscriptionData.lost;\n  if (value === true || value === 1 || value === '1') {\n    return true;\n  }\n  return false;\n}\n\nfunction hasCurrentLocationInfo(inscriptionData) {\n  const currentLocation = inscriptionData && inscriptionData.current_location;\n  return String(currentLocation || '').trim().length > 0;\n}\n\nfunction makeMobileTooltipOpenPopup(marker, tooltip) {\n  const tooltipElement = tooltip && typeof tooltip.getElement === 'function'\n    ? tooltip.getElement()\n    : null;\n  if (!tooltipElement || tooltipElement.dataset.mobilePopupTrigger === 'true') {\n    return;\n  }\n\n  tooltipElement.dataset.mobilePopupTrigger = 'true';\n  tooltipElement.setAttribute('role', 'button');\n  tooltipElement.setAttribute('tabindex', '0');\n  tooltipElement.setAttribute('aria-label', 'Open Drive here and warnings');\n\n  const openPopupFromTooltip = (event) => {\n    if (event && typeof event.preventDefault === 'function') {\n      event.preventDefault();\n    }\n    if (event && typeof event.stopPropagation === 'function') {\n      event.stopPropagation();\n    }\n    marker.openPopup();\n  };\n\n  tooltipElement.addEventListener('click', openPopupFromTooltip);\n  tooltipElement.addEventListener('touchend', openPopupFromTooltip);\n  tooltipElement.addEventListener('keydown', (event) => {\n    if (event.key === 'Enter' || event.key === ' ') {\n      openPopupFromTooltip(event);\n    }\n  });\n}\n\nfunction inscription2marker(inscriptionData, lat, lon, locationType = 'found', leaflet=L) {\n  // Inscriptions have two sets of latitude and longitude values: one for the\n  // original location and one for the present location. We will always create two\n  // markers for each inscription. This means that even if the present location is\n  // the same as the original location, we will still create two markers.\n\n  if (lat === 0.0 || lon === 0.0) {\n    return null;\n  }\n  let marker = leaflet.marker([lat, lon], {\n    signature: inscriptionData.signature_text,\n    id: inscriptionData.id,\n  });\n  let popupText = `${inscriptionData.signature_text}<br>`;\n  const isMobile = isMobileDevice();\n  const hasCurrentLocation = hasCurrentLocationInfo(inscriptionData);\n  const warningTexts = [];\n  const infoTexts = [];\n  const confirmTexts = [];\n  if (isLostInscription(inscriptionData)) {\n    warningTexts.push('Warning: this inscription is lost.');\n    confirmTexts.push('Are you sure you want to drive here? The inscription is lost!');\n  }\n  if (locationType === 'found' && hasCurrentLocation) {\n    warningTexts.push('Warning: this inscription is moved.');\n  }\n  if (isMobile && locationType === 'present' && hasCurrentLocation) {\n    warningTexts.push('Warning: this inscription is moved.');\n    infoTexts.push('You are driving to Current location.');\n  }\n  if (locationType === 'found' && hasCurrentLocation) {\n    confirmTexts.push('Are you sure you want to drive to Found location? The inscription is moved! Check its current location.');\n  }\n  warningTexts.forEach(text => {\n    if (isMobile) {\n      popupText += `<span class=\"map-popup-warning\">${text}</span><br>`;\n    } else {\n      popupText += `<span style=\"color:#b94a48;font-weight:600;\">${text}</span><br>`;\n    }\n  });\n  infoTexts.forEach(text => {\n    popupText += `<span class=\"map-popup-note\">${text}</span><br>`;\n  });\n  const destinationUrl = getGeoIntentURL(lat, lon);\n  const driveLinkClass = isMobile ? ' class=\"map-drive-link\"' : '';\n  const driveLinkTarget = ' target=\"_blank\" rel=\"noopener\"';\n  if (confirmTexts.length > 0) {\n    const confirmText = confirmTexts.join('\\n');\n    popupText += `<a${driveLinkClass} href=\"${destinationUrl}\"${driveLinkTarget} onclick=\"return window.confirm('${confirmText}')\">Drive here!</a>`;\n  } else {\n    popupText += `<a${driveLinkClass} href=\"${destinationUrl}\"${driveLinkTarget}>Drive here!</a>`;\n  }\n  if (isMobile) {\n    const inscriptionId = JSON.stringify(String(inscriptionData.id));\n    popupText += `<button type=\"button\" class=\"map-open-info-link\" onclick='return window.openMobileInscriptionInfo(${inscriptionId})'>Open info</button>`;\n  }\n  // Tooltip is simple and is always on, popup supports HTML and is opened  /closed by user\n  const popupOptions = isMobile\n    ? {\n        autoClose: false,\n        autoPan: true,\n        closeButton: true,\n        maxWidth: 260,\n      }\n    : {autoClose: false};\n  marker.bindPopup(popupText, popupOptions);\n  const tooltipOptions = isMobile\n    ? {permanent: true, interactive: true, className: 'mobile-map-id-tooltip'}\n    : {permanent: true};\n  marker.bindTooltip(inscriptionData.signature_text, tooltipOptions).openTooltip();\n  if (isMobile && typeof marker.on === 'function') {\n    marker.on('tooltipopen', (event) => {\n      makeMobileTooltipOpenPopup(marker, event.tooltip);\n    });\n  }\n  if (isMobile && typeof marker.getTooltip === 'function') {\n    makeMobileTooltipOpenPopup(marker, marker.getTooltip());\n  }\n\n  return marker;\n}\n\n/**\n * Converts inscription data to map markers and returns a collection of markers.\n *\n * @param {Map} dbMap - A map containing inscription data with keys as unique identifiers.\n * @param {Object} [leaflet=L] - The Leaflet library instance to use for creating markers.\n * @returns {Map} A map where each key corresponds to an inscription and the value is an object\n *                containing 'found' and 'present' markers. The key is the same as in dbMap.\n */\nexport function inscriptions2markers(dbMap, leaflet=L) {\n  const mapMarkers = new Map(); // Collection of all created map markers. This is used\n  // in order to create markers only once.\n\n  dbMap.forEach((inscriptionData, key) => {\n    const signatureName = inscriptionData.signature_text;\n\n    const found_lat = parseFloat(inscriptionData.latitude) || 0.0;\n    const found_lon = parseFloat(inscriptionData.longitude) || 0.0;\n    const present_lat = parseFloat(inscriptionData.present_latitude) || 0.0;\n    const present_lon = parseFloat(inscriptionData.present_longitude) || 0.0;\n    const marker_found = inscription2marker(inscriptionData, found_lat, found_lon, 'found', leaflet);\n    if (!marker_found) {\n      return;\n    }\n    if (!mapMarkers.has(key)) {\n      mapMarkers.set(key, {found: null, present: null});\n    }\n    mapMarkers.get(key).found = marker_found;\n\n    const marker_present = inscription2marker(inscriptionData, present_lat, present_lon, 'present', leaflet);\n    mapMarkers.get(key).present = marker_present ? marker_present : marker_found;\n  });\n  return mapMarkers;\n}\n\n\n/**\n * Displays markers on the map based on the provided parameters.\n *\n * @param {Object} options - The options for displaying markers.\n * @param {boolean} [options.preserveMapArea=false] - If true, the map area will not be adjusted to fit the markers.\n * @param {boolean} [options.showOriginalLocation=false] - If true, markers will be shown for the original (found) location of inscriptions, otherwise for the present location.\n * @param {Array<string>} [options.inscriptionIds=[]] - An array of inscription IDs to display markers for.\n * @param {Map<string, Object>} [options.allMarkers=new Map()] - A map containing all markers, keyed by inscription ID.\n * @param {Object} [options.mapObject=null] - The Leaflet map object.\n * @param {Object} [options.markersLayer=null] - The Leaflet layer group to which markers will be added.\n */\nexport function showMarkers({\n  preserveMapArea = false,\n  showOriginalLocation = false,\n  inscriptionIds = [],\n  allMarkers = new Map(),\n  mapObject = null,\n  markersLayer = null,\n} = {}) {\n  // array of all marker's lat/lon. Used to calculate new bounds.\n  let markersLatLon = [];\n\n  if (!markersLayer || !mapObject) {\n    console.log('No markers layer or map object provided');\n    return;\n  }\n\n  // clear any markers from the map\n  markersLayer.clearLayers();\n\n  for (let i = 0; i < inscriptionIds.length; i++) {\n    const key = inscriptionIds[i];\n    if (!allMarkers.has(key)) {\n      continue;\n    }\n    const inscriptionMarkers = allMarkers.get(key);\n    const markerToShow = showOriginalLocation ? inscriptionMarkers.found : inscriptionMarkers.present;\n    markersLayer.addLayers(markerToShow);\n    markersLatLon.push(markerToShow.getLatLng());\n  }\n\n  if (markersLatLon.length > 0 && !preserveMapArea) {\n    if (typeof mapObject.invalidateSize === 'function') {\n      mapObject.invalidateSize({pan: false});\n    }\n    mapObject.fitBounds(markersLatLon, {padding: [20, 20]});\n  }\n}\n","import { isCompactDbLayout } from './index_layout.js';\n\n/*\nThis file contains code to work with jquery query builder. The query builder must be\nincluded in your code prior to using this file.\n*/\n\n\nconst queryBuilderPlugins = {\n  'bt-tooltip-errors': null,\n  'sortable': null,\n  'not-group': null,\n  'case-rule': null,\n  'special-symbols-rule': null,\n};\n\nconst optGroups = {\n  \"gr_inscription\": {\n    \"en\": \"Inscription\",\n    \"sv\": \"Signatura\",\n  },\n  \"gr_texts\": \"Texts\",\n  \"gr_location\": \"Location\",\n  \"gr_time_period\": \"Time Period\",\n  \"gr_design\": \"Design\",\n  \"gr_more_info\": \"More information\",\n};\n\nfunction ensureTextOptionsRow(rule) {\n  const $container = rule.$el.find('.rule-value-container');\n  let $row = $container.find('[data-text-options-row=rule]').first();\n  if ($row.length > 0) {\n    return $row;\n  }\n\n  const $personalModeInput = $container.find('[name$=_personalNamesMode]').first();\n  if ($personalModeInput.length > 0) {\n    $row = $personalModeInput.closest('.mt-2');\n    $row.attr('data-text-options-row', 'rule');\n    return $row;\n  }\n\n  $row = $('<div class=\"mt-2\" data-text-options-row=\"rule\"></div>');\n  $container.append($row);\n  return $row;\n}\n\n// QueryBuilder plugin for case-(in)sensitive search\n$.fn.queryBuilder.define('case-rule', function(options) {\n  let self = this;\n\n  // Bind events\n  this.on('afterInit', function() {\n    self.$el.on('change.queryBuilder', '[data-case=rule]', function () {\n      let $rule = $(this).closest($.fn.queryBuilder.constructor.selectors.rule_container);\n      let rule = self.getModel($rule);\n      // Checked \"match case\" means case-sensitive search.\n      rule.ignoreCase = !$(this).is(':checked');\n    });\n\n    self.model.on('update', function(e, node, field) {\n      if (node instanceof $.fn.queryBuilder.constructor.Rule && field === 'ignoreCase') {\n        self.updateRuleCaseIgnore(node);\n      }\n    });\n  });\n\n  // Init case-sensitivity property\n  this.on('afterAddRule', function(e, rule) {\n    // Default behavior requested: ignore case.\n    rule.__.ignoreCase = true;\n  });\n\n  this.on('afterCreateRuleInput.filter', function(e, rule) {\n    // Show plugin's button only for normalization, transliteration and translation filters\n    const caseRuleFilterIds = [\n      'normalization_norse_to_transliteration',\n      'normalization_scandinavian_to_transliteration',\n      'search_runic_texts',\n      'english_translation',\n      'swedish_translation',\n    ];\n    const shouldShow = rule.filter && caseRuleFilterIds.includes(rule.filter.id);\n    const $row = ensureTextOptionsRow(rule);\n    if ($row.find(cssSelectorPluginCaseRule).length === 0) {\n      $row.append(\n        `<div class=\"form-check form-check-inline\" data-case-container=\"rule\">\n          <input class=\"form-check-input\" type=\"checkbox\" data-case=\"rule\" id=\"${rule.id}_matchCaseInput\">\n          <label class=\"form-check-label\" for=\"${rule.id}_matchCaseInput\">Match case</label>\n        </div>`\n      );\n    }\n\n    if (shouldShow) {\n      $row.find(cssSelectorPluginCaseRule).show();\n      self.updateRuleCaseIgnore(rule);\n    } else {\n      $row.find(cssSelectorPluginCaseRule).hide();\n    }\n  });\n\n  // Export \"case-rule\" to JSON\n  this.on('ruleToJson.filter', function(e, rule) {\n    e.value.ignoreCase = rule.ignoreCase;\n  });\n\n  // Read \"case-rule\" from JSON\n  this.on('jsonToRule.filter', function(e, json) {\n    e.value.ignoreCase = !!json.ignoreCase;\n  });\n\n  // Export case selector to SQL\n  this.on('ruleToSQL.filter', function(e, rule, value, sqlFn) {\n    console.log(`ruleToSQL.filter: ${rule.id}, ignoreCase: ${rule.ignoreCase}`);\n    if (rule.ignoreCase) {\n      e.value = 'NOCASE ( ' + e.value + ' )';\n    }\n  });\n}, {\n  disable_template: true\n});\n\n$.fn.queryBuilder.constructor.utils.defineModelProperties($.fn.queryBuilder.constructor.Rule, ['ignoreCase']);\n\nconst cssSelectorPluginCaseRule = '[data-case-container=rule]';\nconst cssSelectorPluginCaseRuleInput = cssSelectorPluginCaseRule + ' [data-case=rule]';\n\n$.fn.queryBuilder.extend({\n  /**\n   * Performs actions when a rule's case selector changes\n   * @param {Rule} rule\n   * @fires module:plugins.CaseSelector.updateRuleCaseIgnore\n   * @private\n   */\n  updateRuleCaseIgnore: function(rule) {\n      rule.$el.find(cssSelectorPluginCaseRuleInput).prop('checked', !rule.ignoreCase);\n\n      /**\n       * After the rule's case selector has been modified\n       * @event afterUpdateRuleCaseSelector\n       * @memberof module:plugins.CaseSelector\n       * @param {Rule} rule\n       */\n      this.trigger('afterUpdateRuleCaseSelector', rule);\n\n      this.trigger('rulesChanged');\n  }\n});\n\n// QueryBuilder plugin for including/excluding special editorial symbols in search\n// Applies to normalisation and transliteration filters only.\n// When includeSpecialSymbols is false (default), the search engine strips editorial\n// symbols (e.g. \" [ ] ( ) { } | ^ ´ < > ?) from both the data words and the query\n// before comparing, making symbols invisible to the search.\n// When true, symbols are included as-is in the comparison.\nconst specialSymbolsFilterIds = [\n  'normalization_norse_to_transliteration',\n  'normalization_scandinavian_to_transliteration',\n  'search_runic_texts',\n];\n\n$.fn.queryBuilder.define('special-symbols-rule', function(options) {\n  let self = this;\n\n  this.on('afterInit', function() {\n    self.$el.on('change.queryBuilder', '[data-special-symbols=rule]', function() {\n      let $rule = $(this).closest($.fn.queryBuilder.constructor.selectors.rule_container);\n      let rule = self.getModel($rule);\n      rule.includeSpecialSymbols = $(this).is(':checked');\n    });\n\n    self.model.on('update', function(e, node, field) {\n      if (node instanceof $.fn.queryBuilder.constructor.Rule && field === 'includeSpecialSymbols') {\n        self.updateRuleSpecialSymbols(node);\n      }\n    });\n  });\n\n  // Default: strip special symbols (includeSpecialSymbols = false)\n  this.on('afterAddRule', function(e, rule) {\n    rule.__.includeSpecialSymbols = false;\n  });\n\n  this.on('afterCreateRuleInput.filter', function(e, rule) {\n    const shouldShow = rule.filter && specialSymbolsFilterIds.includes(rule.filter.id);\n    const $row = ensureTextOptionsRow(rule);\n    if ($row.find(cssSelectorPluginSpecialSymbolsRule).length === 0) {\n      $row.append(\n        `<div class=\"form-check form-check-inline\" data-special-symbols-container=\"rule\">\n          <input class=\"form-check-input\" type=\"checkbox\" data-special-symbols=\"rule\" id=\"${rule.id}_includeSymbolsInput\">\n          <label class=\"form-check-label\" for=\"${rule.id}_includeSymbolsInput\">Include symbols</label>\n        </div>`\n      );\n    }\n\n    if (shouldShow) {\n      $row.find(cssSelectorPluginSpecialSymbolsRule).show();\n      self.updateRuleSpecialSymbols(rule);\n    } else {\n      $row.find(cssSelectorPluginSpecialSymbolsRule).hide();\n    }\n  });\n\n  this.on('ruleToJson.filter', function(e, rule) {\n    e.value.includeSpecialSymbols = rule.includeSpecialSymbols;\n  });\n\n  this.on('jsonToRule.filter', function(e, json) {\n    e.value.includeSpecialSymbols = !!json.includeSpecialSymbols;\n  });\n}, {\n  disable_template: true\n});\n\n$.fn.queryBuilder.constructor.utils.defineModelProperties($.fn.queryBuilder.constructor.Rule, ['includeSpecialSymbols']);\n\nconst cssSelectorPluginSpecialSymbolsRule = '[data-special-symbols-container=rule]';\nconst cssSelectorPluginSpecialSymbolsRuleInput = cssSelectorPluginSpecialSymbolsRule + ' [data-special-symbols=rule]';\n\n$.fn.queryBuilder.extend({\n  updateRuleSpecialSymbols: function(rule) {\n    rule.$el.find(cssSelectorPluginSpecialSymbolsRuleInput).prop('checked', !!rule.includeSpecialSymbols);\n    this.trigger('rulesChanged');\n  }\n});\n\n\nexport function sortGroupsByOrder(items, groupOrder) {\n  const key = 'optgroup'; // The key in items to group by\n\n  // Create priority map\n  const priorityMap = {};\n  groupOrder.forEach((group, index) => {\n    priorityMap[group] = index;\n  });\n\n  // Group items by their key\n  const groups = {};\n  items.forEach(item => {\n    const groupKey = item[key] || '';\n    if (!groups[groupKey]) groups[groupKey] = [];\n    groups[groupKey].push(item);\n  });\n\n  // Sort each group alphabetically by label\n  Object.keys(groups).forEach(groupKey => {\n    groups[groupKey].sort((a, b) => {\n      const labelA = a.label || '';\n      const labelB = b.label || '';\n      return labelA.localeCompare(labelB);\n    });\n  });\n\n  // Create result array by concatenating groups in specified order\n  let result = [];\n  groupOrder.forEach(groupKey => {\n    if (groups[groupKey]) {\n      result = result.concat(groups[groupKey]);\n      delete groups[groupKey];\n    }\n  });\n\n  // Add any remaining groups not specified in groupOrder\n  Object.values(groups).forEach(group => {\n    result = result.concat(group);\n  });\n\n  return result;\n}\n\n/**\n * Gets the minimum and maximum values of a numerical field from a data source\n *\n * @param {Map|Array} dataSource - Either a Map containing database records or an array of items\n * @param {string} fieldName - The name of the field to analyze\n * @returns {Object} Object containing min and max values, or null if field doesn't exist or has no numeric values\n */\nexport function getMinMaxValues(dataSource, fieldName) {\n  if (!dataSource) {\n    throw new Error(\"dataSource parameter is required\");\n  }\n\n  if (!fieldName || typeof fieldName !== 'string') {\n    throw new Error(\"fieldName parameter is required and must be a string\");\n  }\n\n  let min = null;\n  let max = null;\n  let hasValues = false;\n\n  // Function to process each item\n  const processItem = (item) => {\n    // Skip if item doesn't have the field or value isn't numeric\n    if (!item || item[fieldName] === undefined || item[fieldName] === null) {\n      return;\n    }\n\n    // Convert to number if it's a string\n    const value = typeof item[fieldName] === 'string' ?\n      parseFloat(item[fieldName]) : item[fieldName];\n\n    // Skip if not a valid number\n    if (isNaN(value)) {\n      return;\n    }\n\n    // Initialize min/max on first valid value\n    if (!hasValues) {\n      min = value;\n      max = value;\n      hasValues = true;\n      return;\n    }\n\n    // Update min/max\n    if (value < min) min = value;\n    if (value > max) max = value;\n  };\n\n  // Handle different data source types\n  if (dataSource instanceof Map) {\n    // Process Map values\n    for (const item of dataSource.values()) {\n      processItem(item);\n    }\n  } else if (Array.isArray(dataSource)) {\n    // Process array items\n    for (const item of dataSource) {\n      processItem(item);\n    }\n  } else {\n    throw new Error(\"dataSource must be either a Map or an Array\");\n  }\n\n  return hasValues ? { min, max } : null;\n}\n\nexport function getValuesFromAllData(term, suggest, fieldName, dbMap, isTomSelect = false) {\n  // Get all unique values from dbMap for the specified fieldName\n  let allValues = [];\n  const uniqueTracker = new Set();\n  let nextArtificialId = 20000; // Starting ID for aliases\n\n  Array.from(dbMap.values()).forEach(item => {\n    const visited = uniqueTracker.has(item[fieldName]);\n    if (item[fieldName] && item[fieldName] !== '' && !visited) {\n      uniqueTracker.add(item[fieldName]);\n      allValues.push({\n        text: item[fieldName],\n        id: item[fieldName],\n        score: item.id || 0\n      });\n      if (fieldName === 'signature_text' && item.aliases) {\n        const aliases = item.aliases.split('|').map(a => a.trim()).filter(a => a);\n\n        // Add each alias with an artificial ID\n        aliases.forEach(alias => {\n          if (!uniqueTracker.has(alias)) {\n            uniqueTracker.add(alias);\n            allValues.push({\n              text: alias,\n              id: alias,\n              score: nextArtificialId++,\n            });\n          }\n        });\n      }\n    }\n  });\n\n  allValues.sort((a, b) => a.score - b.score);\n\n  // Comparison without diacritics:\n  if (term !== '') {\n    // Normalize strings to remove diacritics\n    const normalizedTerm = term.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase();\n    allValues = allValues.filter(item => {\n      const normalizedText = item.text.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '').toLowerCase();\n      return normalizedText.includes(normalizedTerm);\n    });\n  }\n\n  if (isTomSelect) {\n    suggest(allValues);\n    return allValues;\n  }\n\n  const values = allValues.map(item => item.text);\n  suggest(values);\n  return values;\n}\n\n\n/**\n * Creates an autocomplete configuration object for QueryBuilder.\n *\n * @param {string} ruleId - The unique identifier for the rule.\n * @param {Map} dbMap - A Map containing the database values for autocomplete.\n * @param {Function} humanNameGetter - Function that returns a human-readable name for the given rule ID.\n * @param {Object} [opt={}] - Optional configuration parameters.\n * @param {string} [opt.fieldId] - The field ID to use (defaults to ruleId if not provided).\n * @param {string[]} [opt.operators] - Array of operators to use with this field.\n * @param {string} [opt.type='string'] - The data type for the field.\n * @param {number} [opt.size=100] - The display size of the field.\n * @param {string} [opt.optgroup='other'] - The option group to which this field belongs.\n * @returns {Object} Configuration object for QueryBuilder autocomplete field.\n * @throws {Error} If required parameters are missing or invalid.\n */\nfunction prepareAutoComplete(ruleId, dbMap, humanNameGetter, opt = {}) {\n  // Check required arguments\n  if (ruleId === undefined) {\n    throw new Error(\"prepareAutoComplete: 'ruleId' parameter is required\");\n  }\n  if (!dbMap || !(dbMap instanceof Map)) {\n    throw new Error(\"prepareAutoComplete: 'dbMap' parameter is required and must be a Map\");\n  }\n  if (!humanNameGetter || typeof humanNameGetter !== 'function') {\n    throw new Error(\"prepareAutoComplete: 'humanNameGetter' parameter is required and must be a function\");\n  }\n\n  const fieldId = opt.fieldId || ruleId;\n  const operators = opt.operators || [\"contains\", \"not_contains\",\n        'equal', 'not_equal', 'begins_with', \"not_begins_with\",\n        \"ends_with\", \"not_ends_with\", \"is_empty\", 'is_not_empty'];\n  const type = opt.type || 'string';\n  const size = opt.size || 100;\n  const optgroup = opt.optgroup || \"other\";\n\n  return {\n    id: ruleId,\n    field: fieldId,\n    optgroup: optgroup,\n    label: humanNameGetter(ruleId),\n    type: type,\n    plugin: 'autoComplete',\n    plugin_config: {\n      minChars: 0,\n      delay: 100,\n      source: function (term, suggest) {\n        getValuesFromAllData(term, suggest, fieldId, dbMap);\n      },\n      menuClass: ' clusterize-content ',\n      attachToParent: true,\n    },\n    size: size,\n    operators: operators,\n  }\n}\n\n/**\n * Creates a jQuery QueryBuilder filter configuration for integer rules\n *\n * @param {string} ruleId - ID for the rule/filter\n * @param {Map} dbMap - A Map containing the database values for autocomplete.\n * @param {Function} humanNameGetter - Function that returns a human-readable name for the given rule ID.\n * @param {Object} opt - Optional configuration parameters\n * @param {string} opt.fieldId - Field name in data (defaults to ruleId if not provided)\n * @param {Array} opt.operators - Array of operators to use for this filter\n * @param {number} opt.size - Size attribute for the input field\n * @param {string} opt.optgroup - Group to which this filter belongs\n * @param {number} opt.min - Minimum allowed value (optional)\n * @param {number} opt.max - Maximum allowed value (optional)\n * @param {number} opt.step - Step value for input (optional)\n * @param {number} opt.default_value - Default value for the field (optional)\n * @returns {Object} Filter configuration object for QueryBuilder\n */\nfunction prepareIntegerRule(ruleId, dbMap, humanNameGetter, opt) {\n  // Check required arguments\n  if (ruleId === undefined) {\n    throw new Error(\"prepareIntegerRule: 'ruleId' parameter is required\");\n  }\n  if (!dbMap || !(dbMap instanceof Map)) {\n    throw new Error(\"prepareIntegerRule: 'dbMap' parameter is required and must be a Map\");\n  }\n  if (!humanNameGetter || typeof humanNameGetter !== 'function') {\n    throw new Error(\"prepareIntegerRule: 'humanNameGetter' parameter is required and must be a function\");\n  }\n  if (!opt) opt = {};\n  const fieldId = opt.fieldId || ruleId;\n  const operators = opt.operators || ['equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between', 'not_between'];\n  const size = opt.size || 10;\n  const optgroup = opt.optgroup || \"other\";\n  const inputType = opt.input || 'number';\n\n  let config = {\n    id: ruleId,\n    field: fieldId,\n    optgroup: optgroup,\n    label: humanNameGetter(fieldId),\n    type: 'integer',\n    size: size,\n    operators: operators,\n    input: inputType\n  };\n  const dataLimitValues = getMinMaxValues(dbMap, fieldId);\n  opt.min = opt.min || (dataLimitValues && dataLimitValues.min);\n  opt.max = opt.max || (dataLimitValues && dataLimitValues.max);\n\n  // Add validation if any constraints are specified\n  if (opt.min !== undefined || opt.max !== undefined || opt.step !== undefined) {\n    config.validation = {\n      allow_empty_value: true\n    };\n\n    if (opt.min !== undefined) config.validation.min = opt.min;\n    if (opt.max !== undefined) config.validation.max = opt.max;\n    if (opt.step !== undefined) config.validation.step = opt.step;\n  }\n\n  // Add default value if provided\n  if (opt.default_value !== undefined) {\n    config.default_value = opt.default_value;\n  }\n\n  return config;\n}\n\n\n/**\n * Adjusts the input element in a query rule by applying either TomSelect or AutoComplete plugin\n * based on the rule's operator type.\n *\n * @param {Object} rule - The query rule object containing the element and operator information\n * @param {Object} [tomSelectConfig={}] - Configuration options for TomSelect plugin\n * @param {Object} [autoCompleteConfig={}] - Configuration options for AutoComplete plugin\n *\n * @description This function first cleans up any existing TomSelect or AutoComplete plugins\n * attached to the input element, then initializes the appropriate plugin based on the\n * operator type. If the operator is 'in' or 'not_in', TomSelect is applied; otherwise,\n * AutoComplete is used.\n */\nfunction adjustTomSelectAndAutoComplete(rule, tomSelectConfig = {}, autoCompleteConfig = {}) {\n  var $input = rule.$el.find('.rule-value-container input');\n  const operator = rule.operator.type;\n\n  // Clean up existing plugins\n  if ($input.data('tomSelect') !== undefined) {\n    $input.tomSelect('destroy');\n  }\n  if ($input.hasClass('autocomplete')) {\n    $input.autoComplete('destroy');\n  }\n\n  // Initialize the appropriate plugin based on operator\n  if (operator === 'in' || operator === 'not_in') {\n    $input.tomSelect(tomSelectConfig);\n  } else {\n    $input.autoComplete(autoCompleteConfig);\n  }\n}\n\n/**\n * Creates a rule for word search in runic texts\n *\n * @param {Object} config Configuration object\n * @param {string} config.id Rule ID\n * @param {string} config.field Field name in data\n * @param {string} config.label Human-readable label\n * @param {string} config.optgroup Option group\n * @param {string[]} config.operators Array of supported operators\n * @returns {Object} Configured QueryBuilder rule\n */\nfunction createWordSearchRule(config) {\n  const input1Label = \"Normalization\";\n  const input2Label = \"Transliteration\";\n  return {\n    id: config.id,\n    field: config.field,\n    label: config.label,\n    type: 'string',\n    optgroup: config.optgroup || 'gr_texts',\n    data: {\n      multiField: true,\n    },\n    input: function(rule, name) {\n      return `\n        <div class=\"form-group\">\n          <div class=\"input-group mb-3 pt-2\">\n            <span class=\"input-group-text\" id=\"${name}_normalization_input_span\">${input1Label}</span>\n            <input type=\"text\" id=\"${name}_normalizationInput\" class=\"form-control\" placeholder=\"\" aria-label=\"${input1Label}\" aria-describedby=\"${name}_normalization_input_span\">\n          </div>\n          <div class=\"input-group\">\n            <span class=\"input-group-text\" id=\"${name}_transliteration_input_span\">${input2Label}</span>\n            <input type=\"text\" id=\"${name}_transliterationInput\" class=\"form-control\" placeholder=\"\" aria-label=\"${input2Label}\" aria-describedby=\"${name}_transliteration_input_span\">\n          </div>\n          <div class=\"mt-2\">\n            <div class=\"form-check form-check-inline\">\n              <input class=\"form-check-input\" type=\"radio\" name=\"${name}_personalNamesMode\" value=\"includeAll\" id=\"${name}_includeAddInput\" checked>\n              <label class=\"form-check-label\" for=\"${name}_includeAddInput\">Include names</label>\n            </div>\n            <div class=\"form-check form-check-inline\">\n              <input class=\"form-check-input\" type=\"radio\" name=\"${name}_personalNamesMode\" value=\"excludeNames\" id=\"${name}_excludeNamesInput\">\n              <label class=\"form-check-label\" for=\"${name}_excludeNamesInput\">Exclude names</label>\n            </div>\n            <div class=\"form-check form-check-inline\">\n              <input class=\"form-check-input\" type=\"radio\" name=\"${name}_personalNamesMode\" value=\"namesOnly\" id=\"${name}_namesOnlyInput\">\n              <label class=\"form-check-label\" for=\"${name}_namesOnlyInput\">Only names</label>\n            </div>\n          </div>\n        </div>\n      `;\n    },\n    operators: config.operators || ['contains', 'equal', 'begins_with', 'ends_with'],\n    valueGetter: function(rule) {\n      var $container = rule.$el.find('.rule-value-container');\n      return {\n        normalization: $container.find('[id$=_normalizationInput]').val(),\n        transliteration: $container.find('[id$=_transliterationInput]').val(),\n        names_mode: $container.find('[name$=_personalNamesMode]:checked').val()\n      };\n    },\n    valueSetter: function(rule, value) {\n      const names_mode = value.names_mode || 'includeAll';\n      var $container = rule.$el.find('.rule-value-container');\n      $container.find('[id$=_normalizationInput]').val(value.normalization || '');\n      $container.find('[id$=_transliterationInput]').val(value.transliteration || '');\n      $container.find('[name$=_personalNamesMode][value=\"' + names_mode + '\"]').prop('checked', true);\n    }\n  };\n}\n\n/*export const rundataOperators = [\n  { type: 'texts_contains', nb_inputs: 2, multiple: false, apply_to: ['string'] },\n  { type: 'texts_equal', nb_inputs: 2, multiple: false, apply_to: ['string'] },\n  { type: 'texts_begins_with', nb_inputs: 2, multiple: false, apply_to: ['string'] },\n  { type: 'texts_ends_with', nb_inputs: 2, multiple: false, apply_to: ['string'] },\n];*/\n\nexport function initQueryBuilder(containerId, viewModel, getHumanName) {\n  const dbMap = viewModel.getAllInscriptions();\n  const queryBuilder = $(`#${containerId}`);\n\n  const qbOperators = $.fn.queryBuilder.constructor.DEFAULTS.operators.concat([\n    // Add to default operators\n    { type: 'in_separated_list', nb_inputs: 1, multiple: false, apply_to: ['string'] },\n    { type: 'cross_form', nb_inputs: 1, multiple: false, apply_to: ['string'] },\n  ]);\n  const qbLang = {\n    operators: {\n      'in_separated_list': \"is in |-separated list\",\n      'cross_form': \" \",\n    }\n  };\n  const qbSqlOperators = {\n    'in_separated_list': { 'op': 'IN', mod: '{0}' },\n    'cross_form': { 'op': 'IN' },\n  };\n\n  const signature_text_autocomplete_cfg = {\n    minChars: 0,\n    delay: 100,\n    source: function(term, suggest) {\n      getValuesFromAllData(term, suggest, 'signature_text', dbMap);\n    },\n    menuClass: 'clusterize-content',\n    attachToParent: true,\n  };\n\n  const signature_text_tomselect_cfg = {\n    plugins: ['remove_button'],\n    // options: getValuesFromAllData('', undefined, 'signature_text', dbMap),\n    load: function(query, callback) {\n      let self = this;\n      getValuesFromAllData(query, callback, 'signature_text', dbMap, true);\n      // prevent further loading\n      self.settings.load = null;\n    },\n    // invoke data loading at once\n    preload: true,\n    valueField: 'id',\n    hideSelected: true,\n    delimiter: '|',\n  };\n\n  let queryBuilderFilters = [\n    // gr_inscription filters\n    {\n      id: 'inscription_id',\n      optgroup: 'gr_inscription',\n      field: 'signature_text',\n      label: getHumanName('signature_text'),\n      type: 'string',\n      multiple: true,\n      data: {\n        multiField: true,\n      },\n      operators: [\n        'in', 'in_separated_list', 'begins_with', 'not_begins_with',\n        'ends_with', 'not_ends_with', 'contains', 'not_contains',\n      ],\n      valueSetter: function (rule, value) {\n        const $input = rule.$el.find('.rule-value-container input');\n        $input.val(value);\n        adjustTomSelectAndAutoComplete(rule, signature_text_tomselect_cfg, signature_text_autocomplete_cfg);\n      },\n      plugin: 'tomSelect',\n      plugin_config: signature_text_tomselect_cfg,\n    },\n    {\n      id: 'lost',\n      label: getHumanName('lost'),\n      field: 'lost',\n      optgroup: 'gr_inscription',\n      type: 'integer',\n      input: 'radio',\n      values: [\n        {0: 'No'},\n        {1: 'Yes'},\n      ],\n      default_value: 0,\n      operators: ['equal'],\n    },\n    // gr_texts filters\n    createWordSearchRule({\n      id: 'normalization_norse_to_transliteration',\n      field: 'normalization_norse',\n      label: 'Transliteration and Normalization \"Old Norse\"',\n      optgroup: 'gr_texts',\n    }),\n    createWordSearchRule({\n      id: 'normalization_scandinavian_to_transliteration',\n      field: 'normalisation_scandinavian',\n      label: 'Transliteration and Normalization \"Old Scandinavian\"',\n      optgroup: 'gr_texts',\n    }),\n    prepareAutoComplete('english_translation', dbMap, getHumanName, { optgroup: 'gr_texts', operators: [\"contains\", \"not_contains\", \"is_empty\", 'is_not_empty'] }),\n    prepareAutoComplete('swedish_translation', dbMap, getHumanName, { optgroup: 'gr_texts', operators: [\"contains\", \"not_contains\", \"is_empty\", 'is_not_empty'] }),\n    {\n      id: 'search_runic_texts',\n      field: 'normalisation_norse',\n      label: 'Search in runic texts',\n      type: 'string',\n      input: 'text',\n      optgroup: 'gr_texts',\n      data: {\n        multiField: true,\n      },\n      operators: ['contains', 'equal', 'begins_with', 'ends_with'],\n    },\n    {\n      id: 'has_personal_name',\n      label: \"Contains names\",\n      field: 'num_names',\n      optgroup: 'gr_texts',\n      type: 'integer',\n      input: 'radio',\n      values: [\n        {0: 'No'},\n        {1: 'Yes'},\n      ],\n      default_value: 1,\n      operators: ['equal'],\n    },\n    // gr_location filters\n    {\n      id: 'inscription_country',\n      optgroup: \"gr_location\",\n      field: 'signature_text',\n      label: 'Country or Swedish Province',\n      type: 'string',\n      input: 'select',\n      multiple: true,\n      data: {\n        multiField: true,\n      },\n      operators: isCompactDbLayout()\n        ? ['in', 'contains']\n        : ['in'],\n      plugin: 'tomSelect',\n      plugin_config: {\n        plugins: ['remove_button'],\n        options: [\n          {text: 'Sweden, whole', value: 'all_sweden'},\n          {text: 'Öland (Öl)', value: 'Öl '}, {text: 'Östergötland (Ög)', value: 'Ög '}, {text: 'Södermanland (Sö)', value: 'Sö '},\n          {text: 'Småland (Sm)', value: 'Sm '}, {text: 'Västergötland (Vg)', value: 'Vg '}, {text: 'Uppland (U)', value: 'U '},\n          {text: 'Västmanland (Vs)', value: 'Vs '}, {text: 'Närke (Nä)', value: 'Nä '}, {text: 'Värmland (Vr)', value: 'Vr '},\n          {text: 'Gästrikland (Gs)', value: 'Gs '}, {text: 'Hälsingland (Hs)', value: 'Hs '}, {text: 'Medelpad (M)', value: 'M '},\n          {text: 'Ångermanland (Ån)', value: 'Ån '}, {text: 'Dalarna (D)', value: 'D '}, {text: 'Härjedalen (Hr)', value: 'Hr '},\n          {text: 'Jämtland (J)', value: 'J '}, {text: 'Lappland (Lp)', value: 'Lp '}, {text: 'Dalsland (Ds)', value: 'Ds '},\n          {text: 'Bohuslän (Bo)', value: 'Bo '}, {text: 'Gotland (G)', value: 'G '}, {text: 'Sweden, other (SE)', value: 'SE '},\n          {text: 'Denmark (DR)', value: 'DR '}, {text: 'Norway (N)', value: 'N '}, {text: 'Faroe Islands (FR)', value: 'FR '},\n          {text: 'Greenland (GR)', value: 'GR '}, {text: 'Iceland (IS)', value: 'IS '}, {text: 'Finland (FI)', value: 'FI '},\n          {text: 'Shetland (Sh)', value: 'Sh '}, {text: 'Orkney (Or)', value: 'Or '}, {text: 'Scotland (Sc)', value: 'Sc '},\n          {text: 'England (E)', value: 'E '}, {text: 'Isle of Man (IM)', value: 'IM '}, {text: 'Ireland (IR)', value: 'IR '},\n          {text: 'France (F)', value: 'F '}, {text: 'Netherlands (NL)', value: 'NL '}, {text: 'Germany (DE)', value: 'DE '},\n          {text: 'Poland (PL)', value: 'PL '}, {text: 'Latvia (LV)', value: 'LV '}, {text: 'Russia (RU)', value: 'RU '},\n          {text: 'Ukraine (UA)', value: 'UA '}, {text: 'Byzantium (By)', value: 'By '}, {text: 'Italy (IT)', value: 'IT '},\n          {text: 'Other areas (X)', value: 'X '}\n        ],\n        hideSelected: true,\n      },\n      valueSetter: function (rule, value) {\n        const $input = rule.$el.find('.rule-value-container select');\n        $input.tomSelect('setValue', value);\n      }\n    },\n    prepareAutoComplete('full_address', dbMap, getHumanName, { optgroup: 'gr_location', operators: ['contains'] }),\n    prepareAutoComplete('found_location', dbMap, getHumanName, { optgroup: 'gr_location' }),\n    prepareAutoComplete('parish', dbMap, getHumanName, { optgroup: 'gr_location' }),\n    prepareAutoComplete('district', dbMap, getHumanName, { optgroup: 'gr_location' }),\n    prepareAutoComplete('municipality', dbMap, getHumanName, { optgroup: 'gr_location' }),\n    prepareAutoComplete('current_location', dbMap, getHumanName, { optgroup: 'gr_location' }),\n    prepareAutoComplete('original_site', dbMap, getHumanName, { optgroup: 'gr_location' }),\n    prepareAutoComplete('parish_code', dbMap, getHumanName, { optgroup: 'gr_location' }),\n    // gr_time_period filters\n    prepareAutoComplete('dating', dbMap, getHumanName, { optgroup: 'gr_time_period' }),\n    prepareIntegerRule('year_from', dbMap, getHumanName, { operators: ['equal', 'less', 'greater', 'between'], optgroup: 'gr_time_period' }),\n    prepareIntegerRule('year_to', dbMap, getHumanName, { operators: ['equal', 'less', 'greater', 'between'], optgroup: 'gr_time_period' }),\n    prepareAutoComplete('style', dbMap, getHumanName, { optgroup: 'gr_time_period' }),\n    // gr_design filters\n    prepareAutoComplete('carver', dbMap, getHumanName, { optgroup: 'gr_design' }),\n    prepareAutoComplete('rune_type', dbMap, getHumanName, { optgroup: 'gr_design' }),\n    prepareAutoComplete('material', dbMap, getHumanName, { optgroup: 'gr_design' }),\n    prepareAutoComplete('material_type', dbMap, getHumanName, { optgroup: 'gr_design' }),\n    prepareAutoComplete('objectInfo', dbMap, getHumanName, { optgroup: 'gr_design' }),\n    prepareIntegerRule('num_crosses', dbMap, getHumanName,\n      {\n        operators: ['equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'between'],\n        optgroup: 'gr_design',\n        default_value: 0,\n        step: 1,\n      }\n    ),\n    {\n      id: 'cross_form',\n      field: 'crosses',\n      label: getHumanName('cross_form'),\n      operators: ['cross_form'],\n      optgroup: 'gr_design',\n      input: function (rule, name) {\n        // this is a bit of a hack as getValuesFromAllData function is intended for other use\n        const allCrossForms = viewModel.getAllCrossForms().map(item => {\n          return `<option value=\"${item}\">${item}</option>`;\n        }).join('');\n        return `\n          <select name=\"${name}_1\" class=\"form-select\" aria-label=\"Cross form\">${allCrossForms}</select>\n          <div>Certain?\n            <div class=\"form-check form-check-inline\">\n              <input type=\"radio\" name=\"${name}_2\" value=\"0\" class=\"form-check-input\" id=\"${name}_2_0\">\n              <label for=\"${name}_2_0\" class=\"form-check-label\">No</label>\n            </div>\n\n            <div class=\"form-check form-check-inline\">\n              <input type=\"radio\" name=\"${name}_2\" value=\"1\" class=\"form-check-input\" id=\"${name}_2_1\">\n              <label for=\"${name}_2_1\" class=\"form-check-label\">Yes</label>\n            </div>\n\n            <div class=\"form-check form-check-inline\">\n              <input type=\"radio\" name=\"${name}_2\" value=\"2\" class=\"form-check-input\" id=\"${name}_2_2\" checked>\n              <label for=\"${name}_2_2\" class=\"form-check-label\">Doesn't matter</label>\n            </div>\n          </div>`;\n      },\n      valueGetter: function (rule) {\n        const val1 = rule.$el.find('.rule-value-container [name$=_1]').val();\n        const val2 = rule.$el.find('.rule-value-container [name$=_2]:checked').val();\n        return {form: val1, is_certain: val2};\n      },\n      valueSetter: function (rule, value) {\n        $(rule.$el.find('.rule-value-container [name$=_1]')[0]).val(value.form);\n        rule.$el.find(`.rule-value-container [name$=_2][value=${value.is_certain}]`).prop('checked', true);\n      },\n    },\n    // gr_more_info filters\n    prepareAutoComplete('references_combined', dbMap, getHumanName, { optgroup: 'gr_more_info' }),\n    prepareAutoComplete('additional', dbMap, getHumanName, { optgroup: 'gr_more_info' }),\n  ];\n\n  const my_rule_template = ({ rule_id, icons, settings, translate, builder }) => {\n    return `\n  <div id=\"${rule_id}\" class=\"rule-container d-flex align-items-center w-100\">\n    <div class=\"rule-header\">\n    </div>\n    ${settings.display_errors ? `\n      <div class=\"error-container flex-shrink-0\"><i class=\"${icons.error}\"></i></div>\n    ` : ''}\n    <div class=\"rule-filter-container flex-shrink-0\"></div>\n    <div class=\"rule-operator-container flex-shrink-0\"></div>\n    <div class=\"rule-value-container flex-grow-1 me-2\"></div>\n    <div class=\"rule-footer d-flex flex-wrap align-items-center gap-1 ms-auto\">\n      <div class=\"d-flex flex-wrap gap-1 rule-actions\">\n        <button type=\"button\" class=\"btn btn-sm btn-danger\" data-delete=\"rule\">\n          <i class=\"${icons.remove_rule}\"></i> ${translate(\"delete_rule\")}\n        </button>\n      </div>\n    </div>\n  </div>`;\n  };\n\n  // Filters are already sorted in the correct order to match optGroups.\n  // The sortGroupsByOrder function is kept for redundancy to ensure alphabetical\n  // sorting within each group.\n  queryBuilderFilters = sortGroupsByOrder(queryBuilderFilters, Object.keys(optGroups));\n\n  queryBuilder.queryBuilder({\n    display_empty_filter: false,\n    //operators: $.fn.queryBuilder.constructor.DEFAULTS.operators.concat(rundataOperators),\n\n    plugins: queryBuilderPlugins,\n    filters: queryBuilderFilters,\n    sort_filters: false,\n    allow_empty: false,\n    optgroups: optGroups,\n\n    operators: qbOperators,\n    lang: qbLang,\n    sqlOperators: qbSqlOperators,\n\n    templates: {\n      rule: my_rule_template,\n    },\n  });\n\n  // Event handler when rule is created and rule operator is changed\n  $('#builder').on('afterCreateRuleInput.queryBuilder afterUpdateRuleOperator.queryBuilder', function(e, rule) {\n    if (rule.filter.id !== 'inscription_id') {\n      return;\n    }\n    adjustTomSelectAndAutoComplete(rule, signature_text_tomselect_cfg, signature_text_autocomplete_cfg);\n  });\n\n}\n","import {\n  getSearchDirectionConfig,\n  getWordSearchFunction,\n  getPhraseMatchFunction,\n  isPersonalName,\n  prepareForComparison,\n  splitPhraseTokens,\n  stripSpecialSymbols,\n} from './search_core.js';\n\nexport {\n  getSearchDirectionConfig,\n  getWordSearchFunction,\n  getPhraseMatchFunction,\n  isPersonalName,\n  normalizeWhitespace,\n  prepareForComparison,\n  splitPhraseTokens,\n  stripSpecialSymbols,\n} from './search_core.js';\n\n/*\nThis file contains code to do search in the inscriptions\n*/\n\n// Standard comparison operators that can be used both in QueryBuilderParser and custom search functions.\n// String-based operators accept an optional third argument `ignoreCase` (default false).\nexport const operators = {\n  equal: (a, b, ignoreCase) => ignoreCase ? String(a).toLowerCase() == String(b).toLowerCase() : a == b,\n  not_equal: (a, b, ignoreCase) => ignoreCase ? String(a).toLowerCase() != String(b).toLowerCase() : a != b,\n  in: (a, b) => b.includes(a),\n  not_in: (a, b) => !b.includes(a),\n  less: (a, b) => a < b,\n  less_or_equal: (a, b) => a <= b,\n  greater: (a, b) => a > b,\n  greater_or_equal: (a, b) => a >= b,\n  between: (a, b) => b[0] <= a && a <= b[1],\n  not_between: (a, b) => !(b[0] <= a && a <= b[1]),\n  begins_with: (a, b, ignoreCase) => prepareForComparison(a, ignoreCase).startsWith(prepareForComparison(b, ignoreCase)),\n  not_begins_with: (a, b, ignoreCase) => !prepareForComparison(a, ignoreCase).startsWith(prepareForComparison(b, ignoreCase)),\n  contains: (a, b, ignoreCase) => prepareForComparison(a, ignoreCase).includes(prepareForComparison(b, ignoreCase)),\n  not_contains: (a, b, ignoreCase) => !prepareForComparison(a, ignoreCase).includes(prepareForComparison(b, ignoreCase)),\n  ends_with: (a, b, ignoreCase) => prepareForComparison(a, ignoreCase).endsWith(prepareForComparison(b, ignoreCase)),\n  not_ends_with: (a, b, ignoreCase) => !prepareForComparison(a, ignoreCase).endsWith(prepareForComparison(b, ignoreCase)),\n  is_empty: (a) => a === '' || a === null || a === undefined || (Array.isArray(a) && a.length === 0),\n  is_not_empty: (a) => a !== '' && a !== null && a !== undefined && (!Array.isArray(a) || a.length > 0),\n  is_null: (a) => a === null || a === undefined,\n  is_not_null: (a) => a !== null && a !== undefined,\n};\n\n/**\n * JavaScript processor for jQuery QueryBuilder rules.\n * This module allows searching through data using rules\n * generated by jQuery QueryBuilder's getRules() function.\n */\n\nexport class QueryBuilderParser {\n  /**\n   * Initialize the parser with standard operators\n   */\n  constructor(customSearchFunctions = {}) {\n    // Use the external operators\n    this.operators = { ...operators };\n    this.customSearchFunctions = customSearchFunctions;\n  }\n\n  /**\n   * Apply rules from jQuery QueryBuilder to filter a list of data.\n   *\n   * @param {Object} rules - Rules object from jQuery QueryBuilder's getRules() method\n   * @param {Array} data - Array of objects (records) to filter\n   * @returns {Array} Filtered list of records with match details\n   */\n  parseRules(rules, data) {\n    if (!rules || !data) {\n      return [];\n    }\n\n    // If rules are invalid, return empty array\n    if (rules.valid === false) {\n      return [];\n    }\n\n    const result = [];\n    // If data is an array, process each item\n    if (Array.isArray(data)) {\n      data.forEach(item => {\n        const resultWithDetails = this._evaluateGroup(rules, item);\n        if (resultWithDetails.match) {\n          result.push({\n            record: item,\n            matchDetails: resultWithDetails.details || null\n          });\n        }\n      });\n    }\n    // If data is any iterable (e.g. a generator, Set, etc.)\n    else if (typeof data[Symbol.iterator] === 'function') {\n      for (const item of data) {\n        const resultWithDetails = this._evaluateGroup(rules, item);\n        if (resultWithDetails.match) {\n          result.push({\n            record: item,\n            matchDetails: resultWithDetails.details || null\n          });\n        }\n      }\n    }\n    // Fallback: if data implements the iterator protocol (has a next() method)\n    else if (typeof data.next === 'function') {\n      let nextItem = data.next();\n      while (!nextItem.done) {\n        const resultWithDetails = this._evaluateGroup(rules, nextItem.value);\n        if (resultWithDetails.match) {\n          result.push({\n            record: nextItem.value,\n            matchDetails: resultWithDetails.details || null\n          });\n        }\n        nextItem = data.next();\n      }\n    } else {\n      throw new Error('Data is not iterable. It must be an array or implement the iterator protocol.');\n    }\n\n    return result;\n  }\n\n  /**\n   * Recursively evaluate a group of rules.\n   *\n   * @param {Object} group - Group object with condition and rules\n   * @param {Object} record - Data record to evaluate against\n   * @returns {Object} Object with match (boolean) and details (object) properties\n   * @private\n   */\n  _evaluateGroup(group, record) {\n    // Helper function to apply negation if needed\n    const applyNegation = (result, shouldNegate) => {\n      if (!shouldNegate) return result;\n\n      return {\n        match: !result.match\n      };\n    };\n\n    if (!group.condition || !group.rules) {\n      return applyNegation({ match: false }, group.not);\n    }\n\n    const condition = group.condition.toUpperCase();\n    const rules = group.rules;\n\n    if (!rules || !rules.length) {\n      return applyNegation({ match: true }, group.not);\n    }\n\n    const results = rules.map(rule => {\n      // If it's a nested group\n      if (rule.rules) {\n        return this._evaluateGroup(rule, record);\n      }\n      // It's a rule\n      else if (rule.id && rule.operator) {\n        return this._evaluateRule(rule, record);\n      }\n      return { match: false };\n    });\n\n    // Combine the results based on the condition\n    let combinedResult = null;\n\n    if (condition === 'AND') {\n      const isMatch = results.every(result => result.match);\n      // For AND condition, we collect all details\n      const combinedDetails = isMatch ?\n        results.reduce((acc, result) => {\n          if (result.details) {\n            Object.entries(result.details).forEach(([field, details]) => {\n              if (field === 'fieldRanges' && details && typeof details === 'object') {\n                // Merge per-field substring ranges, deduping identical [start, end] pairs.\n                if (!acc.fieldRanges) acc.fieldRanges = {};\n                Object.entries(details).forEach(([fname, ranges]) => {\n                  if (!Array.isArray(ranges)) return;\n                  const existing = acc.fieldRanges[fname] || [];\n                  const seen = new Set(existing.map(r => `${r[0]},${r[1]}`));\n                  const merged = existing.slice();\n                  ranges.forEach(r => {\n                    const key = `${r[0]},${r[1]}`;\n                    if (!seen.has(key)) {\n                      seen.add(key);\n                      merged.push(r);\n                    }\n                  });\n                  acc.fieldRanges[fname] = merged;\n                });\n              } else if (!acc[field]) acc[field] = details;\n              else if (Array.isArray(acc[field]) && Array.isArray(details)) {\n                // Merge arrays and remove duplicates\n                acc[field] = [...new Set([...acc[field], ...details])];\n              }\n            });\n          }\n          return acc;\n        }, {}) : null;\n\n      combinedResult = {\n        match: isMatch,\n        details: combinedDetails\n      };\n    } else if (condition === 'OR') {\n      const matchingResults = results.filter(result => result.match);\n      const isMatch = matchingResults.length > 0;\n\n      // For OR condition, we take the first matching details\n      combinedResult = {\n        match: isMatch,\n        details: isMatch ? matchingResults[0].details : null\n      };\n    } else {\n      throw new Error(`Unknown condition: ${condition}`);\n    }\n\n    return applyNegation(combinedResult, group.not);\n  }\n\n  /**\n   * Evaluate a single rule against a record.\n   *\n   * @param {Object} rule - Rule object with id, operator, and value\n   * @param {Object} record - Data record to check against\n   * @returns {Object} Object with match (boolean) and details (object) properties\n   * @private\n   */\n  _evaluateRule(rule, record) {\n    const field = rule.field || rule.id;\n    const operatorName = rule.operator;\n    const isMultiFieldRule = rule.data && rule.data.multiField === true;\n    const ignoreCase = !!rule.ignoreCase;\n\n    // Fast path: handle custom search functions first\n    if (rule.id &&\n        this.customSearchFunctions[rule.id] &&\n        typeof this.customSearchFunctions[rule.id][operatorName] === \"function\") {\n\n      // For custom functions, provide field value or entire record as needed\n      const valueToCheck = isMultiFieldRule ? record : record[field];\n      const result = this.customSearchFunctions[rule.id][operatorName](valueToCheck, rule.value, rule);\n\n      // Normalize the result to always have match and details properties\n      return typeof result === 'object' && 'match' in result ?\n        result : { match: Boolean(result) };\n    }\n\n    // For standard single-field rules, verify the field exists\n    if (!isMultiFieldRule && !(field in record)) {\n      return { match: false };\n    }\n\n    // For multi-field rules without custom handlers, we can't process with standard operators\n    if (isMultiFieldRule) {\n      return { match: false };\n    }\n\n    const fieldValue = record[field];\n\n    // Handle special operators that don't need a value\n    if (['is_empty', 'is_not_empty', 'is_null', 'is_not_null'].includes(operatorName)) {\n      const result = this.operators[operatorName](fieldValue);\n      return {\n        match: result,\n        details: result ? { [field]: true } : null\n      };\n    }\n\n    // For regular operators, ensure we have a value to compare against\n    const ruleValue = rule.value;\n    if (ruleValue === null || ruleValue === undefined) {\n      return { match: false };\n    }\n\n    if (typeof ruleValue === 'number' && fieldValue === '') {\n      // Handle case where fieldValue is an empty string but ruleValue is a number\n      return { match: false };\n    }\n\n    // Apply the operator and normalize the result, passing ignoreCase for string operators\n    const result = this.operators[operatorName](fieldValue, ruleValue, ignoreCase);\n    const isMatch = Boolean(result);\n\n    return {\n      match: isMatch,\n      details: isMatch ? { [field]: true } : null\n    };\n  }\n\n  /**\n   * Add a custom operator function.\n   *\n   * @param {string} name - Name of the operator\n   * @param {Function} func - Function that takes two arguments (fieldValue, ruleValue) and returns an object with match and details properties\n   */\n  addOperator(name, func) {\n    this.operators[name] = func;\n  }\n\n  /**\n   * Add a custom type converter.\n   *\n   * @param {string} typeName - Name of the type\n   * @param {Function} func - Function that converts a value to the specified type\n   */\n  addTypeConverter(typeName, func) {\n    this.typeConverters[typeName] = func;\n  }\n}\n\n// Custom search functions using the new interface\nconst searchViaList = (fieldValue, ruleValue) => {\n  const items = ruleValue.split('|');\n  const match = items.some(item => item === fieldValue);\n  return { match };\n};\n\nconst splitSignatureListValue = (ruleValue) => {\n  if (Array.isArray(ruleValue)) {\n    return ruleValue;\n  }\n  if (typeof ruleValue === 'string') {\n    // Commas/newlines are user-facing separators for inscription IDs; pipe is\n    // the internal/backwards-compatible separator.  Do not split on semicolon:\n    // signatures such as \"Sö Fv1986;218\" use semicolon as part of the ID.\n    return ruleValue.split(/[|,\\n]+/).map(item => item.trim()).filter(Boolean);\n  }\n  return [ruleValue];\n};\n\n/**\n * Wrapper function for searching in signature_text and aliases\n * @param {Object} record - The complete record object containing signature_text and aliases\n * @param {string|Array} ruleValue - The value to search for\n * @param {Function} operatorFn - The comparison function to apply (e.g., String.includes, String.startsWith)\n * @param {boolean} [negate=false] - Whether to negate the result\n * @returns {Object} Result object with match property\n */\nconst searchSignatureWrapper = (record, ruleValue, operatorFn, negate = false, ignoreCase = false) => {\n  // Get the main signature text\n  const signatureText = record.signature_text;\n\n  // Get the aliases (if any) as an array\n  const aliases = record.aliases ?\n    record.aliases.split('|').map(a => a.trim()).filter(a => a) :\n    [];\n\n  // Combine into one array of values to check\n  const allSignatures = [signatureText, ...aliases];\n\n  // Process the rule value based on the input type\n  const items = splitSignatureListValue(ruleValue);\n\n  // Apply the operator function to check if any signature matches any item\n  let match = allSignatures.some(sig =>\n    items.some(item => operatorFn(sig, item, ignoreCase))\n  );\n\n  // Negate the result if needed\n  if (negate) {\n    match = !match;\n  }\n\n  return { match };\n};\n\nconst doWordSearch = (entry, ruleValue, searchDirection, searchMode, ignoreCase = false, includeSpecialSymbols = false) => {\n  const isAHit = getWordSearchFunction(searchMode, { ignoreCase, includeSpecialSymbols });\n  const phraseMatcher = getPhraseMatchFunction(searchMode, { ignoreCase, includeSpecialSymbols });\n  // key names defined in valueGetter, e.g. normalization_norse_to_transliteration\n  const normalisationQuery = ruleValue['normalization'];\n  const transliterationQuery = ruleValue['transliteration'];\n  const namesMode = ruleValue['names_mode'];\n\n  const searchDirectionConfig = getSearchDirectionConfig(searchDirection);\n  if (!searchDirectionConfig) {\n    return { match: false, details: null };\n  }\n\n  const normalizationField = searchDirectionConfig.normalisationField;\n\n  const normalWords = entry[`${normalizationField}_words`];\n  const transliterationWords = entry['transliteration_words'];\n\n  const hasEffectiveToken = (query) => {\n    if (query === null || query === undefined) return false;\n    const tokens = splitPhraseTokens(String(query));\n    if (tokens.length === 0) return false;\n    if (includeSpecialSymbols) return tokens.some(token => token.length > 0);\n    return tokens.some(token => stripSpecialSymbols(token).length > 0);\n  };\n\n  // If symbols are excluded and the query is made only of stripped symbols,\n  // prevent accidental \"match everything\" behavior via includes('').\n  if (normalisationQuery && !hasEffectiveToken(normalisationQuery)) {\n    return { match: false, details: null };\n  }\n  if (transliterationQuery && !hasEffectiveToken(transliterationQuery)) {\n    return { match: false, details: null };\n  }\n\n  const transliterationHasSymbol = (query) => {\n    if (query === null || query === undefined) return false;\n    return /(&quot;|&lt;|&gt;|[\"<>|[\\](){}^\\u00b4?+:\\u00d7\\u00b7\\u00a4'÷¶])/.test(String(query));\n  };\n\n  const getRawTransliterationMatchRanges = (query) => {\n    if (query === null || query === undefined) return [];\n\n    const rawSource = String(entry.transliteration || '');\n    const preparedSource = prepareForComparison(rawSource, ignoreCase);\n    // Canonicalize query the same way phrase searches do:\n    // trim outer whitespace and collapse internal whitespace to single spaces.\n    // This ensures symbol contains-search also catches symbols at string edges.\n    const preparedQuery = prepareForComparison(\n      splitPhraseTokens(String(query)).join(' '),\n      ignoreCase\n    );\n    if (preparedQuery.length === 0) return [];\n\n    switch (searchMode) {\n      case 'exact':\n        return preparedSource === preparedQuery ? [[0, rawSource.length]] : [];\n      case 'beginsWith':\n        return preparedSource.startsWith(preparedQuery) ? [[0, preparedQuery.length]] : [];\n      case 'endsWith': {\n        if (!preparedSource.endsWith(preparedQuery)) return [];\n        const start = preparedSource.length - preparedQuery.length;\n        return [[start, start + preparedQuery.length]];\n      }\n      case 'includes':\n      default: {\n        const ranges = [];\n        let cursor = 0;\n        while (cursor <= preparedSource.length - preparedQuery.length) {\n          const start = preparedSource.indexOf(preparedQuery, cursor);\n          if (start === -1) break;\n          ranges.push([start, start + preparedQuery.length]);\n          cursor = start + preparedQuery.length;\n        }\n        return ranges;\n      }\n    }\n  };\n\n  // Build matching windows for a single query against a word list.\n  // Returns Map<startIndex, number[]> where value is the window of indices.\n  // For single-word queries, windows are of length 1.\n  const findWindows = (words, query) => {\n    const result = new Map();\n    if (!query || !Array.isArray(words)) return result;\n    const tokens = splitPhraseTokens(query);\n    if (tokens.length === 0) return result;\n    if (tokens.length > 1) {\n      const windows = phraseMatcher(words, query);\n      windows.forEach(w => result.set(w[0], w));\n    } else {\n      words.forEach((word, i) => {\n        if (isAHit(word, tokens[0])) {\n          result.set(i, [i]);\n        }\n      });\n    }\n    return result;\n  };\n\n  // Determine whether a window passes the names-mode filter.\n  // ANY-word semantics:\n  //   excludeNames: reject if ANY word in window is a personal name\n  //   namesOnly:    require at least ONE word in window to be a personal name\n  const windowPassesNamesFilter = (indices) => {\n    if (namesMode !== 'excludeNames' && namesMode !== 'namesOnly') return true;\n    const hasPersonal = indices.some(i => i < normalWords.length && isPersonalName(normalWords[i]));\n    if (namesMode === 'excludeNames') return !hasPersonal;\n    return hasPersonal; // namesOnly\n  };\n\n  const matchedSet = new Set();\n  let matchFound = false;\n\n  const acceptWindow = (indices) => {\n    if (!windowPassesNamesFilter(indices)) return;\n    matchFound = true;\n    indices.forEach(i => matchedSet.add(i));\n  };\n\n  const buildResultFromMatchedSet = (extraDetails = null) => {\n    const matchedWords = [...matchedSet].sort((a, b) => a - b);\n    const numFoundNames = matchedWords.reduce((acc, i) => (\n      i < normalWords.length && isPersonalName(normalWords[i]) ? acc + 1 : acc\n    ), 0);\n    const details = matchFound ? {\n      wordIndices: matchedWords,\n      numPersonalNames: numFoundNames\n    } : null;\n    if (details && extraDetails && typeof extraDetails === 'object') {\n      if (extraDetails.fieldRanges && typeof extraDetails.fieldRanges === 'object') {\n        details.fieldRanges = { ...extraDetails.fieldRanges };\n      }\n    }\n    return {\n      match: matchFound,\n      details\n    };\n  };\n\n  // Symbol-aware transliteration search (raw-string mode):\n  // When include symbols is enabled and transliteration query contains symbols,\n  // allow matching against the raw transliteration string. If normalization query\n  // is also present, require both to match but do NOT require same word index.\n  const useRawTransliterationMode = includeSpecialSymbols && transliterationHasSymbol(transliterationQuery);\n  if (useRawTransliterationMode) {\n    const transliterationRanges = getRawTransliterationMatchRanges(transliterationQuery);\n    const transliterationMatches = transliterationRanges.length > 0;\n    if (!normalisationQuery) {\n      return {\n        match: transliterationMatches,\n        details: transliterationMatches ? {\n          fieldRanges: {\n            transliteration: transliterationRanges\n          }\n        } : null\n      };\n    }\n    findWindows(normalWords, normalisationQuery).forEach(win => acceptWindow(win));\n    if (!transliterationMatches || !matchFound) {\n      return { match: false, details: null };\n    }\n    return buildResultFromMatchedSet({\n      fieldRanges: {\n        transliteration: transliterationRanges\n      }\n    });\n  }\n\n  if (normalisationQuery && transliterationQuery) {\n    // Combined case: both queries must match starting at the same index.\n    // Highlighted indices are the union of both windows.\n    const norseWindows = findWindows(normalWords, normalisationQuery);\n    const translitWindows = findWindows(transliterationWords, transliterationQuery);\n    norseWindows.forEach((norseWin, startIdx) => {\n      const translitWin = translitWindows.get(startIdx);\n      if (!translitWin) return;\n      const union = new Set([...norseWin, ...translitWin]);\n      acceptWindow([...union]);\n    });\n  } else if (normalisationQuery) {\n    findWindows(normalWords, normalisationQuery).forEach(win => acceptWindow(win));\n  } else if (transliterationQuery) {\n    findWindows(transliterationWords, transliterationQuery).forEach(win => acceptWindow(win));\n  }\n\n  return buildResultFromMatchedSet();\n};\n\nconst searchCrossForm = (crosses, ruleValue) => {\n  if (!crosses || crosses.length === 0) {\n    return { match: false };\n  }\n\n  const ruleIsCertain = parseInt(ruleValue.is_certain, 10);\n  const searchForm = ruleValue.form;\n\n  // Flatten the array structure and search through all elements\n  for (const cross of crosses) {\n    if (!Array.isArray(cross)) continue;\n\n    for (const group of cross) {\n      if (!Array.isArray(group)) continue;\n\n      for (const element of group) {\n        // Check if the form name matches\n        if (element.name === searchForm) {\n          // If certainty doesn't matter (option 2) or certainty matches\n          if (ruleIsCertain === 2 || ruleIsCertain === element.isCertain) {\n            return { match: true };\n          }\n        }\n      }\n    }\n  }\n\n  return { match: false };\n};\n\nconst searchHasPersonalName = (numNames, ruleValue) => {\n  const isMatch = ruleValue === 0 ? numNames === 0 : numNames > 0;\n  return { match: isMatch };\n};\n\n/**\n * Build substring-range search functions for a free-text field (e.g. translations).\n * Each returned operator handler returns { match, details: { fieldRanges: { <field>: [[start,end], ...] } } }.\n * Ranges are indices into the original (un-escaped) field value and are used later for\n * rendering highlights. Honors `rule.ignoreCase` on the rule.\n *\n * @param {string} fieldName - Name of the record field (e.g. 'english_translation').\n * @returns {Object} Map of operator name -> handler function.\n */\nfunction buildTranslationSearchFunctions(fieldName) {\n  const buildMatch = (ranges) => ({\n    match: true,\n    details: { fieldRanges: { [fieldName]: ranges } }\n  });\n\n  const findAllRanges = (haystack, needle, ignoreCase) => {\n    const ranges = [];\n    if (!needle) return ranges;\n    const h = ignoreCase ? haystack.toLowerCase() : haystack;\n    const n = ignoreCase ? needle.toLowerCase() : needle;\n    let from = 0;\n    while (from <= h.length - n.length) {\n      const idx = h.indexOf(n, from);\n      if (idx === -1) break;\n      ranges.push([idx, idx + n.length]);\n      from = idx + n.length; // non-overlapping\n    }\n    return ranges;\n  };\n\n  return {\n    contains: (fieldValue, ruleValue, rule) => {\n      const value = fieldValue == null ? '' : String(fieldValue);\n      const query = ruleValue == null ? '' : String(ruleValue);\n      const ranges = findAllRanges(value, query, !!(rule && rule.ignoreCase));\n      if (ranges.length === 0) return { match: false };\n      return buildMatch(ranges);\n    },\n    begins_with: (fieldValue, ruleValue, rule) => {\n      const value = fieldValue == null ? '' : String(fieldValue);\n      const query = ruleValue == null ? '' : String(ruleValue);\n      if (query.length === 0 || query.length > value.length) return { match: false };\n      const ignoreCase = !!(rule && rule.ignoreCase);\n      const a = ignoreCase ? value.slice(0, query.length).toLowerCase() : value.slice(0, query.length);\n      const b = ignoreCase ? query.toLowerCase() : query;\n      if (a !== b) return { match: false };\n      return buildMatch([[0, query.length]]);\n    },\n    ends_with: (fieldValue, ruleValue, rule) => {\n      const value = fieldValue == null ? '' : String(fieldValue);\n      const query = ruleValue == null ? '' : String(ruleValue);\n      if (query.length === 0 || query.length > value.length) return { match: false };\n      const ignoreCase = !!(rule && rule.ignoreCase);\n      const start = value.length - query.length;\n      const a = ignoreCase ? value.slice(start).toLowerCase() : value.slice(start);\n      const b = ignoreCase ? query.toLowerCase() : query;\n      if (a !== b) return { match: false };\n      return buildMatch([[start, value.length]]);\n    },\n    equal: (fieldValue, ruleValue, rule) => {\n      const value = fieldValue == null ? '' : String(fieldValue);\n      const query = ruleValue == null ? '' : String(ruleValue);\n      const ignoreCase = !!(rule && rule.ignoreCase);\n      const a = ignoreCase ? value.toLowerCase() : value;\n      const b = ignoreCase ? query.toLowerCase() : query;\n      if (a !== b) return { match: false };\n      if (value.length === 0) return { match: true }; // nothing to highlight\n      return buildMatch([[0, value.length]]);\n    },\n  };\n}\n\nconst englishTranslationSearchFunctions = buildTranslationSearchFunctions('english_translation');\nconst swedishTranslationSearchFunctions = buildTranslationSearchFunctions('swedish_translation');\n\nfunction mergeFieldRanges(target, incoming) {\n  if (!incoming || typeof incoming !== 'object') {\n    return;\n  }\n\n  Object.entries(incoming).forEach(([fieldName, ranges]) => {\n    if (!Array.isArray(ranges)) {\n      return;\n    }\n\n    const existing = target[fieldName] || [];\n    const seen = new Set(existing.map(range => `${range[0]},${range[1]}`));\n    const merged = existing.slice();\n    ranges.forEach(range => {\n      if (!Array.isArray(range) || range.length < 2) {\n        return;\n      }\n      const key = `${range[0]},${range[1]}`;\n      if (!seen.has(key)) {\n        seen.add(key);\n        merged.push(range);\n      }\n    });\n    target[fieldName] = merged;\n  });\n}\n\nfunction countPersonalNamesByWordIndices(entry, wordIndices) {\n  const normalWords = Array.isArray(entry.normalisation_norse_words) ? entry.normalisation_norse_words : [];\n  return wordIndices.reduce((acc, index) => {\n    if (index >= normalWords.length) {\n      return acc;\n    }\n    return isPersonalName(normalWords[index]) ? acc + 1 : acc;\n  }, 0);\n}\n\nfunction searchRunicTextsCombined(entry, ruleValue, operatorName, rule) {\n  const operatorToSearchMode = {\n    contains: 'includes',\n    equal: 'exact',\n    begins_with: 'beginsWith',\n    ends_with: 'endsWith',\n  };\n  const searchMode = operatorToSearchMode[operatorName];\n  if (!searchMode) {\n    return { match: false };\n  }\n\n  const query = String(ruleValue || '').trim();\n  if (!query) {\n    return { match: false };\n  }\n\n  const ignoreCase = !!(rule && rule.ignoreCase);\n  const includeSpecialSymbols = !!(rule && rule.includeSpecialSymbols);\n  const wordsFromNorse = doWordSearch(\n    entry,\n    { normalization: query, transliteration: '', names_mode: 'includeAll' },\n    'norseToTransliteration',\n    searchMode,\n    ignoreCase,\n    includeSpecialSymbols\n  );\n  const wordsFromScandinavian = doWordSearch(\n    entry,\n    { normalization: query, transliteration: '', names_mode: 'includeAll' },\n    'scandinavianToTransliteration',\n    searchMode,\n    ignoreCase,\n    includeSpecialSymbols\n  );\n  const wordsFromTransliteration = doWordSearch(\n    entry,\n    { normalization: '', transliteration: query, names_mode: 'includeAll' },\n    'norseToTransliteration',\n    searchMode,\n    ignoreCase,\n    includeSpecialSymbols\n  );\n  const englishMatch = englishTranslationSearchFunctions[operatorName](entry.english_translation, query, rule);\n  const swedishMatch = swedishTranslationSearchFunctions[operatorName](entry.swedish_translation, query, rule);\n\n  const candidates = [\n    wordsFromNorse,\n    wordsFromScandinavian,\n    wordsFromTransliteration,\n    englishMatch,\n    swedishMatch,\n  ];\n\n  const matchingCandidates = candidates.filter(candidate => candidate && candidate.match);\n  if (matchingCandidates.length === 0) {\n    return { match: false };\n  }\n\n  const wordIndexSet = new Set();\n  const fieldRanges = {};\n  matchingCandidates.forEach(candidate => {\n    const details = candidate.details;\n    if (!details || typeof details !== 'object') {\n      return;\n    }\n    if (Array.isArray(details.wordIndices)) {\n      details.wordIndices.forEach(index => wordIndexSet.add(index));\n    }\n    if (details.fieldRanges && typeof details.fieldRanges === 'object') {\n      mergeFieldRanges(fieldRanges, details.fieldRanges);\n    }\n  });\n\n  const mergedDetails = {};\n  if (wordIndexSet.size > 0) {\n    const wordIndices = Array.from(wordIndexSet).sort((a, b) => a - b);\n    mergedDetails.wordIndices = wordIndices;\n    mergedDetails.numPersonalNames = countPersonalNamesByWordIndices(entry, wordIndices);\n  }\n  if (Object.keys(fieldRanges).length > 0) {\n    mergedDetails.fieldRanges = fieldRanges;\n  }\n\n  return {\n    match: true,\n    details: Object.keys(mergedDetails).length > 0 ? mergedDetails : null,\n  };\n}\n\nconst SWEDISH_AREA_CODES = new Set([\n  'Öl', 'Ög', 'Sö', 'Sm', 'Vg', 'U', 'Vs', 'Nä', 'Vr', 'Gs',\n  'Hs', 'M', 'Ån', 'D', 'Hr', 'J', 'Lp', 'Ds', 'Bo', 'G', 'SE'\n]);\nconst COUNTRY_OR_PROVINCE_OPTIONS = [\n  { text: 'Sweden, whole', value: 'all_sweden' },\n  { text: 'Öland (Öl)', value: 'Öl ' }, { text: 'Östergötland (Ög)', value: 'Ög ' }, { text: 'Södermanland (Sö)', value: 'Sö ' },\n  { text: 'Småland (Sm)', value: 'Sm ' }, { text: 'Västergötland (Vg)', value: 'Vg ' }, { text: 'Uppland (U)', value: 'U ' },\n  { text: 'Västmanland (Vs)', value: 'Vs ' }, { text: 'Närke (Nä)', value: 'Nä ' }, { text: 'Värmland (Vr)', value: 'Vr ' },\n  { text: 'Gästrikland (Gs)', value: 'Gs ' }, { text: 'Hälsingland (Hs)', value: 'Hs ' }, { text: 'Medelpad (M)', value: 'M ' },\n  { text: 'Ångermanland (Ån)', value: 'Ån ' }, { text: 'Dalarna (D)', value: 'D ' }, { text: 'Härjedalen (Hr)', value: 'Hr ' },\n  { text: 'Jämtland (J)', value: 'J ' }, { text: 'Lappland (Lp)', value: 'Lp ' }, { text: 'Dalsland (Ds)', value: 'Ds ' },\n  { text: 'Bohuslän (Bo)', value: 'Bo ' }, { text: 'Gotland (G)', value: 'G ' }, { text: 'Sweden, other (SE)', value: 'SE ' },\n  { text: 'Denmark (DR)', value: 'DR ' }, { text: 'Norway (N)', value: 'N ' }, { text: 'Faroe Islands (FR)', value: 'FR ' },\n  { text: 'Greenland (GR)', value: 'GR ' }, { text: 'Iceland (IS)', value: 'IS ' }, { text: 'Finland (FI)', value: 'FI ' },\n  { text: 'Shetland (Sh)', value: 'Sh ' }, { text: 'Orkney (Or)', value: 'Or ' }, { text: 'Scotland (Sc)', value: 'Sc ' },\n  { text: 'England (E)', value: 'E ' }, { text: 'Isle of Man (IM)', value: 'IM ' }, { text: 'Ireland (IR)', value: 'IR ' },\n  { text: 'France (F)', value: 'F ' }, { text: 'Netherlands (NL)', value: 'NL ' }, { text: 'Germany (DE)', value: 'DE ' },\n  { text: 'Poland (PL)', value: 'PL ' }, { text: 'Latvia (LV)', value: 'LV ' }, { text: 'Russia (RU)', value: 'RU ' },\n  { text: 'Ukraine (UA)', value: 'UA ' }, { text: 'Byzantium (By)', value: 'By ' }, { text: 'Italy (IT)', value: 'IT ' },\n  { text: 'Other areas (X)', value: 'X ' },\n];\n\nfunction getSignatureAreaCode(signatureText) {\n  const signature = String(signatureText || '').trim();\n  if (!signature) return '';\n  const firstSpace = signature.indexOf(' ');\n  return firstSpace === -1 ? signature : signature.slice(0, firstSpace);\n}\n\nconst searchCountryOrProvince = (entry, ruleValues) => {\n  const signatureAreaCode = getSignatureAreaCode(entry['signature_text']);\n  const normalizedRuleValues = Array.isArray(ruleValues) ? ruleValues : [ruleValues];\n  for (let i = 0; i < normalizedRuleValues.length; i++) {\n    const ruleCode = String(normalizedRuleValues[i] || '').trim();\n    if (!ruleCode) continue;\n    if (ruleCode === 'all_sweden') {\n      if (SWEDISH_AREA_CODES.has(signatureAreaCode)) {\n        return { match: true };\n      }\n      const districts = ['Skåne', 'Halland', 'Blekinge'];\n      for (let j = 0; j < districts.length; j++) {\n        if (operators.contains(entry['district'], districts[j])) {\n          return { match: true };\n        }\n      }\n      continue;\n    }\n    if (signatureAreaCode === ruleCode) {\n      return { match: true };\n    }\n  }\n  return { match: false };\n}\n\nfunction searchCountryOrProvinceContains(entry, ruleValue, rule) {\n  const query = prepareForComparison(ruleValue, !!(rule && rule.ignoreCase)).trim();\n  if (!query) {\n    return { match: false };\n  }\n  const signatureAreaCode = getSignatureAreaCode(entry['signature_text']);\n  const matchingCodes = COUNTRY_OR_PROVINCE_OPTIONS\n    .filter(option => {\n      const text = prepareForComparison(option.text, !!(rule && rule.ignoreCase));\n      const code = prepareForComparison(String(option.value || '').trim(), !!(rule && rule.ignoreCase));\n      return text.includes(query) || code.includes(query);\n    })\n    .map(option => option.value);\n  if (matchingCodes.length === 0) {\n    return { match: false };\n  }\n  return searchCountryOrProvince(entry, matchingCodes);\n}\n\nfunction stripDiacritics(value) {\n  return String(value || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\n}\n\nfunction normalizeReferencesSearchValue(value, ignoreCase) {\n  // References often mix plain URLs and human labels; fold diacritics so\n  // \"Fornvännen\" can match \"fornvannen.se\" style URLs.\n  return stripDiacritics(prepareForComparison(value, ignoreCase));\n}\n\nfunction searchReferencesCombined(fieldValue, ruleValue, operatorName, rule) {\n  const ignoreCase = !!(rule && rule.ignoreCase);\n  const value = normalizeReferencesSearchValue(fieldValue, ignoreCase);\n  const query = normalizeReferencesSearchValue(ruleValue, ignoreCase);\n\n  switch (operatorName) {\n    case 'contains':\n      return { match: value.includes(query) };\n    case 'not_contains':\n      return { match: !value.includes(query) };\n    case 'begins_with':\n      return { match: value.startsWith(query) };\n    case 'not_begins_with':\n      return { match: !value.startsWith(query) };\n    case 'ends_with':\n      return { match: value.endsWith(query) };\n    case 'not_ends_with':\n      return { match: !value.endsWith(query) };\n    case 'equal':\n      return { match: value === query };\n    case 'not_equal':\n      return { match: value !== query };\n    default:\n      return { match: false };\n  }\n}\n\nconst customSearchFunctions = {\n  inscription_id: {\n    in: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.equal, false, !!rule.ignoreCase),\n    in_separated_list: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.equal, false, !!rule.ignoreCase),\n    begins_with: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.begins_with, false, !!rule.ignoreCase),\n    not_begins_with: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.begins_with, true, !!rule.ignoreCase),\n    ends_with: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.ends_with, false, !!rule.ignoreCase),\n    not_ends_with: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.ends_with, true, !!rule.ignoreCase),\n    contains: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.contains, false, !!rule.ignoreCase),\n    not_contains: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.contains, true, !!rule.ignoreCase),\n    equal: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.equal, false, !!rule.ignoreCase),\n    not_equal: (record, ruleValue, rule) => searchSignatureWrapper(record, ruleValue, operators.not_equal, false, !!rule.ignoreCase),\n  },\n  inscription_country: {\n    in: searchCountryOrProvince,\n    contains: searchCountryOrProvinceContains,\n  },\n  normalization_norse_to_transliteration: {\n    contains: (fieldValue, ruleValue, rule) => {\n      return doWordSearch(fieldValue, ruleValue, 'norseToTransliteration', 'includes', !!rule.ignoreCase, !!rule.includeSpecialSymbols);\n    },\n    equal: (fieldValue, ruleValue, rule) => {\n      return doWordSearch(fieldValue, ruleValue, 'norseToTransliteration', 'exact', !!rule.ignoreCase, !!rule.includeSpecialSymbols);\n    },\n    begins_with: (fieldValue, ruleValue, rule) => {\n      return doWordSearch(fieldValue, ruleValue, 'norseToTransliteration', 'beginsWith', !!rule.ignoreCase, !!rule.includeSpecialSymbols);\n    },\n    ends_with: (fieldValue, ruleValue, rule) => {\n      return doWordSearch(fieldValue, ruleValue, 'norseToTransliteration', 'endsWith', !!rule.ignoreCase, !!rule.includeSpecialSymbols);\n    },\n  },\n  normalization_scandinavian_to_transliteration: {\n    contains: (fieldValue, ruleValue, rule) => {\n      return doWordSearch(fieldValue, ruleValue, 'scandinavianToTransliteration', 'includes', !!rule.ignoreCase, !!rule.includeSpecialSymbols);\n    },\n    equal: (fieldValue, ruleValue, rule) => {\n      return doWordSearch(fieldValue, ruleValue, 'scandinavianToTransliteration', 'exact', !!rule.ignoreCase, !!rule.includeSpecialSymbols);\n    },\n    begins_with: (fieldValue, ruleValue, rule) => {\n      return doWordSearch(fieldValue, ruleValue, 'scandinavianToTransliteration', 'beginsWith', !!rule.ignoreCase, !!rule.includeSpecialSymbols);\n    },\n    ends_with: (fieldValue, ruleValue, rule) => {\n      return doWordSearch(fieldValue, ruleValue, 'scandinavianToTransliteration', 'endsWith', !!rule.ignoreCase, !!rule.includeSpecialSymbols);\n    },\n  },\n  search_runic_texts: {\n    contains: (fieldValue, ruleValue, rule) => searchRunicTextsCombined(fieldValue, ruleValue, 'contains', rule),\n    equal: (fieldValue, ruleValue, rule) => searchRunicTextsCombined(fieldValue, ruleValue, 'equal', rule),\n    begins_with: (fieldValue, ruleValue, rule) => searchRunicTextsCombined(fieldValue, ruleValue, 'begins_with', rule),\n    ends_with: (fieldValue, ruleValue, rule) => searchRunicTextsCombined(fieldValue, ruleValue, 'ends_with', rule),\n  },\n  cross_form: {\n    cross_form: searchCrossForm,\n  },\n  has_personal_name: {\n    equal: searchHasPersonalName,\n    not_equal: (fieldValue, ruleValue) => {\n      const result = searchHasPersonalName(fieldValue, ruleValue);\n      return { match: !result.match };\n    },\n  },\n  english_translation: englishTranslationSearchFunctions,\n  swedish_translation: swedishTranslationSearchFunctions,\n  references_combined: {\n    contains: (fieldValue, ruleValue, rule) => searchReferencesCombined(fieldValue, ruleValue, 'contains', rule),\n    not_contains: (fieldValue, ruleValue, rule) => searchReferencesCombined(fieldValue, ruleValue, 'not_contains', rule),\n    begins_with: (fieldValue, ruleValue, rule) => searchReferencesCombined(fieldValue, ruleValue, 'begins_with', rule),\n    not_begins_with: (fieldValue, ruleValue, rule) => searchReferencesCombined(fieldValue, ruleValue, 'not_begins_with', rule),\n    ends_with: (fieldValue, ruleValue, rule) => searchReferencesCombined(fieldValue, ruleValue, 'ends_with', rule),\n    not_ends_with: (fieldValue, ruleValue, rule) => searchReferencesCombined(fieldValue, ruleValue, 'not_ends_with', rule),\n    equal: (fieldValue, ruleValue, rule) => searchReferencesCombined(fieldValue, ruleValue, 'equal', rule),\n    not_equal: (fieldValue, ruleValue, rule) => searchReferencesCombined(fieldValue, ruleValue, 'not_equal', rule),\n  },\n};\n\nconst TRANSLATION_FIELDS = new Set(['english_translation', 'swedish_translation']);\nconst VALUELESS_OPERATORS = new Set(['is_empty', 'is_not_empty', 'is_null', 'is_not_null']);\nconst SYMBOL_AWARE_RULE_IDS = new Set([\n  'normalization_norse_to_transliteration',\n  'normalization_scandinavian_to_transliteration',\n]);\nconst SPECIAL_SYMBOL_PATTERN = /(&quot;|&lt;|&gt;|[\"<>|[\\](){}^\\u00b4?+:\\u00d7\\u00b7\\u00a4'÷¶])/;\n\nfunction trimToken(token) {\n  return String(token || '').replace(/^[\\s\"'`.,;:!?()[\\]{}<>]+|[\\s\"'`.,;:!?()[\\]{}<>]+$/g, '');\n}\n\nfunction collectTokensFromRuleValue(value, outputMap) {\n  if (value === null || value === undefined) return;\n  if (Array.isArray(value)) {\n    value.forEach(v => collectTokensFromRuleValue(v, outputMap));\n    return;\n  }\n  if (typeof value === 'object') {\n    Object.values(value).forEach(v => collectTokensFromRuleValue(v, outputMap));\n    return;\n  }\n  const tokens = splitPhraseTokens(String(value)).map(trimToken).filter(Boolean);\n  tokens.forEach(token => {\n    const key = token.toLowerCase();\n    if (!outputMap.has(key)) {\n      outputMap.set(key, token);\n    }\n  });\n}\n\nfunction traverseTranslationRules(group, outputMap) {\n  if (!group || !Array.isArray(group.rules)) return;\n  group.rules.forEach(rule => {\n    if (rule && Array.isArray(rule.rules)) {\n      traverseTranslationRules(rule, outputMap);\n      return;\n    }\n    if (!rule || !TRANSLATION_FIELDS.has(rule.id)) return;\n    if (VALUELESS_OPERATORS.has(rule.operator)) return;\n    collectTokensFromRuleValue(rule.value, outputMap);\n  });\n}\n\nexport function getTranslationSearchTokens(rules) {\n  const tokenMap = new Map();\n  traverseTranslationRules(rules, tokenMap);\n  return Array.from(tokenMap.values());\n}\n\nexport function getTranslationSearchTokenCount(rules) {\n  return getTranslationSearchTokens(rules).length;\n}\n\nexport function getTranslationOccurrenceCount(searchResults) {\n  if (!Array.isArray(searchResults)) {\n    return 0;\n  }\n\n  let totalOccurrences = 0;\n  for (const result of searchResults) {\n    const rangesByField = result?.matchDetails?.fieldRanges;\n    if (!rangesByField || typeof rangesByField !== 'object') {\n      continue;\n    }\n    ['english_translation', 'swedish_translation'].forEach(fieldName => {\n      const ranges = rangesByField[fieldName];\n      if (Array.isArray(ranges)) {\n        totalOccurrences += ranges.length;\n      }\n    });\n  }\n\n  return totalOccurrences;\n}\n\nfunction collectStringValues(value, output) {\n  if (value === null || value === undefined) return;\n  if (typeof value === 'string') {\n    const trimmed = value.trim();\n    if (trimmed.length > 0) output.push(trimmed);\n    return;\n  }\n  if (Array.isArray(value)) {\n    value.forEach(v => collectStringValues(v, output));\n    return;\n  }\n  if (typeof value === 'object') {\n    Object.values(value).forEach(v => collectStringValues(v, output));\n  }\n}\n\nfunction findRuleNeedingIncludeSymbols(group) {\n  if (!group || !Array.isArray(group.rules)) return false;\n\n  for (const rule of group.rules) {\n    if (!rule) continue;\n    if (Array.isArray(rule.rules)) {\n      if (findRuleNeedingIncludeSymbols(rule)) return true;\n      continue;\n    }\n    if (!SYMBOL_AWARE_RULE_IDS.has(rule.id)) continue;\n    if (rule.includeSpecialSymbols) continue;\n    if (VALUELESS_OPERATORS.has(rule.operator)) continue;\n\n    const values = [];\n    collectStringValues(rule.value, values);\n    if (values.some(v => SPECIAL_SYMBOL_PATTERN.test(v))) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nexport function shouldSuggestIncludeSymbols(rules) {\n  return findRuleNeedingIncludeSymbols(rules);\n}\n\n\n\n/**\n * Perform a search using QueryBuilder rules\n * @param {Object} rules - Rules object from jQuery QueryBuilder's getRules() method\n * @param {Array|Iterable} dbMap - Data to search through\n * @returns {Array} Array of objects that match search rules. Each item includes record data from\n *                  dbMap and matchDetails property with details of the match.\n */\nexport function doSearch(rules, dbMap) {\n  const parser = new QueryBuilderParser(customSearchFunctions);\n  const resultsRaw = parser.parseRules(rules, dbMap);\n  const results = resultsRaw.map(({ record, matchDetails }) => ({\n    ...record,\n    matchDetails\n  }));\n\n  return results;\n}\n\nexport function calcWordsAndPersonalNames(dbMap) {\n  let totalWordMatches = 0;\n  let totalPersonalNames = 0;\n  let totalSignatures = 0;\n  let isWordSpecific = false;\n  let isGeneral = false;\n\n  try {\n    const entries = dbMap.values();\n    for (const entry of entries) {\n      if (entry.matchDetails && entry.matchDetails.wordIndices) {\n        totalWordMatches += entry.matchDetails.wordIndices.length;\n        totalPersonalNames += entry.matchDetails.numPersonalNames;\n        isWordSpecific = true;\n      } else {\n        totalWordMatches += entry.normalisation_norse_word_boundaries.length;\n        entry.normalisation_norse_word_boundaries.forEach(boundary => {\n          totalPersonalNames += boundary.isPersonal;\n        });\n        isGeneral = true;\n      }\n      totalSignatures++;\n    }\n  } catch (error) {\n    console.error('Error calculating words and personal names:', error);\n  }\n\n  const countType = (isGeneral && isWordSpecific)\n    ? 'mixed'\n    : (isWordSpecific ? 'word-specific' : 'general');\n\n  $(document).trigger('updateSignatureCount', { count: totalSignatures });\n  $(document).trigger('updateWordCount', { count: totalWordMatches, type: countType });\n  $(document).trigger('updatePersonalNameCount', { count: totalPersonalNames, type: countType });\n}\n","// view-model.js\nexport class RunicViewModel {\n  constructor(dbMap) {\n    this.dbMap = dbMap;            // Original data source\n    this.searchResults = null;     // Last search results (or null if no search active)\n    this.currentSelectionIds = []; // Currently selected IDs\n    this.allCrossForms = new Set();\n\n    for (const inscription of dbMap.values()) {\n      if (inscription.crosses && Array.isArray(inscription.crosses)) {\n        for (const cross of inscription.crosses) {\n          if (Array.isArray(cross)) {\n            for (const group of cross) {\n              if (Array.isArray(group)) {\n                for (const element of group) {\n                  this.allCrossForms.add(element.name);\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n    this.allCrossForms = Array.from(this.allCrossForms).sort();\n  }\n  \n  // Get data for a specific ID, prioritizing search results if available\n  getInscriptionData(id) {\n    if (this.searchResults && this.searchResults.has(id)) {\n      return this.searchResults.get(id);\n    }\n    return this.dbMap.get(id);\n  }\n\n  getAllCrossForms() {\n    return this.allCrossForms;\n  }\n\n  getInscriptions(ids) {\n    if (this.searchResults && this.searchResults.size > 0) {\n      return ids.map(id => this.searchResults.get(parseInt(id, 10)));\n    }\n    return ids.map(id => this.dbMap.get(parseInt(id, 10)));\n  }\n  \n  /**\n   * Retrieves all inscriptions from the database map.\n   * @returns {Iterator} An iterator over all values in the database map.\n   */\n  getAllInscriptions() {\n    return this.dbMap;\n  }\n\n  // Get all currently active inscriptions (filtered by search if a search is active)\n  getActiveInscriptions() {\n    if (this.searchResults) {\n      return Array.from(this.searchResults.values());\n    }\n    return Array.from(this.dbMap.values());\n  }\n  \n  // Get all currently active inscription IDs (filtered by search if a search is active)\n  getActiveInscriptionIds() {\n    if (this.searchResults) {\n      return Array.from(this.searchResults.keys());\n    }\n    return Array.from(this.dbMap.keys());\n  }\n\n  // Update search results\n  setSearchResults(results) {\n    if (!results) {\n      this.searchResults = null;\n      return;\n    }\n    \n    // Convert array to Map for O(1) lookups\n    this.searchResults = new Map();\n    results.forEach(result => {\n      this.searchResults.set(result.id, result);\n    });\n    \n    // Update current selection to match search results\n    // this.currentSelectionIds = Array.from(this.searchResults.keys());\n    \n    // Notify subscribers that data has changed\n    $(document).trigger('viewModelUpdated', { \n      source: 'search',\n      count: this.searchResults.size,\n      model: this\n    });\n  }\n\n  // Clear search results\n  clearSearchResults() {\n    this.searchResults = null;\n    //this.currentSelectionIds = Array.from(this.dbMap.keys());\n    \n    $(document).trigger('viewModelUpdated', { \n      source: 'reset',\n      count: this.dbMap.size,\n      model: this\n    });\n  }\n  \n  // Update current selections\n  // setSelection(ids) {\n  //   this.currentSelectionIds = ids;\n  //   $(document).trigger('selectionChanged', { ids });\n  // }\n}","function handleImportWrapper(evt) {\n  handleFiles(evt.target.files);\n  closeResultsIoModal();\n\n  // reset form, so that we can import the same file again if we need to\n  $('form#form-signature-import').get(0).reset();\n}\n\nfunction handleFiles(files) {\n  if (files.length == 0) {\n    return;\n  }\n  var fileReader = new FileReader();\n  var file = files[0]; // be sure to take just a single file\n  const isExcelImport = isExcelFile(file);\n  fileReader.onloadend = function(evt) {\n    if (evt.target.readyState == FileReader.DONE) {\n      $('#loading-sub-text').html('This dialog will disappear once data is ready');\n      showLoading();\n      if (isExcelImport) {\n        setTimeout(importSignaturesFromExcel, 10, evt.target.result);\n      } else {\n        setTimeout(importSignaturesFromText, 10, evt.target.result);\n      }\n    }\n  }\n  if (isExcelImport) {\n    fileReader.readAsArrayBuffer(file);\n  } else {\n    fileReader.readAsText(file);\n  }\n}\n\nfunction handleDrop(e) {\n  let dt = e.dataTransfer;\n  let files = dt.files;\n\n  handleFiles(files);\n  closeResultsIoModal();\n}\n\n// Event handler that stops event propagation. Used for drag and drop support\nfunction preventDefaults (e) {\n  e.preventDefault()\n  e.stopPropagation()\n}\nfunction highlight(e) {\n  dropArea.classList.add('highlight')\n}\n\nfunction unhighlight(e) {\n  dropArea.classList.remove('highlight')\n}\n\n// Import list of signatures provided from a file\n// Function argument fileContent is the actual file content.\nfunction importSignaturesFromText(fileContent) {\n  if (fileContent === null) {\n    return;\n  }\n  if (/[\\x00-\\x08\\x0E-\\x1F]/.test(fileContent)) {\n    alert(\"Seems like the file you've selected is not textual. Please select a different file.\");\n    hideLoading();\n    return;\n  }\n\n  const csvData = Papa.parse(fileContent);\n  const values = extractValuesFromRows(csvData.data);\n  applyImportedSignatures(values);\n}\n\nfunction importSignaturesFromExcel(arrayBuffer) {\n  if (typeof XLSX === 'undefined') {\n    alert('Excel import is not available right now. Please try CSV/TXT format.');\n    hideLoading();\n    return;\n  }\n  if (arrayBuffer === null) {\n    return;\n  }\n\n  try {\n    const workbook = readWorkbookWithFallbacks(arrayBuffer);\n    if (!workbook.SheetNames || workbook.SheetNames.length === 0) {\n      alert('No worksheet found in this Excel file.');\n      hideLoading();\n      return;\n    }\n    let values = [];\n    for (const sheetName of workbook.SheetNames) {\n      const currentSheet = workbook.Sheets[sheetName];\n      const rows = XLSX.utils.sheet_to_json(currentSheet, {\n        header: 1,\n        raw: false,\n        defval: '',\n      });\n      values = extractValuesFromRows(rows);\n      if (values.length > 0) {\n        break;\n      }\n    }\n    applyImportedSignatures(values);\n  } catch (error) {\n    console.error('Failed to parse Excel import', error);\n    alert(`Could not read this Excel file. Please check the file and try again.\\n\\nDetails: ${formatImportError(error)}`);\n    hideLoading();\n  }\n}\n\nfunction readWorkbookWithFallbacks(arrayBuffer) {\n  const uint8 = new Uint8Array(arrayBuffer);\n  const readers = [\n    { label: 'array/uint8', fn: () => XLSX.read(uint8, { type: 'array' }) },\n    { label: 'array/arrayBuffer', fn: () => XLSX.read(arrayBuffer, { type: 'array' }) },\n    { label: 'buffer/uint8', fn: () => XLSX.read(uint8, { type: 'buffer' }) },\n    {\n      label: 'binary-string',\n      fn: () => {\n        const binary = arrayBufferToBinaryString(arrayBuffer);\n        return XLSX.read(binary, { type: 'binary' });\n      },\n    },\n    {\n      label: 'base64',\n      fn: () => {\n        const base64 = arrayBufferToBase64(arrayBuffer);\n        return XLSX.read(base64, { type: 'base64' });\n      },\n    },\n  ];\n\n  const failures = [];\n  for (const reader of readers) {\n    try {\n      return reader.fn();\n    } catch (error) {\n      failures.push(`[${reader.label}] ${formatImportError(error)}`);\n    }\n  }\n  throw new Error(`Unable to parse workbook. Tried: ${failures.join(' | ')}`);\n}\n\nfunction arrayBufferToBinaryString(arrayBuffer) {\n  const bytes = new Uint8Array(arrayBuffer);\n  const chunkSize = 0x8000;\n  let binary = '';\n  for (let i = 0; i < bytes.length; i += chunkSize) {\n    const chunk = bytes.subarray(i, i + chunkSize);\n    binary += String.fromCharCode.apply(null, chunk);\n  }\n  return binary;\n}\n\nfunction arrayBufferToBase64(arrayBuffer) {\n  return btoa(arrayBufferToBinaryString(arrayBuffer));\n}\n\nfunction formatImportError(error) {\n  if (!error) {\n    return 'Unknown error';\n  }\n  if (typeof error === 'string') {\n    return error;\n  }\n  if (error.message) {\n    return error.message;\n  }\n  try {\n    return JSON.stringify(error);\n  } catch (_) {\n    return String(error);\n  }\n}\n\nfunction applyImportedSignatures(values) {\n  if (!values || values.length === 0) {\n    alert('No signatures or IDs were found in the imported file.');\n    hideLoading();\n    return;\n  }\n  const uniqueValues = [...new Set(values)];\n  const joinedValues = uniqueValues.join('|');\n\n  var rule = [{\n    id: \"inscription_id\",\n    field: \"signature_text\",\n    type: \"string\",\n    input: \"text\",\n    operator: \"in_separated_list\",\n    value: joinedValues,\n    ignoreCase: false,\n  }];\n\n  var allRules = {\n    condition: 'OR',\n    rules: rule,\n    not: false,\n  };\n\n  $(`#${inpQueryBuilder}`).queryBuilder('setRules', allRules, true);\n  // doSearch();\n  hideLoading();\n  // After importing signatures, automatically trigger the search\n  document.getElementById('btnSearch').click();\n}\n\nfunction normalizeCellValue(value) {\n  if (value === null || value === undefined) {\n    return '';\n  }\n  return String(value).trim();\n}\n\nfunction extractValuesFromRows(rows) {\n  if (!Array.isArray(rows) || rows.length === 0) {\n    return [];\n  }\n\n  const headerCandidates = new Set(['signature', 'signature_text', 'id', 'inscription_id']);\n  const firstRow = Array.isArray(rows[0]) ? rows[0] : [rows[0]];\n  let signatureColumn = 0;\n  let firstDataRow = 0;\n\n  const headerIndex = firstRow.findIndex(item => headerCandidates.has(normalizeCellValue(item).toLowerCase()));\n  if (headerIndex !== -1) {\n    signatureColumn = headerIndex;\n    firstDataRow = 1;\n  }\n\n  const values = [];\n  for (let i = firstDataRow; i < rows.length; i++) {\n    const row = Array.isArray(rows[i]) ? rows[i] : [rows[i]];\n    const candidate = normalizeCellValue(row[signatureColumn]);\n    if (candidate.length === 0) {\n      continue;\n    }\n    values.push(candidate);\n  }\n\n  return values;\n}\n\nfunction isExcelFile(file) {\n  const excelExtPattern = /\\.(xlsx|xls|xlsm|xlsb|ods)$/i;\n  if (file && file.name && excelExtPattern.test(file.name)) {\n    return true;\n  }\n  const mime = (file && file.type) ? file.type : '';\n  return mime.includes('spreadsheetml') || mime.includes('ms-excel') || mime.includes('opendocument.spreadsheet');\n}\n\nexport function initDragAndDrop() {\n  const dropArea = document.getElementById('drop-area');\n  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {\n    dropArea.addEventListener(eventName, preventDefaults, false)\n  });\n  ['dragenter', 'dragover'].forEach(eventName => {\n    dropArea.addEventListener(eventName, highlight, false)\n  });\n  ['dragleave', 'drop'].forEach(eventName => {\n    dropArea.addEventListener(eventName, unhighlight, false)\n  });\n  dropArea.addEventListener('drop', handleDrop, false);\n  document.getElementById('fileElem').addEventListener('change', handleImportWrapper, false);\n}\n","// src/index.js\nimport * as module1 from './index_scripts.js';\nimport * as module2 from './index_multiselect.js';\nimport * as map_module from './index_map.js';\nimport * as query_builder_module from './index_query_builder.js';\nimport * as search_module from './index_search.js';\nimport * as view_model_module from './index_view_model.js';\nimport * as import_utils_module from './index_import_utils.js';\nimport * as layout_module from './index_layout.js';\n\nObject.assign(window, module1, module2, map_module, query_builder_module, search_module, view_model_module, import_utils_module, layout_module);\nlayout_module.initCompactDbLayout();\n"],"names":["isPersonalName","word","startsWith","includes","normalizeWhitespace","value","String","replace","prepareForComparison","ignoreCase","normalizedValue","toLowerCase","stripSpecialSymbols","str","getWordSearchFunction","searchMode","options","includeSpecialSymbols","prepareString","preparedValue","prepareWord","split","map","name","trim","query","some","oneWord","endsWith","RegExp","test","error","Error","splitPhraseTokens","normalized","getPhraseMatchFunction","normalizedSearchMode","prepareDocWordVariants","docWordEquals","docWord","tokenPrepared","v","compareAt","token","j","n","isFirst","isLast","docWordIncludes","docWordStartsWith","docWordEndsWith","docWords","tokens","windows","length","Array","isArray","i","matches","window","push","getSearchDirectionConfig","searchDirection","normalisationField","fromField","toField","COMPACT_DB_LAYOUT_MEDIA_QUERY","isTouchTabletFallback","windowObject","navigatorObject","viewportWidth","documentWidth","document","documentElement","clientWidth","Math","max","innerWidth","getViewportWidth","userAgent","platform","hasTouch","Number","maxTouchPoints","isIPadOS","isAndroidTablet","isCompactDbLayout","navigator","matchMedia","syncCompactDbLayoutClass","compact","classList","toggle","initCompactDbLayout","addEventListener","schemaFieldsInfo","schemaName","text","en","css","highlight","normalizationWordsToSkip","transliterationWordsToSkip","gRenderInProgress","gMainDisplayScrollTargetId","NORMALISATION_COLUMNS","Set","concealPersonalNameMarkersForDisplay","columnName","htmlValue","has","getWordBoundaries","sourceIsEscaped","wordBoundariesSource","punctuationSource","reg","escapeHtml","purePunctuation","words","arr","wordBegin","processWord","wordEnd","wordText","slice","punctuationCheck","exec","start","end","isPersonal","index","prepareSignumForDisplay","signature_text","lost","new_reading","additional","resolveUrlOrBlob","urlOrBlob","blobBase","BLOB_BASE_URL","encodedBlobPath","normalize","part","encodeURIComponent","join","fetchAllImages","db","content","e","allRows","values","hasInfoColumn","columns","allImages","row","indirect","direct","info","metaId","hasOwnProperty","links","makeImagesMarkup","signatureImageLinks","directImages","indirectImages","isMobileViewport","replaceAll","galleryLinks","hiddenLinks","indirectUrl","directUrl","sourceLabel","url","URL","hostname","imageSourceLabel","infoText","toString","caption","crossesForMeta","contents","lastCrossId","crosses","crossId","parseInt","apply","formName","groupId","isCertain","setTooltip","el","message","that","$","attr","tooltip","setTimeout","renderSignatures","selectedSignatureIds","jstree","get_selected","html","inscriptions2markup","gViewModel","searchResults","getActiveInscriptions","getInscriptions","getElementById","innerHTML","mainDisplay","targetId","fallbackId","isNaN","targetArticle","querySelector","top","offsetTop","scrollTo","behavior","scrollMainDisplayToSignature","console","string","entityMap","s","renderSpecialReferenceLinks","refText","raw","regex","label","forEach","rule","match","matchAll","fullUrl","sort","a","b","result","lastIndex","cleanUrl","trailing","highlightWordsFromWordBoundaries","wordBoundaries","highlightedStr","highlightSubstringRanges","rawStr","ranges","sorted","min","filter","merged","out","last","inscriptions","showHeaders","is","userSelectedFields","getUserSelectedFields","markupData","inscriptionData","signatureId","id","signatureName","paragraph","field","humanName","cssStyle","shouldHighlight","columnData","trimmedColumnData","hasCrossText","allCrosses","k","gr","res","getCrossUrl","isCerain","lines","item","line","refs","r","ref","sep","indexOf","substring","specialLinksMarkup","fieldRanges","matchDetails","entryWordBoundaries","matchedWords","wordIndices","matchedWordBoundaries","_","parts","sidesHeader","onExportError","hideLoading","gExportInProgress","showAlert","alertObj","textContent","style","display","hide","closeResultsIoModal","modal","numCrossesColumn","metaColumn","wordColumns","crossFormByMetaId","crossFormContent","crossesTextual","allDbImages","numDiffers","dbArray","objSignature","hasOriginalCoordinates","latitude","longitude","parseFloat","hasCurrentCoordinates","present_latitude","present_longitude","coordinationParts","transliteration","wordsToSkip","normalisation_norse_word_boundaries","normalisation_scandinavian_word_boundaries","transliteration_word_boundaries","boundary","imagesMarkup","numCrosses","dbAsJson","Map","inscription","set","log","displaySignatureInfo","lang","find","querySelectorAll","button","symbol","innerText","clipboard","writeText","then","catch","err","container","dbMap","option","createElement","signature_display","appendChild","from","normalizedText","icon","signature","data","buffer","blob","Blob","type","link","href","createObjectURL","download","body","click","removeChild","_signature","inscriptionId","tree","deselect_all","select_node","containerRect","getBoundingClientRect","elRect","scrollTop","root","get_node","children","parsedId","description","txtLoadingDescription","show","gUserSelectedDisplayKey","gShowHeadersKey","gMobileDisplayDefaultsVersionKey","MOBILE_DISPLAY_DEFAULTS_VERSION","REQUIRED_DISPLAY_VALUES","DEFAULT_SELECTED_DISPLAY_VALUES","MOBILE_DEFAULT_SELECTED_DISPLAY_VALUES","DISPLAY_FIELD_GROUPS","title","fields","normalizeSelectedValues","selectedValues","requiredValue","shouldUseMobileDefaults","isMobileDisplayOptionsUi","getDefaultSelectedDisplayValues","storageAvailable","storage","x","setItem","removeItem","DOMException","code","getUserSelectedDisplay","getItem","JSON","parse","saveUserSelectedDisplay","selectedValuesArray","stringify","concat","getSchemaField","prop","updateMobileDisplayOrderButtons","rows","upButton","downButton","disabled","renderMobileDisplayOptionsChecklist","selectedSet","$checklist","empty","orderedFieldNames","usedFields","add","append","isRequired","isChecked","disabledAttr","requiredHint","renderDisplayOptionRow","renderDisplayOptionsChecklist","group","groupFields","Boolean","$group","rowHtml","remainingFields","$otherGroup","renderGroupedDisplayOptionsChecklist","getCurrentSelectedValues","toArray","val","setMultiselectOptions","sortValue","schemaField","setDesktopMultiselectOptions","applyDisplayFormatChanges","closeDialog","lastShowHeaders","localStorage","lastUserSelectedValues","arraysEqual","trigger","applyMobileDisplayFormatImmediately","moveMobileDisplayOption","event","target","closest","dataset","displayMove","previousElementSibling","parentElement","insertBefore","nextElementSibling","resortDisplayOptions","newSort","each","this","onDisplayFormatClicked","preventDefault","savedSelected","defaultValues","orderedDefaults","remainingSelected","migratedValues","applyMobileDefaultMigration","savedShowHeaders","multiselect","keepRenderingSortRight","skipInitSortRight","left","right","afterMoveUp","afterMoveDown","afterMoveToRight","afterMoveToLeft","shouldSpiderfyNearbyPair","cluster","maxDistanceMeters","getAllChildMarkers","distance","getChildCount","childMarkers","getLatLng","shouldSpiderfySharedCoordinates","maxMarkers","childCount","firstLatLng","every","marker","latLng","lat","lng","handleMobileClusterClick","layer","spiderfy","getZoom","getMaxZoom","zoomToBounds","getMarkerClusterOptions","isMobile","showCoverageOnHover","chunkedLoading","maxClusterRadius","zoomToBoundsOnClick","spiderfyOnMaxZoom","getSpiderfiedTooltipDirection","latLngToLayerPoint","markerPoint","clusterPoint","deltaX","deltaY","y","abs","getSpiderfyEventMarkers","markers","positionSpiderfiedTooltips","getTooltip","_rundataOriginalDirection","direction","update","resetSpiderfiedTooltips","isMobileDevice","makeMobileTooltipOpenPopup","tooltipElement","getElement","mobilePopupTrigger","setAttribute","openPopupFromTooltip","stopPropagation","openPopup","key","inscription2marker","lon","locationType","leaflet","L","popupText","hasCurrentLocation","currentLocation","current_location","hasCurrentLocationInfo","warningTexts","infoTexts","confirmTexts","isLostInscription","destinationUrl","getGeoIntentURL","driveLinkClass","driveLinkTarget","confirmText","popupOptions","autoClose","autoPan","closeButton","maxWidth","bindPopup","tooltipOptions","permanent","interactive","className","bindTooltip","openTooltip","on","divId","center","zoom","fullscreenControl","fullscreenControlOptions","forcePseudoFullscreen","pseudoFullscreen","setView","tileLayer","maxZoom","attribution","referrerPolicy","addTo","control","locate","locateOptions","enableHighAccuracy","timeout","maximumAge","strings","details","browserMessage","getGeoLocationErrorDetails","alert","markerClusterGroup","scrollToInscription","mapMarkers","found_lat","found_lon","present_lat","present_lon","marker_found","found","present","get","marker_present","mapContainerId","menuItemId","mapContainerJquery","menuItemJquery","jquery","jQuery","selectInscriptionForMobileInfo","infoButton","preserveMapArea","showOriginalLocation","inscriptionIds","allMarkers","mapObject","markersLayer","markersLatLon","clearLayers","inscriptionMarkers","markerToShow","addLayers","invalidateSize","pan","fitBounds","padding","queryBuilderPlugins","sortable","optGroups","gr_inscription","sv","gr_texts","gr_location","gr_time_period","gr_design","gr_more_info","ensureTextOptionsRow","$container","$el","$row","first","$personalModeInput","fn","queryBuilder","define","self","$rule","constructor","selectors","rule_container","getModel","model","node","Rule","updateRuleCaseIgnore","__","shouldShow","cssSelectorPluginCaseRule","json","sqlFn","disable_template","utils","defineModelProperties","cssSelectorPluginCaseRuleInput","extend","specialSymbolsFilterIds","updateRuleSpecialSymbols","cssSelectorPluginSpecialSymbolsRule","cssSelectorPluginSpecialSymbolsRuleInput","sortGroupsByOrder","items","groupOrder","groups","groupKey","Object","keys","labelA","labelB","localeCompare","getMinMaxValues","dataSource","fieldName","hasValues","processItem","undefined","getValuesFromAllData","term","suggest","isTomSelect","allValues","uniqueTracker","nextArtificialId","visited","score","aliases","alias","normalizedTerm","prepareAutoComplete","ruleId","humanNameGetter","opt","fieldId","operators","size","optgroup","plugin","plugin_config","minChars","delay","source","menuClass","attachToParent","prepareIntegerRule","inputType","input","config","dataLimitValues","step","validation","allow_empty_value","default_value","adjustTomSelectAndAutoComplete","tomSelectConfig","autoCompleteConfig","$input","operator","tomSelect","hasClass","autoComplete","createWordSearchRule","input1Label","input2Label","multiField","valueGetter","normalization","names_mode","valueSetter","containerId","viewModel","getHumanName","getAllInscriptions","qbOperators","DEFAULTS","nb_inputs","multiple","apply_to","signature_text_autocomplete_cfg","signature_text_tomselect_cfg","plugins","load","callback","settings","preload","valueField","hideSelected","delimiter","queryBuilderFilters","getAllCrossForms","form","is_certain","display_empty_filter","filters","sort_filters","allow_empty","optgroups","in_separated_list","cross_form","sqlOperators","op","mod","templates","rule_id","icons","translate","builder","display_errors","remove_rule","equal","not_equal","in","not_in","less","less_or_equal","greater","greater_or_equal","between","not_between","begins_with","not_begins_with","contains","not_contains","ends_with","not_ends_with","is_empty","is_not_empty","is_null","is_not_null","QueryBuilderParser","customSearchFunctions","parseRules","rules","valid","resultWithDetails","_evaluateGroup","record","Symbol","iterator","next","nextItem","done","applyNegation","shouldNegate","condition","not","toUpperCase","results","_evaluateRule","combinedResult","isMatch","reduce","acc","entries","fname","existing","seen","matchingResults","operatorName","isMultiFieldRule","valueToCheck","fieldValue","ruleValue","addOperator","func","addTypeConverter","typeName","typeConverters","searchSignatureWrapper","operatorFn","negate","allSignatures","splitSignatureListValue","sig","doWordSearch","entry","isAHit","phraseMatcher","normalisationQuery","transliterationQuery","namesMode","searchDirectionConfig","normalizationField","normalWords","transliterationWords","hasEffectiveToken","findWindows","w","matchedSet","matchFound","acceptWindow","indices","hasPersonal","windowPassesNamesFilter","buildResultFromMatchedSet","extraDetails","numFoundNames","numPersonalNames","transliterationRanges","rawSource","preparedSource","preparedQuery","cursor","getRawTransliterationMatchRanges","transliterationMatches","win","norseWindows","translitWindows","norseWin","startIdx","translitWin","union","searchHasPersonalName","numNames","buildTranslationSearchFunctions","buildMatch","haystack","needle","h","idx","findAllRanges","englishTranslationSearchFunctions","swedishTranslationSearchFunctions","searchRunicTextsCombined","matchingCandidates","english_translation","swedish_translation","candidate","wordIndexSet","incoming","range","mergedDetails","normalisation_norse_words","countPersonalNamesByWordIndices","SWEDISH_AREA_CODES","COUNTRY_OR_PROVINCE_OPTIONS","getSignatureAreaCode","signatureText","firstSpace","searchCountryOrProvince","ruleValues","signatureAreaCode","normalizedRuleValues","ruleCode","districts","normalizeReferencesSearchValue","stripDiacritics","searchReferencesCombined","inscription_id","inscription_country","matchingCodes","normalization_norse_to_transliteration","normalization_scandinavian_to_transliteration","search_runic_texts","ruleIsCertain","searchForm","cross","element","has_personal_name","references_combined","TRANSLATION_FIELDS","VALUELESS_OPERATORS","SYMBOL_AWARE_RULE_IDS","SPECIAL_SYMBOL_PATTERN","trimToken","collectTokensFromRuleValue","outputMap","traverseTranslationRules","getTranslationSearchTokens","tokenMap","collectStringValues","output","trimmed","findRuleNeedingIncludeSymbols","totalWordMatches","totalPersonalNames","totalSignatures","isWordSpecific","isGeneral","countType","count","totalOccurrences","rangesByField","currentSelectionIds","allCrossForms","getInscriptionData","ids","getActiveInscriptionIds","setSearchResults","clearSearchResults","handleImportWrapper","evt","handleFiles","files","reset","fileReader","FileReader","file","isExcelImport","excelExtPattern","mime","isExcelFile","onloadend","readyState","DONE","showLoading","importSignaturesFromExcel","importSignaturesFromText","readAsArrayBuffer","readAsText","handleDrop","dataTransfer","preventDefaults","dropArea","unhighlight","remove","fileContent","applyImportedSignatures","extractValuesFromRows","Papa","arrayBuffer","XLSX","workbook","uint8","Uint8Array","readers","read","binary","arrayBufferToBinaryString","base64","btoa","arrayBufferToBase64","failures","reader","formatImportError","readWorkbookWithFallbacks","SheetNames","sheetName","currentSheet","Sheets","sheet_to_json","header","defval","bytes","chunk","subarray","fromCharCode","allRules","inpQueryBuilder","normalizeCellValue","headerCandidates","signatureColumn","firstDataRow","headerIndex","findIndex","eventName","assign","module1","module2","map_module","query_builder_module","search_module","view_model_module","import_utils_module","layout_module","layout_module.initCompactDbLayout"],"mappings":"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"}