Pengguna:William Surya Permana/comments in local time.js

Dari Wikipedia bahasa Indonesia, ensiklopedia bebas

Catatan: Setelah menyimpan, Anda harus memintas tembolok (cache) peramban Anda untuk melihat perubahannya. Google Chrome, Firefox, Microsoft Edge dan Safari: Tahan tombol Shift dan klik Muat ulang (Reload) di tombol bilah alat. Untuk detail dan instruksi tentang peramban lain, lihat halaman menghapus singgahan (Inggris).

/**
 * COMMENTS IN LOCAL TIME
 *
 * Description:
 * Changes [[Coordinated Universal Time|UTC]]-based times and dates,
 * such as those used in signatures, to be relative to local time.
 *
 * Documentation:
 * [[Wikipedia:Comments in Local Time]]
 */
$(() => {
  /**
   * Given a number, add a leading zero if necessary, so that the final number
   * has two characters.
   *
   * @param {number} number Number
   * @returns {string} The number with a leading zero, if necessary.
   */
  function addLeadingZero(number) {
    const numberArg = number;

    if (numberArg < 10) {
      return `0${numberArg}`;
    }

    return numberArg;
  }

  function convertMonthToNumber(month) {
    return new Date(`${month} 1, 2001`).getMonth();
  }

  function getDates(time) {
    const [, oldDay, oldMonth, oldYear, oldHour, oldMinute] = time;

    // Today
    const today = new Date();

    // Yesterday
    const yesterday = new Date();

    yesterday.setDate(yesterday.getDate() - 1);

    // Tomorrow
    const tomorrow = new Date();

    tomorrow.setDate(tomorrow.getDate() + 1);

    // Set the date entered.
    const newTime = new Date();

    newTime.setUTCFullYear(oldYear, convertMonthToNumber(oldMonth), oldDay);
    newTime.setUTCHours(oldHour);
    newTime.setUTCMinutes(oldMinute);

    return { time: newTime, today, tomorrow, yesterday };
  }

  /**
   * Determine whether to use the singular or plural word, and use that.
   *
   * @param {string} term Original term
   * @param {number} count Count of items
   * @param {string} plural Pluralized term
   * @returns {string} The word to use
   */
  function pluralize(term, count, plural = null) {
    let pluralArg = plural;

    // No unique pluralized word is found, so just use a general one.
    if (!pluralArg) {
      pluralArg = `${term}s`;
    }

    // There's only one item, so just use the singular word.
    if (count === 1) {
      return term;
    }

    // There are multiple items, so use the plural word.
    return pluralArg;
  }

  class CommentsInLocalTime {
    constructor() {
      this.language = '';
      this.LocalComments = {};

      /**
       * Settings
       */
      this.settings();

      this.language = this.setDefaultSetting(
        'language',
        this.LocalComments.language
      );

      // These values are also reflected in the documentation:
      // https://en.wiki-indonesia.club/wiki/Wikipedia:Comments_in_Local_Time#Default_settings
      this.setDefaultSetting({
        dateDifference: true,
        dateFormat: 'dmy',
        dayOfWeek: true,
        dropDays: 0,
        dropMonths: 0,
        timeFirst: false,
        twentyFourHours: false,
      });
    }

    adjustTime(originalTimestamp, search) {
      const { time, today, tomorrow, yesterday } = getDates(
        originalTimestamp.match(search)
      );

      // A string matching the date pattern was found, but it cannot be
      // converted to a Date object. Return it with no changes made.
      if (Number.isNaN(time)) {
        return [originalTimestamp, ''];
      }

      const date = this.determineDateText({
        time,
        today,
        tomorrow,
        yesterday,
      });

      const { ampm, hour } = this.getHour(time);
      const minute = addLeadingZero(time.getMinutes());
      const finalTime = `${hour}:${minute}${ampm}`;
      let returnDate;

      // Determine the time offset.
      const utcValue = (-1 * time.getTimezoneOffset()) / 60;
      const utcOffset =
        utcValue >= 0 ? `+${utcValue}` : `−${Math.abs(utcValue.toFixed(1))}`;

      let tz;
      tz = `(UTC${utcOffset})`;
      switch (utcValue) {
      	case 7: tz = 'WIB'; return;
      	case 8: tz = 'WITA'; return;
      	case 9: tz = 'WIT'; return;
      }
      if (this.LocalComments.timeFirst) {
        returnDate = `${finalTime}, ${date} ${tz}`;
      } else {
        returnDate = `${date} ${finalTime} ${tz}`;
      }

      return { returnDate, time };
    }

    convertNumberToMonth(number) {
      return [
        this.language.January,
        this.language.February,
        this.language.March,
        this.language.April,
        this.language.May,
        this.language.June,
        this.language.July,
        this.language.August,
        this.language.September,
        this.language.October,
        this.language.November,
        this.language.December,
      ][number];
    }

    createDateText({ day, month, time, today, year }) {
      // Calculate day of week
      const dayNames = [
        this.language.Sunday,
        this.language.Monday,
        this.language.Tuesday,
        this.language.Wednesday,
        this.language.Thursday,
        this.language.Friday,
        this.language.Saturday,
      ];
      const dayOfTheWeek = dayNames[time.getDay()];
      let descriptiveDifference = '';
      let last = '';

      // Create a relative descriptive difference
      if (this.LocalComments.dateDifference) {
        ({ descriptiveDifference, last } = this.createRelativeDate(
          today,
          time
        ));
      }

      const monthName = this.convertNumberToMonth(time.getMonth());

      // Format the date according to user preferences
      let formattedDate = '';

      switch (this.LocalComments.dateFormat.toLowerCase()) {
        case 'dmy':
          formattedDate = `${day} ${monthName} ${year}`;

          break;
        case 'mdy':
          formattedDate = `${monthName} ${day}, ${year}`;

          break;
        default:
          formattedDate = `${year}-${month}-${addLeadingZero(day)}`;
      }

      let formattedDayOfTheWeek = '';

      if (this.LocalComments.dayOfWeek) {
        formattedDayOfTheWeek = `, ${last}${dayOfTheWeek}`;
      }

      return formattedDate + formattedDayOfTheWeek + descriptiveDifference;
    }

    /**
     * Create relative date data.
     *
     * @param {Date} today Today
     * @param {Date} time The timestamp from a comment
     * @returns {Object.<string, *>} Relative date data
     */
    createRelativeDate(today, time) {
      /**
       * The time difference from today, in milliseconds.
       *
       * @type {number}
       */
      const millisecondsAgo = today.getTime() - time.getTime();

      /**
       * The number of days ago, that we will display. It's not necessarily the
       * total days ago.
       *
       * @type {number}
       */
      let daysAgo = Math.abs(Math.round(millisecondsAgo / 1000 / 60 / 60 / 24));
      const { differenceWord, last } = this.relativeText({
        daysAgo,
        millisecondsAgo,
      });

      // This method of computing the years and months is not exact. However,
      // it's better than the previous method that used 1 January + delta days.
      // That was usually quite off because it mapped the second delta month to
      // February, which has only 28 days. This method is usually not more than
      // one day off, except perhaps over very distant dates.

      /**
       * The number of months ago, that we will display. It's not necessarily
       * the total months ago.
       *
       * @type {number}
       */
      let monthsAgo = Math.floor((daysAgo / 365) * 12);

      /**
       * The total amount of time ago, in months.
       *
       * @type {number}
       */
      const totalMonthsAgo = monthsAgo;

      /**
       * The number of years ago that we will display. It's not necessarily the
       * total years ago.
       *
       * @type {number}
       */
      let yearsAgo = Math.floor(totalMonthsAgo / 12);

      if (totalMonthsAgo < this.LocalComments.dropMonths) {
        yearsAgo = 0;
      } else if (this.LocalComments.dropMonths > 0) {
        monthsAgo = 0;
      } else {
        monthsAgo -= yearsAgo * 12;
      }

      if (daysAgo < this.LocalComments.dropDays) {
        monthsAgo = 0;
        yearsAgo = 0;
      } else if (this.LocalComments.dropDays > 0 && totalMonthsAgo >= 1) {
        daysAgo = 0;
      } else {
        daysAgo -= Math.floor((totalMonthsAgo * 365) / 12);
      }

      const descriptiveParts = [];

      // There is years text to add.
      if (yearsAgo > 0) {
        descriptiveParts.push(
          `${yearsAgo} ${pluralize(
            this.language.year,
            yearsAgo,
            this.language.years
          )}`
        );
      }

      // There is months text to add.
      if (monthsAgo > 0) {
        descriptiveParts.push(
          `${monthsAgo} ${pluralize(
            this.language.month,
            monthsAgo,
            this.language.months
          )}`
        );
      }

      // There is days text to add.
      if (daysAgo > 0) {
        descriptiveParts.push(
          `${daysAgo} ${pluralize(
            this.language.day,
            daysAgo,
            this.language.days
          )}`
        );
      }

      return {
        descriptiveDifference: ` (${descriptiveParts.join(
          ', '
        )} ${differenceWord})`,
        last,
      };
    }

    determineDateText({ time, today, tomorrow, yesterday }) {
      // Set the date bits to output.
      const year = time.getFullYear();
      const month = addLeadingZero(time.getMonth() + 1);
      const day = time.getDate();

      // Return 'today' or 'yesterday' if that is the case
      if (
        year === today.getFullYear() &&
        month === addLeadingZero(today.getMonth() + 1) &&
        day === today.getDate()
      ) {
        return this.language.Today;
      }

      if (
        year === yesterday.getFullYear() &&
        month === addLeadingZero(yesterday.getMonth() + 1) &&
        day === yesterday.getDate()
      ) {
        return this.language.Yesterday;
      }

      if (
        year === tomorrow.getFullYear() &&
        month === addLeadingZero(tomorrow.getMonth() + 1) &&
        day === tomorrow.getDate()
      ) {
        return this.language.Tomorrow;
      }

      return this.createDateText({ day, month, time, today, year });
    }

    getHour(time) {
      let ampm;
      let hour = parseInt(time.getHours(), 10);

      if (this.LocalComments.twentyFourHours) {
        ampm = '';
        hour = addLeadingZero(hour);
      } else {
        // Output am or pm depending on the date.
        // ampm = hour <= 11 ? this.language.am : this.language.pm;
        
        if (hour <= 11) ampm = this.language.morning;
        else if (hour <= 14) ampm = this.language.afternoon;
        else if (hour <= 17) ampm = this.language.lateafternoon;
        else ampm = this.language.night;

        if (hour > 12) {
          hour -= 12;
        } else if (hour === 0) {
          hour = 12;
        }
      }

      return { ampm, hour };
    }

    relativeText({ daysAgo, millisecondsAgo }) {
      let differenceWord = '';
      let last = '';

      // The date is in the past.
      if (millisecondsAgo >= 0) {
        differenceWord = this.language.ago;

        if (daysAgo <= 7) {
          last = `${this.language.last} `;
        }

        // The date is in the future.
      } else {
        differenceWord = this.language['from now'];

        if (daysAgo <= 7) {
          last = `${this.language.this} `;
        }
      }

      return { differenceWord, last };
    }

    replaceText(node, search) {
      if (!node) {
        return;
      }

      // Check if this is a text node.
      if (node.nodeType === 3) {
        let parent = node.parentNode;

        const parentNodeName = parent.nodeName;

        if (['CODE', 'PRE'].includes(parentNodeName)) {
          return;
        }

        const value = node.nodeValue
	    	.replace(this.language.January, 'January')
	    	.replace(this.language.February, 'February')
	    	.replace(this.language.March, 'March')
	    	.replace(this.language.April, 'April')
	    	.replace(this.language.May, 'May')
	    	.replace(this.language.June, 'June')
	    	.replace(this.language.July, 'July')
	    	.replace(this.language.August, 'August')
	    	.replace(this.language.September, 'September')
	    	.replace(this.language.October, 'October')
	    	.replace(this.language.November, 'November')
	    	.replace(this.language.December, 'December');
	    console.log(value);
        const matches = value.match(search);

        // Stick with manipulating the DOM directly rather than using jQuery.
        // I've got more than a 100% speed improvement afterward.
        if (matches) {
          // Only act on the first timestamp we found in this node. This is
          // really a temporary fix for the situation in which there are two or
          // more timestamps in the same node.
          const [match] = matches;
          const position = value.search(search);
          const stringLength = match.toString().length;
          const beforeMatch = value.substring(0, position);
          const afterMatch = value.substring(position + stringLength);
          const { returnDate, time } = this.adjustTime(
            match.toString(),
            search
          );
          const timestamp = time ? time.getTime() : '';

          // Is the "timestamp" attribute used for microformats?
          const span = document.createElement('span');

          span.className = 'localcomments';
          span.style.fontSize = '95%';
          span.style.whiteSpace = 'nowrap';
          span.setAttribute('timestamp', timestamp);
          span.title = match;
          span.append(document.createTextNode(returnDate));

          parent = node.parentNode;
          parent.replaceChild(span, node);

          const before = document.createElement('span');

          before.className = 'before-localcomments';
          before.append(document.createTextNode(beforeMatch));

          const after = document.createElement('span');

          after.className = 'after-localcomments';
          after.append(document.createTextNode(afterMatch));

          parent.insertBefore(before, span);
          parent.insertBefore(after, span.nextSibling);
        }
      } else {
        const children = [];
        let child;

        [child] = node.childNodes;

        while (child) {
          children.push(child);
          child = child.nextSibling;
        }

        // Loop through children and run this func on it again, recursively.
        children.forEach((child2) => {
          this.replaceText(child2, search);
        });
      }
    }

    run() {
      if (
        ['', 'MediaWiki', 'Special'].includes(
          mw.config.get('wgCanonicalNamespace')
        )
      ) {
        return;
      }

      // Check for disabled URLs.
      const isDisabledUrl = ['action=history'].some((disabledUrl) =>
        document.location.href.includes(disabledUrl)
      );

      if (isDisabledUrl) {
        return;
      }

	  // old format
	  /* this.replaceText(
        document.querySelector('.mw-parser-output'),
        /(\d{1,2}):(\d{2}), (\d{1,2}) ([A-Z][a-z]+) (\d{4}) \(UTC\)/
      ); */
      
      // new format
      this.replaceText(
        document.querySelector('.mw-parser-output'),
        /(\d{1,2}) ([A-Z][a-z]+) (\d{4}) (\d{1,2})\.(\d{2}) \(UTC\)/
      );
    }

    setDefaultSetting(...args) {
      // There are no arguments.
      if (args.length === 0) {
        return false;
      }

      // The first arg is an object, so just set that data directly onto the
      // settings object. like {setting 1: true, setting 2: false}
      if (typeof args[0] === 'object') {
        const [settings] = args;

        // Loop through each setting.
        Object.keys(settings).forEach((name) => {
          const value = settings[name];

          if (typeof this.LocalComments[name] === 'undefined') {
            this.LocalComments[name] = value;
          }
        });

        return settings;
      }

      // The first arg is a string, so use the first arg as the settings key,
      // and the second arg as the value to set it to.
      const [name, setting] = args;

      if (typeof this.LocalComments[name] === 'undefined') {
        this.LocalComments[name] = setting;
      }

      return this.LocalComments[name];
    }

    /**
     * Set the script's settings.
     *
     * @returns {undefined}
     */
    settings() {
      // The user has set custom settings, so use those.
      if (window.LocalComments) {
        this.LocalComments = window.LocalComments;
      }

      /**
       * Language
       *
       * LOCALIZING THIS SCRIPT
       * To localize this script, change the terms below,
       * to the RIGHT of the colons, to the correct term used in that language.
       *
       * For example, in the French language,
       *
       * 'Today' : 'Today',
       *
       * would be
       *
       * 'Today' : "Aujourd'hui",
       */
      this.LocalComments.language = {
        // Relative terms
        Today: 'Hari ini',
        Yesterday: 'Kemarin',
        Tomorrow: 'Besok',
        last: 'lalu',
        this: 'ini',

        // Days of the week
        Sunday: 'Minggu',
        Monday: 'Senin',
        Tuesday: 'Selasa',
        Wednesday: 'Rabu',
        Thursday: 'Kamis',
        Friday: 'Jumat',
        Saturday: 'Sabtu',

        // Months of the year
        January: 'Januari',
        February: 'Februari',
        March: 'Maret',
        April: 'April',
        May: 'Mei',
        June: 'Juni',
        July: 'Juli',
        August: 'Agustus',
        September: 'September',
        October: 'Oktober',
        November: 'November',
        December: 'Desember',

        // Difference words
        ago: 'yang lalu',
        'from now': 'dari sekarang',

        // Date phrases
        year: 'tahun',
        years: 'tahun',
        month: 'bulan',
        months: 'bulan',
        day: 'hari',
        days: 'hari',
        
        // Time phrases
        am: 'am',
        pm: 'pm',
        morning: ' pagi',
        afternoon: ' siang',
        lateafternoon: ' sore',
        night: ' malam'
      };
    }
  }

  // Check if we've already ran this script.
  if (window.commentsInLocalTimeWasRun) {
    return;
  }

  window.commentsInLocalTimeWasRun = true;

  const commentsInLocalTime = new CommentsInLocalTime();

  commentsInLocalTime.run();
});