xref: /webtrees/resources/js/webtrees.js (revision 2cf1b3d738df97008ab3c3a92d5939c02a4598a7)
171239cb6SGreg Roach/**
271239cb6SGreg Roach * webtrees: online genealogy
3063107dbSGreg Roach * Copyright (C) 2020 webtrees development team
471239cb6SGreg Roach * This program is free software: you can redistribute it and/or modify
571239cb6SGreg Roach * it under the terms of the GNU General Public License as published by
671239cb6SGreg Roach * the Free Software Foundation, either version 3 of the License, or
771239cb6SGreg Roach * (at your option) any later version.
871239cb6SGreg Roach * This program is distributed in the hope that it will be useful,
971239cb6SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
1071239cb6SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1171239cb6SGreg Roach * GNU General Public License for more details.
1271239cb6SGreg Roach * You should have received a copy of the GNU General Public License
1371239cb6SGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
1471239cb6SGreg Roach */
1571239cb6SGreg Roach
16efd89170SGreg Roach'use strict';
1771239cb6SGreg Roach
180d2905f7SGreg Roach(function (webtrees) {
1959e18f0cSGreg Roach  const lang = document.documentElement.lang;
2059e18f0cSGreg Roach
210d2905f7SGreg Roach  // Identify the script used by some text.
220d2905f7SGreg Roach  const scriptRegexes = {
230d2905f7SGreg Roach    Han: /[\u3400-\u9FCC]/,
240d2905f7SGreg Roach    Grek: /[\u0370-\u03FF]/,
250d2905f7SGreg Roach    Cyrl: /[\u0400-\u04FF]/,
260d2905f7SGreg Roach    Hebr: /[\u0590-\u05FF]/,
27efd89170SGreg Roach    Arab: /[\u0600-\u06FF]/
280d2905f7SGreg Roach  };
290d2905f7SGreg Roach
3059e18f0cSGreg Roach  /**
3159e18f0cSGreg Roach   * Tidy the whitespace in a string.
3206fe1eb5SGreg Roach   * @param {string} str
3306fe1eb5SGreg Roach   * @returns {string}
3459e18f0cSGreg Roach   */
3559e18f0cSGreg Roach  function trim (str) {
36efd89170SGreg Roach    return str.replace(/\s+/g, ' ').trim();
3759e18f0cSGreg Roach  }
3859e18f0cSGreg Roach
3959e18f0cSGreg Roach  /**
4059e18f0cSGreg Roach   * Look for non-latin characters in a string.
4106fe1eb5SGreg Roach   * @param {string} str
4206fe1eb5SGreg Roach   * @returns {string}
4359e18f0cSGreg Roach   */
440d2905f7SGreg Roach  webtrees.detectScript = function (str) {
450d2905f7SGreg Roach    for (const script in scriptRegexes) {
460d2905f7SGreg Roach      if (str.match(scriptRegexes[script])) {
470d2905f7SGreg Roach        return script;
480d2905f7SGreg Roach      }
4959e18f0cSGreg Roach    }
5059e18f0cSGreg Roach
51efd89170SGreg Roach    return 'Latn';
520d2905f7SGreg Roach  };
5359e18f0cSGreg Roach
5459e18f0cSGreg Roach  /**
5559e18f0cSGreg Roach   * In some languages, the SURN uses a male/default form, but NAME uses a gender-inflected form.
5606fe1eb5SGreg Roach   * @param {string} surname
5706fe1eb5SGreg Roach   * @param {string} sex
5806fe1eb5SGreg Roach   * @returns {string}
5959e18f0cSGreg Roach   */
6059e18f0cSGreg Roach  function inflectSurname (surname, sex) {
61efd89170SGreg Roach    if (lang === 'pl' && sex === 'F') {
6259e18f0cSGreg Roach      return surname
63efd89170SGreg Roach        .replace(/ski$/, 'ska')
64efd89170SGreg Roach        .replace(/cki$/, 'cka')
65efd89170SGreg Roach        .replace(/dzki$/, 'dzka')
66efd89170SGreg Roach        .replace(/żki$/, 'żka');
6759e18f0cSGreg Roach    }
6859e18f0cSGreg Roach
6959e18f0cSGreg Roach    return surname;
7059e18f0cSGreg Roach  }
7159e18f0cSGreg Roach
7259e18f0cSGreg Roach  /**
7359e18f0cSGreg Roach   * Build a NAME from a NPFX, GIVN, SPFX, SURN and NSFX parts.
7459e18f0cSGreg Roach   * Assumes the language of the document is the same as the language of the name.
7506fe1eb5SGreg Roach   * @param {string} npfx
7606fe1eb5SGreg Roach   * @param {string} givn
7706fe1eb5SGreg Roach   * @param {string} spfx
7806fe1eb5SGreg Roach   * @param {string} surn
7906fe1eb5SGreg Roach   * @param {string} nsfx
8006fe1eb5SGreg Roach   * @param {string} sex
8106fe1eb5SGreg Roach   * @returns {string}
8259e18f0cSGreg Roach   */
830d2905f7SGreg Roach  webtrees.buildNameFromParts = function (npfx, givn, spfx, surn, nsfx, sex) {
84efd89170SGreg Roach    const usesCJK = webtrees.detectScript(npfx + givn + spfx + givn + surn + nsfx) === 'Han';
85efd89170SGreg Roach    const separator = usesCJK ? '' : ' ';
8659e18f0cSGreg Roach    const surnameFirst = usesCJK || ['hu', 'jp', 'ko', 'vi', 'zh-Hans', 'zh-Hant'].indexOf(lang) !== -1;
8759e18f0cSGreg Roach    const patronym = ['is'].indexOf(lang) !== -1;
88efd89170SGreg Roach    const slash = patronym ? '' : '/';
8959e18f0cSGreg Roach
9059e18f0cSGreg Roach    // GIVN and SURN may be a comma-separated lists.
9159e18f0cSGreg Roach    npfx = trim(npfx);
92063107dbSGreg Roach    givn = trim(givn.replace(/,/g, separator));
9359e18f0cSGreg Roach    spfx = trim(spfx);
94063107dbSGreg Roach    surn = inflectSurname(trim(surn.replace(/,/g, separator)), sex);
9559e18f0cSGreg Roach    nsfx = trim(nsfx);
9659e18f0cSGreg Roach
9759e18f0cSGreg Roach    const surname = trim(spfx + separator + surn);
9859e18f0cSGreg Roach
9959e18f0cSGreg Roach    const name = surnameFirst ? slash + surname + slash + separator + givn : givn + separator + slash + surname + slash;
10059e18f0cSGreg Roach
10159e18f0cSGreg Roach    return trim(npfx + separator + name + separator + nsfx);
1020d2905f7SGreg Roach  };
1030d2905f7SGreg Roach
1040d2905f7SGreg Roach  // Insert text at the current cursor position in a text field.
1050d2905f7SGreg Roach  webtrees.pasteAtCursor = function (element, text) {
1060d2905f7SGreg Roach    if (element !== null) {
1070d2905f7SGreg Roach      const caret_pos = element.selectionStart + text.length;
1080d2905f7SGreg Roach      const textBefore = element.value.substring(0, element.selectionStart);
1090d2905f7SGreg Roach      const textAfter = element.value.substring(element.selectionEnd);
1100d2905f7SGreg Roach      element.value = textBefore + text + textAfter;
1110d2905f7SGreg Roach      element.setSelectionRange(caret_pos, caret_pos);
1120d2905f7SGreg Roach      element.focus();
11359e18f0cSGreg Roach    }
114efd89170SGreg Roach  };
11571239cb6SGreg Roach
11606fe1eb5SGreg Roach  /**
1177fb78f8aSGreg Roach   * @param {Element} datefield
11806fe1eb5SGreg Roach   * @param {string} dmy
11906fe1eb5SGreg Roach   */
120a7a3d6dbSGreg Roach  webtrees.reformatDate = function (datefield, dmy) {
121a7a3d6dbSGreg Roach    const months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
122a7a3d6dbSGreg Roach    const hijri_months = ['MUHAR', 'SAFAR', 'RABIA', 'RABIT', 'JUMAA', 'JUMAT', 'RAJAB', 'SHAAB', 'RAMAD', 'SHAWW', 'DHUAQ', 'DHUAH'];
123a7a3d6dbSGreg Roach    const hebrew_months = ['TSH', 'CSH', 'KSL', 'TVT', 'SHV', 'ADR', 'ADS', 'NSN', 'IYR', 'SVN', 'TMZ', 'AAV', 'ELL'];
124a7a3d6dbSGreg Roach    const french_months = ['VEND', 'BRUM', 'FRIM', 'NIVO', 'PLUV', 'VENT', 'GERM', 'FLOR', 'PRAI', 'MESS', 'THER', 'FRUC', 'COMP'];
125a7a3d6dbSGreg Roach    const jalali_months = ['FARVA', 'ORDIB', 'KHORD', 'TIR', 'MORDA', 'SHAHR', 'MEHR', 'ABAN', 'AZAR', 'DEY', 'BAHMA', 'ESFAN'];
12671239cb6SGreg Roach
127a7a3d6dbSGreg Roach    let datestr = datefield.value;
12871239cb6SGreg Roach    // if a date has a date phrase marked by () this has to be excluded from altering
129a7a3d6dbSGreg Roach    let datearr = datestr.split('(');
130a7a3d6dbSGreg Roach    let datephrase = '';
13171239cb6SGreg Roach    if (datearr.length > 1) {
13271239cb6SGreg Roach      datestr = datearr[0];
13371239cb6SGreg Roach      datephrase = datearr[1];
13471239cb6SGreg Roach    }
13571239cb6SGreg Roach
13671239cb6SGreg Roach    // Gedcom dates are upper case
13771239cb6SGreg Roach    datestr = datestr.toUpperCase();
13871239cb6SGreg Roach    // Gedcom dates have no leading/trailing/repeated whitespace
13980d699d6SGreg Roach    datestr = datestr.replace(/\s+/g, ' ');
14071239cb6SGreg Roach    datestr = datestr.replace(/(^\s)|(\s$)/, '');
14171239cb6SGreg Roach    // Gedcom dates have spaces between letters and digits, e.g. "01JAN2000" => "01 JAN 2000"
14280d699d6SGreg Roach    datestr = datestr.replace(/(\d)([A-Z])/g, '$1 $2');
14380d699d6SGreg Roach    datestr = datestr.replace(/([A-Z])(\d)/g, '$1 $2');
14471239cb6SGreg Roach
14571239cb6SGreg Roach    // Shortcut for quarter format, "Q1 1900" => "BET JAN 1900 AND MAR 1900". See [ 1509083 ]
14671239cb6SGreg Roach    if (datestr.match(/^Q ([1-4]) (\d\d\d\d)$/)) {
14771239cb6SGreg Roach      datestr = 'BET ' + months[RegExp.$1 * 3 - 3] + ' ' + RegExp.$2 + ' AND ' + months[RegExp.$1 * 3 - 1] + ' ' + RegExp.$2;
14871239cb6SGreg Roach    }
14971239cb6SGreg Roach
15071239cb6SGreg Roach    // Shortcut for @#Dxxxxx@ 01 01 1400, etc.
15171239cb6SGreg Roach    if (datestr.match(/^(@#DHIJRI@|HIJRI)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)) {
15271239cb6SGreg Roach      datestr = '@#DHIJRI@' + RegExp.$2 + hijri_months[parseInt(RegExp.$3, 10) - 1] + RegExp.$4;
15371239cb6SGreg Roach    }
15471239cb6SGreg Roach    if (datestr.match(/^(@#DJALALI@|JALALI)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)) {
15571239cb6SGreg Roach      datestr = '@#DJALALI@' + RegExp.$2 + jalali_months[parseInt(RegExp.$3, 10) - 1] + RegExp.$4;
15671239cb6SGreg Roach    }
15771239cb6SGreg Roach    if (datestr.match(/^(@#DHEBREW@|HEBREW)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)) {
15871239cb6SGreg Roach      datestr = '@#DHEBREW@' + RegExp.$2 + hebrew_months[parseInt(RegExp.$3, 10) - 1] + RegExp.$4;
15971239cb6SGreg Roach    }
16071239cb6SGreg Roach    if (datestr.match(/^(@#DFRENCH R@|FRENCH)( \d?\d )(\d?\d)( \d?\d?\d?\d)$/)) {
16171239cb6SGreg Roach      datestr = '@#DFRENCH R@' + RegExp.$2 + french_months[parseInt(RegExp.$3, 10) - 1] + RegExp.$4;
16271239cb6SGreg Roach    }
16371239cb6SGreg Roach
1647fb78f8aSGreg Roach    // All digit dates
1657fb78f8aSGreg Roach    datestr = datestr.replaceAll(/(\d\d)(\d\d)(\d\d)(\d\d)/g, function () {
1667fb78f8aSGreg Roach      if (RegExp.$1 > '12' && RegExp.$3 <= '12' && RegExp.$4 <= '31') {
1677fb78f8aSGreg Roach        return RegExp.$4 + ' ' + months[RegExp.$3 - 1] + ' ' + RegExp.$1 + RegExp.$2;
1687fb78f8aSGreg Roach      }
1697fb78f8aSGreg Roach      if (RegExp.$1 <= '31' && RegExp.$2 <= '12' && RegExp.$3 > '12') {
1707fb78f8aSGreg Roach        return RegExp.$1 + ' ' + months[RegExp.$2 - 1] + ' ' + RegExp.$3 + RegExp.$4;
1717fb78f8aSGreg Roach      }
1727fb78f8aSGreg Roach      return RegExp.$1 + RegExp.$2 + RegExp.$3 + RegExp.$4;
1737fb78f8aSGreg Roach    });
1747fb78f8aSGreg Roach
1757fb78f8aSGreg Roach    // e.g. 17.11.1860, 3/4/2005 or 1999-12-31. Use locale settings since DMY order is ambiguous.
1767fb78f8aSGreg Roach    datestr = datestr.replaceAll(/(\d+)([./-])(\d+)([./-])(\d+)/g, function () {
177*2cf1b3d7SGreg Roach      let f1 = parseInt(RegExp.$1, 10);
178*2cf1b3d7SGreg Roach      let f2 = parseInt(RegExp.$3, 10);
179*2cf1b3d7SGreg Roach      let f3 = parseInt(RegExp.$5, 10);
180*2cf1b3d7SGreg Roach      let yyyy = new Date().getFullYear();
181*2cf1b3d7SGreg Roach      let yy = yyyy % 100;
182*2cf1b3d7SGreg Roach      let cc = yyyy - yy;
18371239cb6SGreg Roach      if (dmy === 'DMY' && f1 <= 31 && f2 <= 12 || f1 > 13 && f1 <= 31 && f2 <= 12 && f3 > 31) {
1847fb78f8aSGreg Roach        return f1 + ' ' + months[f2 - 1] + ' ' + (f3 >= 100 ? f3 : (f3 <= yy ? f3 + cc : f3 + cc - 100));
1857fb78f8aSGreg Roach      }
18671239cb6SGreg Roach      if (dmy === 'MDY' && f1 <= 12 && f2 <= 31 || f2 > 13 && f2 <= 31 && f1 <= 12 && f3 > 31) {
1877fb78f8aSGreg Roach        return f2 + ' ' + months[f1 - 1] + ' ' + (f3 >= 100 ? f3 : (f3 <= yy ? f3 + cc : f3 + cc - 100));
1887fb78f8aSGreg Roach      }
18971239cb6SGreg Roach      if (dmy === 'YMD' && f2 <= 12 && f3 <= 31 || f3 > 13 && f3 <= 31 && f2 <= 12 && f1 > 31) {
1907fb78f8aSGreg Roach        return f3 + ' ' + months[f2 - 1] + ' ' + (f1 >= 100 ? f1 : (f1 <= yy ? f1 + cc : f1 + cc - 100));
19171239cb6SGreg Roach      }
1927fb78f8aSGreg Roach      return RegExp.$1 + RegExp.$2 + RegExp.$3 + RegExp.$4 + RegExp.$5;
1937fb78f8aSGreg Roach    });
19471239cb6SGreg Roach
195a7a3d6dbSGreg Roach    datestr = datestr
19671239cb6SGreg Roach      // Shortcuts for date ranges
197a7a3d6dbSGreg Roach      .replace(/^[>]([\w ]+)$/, 'AFT $1')
198a7a3d6dbSGreg Roach      .replace(/^[<]([\w ]+)$/, 'BEF $1')
199a7a3d6dbSGreg Roach      .replace(/^([\w ]+)[-]$/, 'FROM $1')
200a7a3d6dbSGreg Roach      .replace(/^[-]([\w ]+)$/, 'TO $1')
201a7a3d6dbSGreg Roach      .replace(/^[~]([\w ]+)$/, 'ABT $1')
202a7a3d6dbSGreg Roach      .replace(/^[*]([\w ]+)$/, 'EST $1')
203a7a3d6dbSGreg Roach      .replace(/^[#]([\w ]+)$/, 'CAL $1')
204a7a3d6dbSGreg Roach      .replace(/^([\w ]+) ?- ?([\w ]+)$/, 'BET $1 AND $2')
205a7a3d6dbSGreg Roach      .replace(/^([\w ]+) ?~ ?([\w ]+)$/, 'FROM $1 TO $2')
20671239cb6SGreg Roach      // Convert full months to short months
207a7a3d6dbSGreg Roach      .replaceAll('JANUARY', 'JAN')
208a7a3d6dbSGreg Roach      .replaceAll('FEBRUARY', 'FEB')
209a7a3d6dbSGreg Roach      .replaceAll('MARCH', 'MAR')
210a7a3d6dbSGreg Roach      .replaceAll('APRIL', 'APR')
211a7a3d6dbSGreg Roach      .replaceAll('JUNE', 'JUN')
212a7a3d6dbSGreg Roach      .replaceAll('JULY', 'JUL')
213a7a3d6dbSGreg Roach      .replaceAll('AUGUST', 'AUG')
214a7a3d6dbSGreg Roach      .replaceAll('SEPTEMBER', 'SEP')
215a7a3d6dbSGreg Roach      .replaceAll('OCTOBER', 'OCT')
216a7a3d6dbSGreg Roach      .replaceAll('NOVEMBER', 'NOV')
217a7a3d6dbSGreg Roach      .replaceAll('DECEMBER', 'DEC')
218a7a3d6dbSGreg Roach      // Americans enter dates as SEP 20, 1999
219915908ebSGreg Roach      .replaceAll(/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)\.? (\d\d?)[, ]+(\d\d\d\d)/g, '$2 $1 $3')
22071239cb6SGreg Roach      // Apply leading zero to day numbers
221915908ebSGreg Roach      .replaceAll(/(^| )(\d [A-Z]{3,5} \d{4})/g, '$10$2');
22271239cb6SGreg Roach
22371239cb6SGreg Roach    if (datephrase) {
22471239cb6SGreg Roach      datestr = datestr + ' (' + datephrase;
22571239cb6SGreg Roach    }
226a7a3d6dbSGreg Roach
22771239cb6SGreg Roach    // Only update it if is has been corrected - otherwise input focus
22871239cb6SGreg Roach    // moves to the end of the field unnecessarily
22971239cb6SGreg Roach    if (datefield.value !== datestr) {
23071239cb6SGreg Roach      datefield.value = datestr;
23171239cb6SGreg Roach    }
232*2cf1b3d7SGreg Roach  };
23371239cb6SGreg Roach
234*2cf1b3d7SGreg Roach  let monthLabels = [];
23571239cb6SGreg Roach  monthLabels[1] = 'January';
23671239cb6SGreg Roach  monthLabels[2] = 'February';
23771239cb6SGreg Roach  monthLabels[3] = 'March';
23871239cb6SGreg Roach  monthLabels[4] = 'April';
23971239cb6SGreg Roach  monthLabels[5] = 'May';
24071239cb6SGreg Roach  monthLabels[6] = 'June';
24171239cb6SGreg Roach  monthLabels[7] = 'July';
24271239cb6SGreg Roach  monthLabels[8] = 'August';
24371239cb6SGreg Roach  monthLabels[9] = 'September';
24471239cb6SGreg Roach  monthLabels[10] = 'October';
24571239cb6SGreg Roach  monthLabels[11] = 'November';
24671239cb6SGreg Roach  monthLabels[12] = 'December';
24771239cb6SGreg Roach
248*2cf1b3d7SGreg Roach  let monthShort = [];
24971239cb6SGreg Roach  monthShort[1] = 'JAN';
25071239cb6SGreg Roach  monthShort[2] = 'FEB';
25171239cb6SGreg Roach  monthShort[3] = 'MAR';
25271239cb6SGreg Roach  monthShort[4] = 'APR';
25371239cb6SGreg Roach  monthShort[5] = 'MAY';
25471239cb6SGreg Roach  monthShort[6] = 'JUN';
25571239cb6SGreg Roach  monthShort[7] = 'JUL';
25671239cb6SGreg Roach  monthShort[8] = 'AUG';
25771239cb6SGreg Roach  monthShort[9] = 'SEP';
25871239cb6SGreg Roach  monthShort[10] = 'OCT';
25971239cb6SGreg Roach  monthShort[11] = 'NOV';
26071239cb6SGreg Roach  monthShort[12] = 'DEC';
26171239cb6SGreg Roach
262*2cf1b3d7SGreg Roach  let daysOfWeek = [];
26371239cb6SGreg Roach  daysOfWeek[0] = 'S';
26471239cb6SGreg Roach  daysOfWeek[1] = 'M';
26571239cb6SGreg Roach  daysOfWeek[2] = 'T';
26671239cb6SGreg Roach  daysOfWeek[3] = 'W';
26771239cb6SGreg Roach  daysOfWeek[4] = 'T';
26871239cb6SGreg Roach  daysOfWeek[5] = 'F';
26971239cb6SGreg Roach  daysOfWeek[6] = 'S';
27071239cb6SGreg Roach
271*2cf1b3d7SGreg Roach  let weekStart = 0;
27271239cb6SGreg Roach
27306fe1eb5SGreg Roach  /**
27406fe1eb5SGreg Roach   * @param {string} jan
27506fe1eb5SGreg Roach   * @param {string} feb
27606fe1eb5SGreg Roach   * @param {string} mar
27706fe1eb5SGreg Roach   * @param {string} apr
27806fe1eb5SGreg Roach   * @param {string} may
27906fe1eb5SGreg Roach   * @param {string} jun
28006fe1eb5SGreg Roach   * @param {string} jul
28106fe1eb5SGreg Roach   * @param {string} aug
28206fe1eb5SGreg Roach   * @param {string} sep
28306fe1eb5SGreg Roach   * @param {string} oct
28406fe1eb5SGreg Roach   * @param {string} nov
28506fe1eb5SGreg Roach   * @param {string} dec
286*2cf1b3d7SGreg Roach   * @param {string} sun
287*2cf1b3d7SGreg Roach   * @param {string} mon
288*2cf1b3d7SGreg Roach   * @param {string} tue
289*2cf1b3d7SGreg Roach   * @param {string} wed
290*2cf1b3d7SGreg Roach   * @param {string} thu
291*2cf1b3d7SGreg Roach   * @param {string} fri
292*2cf1b3d7SGreg Roach   * @param {string} sat
293*2cf1b3d7SGreg Roach   * @param {number} day
29406fe1eb5SGreg Roach   */
295*2cf1b3d7SGreg Roach  webtrees.calLocalize = function (jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec, sun, mon, tue, wed, thu, fri, sat, day) {
29671239cb6SGreg Roach    monthLabels[1] = jan;
29771239cb6SGreg Roach    monthLabels[2] = feb;
29871239cb6SGreg Roach    monthLabels[3] = mar;
29971239cb6SGreg Roach    monthLabels[4] = apr;
30071239cb6SGreg Roach    monthLabels[5] = may;
30171239cb6SGreg Roach    monthLabels[6] = jun;
30271239cb6SGreg Roach    monthLabels[7] = jul;
30371239cb6SGreg Roach    monthLabels[8] = aug;
30471239cb6SGreg Roach    monthLabels[9] = sep;
30571239cb6SGreg Roach    monthLabels[10] = oct;
30671239cb6SGreg Roach    monthLabels[11] = nov;
30771239cb6SGreg Roach    monthLabels[12] = dec;
30871239cb6SGreg Roach    daysOfWeek[0] = sun;
30971239cb6SGreg Roach    daysOfWeek[1] = mon;
31071239cb6SGreg Roach    daysOfWeek[2] = tue;
31171239cb6SGreg Roach    daysOfWeek[3] = wed;
31271239cb6SGreg Roach    daysOfWeek[4] = thu;
31371239cb6SGreg Roach    daysOfWeek[5] = fri;
31471239cb6SGreg Roach    daysOfWeek[6] = sat;
31571239cb6SGreg Roach
31671239cb6SGreg Roach    if (day >= 0 && day < 7) {
31771239cb6SGreg Roach      weekStart = day;
31871239cb6SGreg Roach    }
319*2cf1b3d7SGreg Roach  };
32071239cb6SGreg Roach
32106fe1eb5SGreg Roach  /**
32206fe1eb5SGreg Roach   * @param {string} dateDivId
32306fe1eb5SGreg Roach   * @param {string} dateFieldId
32406fe1eb5SGreg Roach   * @returns {boolean}
32506fe1eb5SGreg Roach   */
326*2cf1b3d7SGreg Roach  webtrees.calendarWidget = function (dateDivId, dateFieldId) {
327*2cf1b3d7SGreg Roach    let dateDiv = document.getElementById(dateDivId);
328*2cf1b3d7SGreg Roach    let dateField = document.getElementById(dateFieldId);
32971239cb6SGreg Roach
33071239cb6SGreg Roach    if (dateDiv.style.visibility === 'visible') {
33171239cb6SGreg Roach      dateDiv.style.visibility = 'hidden';
33271239cb6SGreg Roach      return false;
33371239cb6SGreg Roach    }
33471239cb6SGreg Roach    if (dateDiv.style.visibility === 'show') {
33571239cb6SGreg Roach      dateDiv.style.visibility = 'hide';
33671239cb6SGreg Roach      return false;
33771239cb6SGreg Roach    }
33871239cb6SGreg Roach
33971239cb6SGreg Roach    /* Javascript calendar functions only work with precise gregorian dates "D M Y" or "Y" */
340*2cf1b3d7SGreg Roach    let greg_regex = /((\d+ (JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC) )?\d+)/i;
341*2cf1b3d7SGreg Roach    let date;
34271239cb6SGreg Roach    if (greg_regex.exec(dateField.value)) {
34371239cb6SGreg Roach      date = new Date(RegExp.$1);
34471239cb6SGreg Roach    } else {
34571239cb6SGreg Roach      date = new Date();
34671239cb6SGreg Roach    }
34771239cb6SGreg Roach
348*2cf1b3d7SGreg Roach    dateDiv.innerHTML = calGenerateSelectorContent(dateFieldId, dateDivId, date);
34971239cb6SGreg Roach    if (dateDiv.style.visibility === 'hidden') {
35071239cb6SGreg Roach      dateDiv.style.visibility = 'visible';
35171239cb6SGreg Roach      return false;
35271239cb6SGreg Roach    }
35371239cb6SGreg Roach    if (dateDiv.style.visibility === 'hide') {
35471239cb6SGreg Roach      dateDiv.style.visibility = 'show';
35571239cb6SGreg Roach      return false;
35671239cb6SGreg Roach    }
35771239cb6SGreg Roach
35871239cb6SGreg Roach    return false;
359*2cf1b3d7SGreg Roach  };
36071239cb6SGreg Roach
36106fe1eb5SGreg Roach  /**
36206fe1eb5SGreg Roach   * @param {string} dateFieldId
36306fe1eb5SGreg Roach   * @param {string} dateDivId
36406fe1eb5SGreg Roach   * @param {Date} date
36506fe1eb5SGreg Roach   * @returns {string}
36606fe1eb5SGreg Roach   */
367*2cf1b3d7SGreg Roach  function calGenerateSelectorContent (dateFieldId, dateDivId, date) {
368*2cf1b3d7SGreg Roach    let i, j;
369*2cf1b3d7SGreg Roach    let content = '<table border="1"><tr>';
370*2cf1b3d7SGreg Roach    content += '<td><select class="form-control" id="' + dateFieldId + '_daySelect" onchange="return webtrees.calUpdateCalendar(\'' + dateFieldId + '\', \'' + dateDivId + '\');">';
37171239cb6SGreg Roach    for (i = 1; i < 32; i++) {
37271239cb6SGreg Roach      content += '<option value="' + i + '"';
37371239cb6SGreg Roach      if (date.getDate() === i) {
37471239cb6SGreg Roach        content += ' selected="selected"';
37571239cb6SGreg Roach      }
37671239cb6SGreg Roach      content += '>' + i + '</option>';
37771239cb6SGreg Roach    }
37871239cb6SGreg Roach    content += '</select></td>';
379*2cf1b3d7SGreg Roach    content += '<td><select class="form-control" id="' + dateFieldId + '_monSelect" onchange="return webtrees.calUpdateCalendar(\'' + dateFieldId + '\', \'' + dateDivId + '\');">';
38071239cb6SGreg Roach    for (i = 1; i < 13; i++) {
38171239cb6SGreg Roach      content += '<option value="' + i + '"';
38271239cb6SGreg Roach      if (date.getMonth() + 1 === i) {
38371239cb6SGreg Roach        content += ' selected="selected"';
38471239cb6SGreg Roach      }
38571239cb6SGreg Roach      content += '>' + monthLabels[i] + '</option>';
38671239cb6SGreg Roach    }
38771239cb6SGreg Roach    content += '</select></td>';
388*2cf1b3d7SGreg Roach    content += '<td><input class="form-control" type="text" id="' + dateFieldId + '_yearInput" size="5" value="' + date.getFullYear() + '" onchange="return webtrees.calUpdateCalendar(\'' + dateFieldId + '\', \'' + dateDivId + '\');" /></td></tr>';
38971239cb6SGreg Roach    content += '<tr><td colspan="3">';
39071239cb6SGreg Roach    content += '<table width="100%">';
39171239cb6SGreg Roach    content += '<tr>';
39271239cb6SGreg Roach    j = weekStart;
39371239cb6SGreg Roach    for (i = 0; i < 7; i++) {
39471239cb6SGreg Roach      content += '<td ';
39571239cb6SGreg Roach      content += 'class="descriptionbox"';
39671239cb6SGreg Roach      content += '>';
39771239cb6SGreg Roach      content += daysOfWeek[j];
39871239cb6SGreg Roach      content += '</td>';
39971239cb6SGreg Roach      j++;
40071239cb6SGreg Roach      if (j > 6) {
40171239cb6SGreg Roach        j = 0;
40271239cb6SGreg Roach      }
40371239cb6SGreg Roach    }
40471239cb6SGreg Roach    content += '</tr>';
40571239cb6SGreg Roach
406*2cf1b3d7SGreg Roach    let tdate = new Date(date.getFullYear(), date.getMonth(), 1);
407*2cf1b3d7SGreg Roach    let day = tdate.getDay();
40871239cb6SGreg Roach    day = day - weekStart;
409*2cf1b3d7SGreg Roach    let daymilli = 1000 * 60 * 60 * 24;
41071239cb6SGreg Roach    tdate = tdate.getTime() - (day * daymilli) + (daymilli / 2);
41171239cb6SGreg Roach    tdate = new Date(tdate);
41271239cb6SGreg Roach
41371239cb6SGreg Roach    for (j = 0; j < 6; j++) {
41471239cb6SGreg Roach      content += '<tr>';
41571239cb6SGreg Roach      for (i = 0; i < 7; i++) {
41671239cb6SGreg Roach        content += '<td ';
41771239cb6SGreg Roach        if (tdate.getMonth() === date.getMonth()) {
41871239cb6SGreg Roach          if (tdate.getDate() === date.getDate()) {
41971239cb6SGreg Roach            content += 'class="descriptionbox"';
42071239cb6SGreg Roach          } else {
42171239cb6SGreg Roach            content += 'class="optionbox"';
42271239cb6SGreg Roach          }
42371239cb6SGreg Roach        } else {
42471239cb6SGreg Roach          content += 'style="background-color:#EAEAEA; border: solid #AAAAAA 1px;"';
42571239cb6SGreg Roach        }
426*2cf1b3d7SGreg Roach        content += '><a href="#" onclick="return webtrees.calDateClicked(\'' + dateFieldId + '\', \'' + dateDivId + '\', ' + tdate.getFullYear() + ', ' + tdate.getMonth() + ', ' + tdate.getDate() + ');">';
42771239cb6SGreg Roach        content += tdate.getDate();
42871239cb6SGreg Roach        content += '</a></td>';
429*2cf1b3d7SGreg Roach        let datemilli = tdate.getTime() + daymilli;
43071239cb6SGreg Roach        tdate = new Date(datemilli);
43171239cb6SGreg Roach      }
43271239cb6SGreg Roach      content += '</tr>';
43371239cb6SGreg Roach    }
43471239cb6SGreg Roach    content += '</table>';
43571239cb6SGreg Roach    content += '</td></tr>';
43671239cb6SGreg Roach    content += '</table>';
43771239cb6SGreg Roach
43871239cb6SGreg Roach    return content;
43971239cb6SGreg Roach  }
44071239cb6SGreg Roach
44106fe1eb5SGreg Roach  /**
44206fe1eb5SGreg Roach   * @param {string} dateFieldId
44306fe1eb5SGreg Roach   * @param {number} year
44406fe1eb5SGreg Roach   * @param {number} month
44506fe1eb5SGreg Roach   * @param {number} day
44606fe1eb5SGreg Roach   * @returns {boolean}
44706fe1eb5SGreg Roach   */
448*2cf1b3d7SGreg Roach  function calSetDateField (dateFieldId, year, month, day) {
449*2cf1b3d7SGreg Roach    let dateField = document.getElementById(dateFieldId);
450*2cf1b3d7SGreg Roach    dateField.value = (day < 10 ? '0' : '') + day + ' ' + monthShort[month + 1] + ' ' + year;
45171239cb6SGreg Roach    return false;
45271239cb6SGreg Roach  }
45371239cb6SGreg Roach
45406fe1eb5SGreg Roach  /**
45506fe1eb5SGreg Roach   * @param {string} dateFieldId
45606fe1eb5SGreg Roach   * @param {string} dateDivId
45706fe1eb5SGreg Roach   * @returns {boolean}
45806fe1eb5SGreg Roach   */
459*2cf1b3d7SGreg Roach  webtrees.calUpdateCalendar = function (dateFieldId, dateDivId) {
460*2cf1b3d7SGreg Roach    let dateSel = document.getElementById(dateFieldId + '_daySelect');
46171239cb6SGreg Roach    if (!dateSel) {
46271239cb6SGreg Roach      return false;
46371239cb6SGreg Roach    }
464*2cf1b3d7SGreg Roach    let monthSel = document.getElementById(dateFieldId + '_monSelect');
46571239cb6SGreg Roach    if (!monthSel) {
46671239cb6SGreg Roach      return false;
46771239cb6SGreg Roach    }
468*2cf1b3d7SGreg Roach    let yearInput = document.getElementById(dateFieldId + '_yearInput');
46971239cb6SGreg Roach    if (!yearInput) {
47071239cb6SGreg Roach      return false;
47171239cb6SGreg Roach    }
47271239cb6SGreg Roach
473*2cf1b3d7SGreg Roach    let month = parseInt(monthSel.options[monthSel.selectedIndex].value, 10);
47471239cb6SGreg Roach    month = month - 1;
47571239cb6SGreg Roach
476*2cf1b3d7SGreg Roach    let date = new Date(yearInput.value, month, dateSel.options[dateSel.selectedIndex].value);
477*2cf1b3d7SGreg Roach    calSetDateField(dateFieldId, date.getFullYear(), date.getMonth(), date.getDate());
47871239cb6SGreg Roach
479*2cf1b3d7SGreg Roach    let dateDiv = document.getElementById(dateDivId);
48071239cb6SGreg Roach    if (!dateDiv) {
48171239cb6SGreg Roach      alert('no dateDiv ' + dateDivId);
48271239cb6SGreg Roach      return false;
48371239cb6SGreg Roach    }
484*2cf1b3d7SGreg Roach    dateDiv.innerHTML = calGenerateSelectorContent(dateFieldId, dateDivId, date);
48571239cb6SGreg Roach
48671239cb6SGreg Roach    return false;
487*2cf1b3d7SGreg Roach  };
48871239cb6SGreg Roach
48906fe1eb5SGreg Roach  /**
49006fe1eb5SGreg Roach   * @param {string} dateFieldId
49106fe1eb5SGreg Roach   * @param {string} dateDivId
49206fe1eb5SGreg Roach   * @param {number} year
49306fe1eb5SGreg Roach   * @param {number} month
49406fe1eb5SGreg Roach   * @param {number} day
49506fe1eb5SGreg Roach   * @returns {boolean}
49606fe1eb5SGreg Roach   */
497*2cf1b3d7SGreg Roach  webtrees.calDateClicked = function (dateFieldId, dateDivId, year, month, day) {
498*2cf1b3d7SGreg Roach    calSetDateField(dateFieldId, year, month, day);
499*2cf1b3d7SGreg Roach    webtrees.calendarWidget(dateDivId, dateFieldId);
50071239cb6SGreg Roach    return false;
501*2cf1b3d7SGreg Roach  };
50271239cb6SGreg Roach
50306fe1eb5SGreg Roach  /**
504*2cf1b3d7SGreg Roach   * Persistent checkbox options to hide/show extra data.
505*2cf1b3d7SGreg Roach   * @param {string} element_id
50671239cb6SGreg Roach   */
507*2cf1b3d7SGreg Roach  webtrees.persistentToggle = function (element_id) {
508efd89170SGreg Roach    const element = document.getElementById(element_id);
509efd89170SGreg Roach    const key = 'state-of-' + element_id;
510efd89170SGreg Roach    const state = localStorage.getItem(key);
51171239cb6SGreg Roach
51264490ee2SGreg Roach    // Previously selected?
51364490ee2SGreg Roach    if (state === 'true') {
514*2cf1b3d7SGreg Roach      element.click();
51571239cb6SGreg Roach    }
51671239cb6SGreg Roach
51764490ee2SGreg Roach    // Remember state for the next page load.
518*2cf1b3d7SGreg Roach    element.addEventListener('change', function () {
519*2cf1b3d7SGreg Roach      localStorage.setItem(key, element.checked);
520*2cf1b3d7SGreg Roach    });
521*2cf1b3d7SGreg Roach  };
52271239cb6SGreg Roach
52306fe1eb5SGreg Roach  /**
524*2cf1b3d7SGreg Roach   * @param {Element} field
52506fe1eb5SGreg Roach   * @param {string} pos
52606fe1eb5SGreg Roach   * @param {string} neg
52706fe1eb5SGreg Roach   */
528*2cf1b3d7SGreg Roach  function reformatLatLong (field, pos, neg) {
52971239cb6SGreg Roach    // valid LATI or LONG according to Gedcom standard
53071239cb6SGreg Roach    // pos (+) : N or E
53171239cb6SGreg Roach    // neg (-) : S or W
532*2cf1b3d7SGreg Roach    let txt = field.value.toUpperCase();
53371239cb6SGreg Roach    txt = txt.replace(/(^\s*)|(\s*$)/g, ''); // trim
53471239cb6SGreg Roach    txt = txt.replace(/ /g, ':'); // N12 34 ==> N12.34
53571239cb6SGreg Roach    txt = txt.replace(/\+/g, ''); // +17.1234 ==> 17.1234
53671239cb6SGreg Roach    txt = txt.replace(/-/g, neg); // -0.5698 ==> W0.5698
53771239cb6SGreg Roach    txt = txt.replace(/,/g, '.'); // 0,5698 ==> 0.5698
53871239cb6SGreg Roach    // 0°34'11 ==> 0:34:11
53971239cb6SGreg Roach    txt = txt.replace(/\u00b0/g, ':'); // °
54071239cb6SGreg Roach    txt = txt.replace(/\u0027/g, ':'); // '
54171239cb6SGreg Roach    // 0:34:11.2W ==> W0.5698
54271239cb6SGreg Roach    txt = txt.replace(/^([0-9]+):([0-9]+):([0-9.]+)(.*)/g, function ($0, $1, $2, $3, $4) {
543*2cf1b3d7SGreg Roach      let n = parseFloat($1);
54471239cb6SGreg Roach      n += ($2 / 60);
54571239cb6SGreg Roach      n += ($3 / 3600);
54671239cb6SGreg Roach      n = Math.round(n * 1E4) / 1E4;
54771239cb6SGreg Roach      return $4 + n;
54871239cb6SGreg Roach    });
54971239cb6SGreg Roach    // 0:34W ==> W0.5667
55071239cb6SGreg Roach    txt = txt.replace(/^([0-9]+):([0-9]+)(.*)/g, function ($0, $1, $2, $3) {
551*2cf1b3d7SGreg Roach      let n = parseFloat($1);
55271239cb6SGreg Roach      n += ($2 / 60);
55371239cb6SGreg Roach      n = Math.round(n * 1E4) / 1E4;
55471239cb6SGreg Roach      return $3 + n;
55571239cb6SGreg Roach    });
55671239cb6SGreg Roach    // 0.5698W ==> W0.5698
557*2cf1b3d7SGreg Roach    txt = txt.replace(/(.*)(NSEW])$/g, '$2$1');
55871239cb6SGreg Roach    // 17.1234 ==> N17.1234
55971239cb6SGreg Roach    if (txt && txt.charAt(0) !== neg && txt.charAt(0) !== pos) {
56071239cb6SGreg Roach      txt = pos + txt;
56171239cb6SGreg Roach    }
56271239cb6SGreg Roach    field.value = txt;
56371239cb6SGreg Roach  }
56471239cb6SGreg Roach
56506fe1eb5SGreg Roach  /**
566*2cf1b3d7SGreg Roach   * @param {Element} field
567*2cf1b3d7SGreg Roach   */
568*2cf1b3d7SGreg Roach  webtrees.reformatLatitude = function (field) {
569*2cf1b3d7SGreg Roach    return reformatLatLong(field, 'N', 'S');
570*2cf1b3d7SGreg Roach  };
571*2cf1b3d7SGreg Roach
572*2cf1b3d7SGreg Roach  /**
573*2cf1b3d7SGreg Roach   * @param {Element} field
574*2cf1b3d7SGreg Roach   */
575*2cf1b3d7SGreg Roach  webtrees.reformatLongitude = function (field) {
576*2cf1b3d7SGreg Roach    return reformatLatLong(field, 'E', 'W');
577*2cf1b3d7SGreg Roach  };
578*2cf1b3d7SGreg Roach
579*2cf1b3d7SGreg Roach  /**
58006fe1eb5SGreg Roach   * Initialize autocomplete elements.
58106fe1eb5SGreg Roach   * @param {string} selector
58206fe1eb5SGreg Roach   */
583*2cf1b3d7SGreg Roach  webtrees.autocomplete = function (selector) {
58471239cb6SGreg Roach    // Use typeahead/bloodhound for autocomplete
58571239cb6SGreg Roach    $(selector).each(function () {
586efd89170SGreg Roach      const that = this;
58771239cb6SGreg Roach      $(this).typeahead(null, {
58871239cb6SGreg Roach        display: 'value',
5895e6816beSGreg Roach        limit: 0,
59071239cb6SGreg Roach        source: new Bloodhound({
59171239cb6SGreg Roach          datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
59271239cb6SGreg Roach          queryTokenizer: Bloodhound.tokenizers.whitespace,
59371239cb6SGreg Roach          remote: {
59471239cb6SGreg Roach            url: this.dataset.autocompleteUrl,
595f4abaf12SGreg Roach            replace: function (url, uriEncodedQuery) {
596f4abaf12SGreg Roach              if (that.dataset.autocompleteExtra) {
597efd89170SGreg Roach                const extra = $(document.querySelector(that.dataset.autocompleteExtra)).val();
598efd89170SGreg Roach                return url.replace('QUERY', uriEncodedQuery) + '&extra=' + encodeURIComponent(extra);
599f4abaf12SGreg Roach              }
600efd89170SGreg Roach              return url.replace('QUERY', uriEncodedQuery);
601f4abaf12SGreg Roach            },
602efd89170SGreg Roach            wildcard: 'QUERY'
60371239cb6SGreg Roach          }
60471239cb6SGreg Roach        })
60571239cb6SGreg Roach      });
60671239cb6SGreg Roach    });
607*2cf1b3d7SGreg Roach  };
608*2cf1b3d7SGreg Roach}(window.webtrees = window.webtrees || {}));
60971239cb6SGreg Roach
61071239cb6SGreg Roach/**
611*2cf1b3d7SGreg Roach * Initialisation
61271239cb6SGreg Roach */
613*2cf1b3d7SGreg Roach$(function () {
61471239cb6SGreg Roach  // Send the CSRF token on all AJAX requests
61571239cb6SGreg Roach  $.ajaxSetup({
61671239cb6SGreg Roach    headers: {
61771239cb6SGreg Roach      'X-CSRF-TOKEN': $('meta[name=csrf]').attr('content')
61871239cb6SGreg Roach    }
61971239cb6SGreg Roach  });
62071239cb6SGreg Roach
621*2cf1b3d7SGreg Roach  // Page elements that load automatically via AJAX.
62271239cb6SGreg Roach  // This prevents bad robots from crawling resource-intensive pages.
623efd89170SGreg Roach  $('[data-ajax-url]').each(function () {
62471239cb6SGreg Roach    $(this).load($(this).data('ajaxUrl'));
62571239cb6SGreg Roach  });
62671239cb6SGreg Roach
62771239cb6SGreg Roach  // Autocomplete
628*2cf1b3d7SGreg Roach  webtrees.autocomplete('input[data-autocomplete-url]');
62971239cb6SGreg Roach
63071239cb6SGreg Roach  // Select2 - activate autocomplete fields
631bdbdb10cSGreg Roach  const lang = document.documentElement.lang;
632bdbdb10cSGreg Roach  const select2_languages = {
633bdbdb10cSGreg Roach    'zh-Hans': 'zh-CN',
634efd89170SGreg Roach    'zh-Hant': 'zh-TW'
635bdbdb10cSGreg Roach  };
636efd89170SGreg Roach  $('select.select2').select2({
637bdbdb10cSGreg Roach    language: select2_languages[lang] || lang,
638aee7167dSGreg Roach    // Needed for elements that are initially hidden.
639efd89170SGreg Roach    width: '100%',
640896a5721SGreg Roach    // Do not escape - we do it on the server.
64171239cb6SGreg Roach    escapeMarkup: function (x) {
6428ec20abdSGreg Roach      return x;
643efd89170SGreg Roach    }
644896a5721SGreg Roach  });
645896a5721SGreg Roach
646896a5721SGreg Roach  // If we clear the select (using the "X" button), we need an empty value
647896a5721SGreg Roach  // (rather than no value at all) for (non-multiple) selects with name="array[]"
648efd89170SGreg Roach  $('select.select2:not([multiple])')
649efd89170SGreg Roach    .on('select2:unselect', function (evt) {
650efd89170SGreg Roach      $(evt.delegateTarget).html('<option value="" selected></option>');
651bdbdb10cSGreg Roach    });
65271239cb6SGreg Roach
65371239cb6SGreg Roach  // Datatables - locale aware sorting
65471239cb6SGreg Roach  $.fn.dataTableExt.oSort['text-asc'] = function (x, y) {
655efd89170SGreg Roach    return x.localeCompare(y, document.documentElement.lang, { sensitivity: 'base' });
65671239cb6SGreg Roach  };
65771239cb6SGreg Roach  $.fn.dataTableExt.oSort['text-desc'] = function (x, y) {
658efd89170SGreg Roach    return y.localeCompare(x, document.documentElement.lang, { sensitivity: 'base' });
65971239cb6SGreg Roach  };
66071239cb6SGreg Roach
66171239cb6SGreg Roach  // DataTables - start hidden to prevent FOUC.
66271239cb6SGreg Roach  $('table.datatables').each(function () {
6634843b94fSGreg Roach    $(this).DataTable();
6644843b94fSGreg Roach    $(this).removeClass('d-none');
6654843b94fSGreg Roach  });
6664843b94fSGreg Roach
6674843b94fSGreg Roach  // Save button state between pages
668efd89170SGreg Roach  document.querySelectorAll('[data-toggle=button][data-persist]').forEach((element) => {
6694843b94fSGreg Roach    // Previously selected?
670efd89170SGreg Roach    if (localStorage.getItem('state-of-' + element.dataset.persist) === 'T') {
6714843b94fSGreg Roach      element.click();
6724843b94fSGreg Roach    }
6734843b94fSGreg Roach    // Save state on change
674efd89170SGreg Roach    element.addEventListener('click', (event) => {
6754843b94fSGreg Roach      // Event occurs *before* the state changes, so reverse T/F.
676efd89170SGreg Roach      localStorage.setItem('state-of-' + event.target.dataset.persist, event.target.classList.contains('active') ? 'F' : 'T');
6774843b94fSGreg Roach    });
6784843b94fSGreg Roach  });
67971239cb6SGreg Roach
68071239cb6SGreg Roach  // Activate the on-screen keyboard
681*2cf1b3d7SGreg Roach  let osk_focus_element;
68271239cb6SGreg Roach  $('.wt-osk-trigger').click(function () {
68371239cb6SGreg Roach    // When a user clicks the icon, set focus to the corresponding input
68471239cb6SGreg Roach    osk_focus_element = document.getElementById($(this).data('id'));
68571239cb6SGreg Roach    osk_focus_element.focus();
68671239cb6SGreg Roach    $('.wt-osk').show();
68771239cb6SGreg Roach  });
68871239cb6SGreg Roach  $('.wt-osk-script-button').change(function () {
68971239cb6SGreg Roach    $('.wt-osk-script').prop('hidden', true);
69071239cb6SGreg Roach    $('.wt-osk-script-' + $(this).data('script')).prop('hidden', false);
69171239cb6SGreg Roach  });
69271239cb6SGreg Roach  $('.wt-osk-shift-button').click(function () {
69371239cb6SGreg Roach    document.querySelector('.wt-osk-keys').classList.toggle('shifted');
69471239cb6SGreg Roach  });
69571239cb6SGreg Roach  $('.wt-osk-keys').on('click', '.wt-osk-key', function () {
696*2cf1b3d7SGreg Roach    let key = $(this).contents().get(0).nodeValue;
697*2cf1b3d7SGreg Roach    let shift_state = $('.wt-osk-shift-button').hasClass('active');
698*2cf1b3d7SGreg Roach    let shift_key = $('sup', this)[0];
69971239cb6SGreg Roach    if (shift_state && shift_key !== undefined) {
70071239cb6SGreg Roach      key = shift_key.innerText;
70171239cb6SGreg Roach    }
7020d2905f7SGreg Roach    webtrees.pasteAtCursor(osk_focus_element, key);
70371239cb6SGreg Roach    if ($('.wt-osk-pin-button').hasClass('active') === false) {
70471239cb6SGreg Roach      $('.wt-osk').hide();
70571239cb6SGreg Roach    }
70671239cb6SGreg Roach  });
70771239cb6SGreg Roach
70871239cb6SGreg Roach  $('.wt-osk-close').on('click', function () {
70971239cb6SGreg Roach    $('.wt-osk').hide();
71071239cb6SGreg Roach  });
71171239cb6SGreg Roach});
7127adfb8e5SGreg Roach
713b3a775f6SGreg Roach// Convert data-confirm and data-post-url attributes into useful behavior.
714efd89170SGreg Roachdocument.addEventListener('click', (event) => {
715a7a3d6dbSGreg Roach  const target = event.target.closest('a,button');
7167adfb8e5SGreg Roach
7177adfb8e5SGreg Roach  if (target === null) {
7187adfb8e5SGreg Roach    return;
7197adfb8e5SGreg Roach  }
7207adfb8e5SGreg Roach
721efd89170SGreg Roach  if ('confirm' in target.dataset && !confirm(target.dataset.confirm)) {
7227adfb8e5SGreg Roach    event.preventDefault();
7237adfb8e5SGreg Roach    return;
7247adfb8e5SGreg Roach  }
7257adfb8e5SGreg Roach
726efd89170SGreg Roach  if ('postUrl' in target.dataset) {
727efd89170SGreg Roach    const token = document.querySelector('meta[name=csrf]').content;
728ea101122SGreg Roach
729ea101122SGreg Roach    fetch(target.dataset.postUrl, {
730ea101122SGreg Roach      method: 'POST',
731ea101122SGreg Roach      headers: {
732ea101122SGreg Roach        'X-CSRF-TOKEN': token,
733ea101122SGreg Roach        'X-Requested-with': 'XMLHttpRequest',
734ea101122SGreg Roach      },
735*2cf1b3d7SGreg Roach    }).then(() => {
736ea101122SGreg Roach      if ('reloadUrl' in target.dataset) {
737ea101122SGreg Roach        // Go somewhere else.  e.g. home page after logout.
738ea101122SGreg Roach        document.location = target.dataset.reloadUrl;
739ea101122SGreg Roach      } else {
740ea101122SGreg Roach        // Reload the current page. e.g. change language.
7417adfb8e5SGreg Roach        document.location.reload();
7427adfb8e5SGreg Roach      }
743cb8f307bSGreg Roach    }).catch((error) => {
744ea101122SGreg Roach      alert(error);
745ea101122SGreg Roach    });
7467adfb8e5SGreg Roach  }
7477adfb8e5SGreg Roach});
748