Naar inhoud springen

Gebruiker:Lakkie0/subjects age from year.js

Uit Wikipedia, de vrije encyclopedie

Opmerking: na het publiceren is het wellicht nodig uw browsercache te legen om de veranderingen te zien.

  • Firefox / Safari: houd Shift ingedrukt terwijl u:je op Vernieuwen klikt of druk op Ctrl-F5 of Ctrl-R (⌘-Shift-R op een Mac)
  • Google Chrome: druk op Ctrl-Shift-R (⌘-Shift-R op een Mac)
  • Edge: houd Ctrl ingedrukt terwijl u:je op Vernieuwen klikt of druk op Ctrl-F5.
/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * DS202: Simplify dynamic range loops
 * DS205: Consider reworking code to avoid use of IIFEs
 * DS206: Consider reworking classes to avoid initClass
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
/*
  SUBJECT AGE FROM YEAR
  Description: In an article about a person or a company, when the mouse hovers
  over a year in the article, the age of the article's subject by that year
  appears in a tooltip.
*/
var SubjectAgeFromYear = (function() {
    let now = undefined;
    SubjectAgeFromYear = class SubjectAgeFromYear {
      static initClass() {
        now = new Date();
      }
  
      static extractYearFromText({
        yearIndex,
        patternIndex,
        $newNode,
        nodeText,
        subjectYear,
        years,
      }) {
        let $abbr;
        const abbrText = years[yearIndex];
        let currentYear = years[yearIndex];
        const birthYearIndex = nodeText.indexOf(currentYear);
        let workThisYear = true;
  
        // don't work on this year-for AD years
        if (
          patternIndex === 0 &&
          // 'year' is followed by a ' BC'; wait for next pattern to work on this
          (nodeText.substr(birthYearIndex + currentYear.length, 3).indexOf('BC') >
            -1 ||
            // 'year' is preceded by a ','; this is probably a unit such as 1,000 km
            nodeText.substr(birthYearIndex - 1, 1).indexOf(',') > -1 ||
            // 'year' is preceded by a month; this is probably part of a day,
            // like "January 1"
            ((currentYear.length <= 2 &&
              (this.nearAMonth(nodeText, birthYearIndex, -1, years, yearIndex) &&
                currentYear.indexOf('AD') === -1)) ||
              // 'year' is followed by a month; this is probably part of a day,
              // like "January 1"
              this.nearAMonth(
                nodeText,
                birthYearIndex + currentYear.length,
                1
              )) ||
            // 'year' is followed by "?year", such as "-year", " years"
            nodeText
              .substr(birthYearIndex + currentYear.length, 5)
              .indexOf('year') > -1)
        ) {
          workThisYear = false;
        }
  
        // After the following conditionals, currentYear will be converted from a
        // STRING (which possibly holds BC/AD) to an INTEGER
        // currentYear contains "BC" somewhere
        currentYear =
          currentYear.indexOf('BC') > -1 ||
          ((subjectYear.birthYear() < 0 || subjectYear.deathYear() < 0) &&
            nodeText
              .substr(birthYearIndex + currentYear.length + ' BC'.length, 10)
              .indexOf('BC') > -1)
            ? -1 * parseInt(currentYear)
            : // currentYear contains "AD" somewhere
              currentYear.indexOf('AD') > -1 || currentYear.indexOf('CE') > -1
              ? parseInt(currentYear.replace(/AD/, '').replace(/CE/, ''))
              : // currentYear does not contain "BC" or "AD"
                parseInt(currentYear);
  
        const firstPart = nodeText.substring(0, birthYearIndex);
  
        // Subtract one year from difference if it spans year zero
        const difference =
          (subjectYear.birthYear() < 0 && 0 < currentYear) ||
          (subjectYear.birthYear() > 0 && 0 > currentYear)
            ? currentYear - subjectYear.birthYear() - 1
            : currentYear - subjectYear.birthYear();
  
        // find a year to act on; work on AD years first, then BC years
        const condition =
          workThisYear &&
          (currentYear >= subjectYear.birthYear() ||
            currentYear >=
              subjectYear.birthYear() - subjectYear.birthYearBuffer()) &&
          (currentYear <= subjectYear.deathYear() ||
            currentYear <=
              subjectYear.deathYear() + subjectYear.birthYearBuffer());
  
        //#
        // Create the hover with an ABBR tag.
        if (condition) {
          $abbr = $('<abbr class="subject-age-from-year"></abbr>');
  
          const currentYearYearsAgo = now.getFullYear() - currentYear;
          const currentYearYearsAgoText =
            currentYearYearsAgo > 0
              ? `${this.pluralize('year', currentYearYearsAgo, true)} ago`
              : currentYearYearsAgo < 0
                ? `${this.pluralize('year', currentYearYearsAgo, true)} from now`
                : 'this year';
  
          // after death year but before the buffer
          if (
            currentYear > subjectYear.deathYear() &&
            currentYear <= subjectYear.deathYear() + subjectYear.birthYearBuffer()
          ) {
            const yearsLater = currentYear - subjectYear.deathYear();
            $abbr.attr(
              'title',
              `${this.pluralize('year', yearsLater, true)} after \
  ${subjectYear.phrase('death')}`
            );
            // was alive at currentYear
          } else if (difference >= 0) {
            // age at currentYear
            $abbr.attr(
              'title',
              `${this.pluralize('year', difference, true)} old`
            );
  
            // birth year
            if (difference === 0) {
              const currentAge =
                subjectYear.type() === 'biography' && subjectYear.isAlive()
                  ? `; now ${now.getFullYear() -
                      subjectYear.birthYear()} years old`
                  : '';
  
              // Add the person's current age.
              $abbr.attr(
                'title',
                `${$abbr.attr('title')} \
  (${subjectYear.phrase('birth')}${currentAge})`
              );
              // death year
            } else if (currentYear === subjectYear.deathYear()) {
              $abbr.attr(
                'title',
                `${$abbr.attr('title')} \
  (${subjectYear.phrase('death')})`
              );
            }
            // currentYear is before birth year
          } else {
            const absoluteDifference = Math.abs(difference);
            $abbr.attr(
              'title',
              `${this.pluralize('year', absoluteDifference, true)} \
  before ${subjectYear.phrase('birth')}`
            );
          }
  
          // Add a note indicating how far away from now is the year.
          if ($abbr.attr('title').indexOf(' now ') === -1) {
            $abbr.attr(
              'title',
              `${$abbr.attr('title')} \
  (${currentYearYearsAgoText})`
            );
          }
          // Add the existing number from the page's text as the ABBR's text.
          $abbr.append(abbrText);
        } else {
          $abbr = '';
        }
  
        // Append the new ABBR if we found a year we could work with; otherwise,
        // just add the old text content back in.
        $newNode.append(firstPart).append($abbr.length ? $abbr : abbrText);
  
        // after the year, only for the last occurrence of a year in a node
        if (yearIndex + 1 === years.length) {
          const secondPart = nodeText.substring(birthYearIndex + abbrText.length);
          $newNode.append(secondPart);
        }
  
        // This is used for when the loop rolls around again.
        nodeText = nodeText.substring(birthYearIndex + abbrText.length);
  
        return {
          yearIndex,
          patternIndex,
          $newNode,
          nodeText,
          subjectYear,
          years,
        };
      }
  
      static findYearsInText({
        patternIndex,
        $node,
        patterns,
        spansToRemove,
        subjectYear,
      }) {
        if ($node[0].nodeType !== 3) {
          return true;
        }
  
        let nodeText = $node[0].nodeValue;
        let years = nodeText.match(patterns[patternIndex]);
  
        if (years == null) {
          return true;
        }
  
        const minBirthYearBuffer = 100;
        const age = subjectYear.deathYear() - subjectYear.birthYear();
  
        subjectYear.birthYearBuffer(
          age >= minBirthYearBuffer && subjectYear.type() === 'biography'
            ? age
            : minBirthYearBuffer
        );
  
        let $newNode = $('<span></span>');
  
        // loop through each year in the same text node
        for (
          let i = 0, yearIndex = i, end = years.length, asc = 0 <= end;
          asc ? i < end : i > end;
          asc ? i++ : i--, yearIndex = i
        ) {
          ({
            yearIndex,
            patternIndex,
            $newNode,
            nodeText,
            subjectYear,
            years,
          } = this.extractYearFromText({
            yearIndex,
            patternIndex,
            $newNode,
            nodeText,
            subjectYear,
            years,
          }));
        }
  
        if ($newNode.contents().length > 0) {
          $node.replaceWith($newNode);
          return spansToRemove.push($newNode);
        }
      }
  
      static findMatchesinCategory({
        allBirthYears,
        allDeathYears,
        birthYear,
        deathYear,
        matches,
        type,
      }) {
        // Set ordered match results to actual variable names.
        let categoryYear = matches[0];
        const categoryType = matches[1];
  
        // Set the category's year to be negative if it's a BC year.
        categoryYear =
          categoryYear.indexOf('BC') > -1
            ? -1 * parseInt(categoryYear)
            : parseInt(categoryYear);
  
        // If type hasn't already been set to "biography", then check to see if it
        // should. "Biography" type takes precendence over "establishment" type. We
        // have to check for every category if it indicates that the type is actually
        // a biography.
        if (type !== 'biography') {
          type = (categoryType != null
          ? categoryType.match(/(births|deaths)/)
          : undefined)
            ? 'biography'
            : 'establishment';
        }
  
        // Birth years
        if (
          !(categoryType != null
            ? categoryType.match(/(disestablishments|deaths|disestablished)/)
            : undefined) &&
          ((type === 'biography' && categoryType === 'births') ||
            type !== 'biography')
        ) {
          birthYear = categoryYear;
          allBirthYears.push(birthYear);
          // Death years
        } else {
          // Only continue if type is "biography" and category is a "death year", or
          // type is "establishment".
          if (
            (type === 'biography' && categoryType === 'deaths') ||
            type === 'establishment'
          ) {
            deathYear = categoryYear;
            allDeathYears.push(deathYear);
          }
        }
  
        return {
          allBirthYears,
          allDeathYears,
          birthYear,
          deathYear,
          matches,
          type,
        };
      }
  
      static findYearFromCategory({
        allBirthYears,
        allDeathYears,
        allMatches,
        birthYear,
        category,
        deathYear,
        type,
      }) {
        // Format: [pattern<RegExp>, order<Array>].
        // The order should always be: [<year>, <type>].
        const patterns = [
          // Special cases: a four-digit year, followed by a capitalized term
          //   E.g. 1980 Oscar winners
          [/^([0-9]{4,4})\s([\w\s]+)$/, [1, 2]],
          // E.g. 950 BC
          [/^([0-9]{1,4}(\sBC)?)$/, [1]],
          // Match a year at the start, with optionally the word "BC" at the end.
          //   E.g. 123 BC births; 1950 establishments
          [/^([0-9]{1,4}(\sBC)?)\s([A-Za-z\s]+)$/, [1, 3]],
          // E.g. Establishments in 1925
          [/^(.*?)\s(in|for)\s([0-9]{1,4}(\sBC)?)$/, [3, 1]],
        ];
  
        // Match the patterns to the category.
        let matches = [];
  
        for (let pattern of Array.from(patterns)) {
          const matched = category.match(pattern[0]);
  
          if (matched) {
            for (let order of Array.from(pattern[1])) {
              matches.push(matched[order]);
            }
  
            break;
          }
        }
  
        // There is a match
        if (matches.length > 0) {
          allMatches.push(category);
  
          ({
            allBirthYears,
            allDeathYears,
            birthYear,
            deathYear,
            matches,
            type,
          } = this.findMatchesinCategory({
            allBirthYears,
            allDeathYears,
            birthYear,
            deathYear,
            matches,
            type,
          }));
        }
  
        return {
          allBirthYears,
          allDeathYears,
          allMatches,
          birthYear,
          category,
          deathYear,
          type,
        };
      }
  
      static findYearsFromCategories() {
        let birthYear, deathYear, type;
        let category;
        let allBirthYears = [];
        let allDeathYears = [];
        let allMatches = [];
  
        const categories = (() => {
          const result = [];
          for (category of Array.from(window.mw.config.get('wgCategories'))) {
            result.push(category.replace(/_/g, ' '));
          }
          return result;
        })();
  
        for (category of Array.from(categories)) {
          ({
            allBirthYears,
            allDeathYears,
            allMatches,
            birthYear,
            category,
            deathYear,
            type,
          } = this.findYearFromCategory({
            allBirthYears,
            allDeathYears,
            allMatches,
            birthYear,
            category,
            deathYear,
            type,
          }));
        }
  
        // Show which category was matched for birth/death dates. Use a special
        // object for this so I can set defaults without changing the original
        // variable.
        const catText = { type, birthYear, deathYear, allMatches };
  
        if (!catText['type']) {
          catText['type'] = 'establishment';
        }
  
        if (!catText['birthYear']) {
          catText['birthYear'] = '(none)';
        }
  
        if (!catText['deathYear']) {
          catText['deathYear'] = '(none)';
        }
  
        if (!catText['allMatches']) {
          catText['allMatches'] = '(none)';
        }
  
        catText.allMatches = catText.allMatches.map((value) => `- ${value}`);
  
        $('#catlinks').attr(
          'title',
          `Type: ${catText.type}\nBirth year: \
  ${catText.birthYear}\nDeath year: ${catText.deathYear}\n\nMatched \
  categories:\n\n${catText.allMatches.join('\n')}`
        );
  
        return { allBirthYears, allDeathYears, birthYear, deathYear, type };
      }
  
      static init() {
        const wgCNamespace = window.mw.config.get('wgCanonicalNamespace');
        const wgAction = window.mw.config.get('wgAction');
        const wgPageName = window.mw.config.get('wgPageName');
  
        if (
          (wgCNamespace !== '' ||
            window.mw.util.getParamValue('disable') === 'age' ||
            wgAction !== 'view') &&
          !(
            wgPageName === 'User:Gary/Sandbox' &&
            (wgAction === 'view' || wgAction === 'submit')
          )
        ) {
          return false;
        }
  
        // Check if there are any categories.
        if (window.mw.config.get('wgCategories') === null) {
          return false;
        }
  
        let {
          allBirthYears,
          allDeathYears,
          birthYear,
          deathYear,
          type,
        } = this.findYearsFromCategories();
  
        // We can't continue without a birth year
        if (birthYear == null) {
          return false;
        }
  
        // Sort birth years. They will be sorted again, with some removed, later as
        // well.
        allBirthYears.sort(function(a, b) {
          if (a < b) {
            return -1;
          } else if (a > b) {
            return 1;
          } else {
            return 0;
          }
        });
  
        // Do death year first, so we can ensure the birth year comes before the
        // death year
        //
        // Return the death year that is closest to today's year, without going past
        // it
        if (allDeathYears.length > 1) {
          allDeathYears.sort(function(a, b) {
            const aYearsAgo = now.getFullYear() - a;
            const bYearsAgo = now.getFullYear() - b;
  
            if (aYearsAgo < 0) {
              return 1;
            } else if (bYearsAgo < 0) {
              return -1;
            } else {
              return aYearsAgo - bYearsAgo;
            }
          });
  
          deathYear = allDeathYears[0];
          // There are no death years, but there are at least two birth years, so one
          // of them could possibly be a death year. Do this only for BC years because
          // they are particularly problematic, since they only use categories like:
          // "15 BC" and then "10s BC deaths".
        } else if (
          allDeathYears.length === 0 &&
          allBirthYears.length >= 2 &&
          allBirthYears[0] < 0 &&
          allBirthYears[1] < 0
        ) {
          // Set the birth year as the first year.
          birthYear = allBirthYears[0];
  
          // Remove the second birth year and set it as the death year.
          deathYear = allBirthYears.splice(1, 1)[0];
  
          // Set the type as a biography, because we got at least two years that
          // are BC.
          type = 'biography';
        }
  
        // Do birth years
        //
        // Return a birth year that is before the death year, and also closest
        // to today's year.
        if (allBirthYears.length > 1) {
          allBirthYears.sort(function(a, b) {
            if (deathYear != null) {
              const aDeathDiff = deathYear - a;
              const bDeathDiff = deathYear - b;
  
              if (aDeathDiff < 0) {
                return 1;
              } else if (bDeathDiff < 0) {
                return -1;
              } else {
                return aDeathDiff - bDeathDiff;
              }
            } else {
              const aYearsAgo = now.getFullYear() - a;
              const bYearsAgo = now.getFullYear() - b;
  
              if (aYearsAgo < 0) {
                return 1;
              } else if (bYearsAgo < 0) {
                return -1;
              } else {
                return aYearsAgo - bYearsAgo;
              }
            }
          });
  
          birthYear = allBirthYears[0];
        }
  
        // "isAlive" is only used for people, not establishments
        const subjectYear = new SubjectYear();
        subjectYear.type(type);
        subjectYear.isAlive(false);
  
        // The maximum possible age for each type.
        const maxPossibleAge = (() => {
          if (subjectYear.type() === 'biography') {
            return 125;
          } else if (subjectYear.type() === 'establishment') {
            return 1000;
          }
        })();
  
        // No death year is available, so logically determine if the person
        // could possibly be alive right now
        if (deathYear == null) {
          deathYear = birthYear + maxPossibleAge;
  
          if (deathYear >= now.getFullYear()) {
            subjectYear.isAlive(true);
          }
        }
  
        const spansToRemove = [];
        const patterns = [];
        const birthYearLength = Math.abs(birthYear).toString().length;
        const deathYearLength = Math.abs(deathYear).toString().length;
        const todayLength = now.getFullYear().toString().length;
  
        const yearLength =
          birthYear < 0 && deathYear > 0
            ? 1
            : birthYearLength < deathYearLength
              ? birthYearLength
              : deathYearLength;
  
        patterns.push(
          new RegExp(
            `(AD |AD\u00A0)?\\b[0-9]{${yearLength},` +
              todayLength +
              '}\\b( AD|\u00A0AD| CE|\u00A0CE)?',
            'g'
          )
        ); // AD years
  
        if (birthYear < 0) {
          // BC years
          patterns.push(
            new RegExp(
              `\\b[0-9]{${yearLength},${todayLength}` + '}( |\u00A0)?BC[E]?\\b',
              'g'
            )
          );
        }
  
        const $allParagraphs = $(
          wgAction === 'submit' ? '#wikiPreview' : '#bodyContent'
        ).find('> div > p, > div > div > p');
  
        // Set the subject's birth and death years
        subjectYear.birthYear(birthYear);
        subjectYear.deathYear(deathYear);
  
        // loop through each pattern to find
        return (() => {
          const result = [];
          for (
            var patternIndex = 0, end = patterns.length, asc = 0 <= end;
            asc ? patternIndex < end : patternIndex > end;
            asc ? patternIndex++ : patternIndex--
          ) {
            // loop through each paragraph
            // then loop through each text node in each paragraph
            $allParagraphs.each((index, element) => {
              return $(element)
                .contents()
                .each((index, element) => {
                  return this.findYearsInText({
                    patternIndex,
                    $node: $(element),
                    patterns,
                    spansToRemove,
                    subjectYear,
                  });
                });
            });
  
            // remove SPANs from spansToRemove, and merge children with parent
            result.push(
              (() => {
                const result1 = [];
                for (var span of Array.from(spansToRemove)) {
                  const children = span.contents();
                  const parent = span.parent();
  
                  if (!parent.length) {
                    continue;
                  }
  
                  children.each(function(index, element) {
                    const $child = $(element);
                    return span.before($child.clone());
                  });
  
                  span.remove();
                  result1.push(parent[0].normalize());
                }
                return result1;
              })()
            );
          }
          return result;
        })();
      }
  
      static nearAMonth(text, startIndex, beforeOrAfter, years, yearIndex) {
        let match;
        if (beforeOrAfter == null) {
          beforeOrAfter = 1;
        }
        const monthsArray = [
          'January',
          'February',
          'March',
          'April',
          'May',
          'June',
          'July',
          'August',
          'September',
          'October',
          'November',
          'December',
        ];
        const pattern = new RegExp(monthsArray.join('|'));
  
        if (beforeOrAfter === 1) {
          // find the word immediately following the startIndex
          text = text.substring(startIndex, text.length);
          match = text.match(pattern);
  
          // is this match only a few characters ahead of startIndex?
          if (match && text.indexOf(match[0]) === ' '.length) {
            return true;
          } else {
            return false;
          }
        } else if (beforeOrAfter === -1) {
          // first check if after the current year,
          // there is NO ", nextYearIteration"
          if (
            years[yearIndex + 1] &&
            startIndex + years[yearIndex].length + ', '.length !==
              text.indexOf(years[yearIndex + 1])
          ) {
            return false;
          }
  
          text = text.substring(0, startIndex);
          match = text.match(pattern);
  
          if (
            match &&
            text.indexOf(match[0]) === startIndex - ' '.length - match[0].length
          ) {
            return true;
          } else {
            return false;
          }
        }
      }
  
      static pluralize(word, count, includeCount) {
        if (includeCount == null) {
          includeCount = false;
        }
        const includedCount = includeCount ? `${count} ` : '';
  
        if (count === 1) {
          return includedCount + word;
        } else {
          return includedCount + word + 's';
        }
      }
    };
    SubjectAgeFromYear.initClass();
    return SubjectAgeFromYear;
  })();
  
  class SubjectYear {
    birthYear(birthYearValue) {
      if (birthYearValue == null) {
        ({ birthYearValue } = this);
      }
      this.birthYearValue = birthYearValue;
      return this.birthYearValue;
    }
    birthYearBuffer(birthYearBufferValue) {
      if (birthYearBufferValue == null) {
        ({ birthYearBufferValue } = this);
      }
      this.birthYearBufferValue = birthYearBufferValue;
      return this.birthYearBufferValue;
    }
    deathYear(deathYearValue) {
      if (deathYearValue == null) {
        ({ deathYearValue } = this);
      }
      this.deathYearValue = deathYearValue;
      return this.deathYearValue;
    }
    isAlive(isAliveValue) {
      if (isAliveValue == null) {
        ({ isAliveValue } = this);
      }
      this.isAliveValue = isAliveValue;
      return this.isAliveValue;
    }
  
    phrase(phrase) {
      phrase = phrase.toLowerCase();
      const phrases = {
        biography: {
          birth: 'birth',
          death: 'death',
  
          alive: 'alive',
          dead: 'dead',
        },
        establishment: {
          birth: 'established',
          death: 'disestablished',
  
          alive: 'established',
          dead: 'disestablished',
        },
      };
  
      if (
        this.typeValue == null ||
        phrases[this.typeValue] == null ||
        phrases[this.typeValue][phrase] == null
      ) {
        return false;
      }
  
      return phrases[this.typeValue][phrase];
    }
  
    type(typeValue) {
      if (typeValue == null) {
        ({ typeValue } = this);
      }
      this.typeValue = typeValue;
      return (this.typeValue = this.typeValue.toLowerCase());
    }
  }
  
  $(() => SubjectAgeFromYear.init());