<commit_before>Date.CultureInfo = {
	/* Culture Name */
    name: "ar-DZ",
    englishName: "Arabic (Algeria)",
    nativeName: "العربية (الجزائر)",

    /* Day Name Strings */
    dayNames: ["الاحد", "الاثنين", "الثلاثاء", "الاربعاء", "الخميس", "الجمعة", "السبت"],
    abbreviatedDayNames: ["الاحد", "الاثنين", "الثلاثاء", "الاربعاء", "الخميس", "الجمعة", "السبت"],
    shortestDayNames: ["أ", "ا", "ث", "أ", "خ", "ج", "س"],
    firstLetterDayNames: ["أ", "ا", "ث", "أ", "خ", "ج", "س"],

    /* Month Name Strings */
    monthNames: ["جانفييه", "فيفرييه", "مارس", "أفريل", "مي", "جوان", "جوييه", "أوت", "سبتمبر", "اكتوبر", "نوفمبر", "ديسمبر"],
    abbreviatedMonthNames: ["جانفييه", "فيفرييه", "مارس", "أفريل", "مي", "جوان", "جوييه", "أوت", "سبتمبر", "اكتوبر", "نوفمبر", "ديسمبر"],

	/* AM/PM Designators */
    amDesignator: "ص",
    pmDesignator: "م",

    firstDayOfWeek: 6,
    twoDigitYearMax: 2029,

    /**
     * The dateElementOrder is based on the order of the
     * format specifiers in the formatPatterns.DatePattern.
     *
     * Example:

     shortDatePattern    dateElementOrder
     ------------------  ----------------
     "M/d/yyyy"          "mdy"
     "dd/MM/yyyy"        "dmy"
     "yyyy-MM-dd"        "ymd"

     *
     * The correct dateElementOrder is required by the parser to
     * determine the expected order of the date elements in the
     * string being parsed.
     */
    dateElementOrder: "dmy",

    /* Standard date and time format patterns */
    formatPatterns: {
        shortDate: "dd-MM-yyyy",
        longDate: "dd MMMM, yyyy",
        shortTime: "H:mm",
        longTime: "H:mm:ss",
        fullDateTime: "dd MMMM, yyyy H:mm:ss",
        sortableDateTime: "yyyy-MM-ddTHH:mm:ss",
        universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ",
        rfc1123: "ddd, dd MMM yyyy HH:mm:ss GMT",
        monthDay: "dd MMMM",
        yearMonth: "MMMM, yyyy"
    },

    /**
     * NOTE: If a string format is not parsing correctly, but
     * you would expect it parse, the problem likely lies below.
     *
     * The following regex patterns control most of the string matching
     * within the parser.
     *
     * The Month name and Day name patterns were automatically generated
     * and in general should be (mostly) correct.
     *
     * Beyond the month and day name patterns are natural language strings.
     * Example: "next", "today", "months"
     *
     * These natural language string may NOT be correct for this culture.
     * If they are not correct, please translate and edit this file
     * providing the correct regular expression pattern.
     *
     * If you modify this file, please post your revised CultureInfo file
     * to the Datejs Forum located at http://www.datejs.com/forums/.
     *
     * Please mark the subject of the post with [CultureInfo]. Example:
     *    Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)
     *
     * We will add the modified patterns to the master source files.
     *
     * As well, please review the list of "Future Strings" section below.
     */
    regexPatterns: {
        jan: /^جانفييه/i,
        feb: /^فيفرييه/i,
        mar: /^مارس/i,
        apr: /^أفريل/i,
        may: /^مي/i,
        jun: /^جوان/i,
        jul: /^جوييه/i,
        aug: /^أوت/i,
        sep: /^سبتمبر/i,
        oct: /^اكتوبر/i,
        nov: /^نوفمبر/i,
        dec: /^ديسمبر/i,

        sun: /^الاحد/i,
        mon: /^ا(1)?/i,
        tue: /^الثلاثاء/i,
        wed: /^الاربعاء/i,
        thu: /^الخميس/i,
        fri: /^الجمعة/i,
        sat: /^السبت/i,

        future: /^next/i,
        past: /^last|past|prev(ious)?/i,
        add: /^(\+|aft(er)?|from|hence)/i,
        subtract: /^(\-|bef(ore)?|ago)/i,

        yesterday: /^yes(terday)?/i,
        today: /^t(od(ay)?)?/i,
        tomorrow: /^tom(orrow)?/i,
        now: /^n(ow)?/i,

        millisecond: /^ms|milli(second)?s?/i,
        second: /^sec(ond)?s?/i,
        minute: /^mn|min(ute)?s?/i,
		hour: /^h(our)?s?/i,
		week: /^w(eek)?s?/i,
        month: /^m(onth)?s?/i,
        day: /^d(ay)?s?/i,
        year: /^y(ear)?s?/i,

        shortMeridian: /^(a|p)/i,
        longMeridian: /^(a\.?m?\.?|p\.?m?\.?)/i,
        timezone: /^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,
        ordinalSuffix: /^\s*(st|nd|rd|th)/i,
        timeContext: /^\s*(\:|a(?!u|p)|p)/i
    },

	timezones: [{name:"UTC", offset:"-000"}, {name:"GMT", offset:"-000"}, {name:"EST", offset:"-0500"}, {name:"EDT", offset:"-0400"}, {name:"CST", offset:"-0600"}, {name:"CDT", offset:"-0500"}, {name:"MST", offset:"-0700"}, {name:"MDT", offset:"-0600"}, {name:"PST", offset:"-0800"}, {name:"PDT", offset:"-0700"}]
};

/********************
 ** Future Strings **
 ********************
 *
 * The following list of strings may not be currently being used, but
 * may be incorporated into the Datejs library later.
 *
 * We would appreciate any help translating the strings below.
 *
 * If you modify this file, please post your revised CultureInfo file
 * to the Datejs Forum located at http://www.datejs.com/forums/.
 *
 * Please mark the subject of the post with [CultureInfo]. Example:
 *    Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)b
 *
 * English Name        Translated
 * ------------------  -----------------
 * about               about
 * ago                 ago
 * date                date
 * time                time
 * calendar            calendar
 * show                show
 * hourly              hourly
 * daily               daily
 * weekly              weekly
 * bi-weekly           bi-weekly
 * fortnight           fortnight
 * monthly             monthly
 * bi-monthly          bi-monthly
 * quarter             quarter
 * quarterly           quarterly
 * yearly              yearly
 * annual              annual
 * annually            annually
 * annum               annum
 * again               again
 * between             between
 * after               after
 * from now            from now
 * repeat              repeat
 * times               times
 * per                 per
 * min (abbrev minute) min
 * morning             morning
 * noon                noon
 * night               night
 * midnight            midnight
 * mid-night           mid-night
 * evening             evening
 * final               final
 * future              future
 * spring              spring
 * summer              summer
 * fall                fall
 * winter              winter
 * end of              end of
 * end                 end
 * long                long
 * short               short
 */<commit_msg>completed, with timezone CET<commit_after>Date.CultureInfo = {
	/* Culture Name */
    name: "ar-DZ",
    englishName: "Arabic (Algeria)",
    nativeName: "العربية (الجزائر)",

    /* Day Name Strings */
    dayNames: ["الأحد", "الإثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت"],
    abbreviatedDayNames: ["أحد", "إثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت"],
    shortestDayNames: ["حد", "ثن", "ثل", "رع", "خم", "جم", "سب"],
    firstLetterDayNames: ["ح", "ن", "ث", "ر", "خ", "ج", "س"],

    /* Month Name Strings */
    monthNames: ["جانفييه", "فيفرييه", "مارس", "أفريل", "مي", "جوان", "جوييه", "أوت", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
    abbreviatedMonthNames: ["جان", "فيف", "مار", "أفر", "مي", "جوا", "جوي", "أوت", "سبت", "اكت", "نوف", "ديس"],

	/* AM/PM Designators */
    amDesignator: "ص",
    pmDesignator: "م",

    firstDayOfWeek: 7,
    twoDigitYearMax: 2029,

    /**
     * The dateElementOrder is based on the order of the
     * format specifiers in the formatPatterns.DatePattern.
     *
     * Example:

     shortDatePattern    dateElementOrder
     ------------------  ----------------
     "M/d/yyyy"          "mdy"
     "dd/MM/yyyy"        "dmy"
     "yyyy-MM-dd"        "ymd"

     *
     * The correct dateElementOrder is required by the parser to
     * determine the expected order of the date elements in the
     * string being parsed.
     */
    dateElementOrder: "dmy",

    /* Standard date and time format patterns */
    formatPatterns: {
        shortDate: "dd/MM/yy",
        longDate: "dd/MMMM/yyyy",
        shortTime: "hh:mm tt",
        longTime: "hh:mm:ss tt",
        fullDateTime: "dd/MMMM/yyyy hh:mm:ss tt",
        sortableDateTime: "yyyy-MM-ddTHH:mm:ss",
        universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ",
        rfc1123: "ddd, dd MMM yyyy HH:mm:ss GMT",
        monthDay: "dd MMMM",
        yearMonth: "MMMM, yyyy"
    },

    /**
     * NOTE: If a string format is not parsing correctly, but
     * you would expect it parse, the problem likely lies below.
     *
     * The following regex patterns control most of the string matching
     * within the parser.
     *
     * The Month name and Day name patterns were automatically generated
     * and in general should be (mostly) correct.
     *
     * Beyond the month and day name patterns are natural language strings.
     * Example: "next", "today", "months"
     *
     * These natural language string may NOT be correct for this culture.
     * If they are not correct, please translate and edit this file
     * providing the correct regular expression pattern.
     *
     * If you modify this file, please post your revised CultureInfo file
     * to the Datejs Forum located at http://www.datejs.com/forums/.
     *
     * Please mark the subject of the post with [CultureInfo]. Example:
     *    Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)
     *
     * We will add the modified patterns to the master source files.
     *
     * As well, please review the list of "Future Strings" section below.
     */
    regexPatterns: {
        jan: /^جانفييه/i,
        feb: /^فيفرييه/i,
        mar: /^مارس/i,
        apr: /^أفريل/i,
        may: /^مي/i,
        jun: /^جوان/i,
        jul: /^جوييه/i,
        aug: /^أوت/i,
        sep: /^سبتمبر/i,
        oct: /^أكتوبر/i,
        nov: /^نوفمبر/i,
        dec: /^ديسمبر/i,

        sun: /^الأحد/i,
        mon: /^الإثنين/i,
        tue: /^الثلاثاء/i,
        wed: /^الأربعاء/i,
        thu: /^الخميس/i,
        fri: /^الجمعة/i,
        sat: /^السبت/i,

        future: /^القادم/i,
        past: /^الماضي/i,
        add: /^(\+|بعد|عقب)/i,
        subtract: /^(\-|قبل|منذ)/i,

        yesterday: /^((ال)?(ا|أ)مس|(ال)?بارح(ة)?)/i,
        today: /^اليوم/i,
        tomorrow: /^(الغد|غد(ا)?)/i,
        now: /^(ال)?آن/i,

        millisecond: /^(ال)?ج(زء(ا))?( من( ال(أ|ا)لف من)? |( )?.( )?)(ال)?ث(انية)?(.)?/i,
	second: /^(ال)?ث(انية|وان(ي)?)?(.)?/i,
	minute: /^(ال)?د(قيقة|قائق)?(.)?/i,
	hour: /^(ال)?س(اعة|اعات)?(.)?/i,
	week: /^(ال)?(إ|ا)س(بوع|ابيع)/i,
	month: /^(ال)?((أ|ا)?شهر|شهور)/i,
	day: /(ال)?(يوم|أيام)/i,
	year: /^(ال)?سن(ة|ين|ون|وات)/i,

	shortMeridian: /^(ص|م)/i,
	longMeridian: /^(صباح(ا)?|مساء(ا)?)/i,
	timezone: /^(((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc|((ب(ال)?)?توقيت )?((ال)?(جمهورية )?(ال)?جزائر(ية)?|وسط أوروبا|cet|(central)? european tim(e|ing)))/i,        ordinalSuffix: /^\s*(st|nd|rd|th)/i,
        timeContext: /^\s*(\:|a(?!u|p)|p)/i
    },

	timezones: [{name:"UTC", offset:"-000"}, {name:"GMT", offset:"-000"}, {name:"CET", offset:"+0100"}]
};

/********************
 ** Future Strings **
 ********************
 *
 * The following list of strings may not be currently being used, but
 * may be incorporated into the Datejs library later.
 *
 * We would appreciate any help translating the strings below.
 *
 * If you modify this file, please post your revised CultureInfo file
 * to the Datejs Forum located at http://www.datejs.com/forums/.
 *
 * Please mark the subject of the post with [CultureInfo]. Example:
 *    Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)b
 *
 * English Name        Translated
 * ------------------  -----------------
 * about               قرابة
 * ago                 مضت
 * date                التاريخ
 * time                الوقت
 * calendar            التقويم
 * show                اظهار
 * hourly              كل ساعة
 * daily               يومياً
 * weekly              اسبوعياً
 * bi-weekly           مرتين بالإسبوع
 * fortnight           اسبوعان
 * monthly             شهرياً
 * bi-monthly          مرتين بالشهر
 * quarter             ربع
 * quarterly           كل ربع سنة
 * yearly              سنوياً
 * annual              سنوي
 * annually            سنوياً
 * annum               عام
 * again               مرة أخرى
 * between             بين
 * after               بعد
 * from now            من الآن
 * repeat              كرر
 * times               مرات
 * per                 كل
 * min (abbrev minute) د.
 * morning             صباح
 * noon                ظهيرة
 * night               ليل
 * midnight            نصف الليل
 * mid-night           نصف الليل
 * evening             مساء
 * final               ختامي
 * future              مستقبل
 * spring              ربيع
 * summer              صيف
 * fall                خريف
 * winter              شتاء
 * end of              نهاية
 * end                 نهاية
 * long                طويل
 * short               قصير
 */
<|endoftext|>


<commit_before>/***  HIMLE RIA SYSTEM
  **
  **  Copyright (C) 2008 HIMLE GROUP http://himle.sorsacode.com/
  **  Copyright (C) 2006-2007 Helmi Technologies Inc.
  **
  **  This program is free software; you can redistribute it and/or modify it under the terms
  **  of the GNU General Public License as published by the Free Software Foundation;
  **  either version 2 of the License, or (at your option) any later version.
  **  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  **  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  **  See the GNU General Public License for more details.
  **  You should have received a copy of the GNU General Public License along with this program;
  **  if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  ***/

/** class: HTransporter
  *
  * *Simple mid-level AJAX communication system.*
  *
  * Designed as single instance, depends on <HValueManager>.
  *
  * When implementing the server part:
  *  - feed it with raw javascript
  *  - override ses_id as early as possible with your own session id, this tells the clients apart.
  *  - change the syncDelay depending on how fast you want the client to poll the server (a value in ms)
  *  - you may refer to 't' as the HTransporter's namespace.
  *
  * Sample initialization sequence:
  * > HTransporter.ses_id    = 'nhHOZ8Zo64Wfo';
  * > HTransporter.syncDelay = 400;
  * > HTransporter.url_base = '/ui';
  *
  * See Also:
  *  <HValueManager.toXML>
  **/

/* int: HTransportURL
 *
 * Tells the <HTransporter> which url/path to start polling.
 * - "global"
 * - Override it with your server's path or url before the document is loaded.
 */
HTransportURL = false;

/* int: HTransportPoll
 *
 * Tells the <HTransporter> whether to use polling (true) or to sync values only
 * when there's something on the client-side to sync (false).
 * - "global"
 * - Defaults to true (polling), override it with false before the document is
 *   loaded if you don't want polling.
 */
HTransportPoll = true;

/* str: HFailPageUrl
 *
 * Url or uri where the the client goes, if a communication error is encountered.
 */
HFailPageUrl = '/';
HTransporterMaxRetryCount = 60;     // 60 retries
HTransporterMaxRetryTime  = 60000; // 60 seconds
HTransporterRetryDelay    = 1000; // 1 second

/* vars: Instance variables
 *  url_base  - The URL (or path) that the requests are sent to
 *  ses_id    - A value that is the reported in each request by the key 'ses_id'
 *  syncDelay - An integer value (in ms) that the client waits before starting the next request
 */
HTransporter = Base.extend({

  constructor: null,

  start: function(_url_base){
    var _this = HTransporter;
    _this.url_base  = _url_base;
    _this.ses_id    = 0;
    _this.err_msg   = '';
    _this.isBusy    = false;
    _this.syncNum   = 0;
    _this.syncDelay = 100;

    _this.prevData  = '';
    _this.failCount = 0;
    _this.firstFail = 0;

    _this.pollMode  = HTransportPoll;
    _this.req_timeout = setTimeout('HTransporter.sync();',_this.syncDelay);
  },

  setPollMode: function(_flag) {
    HTransporter.pollMode = _flag;
  },

  failure: function(resp){
    //console.log('failure');
    var _currFailAge = HTransporterMaxRetryTime+(new Date().getTime()),
        _this = HTransporter;
    clearTimeout(_this.req_timeout);
    if(_this.firstFail==0){
      _this.isBusy = false;
      _this.firstFail=(new Date().getTime());
      _this.failCount++;
      window.status = 'Communications error, retry attempt '+_this.failCount+' of '+HTransporterMaxRetryCount+'...';
    }
    else if((_this.failCount<HTransporterMaxRetryCount)&&(_this.firstFail<_currFailAge)){
      _this.isBusy = false;
      _this.failCount++;
      window.status = 'Communications error, retry attempt '+_this.failCount+' of '+HTransporterMaxRetryCount+'...';
    }
    else {
      // If the connection fails, automatically try to reload the page.
      window.status = 'Communications error, reloading page...';
      location.href = HFailPageUrl;
      _this.isBusy = true;
    }
    //console.log('fail..retry');
    _this.req_timeout = setTimeout('HTransporter.sync();',HTransporterRetryDelay);
  },

  respond: function(resp){
    var _respText = resp.responseText,
        _this = HTransporter;
    try {
      _this.err_msg = '';
      eval(_respText);
    }
    catch(e) {
      _this.err_msg = '&err_msg='+e+" - "+e.description;
      _this.failure(resp);
    }
    _this.prevData  = '';
    if(_this.failCount!=0){window.status='';}
    _this.failCount = 0;
    _this.firstFail = 0;
    _this.isBusy = false;
    if(_this.pollMode){
      _this.req_timeout = setTimeout('HTransporter.sync();',_this.syncDelay);
    }
  },

  sync: function(){
    var _this = HTransporter,
        _valid_delay = ((_this.syncDelay>0)||(_this.syncDelay==0));
    // Negative syncDelay stops transporter.
    if(_valid_delay && _this.url_base){
      if(!_this.isBusy){
        _this.isBusy = true;
        if(_this.prevData!=''){
          _syncData = _this.prevData;
          //console.log('syncData0:',_syncData);
        }
        else {
          _syncData = HValueManager.toXML();
          _this.prevData = _syncData;
          //console.log('syncData1:',_syncData);
        }
        if(""!=_syncData || _this.pollMode) {
          _this.syncNum++;
          HVM.isGetting=true;
          req_args = {
            onSuccess: function(resp){_this.respond(resp);},
            onFailure: function(resp){_this.failure(resp);},
            method:    'post',
            postBody:  'ses_id='+_this.ses_id+_this.err_msg+_syncData
          };
          try{
            _this.req  = new Ajax.Request( _this.url_base, req_args );
            HVM.isGetting=false;
          }
          catch(e){
            window.status = 'conn error:'+e;
            HVM.isGetting=false;
            _this.failure(null);
          }
        }
        else {
          _this.isBusy = false;
        }
      }
    }
  },

  stop: function() {
    clearTimeout(_this.req_timeout);
  }

});

LOAD("HTransporter.start(HTransportURL);");

<commit_msg>added HTransporterDebug flag to ease ie xhr debugging<commit_after>/***  HIMLE RIA SYSTEM
  **
  **  Copyright (C) 2008 HIMLE GROUP http://himle.sorsacode.com/
  **  Copyright (C) 2006-2007 Helmi Technologies Inc.
  **
  **  This program is free software; you can redistribute it and/or modify it under the terms
  **  of the GNU General Public License as published by the Free Software Foundation;
  **  either version 2 of the License, or (at your option) any later version.
  **  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  **  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  **  See the GNU General Public License for more details.
  **  You should have received a copy of the GNU General Public License along with this program;
  **  if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  ***/

/** class: HTransporter
  *
  * *Simple mid-level AJAX communication system.*
  *
  * Designed as single instance, depends on <HValueManager>.
  *
  * When implementing the server part:
  *  - feed it with raw javascript
  *  - override ses_id as early as possible with your own session id, this tells the clients apart.
  *  - change the syncDelay depending on how fast you want the client to poll the server (a value in ms)
  *  - you may refer to 't' as the HTransporter's namespace.
  *
  * Sample initialization sequence:
  * > HTransporter.ses_id    = 'nhHOZ8Zo64Wfo';
  * > HTransporter.syncDelay = 400;
  * > HTransporter.url_base = '/ui';
  *
  * See Also:
  *  <HValueManager.toXML>
  **/

/* int: HTransportURL
 *
 * Tells the <HTransporter> which url/path to start polling.
 * - "global"
 * - Override it with your server's path or url before the document is loaded.
 */
HTransportURL = false;

/* int: HTransportPoll
 *
 * Tells the <HTransporter> whether to use polling (true) or to sync values only
 * when there's something on the client-side to sync (false).
 * - "global"
 * - Defaults to true (polling), override it with false before the document is
 *   loaded if you don't want polling.
 */
HTransportPoll = true;

/* str: HFailPageUrl
 *
 * Url or uri where the the client goes, if a communication error is encountered.
 */
HFailPageUrl = '/';
HTransporterMaxRetryCount = 60;     // 60 retries
HTransporterMaxRetryTime  = 60000; // 60 seconds
HTransporterRetryDelay    = 1000; // 1 second

// Retarded debug mode for "special" browsers
HTransporterDebug = false;

/* vars: Instance variables
 *  url_base  - The URL (or path) that the requests are sent to
 *  ses_id    - A value that is the reported in each request by the key 'ses_id'
 *  syncDelay - An integer value (in ms) that the client waits before starting the next request
 */
HTransporter = Base.extend({

  constructor: null,

  start: function(_url_base){
    var _this = HTransporter;
    _this.url_base  = _url_base;
    _this.ses_id    = 0;
    _this.err_msg   = '';
    _this.isBusy    = false;
    _this.syncNum   = 0;
    _this.syncDelay = 100;

    _this.prevData  = '';
    _this.failCount = 0;
    _this.firstFail = 0;

    _this.pollMode  = HTransportPoll;
    _this.req_timeout = setTimeout('HTransporter.sync();',_this.syncDelay);
  },

  setPollMode: function(_flag) {
    HTransporter.pollMode = _flag;
  },

  failure: function(resp){
    //console.log('failure');
    var _currFailAge = HTransporterMaxRetryTime+(new Date().getTime()),
        _this = HTransporter;
    clearTimeout(_this.req_timeout);
    if(_this.firstFail==0){
      _this.isBusy = false;
      _this.firstFail=(new Date().getTime());
      _this.failCount++;
      window.status = 'Communications error, retry attempt '+_this.failCount+' of '+HTransporterMaxRetryCount+'...';
    }
    else if((_this.failCount<HTransporterMaxRetryCount)&&(_this.firstFail<_currFailAge)){
      _this.isBusy = false;
      _this.failCount++;
      window.status = 'Communications error, retry attempt '+_this.failCount+' of '+HTransporterMaxRetryCount+'...';
    }
    else {
      // If the connection fails, automatically try to reload the page.
      window.status = 'Communications error, reloading page...';
      location.href = HFailPageUrl;
      _this.isBusy = true;
    }
    //console.log('fail..retry');
    _this.req_timeout = setTimeout('HTransporter.sync();',HTransporterRetryDelay);
  },

  respond: function(resp){
    var _respText = resp.responseText,
        _this = HTransporter;
    if(HTransporterDebug){
      try {
        eval(_respText);
      }
      catch(e) {
        var err_descr = e+' - '+e.description;
        var aaa = window.open();
        aaa.window.title = err_descr;
        aaa.document.write('
'+_respText.replace('<','<').replace('>','>')+'
');
      }
    }
    else {
      try {
        _this.err_msg = '';
        eval(_respText);
      }
      catch(e) {
        _this.err_msg = '&err_msg='+e+" - "+e.description;
        _this.failure(resp);
      }
    }
    _this.prevData  = '';
    if(_this.failCount!=0){window.status='';}
    _this.failCount = 0;
    _this.firstFail = 0;
    _this.isBusy = false;
    if(_this.pollMode){
      _this.req_timeout = setTimeout('HTransporter.sync();',_this.syncDelay);
    }
  },

  sync: function(){
    var _this = HTransporter,
        _valid_delay = ((_this.syncDelay>0)||(_this.syncDelay==0));
    // Negative syncDelay stops transporter.
    if(_valid_delay && _this.url_base){
      if(!_this.isBusy){
        _this.isBusy = true;
        if(_this.prevData!=''){
          _syncData = _this.prevData;
          //console.log('syncData0:',_syncData);
        }
        else {
          _syncData = HValueManager.toXML();
          _this.prevData = _syncData;
          //console.log('syncData1:',_syncData);
        }
        if(""!=_syncData || _this.pollMode) {
          _this.syncNum++;
          HVM.isGetting=true;
          req_args = {
            onSuccess: function(resp){_this.respond(resp);},
            onFailure: function(resp){_this.failure(resp);},
            method:    'post',
            postBody:  'ses_id='+_this.ses_id+_this.err_msg+_syncData
          };
          try{
            _this.req  = new Ajax.Request( _this.url_base, req_args );
            HVM.isGetting=false;
          }
          catch(e){
            window.status = 'conn error:'+e;
            HVM.isGetting=false;
            _this.failure(null);
          }
        }
        else {
          _this.isBusy = false;
        }
      }
    }
  },

  stop: function() {
    clearTimeout(_this.req_timeout);
  }

});

LOAD("HTransporter.start(HTransportURL);");

<|endoftext|>


<commit_before>import Ember from 'ember';
import UnloadModel from 'vault/mixins/unload-model-route';

const SUPPORTED_DYNAMIC_BACKENDS = ['ssh', 'aws', 'pki'];

export default Ember.Route.extend(UnloadModel, {
  templateName: 'vault/cluster/secrets/backend/credentials',

  backendModel() {
    return this.modelFor('vault.cluster.secrets.backend');
  },

  pathQuery(role, backend) {
    const type = this.backendModel().get('type');
    if (type === 'pki') {
      return `${backend}/issue/${role}`;
    }
    return `${backend}/creds/${role}`;
  },

  model(params) {
    const role = params.secret;
    const backendModel = this.backendModel();
    const backend = backendModel.get('id');

    if (!SUPPORTED_DYNAMIC_BACKENDS.includes(backendModel.get('type'))) {
      return this.transitionTo('vault.cluster.secrets.backend.list-root', backend);
    }
    return this.store
      .queryRecord('capabilities', { id: this.pathQuery(role, backend) })
      .then(capabilities => {
        if (!capabilities.get('canUpdate')) {
          return this.transitionTo('vault.cluster.secrets.backend.list-root', backend);
        }
        return Ember.RSVP.resolve({
          backend,
          id: role,
          name: role,
        });
      });
  },

  setupController(controller) {
    this._super(...arguments);
    controller.set('backend', this.backendModel());
  },

  resetController(controller) {
    controller.reset();
  },
});
<commit_msg>don't check capabilities before rendering the credentials route (#4446)<commit_after>import Ember from 'ember';
import UnloadModel from 'vault/mixins/unload-model-route';

const SUPPORTED_DYNAMIC_BACKENDS = ['ssh', 'aws', 'pki'];

export default Ember.Route.extend(UnloadModel, {
  templateName: 'vault/cluster/secrets/backend/credentials',

  backendModel() {
    return this.modelFor('vault.cluster.secrets.backend');
  },

  model(params) {
    const role = params.secret;
    const backendModel = this.backendModel();
    const backend = backendModel.get('id');

    if (!SUPPORTED_DYNAMIC_BACKENDS.includes(backendModel.get('type'))) {
      return this.transitionTo('vault.cluster.secrets.backend.list-root', backend);
    }
    return Ember.RSVP.resolve({
      backend,
      id: role,
      name: role,
    });
  },

  setupController(controller) {
    this._super(...arguments);
    controller.set('backend', this.backendModel());
  },

  resetController(controller) {
    controller.reset();
  },
});
<|endoftext|>


<commit_before>//
// Copyright (C) 2013 - present Instructure, Inc.
//
// This file is part of Canvas.
//
// Canvas is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, version 3 of the License.
//
// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
//

import $ from 'jquery'
import {changeMonth} from '../../jquery/calendar_move' // calendarMonths
import RichContentEditor from '@canvas/rce/RichContentEditor'
import KeyboardShortcuts from '@canvas/tinymce-keyboard-shortcuts'
import '@canvas/datetime' // dateString, datepicker
import '@canvas/forms/jquery/jquery.instructure_forms' // formSubmit, formErrors
import '@canvas/jquery/jquery.instructure_misc_plugins' // ifExists, showIf
import '@canvas/loading-image'
import 'jquery-scroll-to-visible/jquery.scrollTo'
import 'jqueryui/datepicker'
import easy_student_view from '@canvas/easy-student-view'
import mathml from 'mathml'

RichContentEditor.preloadRemoteModule()

// Highlight mini calendar days matching syllabus events
//    Queries the syllabus event list and highlights the
//    corresponding mini calendar dates.
function highlightDaysWithEvents() {
  const $mini_month = $('.mini_month')
  const $syllabus = $('#syllabus')
  if (!$mini_month || !$syllabus) return

  let events = $mini_month.find('.day.has_event')
  events.removeClass('has_event')
  let wrapper = events.find('.day_wrapper')
  wrapper.removeAttr('role')
  wrapper.removeAttr('tabindex')

  $syllabus.find('tr.date:visible').each(function() {
    const date = $(this)
      .find('.day_date')
      .attr('data-date')
    events = $mini_month.find(`#mini_day_${date}`)
    events.addClass('has_event')
    wrapper = events.find('.day_wrapper')
    wrapper.attr('role', 'link')
    wrapper.attr('tabindex', '0')
  })
}

// Sets highlighting on a given date
//    Removes all highlighting then highlights the given
//    date, if provided.
function highlightDate(date) {
  const $mini_month = $('.mini_month')
  const $syllabus = $('#syllabus')

  $('tr.date.related, .day.related').removeClass('related')
  if (date) {
    if ($mini_month) $mini_month.find(`#mini_day_${date}`).addClass('related')
    if ($syllabus) $syllabus.find(`tr.date.events_${date}`).addClass('related')
  }
}

function highlightRelated(related_id, self) {
  const $syllabus = $('#syllabus')

  $syllabus.find('tr.related_event').removeClass('related_event')
  if (related_id && $syllabus) {
    $syllabus
      .find(`tr.related-${related_id}`)
      .not(self)
      .addClass('related_event')
  }
}

// Binds to #syllabus dom events
//    Called to bind behaviors to #syllabus after it's rendered.
function bindToSyllabus() {
  const $syllabus = $('#syllabus')
  $syllabus.on('mouseenter mouseleave', 'tr.date', function(ev) {
    let date
    if (ev.type === 'mouseenter')
      date = $(this)
        .find('.day_date')
        .attr('data-date')
    highlightDate(date)
  })

  $syllabus.on('mouseenter mouseleave', 'tr.date.detail_list', function(ev) {
    let related_id = null
    if (ev.type === 'mouseenter') {
      const classNames = ($(this).attr('class') || '').split(/\s+/)
      classNames.some(c => {
        if (c.substr(0, 8) === 'related-') {
          return (related_id = c.substr(8))
        }
      })
    }

    highlightRelated(related_id, this)
  })

  highlightDaysWithEvents()

  const todayString = $.datepicker.formatDate('yy_mm_dd', new Date())
  highlightDate(todayString)
}

function selectRow($row) {
  if ($row.length > 0) {
    $('tr.selected').removeClass('selected')
    $row.addClass('selected')
    $('html, body').scrollTo($row)
    $row
      .find('a')
      .first()
      .focus()
  }
}

function selectDate(date) {
  $('.mini_month .day.selected').removeClass('selected')
  $('.mini_month')
    .find(`#mini_day_${date}`)
    .addClass('selected')
}

// Binds to mini calendar dom events
function bindToMiniCalendar() {
  const $mini_month = $('.mini_month')

  const prev_next_links = $mini_month.find('.next_month_link, .prev_month_link')
  prev_next_links.on('click', function(ev) {
    ev.preventDefault()
    changeMonth($mini_month, $(this).hasClass('next_month_link') ? 1 : -1)
    highlightDaysWithEvents()
  })

  const miniCalendarDayClick = function(ev) {
    ev.preventDefault()
    const date = $(ev.target)
      .closest('.mini_calendar_day')[0]
      .id.slice(9)
    const [year, month, day] = Array.from(date.split('_'))
    changeMonth($mini_month, `${month}/${day}/${year}`)
    highlightDaysWithEvents()
    selectDate(date)
    $(`.events_${date}`).ifExists($events => setTimeout(() => selectRow($events), 0)) // focus race condition hack. why do you do this to me, IE?
  }

  $mini_month.on('keypress', '.day_wrapper', ev => {
    if (ev.which === 13 || ev.which === 32) miniCalendarDayClick(ev)
  })

  $mini_month.on('click', '.day_wrapper', miniCalendarDayClick)

  $mini_month.on('focus blur mouseover mouseout', '.day_wrapper', ev => {
    let date
    if (ev.type !== 'mouseout' && ev.type !== 'blur') {
      date = $(ev.target)
        .closest('.mini_calendar_day')[0]
        .id.slice(9)
    }
    highlightDate(date)
  })

  $('.jump_to_today_link').on('click', ev => {
    ev.preventDefault()
    const todayString = $.datepicker.formatDate('yy_mm_dd', new Date())
    let $lastBefore
    $('tr.date').each(function() {
      const dateString = $(this)
        .find('.day_date')
        .attr('data-date')

      if (dateString) {
        if (dateString > todayString) return false
        $lastBefore = dateString
      }
    })

    changeMonth($mini_month, $.datepicker.formatDate('mm/dd/yy', new Date()))
    highlightDaysWithEvents()

    if (!$lastBefore) $lastBefore = $('tr.date:first')

    selectDate(todayString)
    selectRow($(`tr.date.events_${$lastBefore}`))
  })
}

// Binds to edit syllabus dom events
const bindToEditSyllabus = function(course_summary_enabled) {
  const $course_syllabus = $('#course_syllabus')
  $course_syllabus.data('syllabus_body', ENV.SYLLABUS_BODY)
  const $edit_syllabus_link = $('.edit_syllabus_link')

  // if there's no edit link, don't need to (and shouldn't) do the rest of
  // this. the edit link is included on the page if and only if the user has
  // :manage_content permission on the course (see assignments'
  // syllabus_right_side view)
  if (!$edit_syllabus_link.length) return

  // Add the backbone view for keyboardshortup help here
  if (!ENV.use_rce_enhancements) {
    $('.toggle_views_link')
      .first()
      .before(new KeyboardShortcuts().render().$el)
  }

  function resetToggleLinks() {
    $('.toggle_html_editor_link').show()
    $('.toggle_rich_editor_link').hide()
  }

  const $edit_course_syllabus_form = $('#edit_course_syllabus_form')
  let $course_syllabus_body = $('#course_syllabus_body')
  const $course_syllabus_details = $('#course_syllabus_details')

  RichContentEditor.initSidebar({
    show() {
      $('#sidebar_content, #course_show_secondary').hide()
    },
    hide() {
      $('#sidebar_content, #course_show_secondary').show()
    }
  })

  $edit_course_syllabus_form.on('edit', () => {
    $edit_course_syllabus_form.show()
    $edit_syllabus_link.hide()
    $course_syllabus.hide()
    $course_syllabus_details.hide()
    easy_student_view.hide()
    $course_syllabus_body = RichContentEditor.freshNode($course_syllabus_body)
    $course_syllabus_body.val($course_syllabus.data('syllabus_body'))
    RichContentEditor.loadNewEditor($course_syllabus_body, {
      focus: true,
      manageParent: true
    })

    $('.jump_to_today_link').focus()
  }) // a11y: Set focus so it doesn't get lost.

  function recreateCourseSyllabusBody() {
    $('#tinymce-parent-of-course_syllabus_body').append($course_syllabus_body)
  }

  $edit_course_syllabus_form.on('hide_edit', () => {
    $edit_course_syllabus_form.hide()
    $edit_syllabus_link.show()
    $course_syllabus.show()
    easy_student_view.show()
    const text = $.trim($course_syllabus.html())
    $course_syllabus_details.showIf(!text)
    RichContentEditor.destroyRCE($course_syllabus_body)
    recreateCourseSyllabusBody()
    resetToggleLinks()
    $edit_syllabus_link.focus()
  })

  $edit_syllabus_link.on('click', ev => {
    ev.preventDefault()
    $edit_course_syllabus_form.triggerHandler('edit')
  })

  $edit_course_syllabus_form.on('click', '.toggle_views_link', ev => {
    ev.preventDefault()
    RichContentEditor.callOnRCE($course_syllabus_body, 'toggle')
    // hide the clicked link, and show the other toggle link.
    // todo: replace .andSelf with .addBack when JQuery is upgraded.
    $(ev.currentTarget)
      .siblings('.toggle_views_link')
      .andSelf()
      .toggle()
      .focus()
  })

  $edit_course_syllabus_form.on('click', '.cancel_button', ev => {
    ev.preventDefault()
    RichContentEditor.closeRCE($course_syllabus_body)
    $edit_course_syllabus_form.triggerHandler('hide_edit')
  })

  return $edit_course_syllabus_form.formSubmit({
    object_name: 'course',

    processData(data) {
      RichContentEditor.closeRCE($course_syllabus_body) // I'd like to wait until success, but by then the RCE is gone
      const syllabus_body = RichContentEditor.callOnRCE($course_syllabus_body, 'get_code')
      data['course[syllabus_body]'] = syllabus_body
      return data
    },

    beforeSubmit(data) {
      $edit_course_syllabus_form.triggerHandler('hide_edit')
      $course_syllabus_details.hide()
      $course_syllabus.loadingImage()
    },

    success(data) {
      if (data.course.settings.syllabus_course_summary !== course_summary_enabled) {
        return window.location.reload()
      }
      /*
      xsslint safeString.property syllabus_body
      */
      $course_syllabus.loadingImage('remove').html(data.course.syllabus_body)
      $course_syllabus.data('syllabus_body', data.course.syllabus_body)
      $course_syllabus_details.hide()
      if (!ENV.FEATURES.new_math_equation_handling) {
        if (mathml.isMathMLOnPage()) {
          if (mathml.isMathJaxLoaded()) {
            mathml.reloadElement('content')
          } else {
            mathml.loadMathJax(undefined)
          }
        }
      }
    },

    error(data) {
      return $edit_course_syllabus_form.triggerHandler('edit').formErrors(data)
    }
  })
}

export default {
  bindToEditSyllabus,
  bindToMiniCalendar,
  bindToSyllabus
}
<commit_msg>Escape html in syllabus 'jump to today'<commit_after>//
// Copyright (C) 2013 - present Instructure, Inc.
//
// This file is part of Canvas.
//
// Canvas is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, version 3 of the License.
//
// Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
//

import $ from 'jquery'
import {changeMonth} from '../../jquery/calendar_move' // calendarMonths
import RichContentEditor from '@canvas/rce/RichContentEditor'
import KeyboardShortcuts from '@canvas/tinymce-keyboard-shortcuts'
import '@canvas/datetime' // dateString, datepicker
import '@canvas/forms/jquery/jquery.instructure_forms' // formSubmit, formErrors
import '@canvas/jquery/jquery.instructure_misc_plugins' // ifExists, showIf
import '@canvas/loading-image'
import 'jquery-scroll-to-visible/jquery.scrollTo'
import 'jqueryui/datepicker'
import easy_student_view from '@canvas/easy-student-view'
import mathml from 'mathml'
import htmlEscape from 'html-escape'

RichContentEditor.preloadRemoteModule()

// Highlight mini calendar days matching syllabus events
//    Queries the syllabus event list and highlights the
//    corresponding mini calendar dates.
function highlightDaysWithEvents() {
  const $mini_month = $('.mini_month')
  const $syllabus = $('#syllabus')
  if (!$mini_month || !$syllabus) return

  let events = $mini_month.find('.day.has_event')
  events.removeClass('has_event')
  let wrapper = events.find('.day_wrapper')
  wrapper.removeAttr('role')
  wrapper.removeAttr('tabindex')

  $syllabus.find('tr.date:visible').each(function() {
    const date = $(this)
      .find('.day_date')
      .attr('data-date')
    events = $mini_month.find(`#mini_day_${date}`)
    events.addClass('has_event')
    wrapper = events.find('.day_wrapper')
    wrapper.attr('role', 'link')
    wrapper.attr('tabindex', '0')
  })
}

// Sets highlighting on a given date
//    Removes all highlighting then highlights the given
//    date, if provided.
function highlightDate(date) {
  const $mini_month = $('.mini_month')
  const $syllabus = $('#syllabus')

  $('tr.date.related, .day.related').removeClass('related')
  if (date) {
    if ($mini_month) $mini_month.find(`#mini_day_${date}`).addClass('related')
    if ($syllabus) $syllabus.find(`tr.date.events_${date}`).addClass('related')
  }
}

function highlightRelated(related_id, self) {
  const $syllabus = $('#syllabus')

  $syllabus.find('tr.related_event').removeClass('related_event')
  if (related_id && $syllabus) {
    $syllabus
      .find(`tr.related-${related_id}`)
      .not(self)
      .addClass('related_event')
  }
}

// Binds to #syllabus dom events
//    Called to bind behaviors to #syllabus after it's rendered.
function bindToSyllabus() {
  const $syllabus = $('#syllabus')
  $syllabus.on('mouseenter mouseleave', 'tr.date', function(ev) {
    let date
    if (ev.type === 'mouseenter')
      date = $(this)
        .find('.day_date')
        .attr('data-date')
    highlightDate(date)
  })

  $syllabus.on('mouseenter mouseleave', 'tr.date.detail_list', function(ev) {
    let related_id = null
    if (ev.type === 'mouseenter') {
      const classNames = ($(this).attr('class') || '').split(/\s+/)
      classNames.some(c => {
        if (c.substr(0, 8) === 'related-') {
          return (related_id = c.substr(8))
        }
      })
    }

    highlightRelated(related_id, this)
  })

  highlightDaysWithEvents()

  const todayString = $.datepicker.formatDate('yy_mm_dd', new Date())
  highlightDate(todayString)
}

function selectRow($row) {
  if ($row.length > 0) {
    $('tr.selected').removeClass('selected')
    $row.addClass('selected')
    $('html, body').scrollTo($row)
    $row
      .find('a')
      .first()
      .focus()
  }
}

function selectDate(date) {
  $('.mini_month .day.selected').removeClass('selected')
  $('.mini_month')
    .find(`#mini_day_${date}`)
    .addClass('selected')
}

// Binds to mini calendar dom events
function bindToMiniCalendar() {
  const $mini_month = $('.mini_month')

  const prev_next_links = $mini_month.find('.next_month_link, .prev_month_link')
  prev_next_links.on('click', function(ev) {
    ev.preventDefault()
    changeMonth($mini_month, $(this).hasClass('next_month_link') ? 1 : -1)
    highlightDaysWithEvents()
  })

  const miniCalendarDayClick = function(ev) {
    ev.preventDefault()
    const date = $(ev.target)
      .closest('.mini_calendar_day')[0]
      .id.slice(9)
    const [year, month, day] = Array.from(date.split('_'))
    changeMonth($mini_month, `${month}/${day}/${year}`)
    highlightDaysWithEvents()
    selectDate(date)
    $(`.events_${date}`).ifExists($events => setTimeout(() => selectRow($events), 0)) // focus race condition hack. why do you do this to me, IE?
  }

  $mini_month.on('keypress', '.day_wrapper', ev => {
    if (ev.which === 13 || ev.which === 32) miniCalendarDayClick(ev)
  })

  $mini_month.on('click', '.day_wrapper', miniCalendarDayClick)

  $mini_month.on('focus blur mouseover mouseout', '.day_wrapper', ev => {
    let date
    if (ev.type !== 'mouseout' && ev.type !== 'blur') {
      date = $(ev.target)
        .closest('.mini_calendar_day')[0]
        .id.slice(9)
    }
    highlightDate(date)
  })

  $('.jump_to_today_link').on('click', ev => {
    ev.preventDefault()
    const todayString = $.datepicker.formatDate('yy_mm_dd', new Date())
    let $lastBefore
    $('tr.date').each(function() {
      const dateString = $(this)
        .find('.day_date')
        .attr('data-date')

      if (dateString) {
        if (dateString > todayString) return false
        $lastBefore = dateString
      }
    })

    changeMonth($mini_month, $.datepicker.formatDate('mm/dd/yy', new Date()))
    highlightDaysWithEvents()

    if (!$lastBefore) $lastBefore = $('tr.date:first')

    selectDate(todayString)
    selectRow($(htmlEscape(`tr.date.events_${$lastBefore}`)))
  })
}

// Binds to edit syllabus dom events
const bindToEditSyllabus = function(course_summary_enabled) {
  const $course_syllabus = $('#course_syllabus')
  $course_syllabus.data('syllabus_body', ENV.SYLLABUS_BODY)
  const $edit_syllabus_link = $('.edit_syllabus_link')

  // if there's no edit link, don't need to (and shouldn't) do the rest of
  // this. the edit link is included on the page if and only if the user has
  // :manage_content permission on the course (see assignments'
  // syllabus_right_side view)
  if (!$edit_syllabus_link.length) return

  // Add the backbone view for keyboardshortup help here
  if (!ENV.use_rce_enhancements) {
    $('.toggle_views_link')
      .first()
      .before(new KeyboardShortcuts().render().$el)
  }

  function resetToggleLinks() {
    $('.toggle_html_editor_link').show()
    $('.toggle_rich_editor_link').hide()
  }

  const $edit_course_syllabus_form = $('#edit_course_syllabus_form')
  let $course_syllabus_body = $('#course_syllabus_body')
  const $course_syllabus_details = $('#course_syllabus_details')

  RichContentEditor.initSidebar({
    show() {
      $('#sidebar_content, #course_show_secondary').hide()
    },
    hide() {
      $('#sidebar_content, #course_show_secondary').show()
    }
  })

  $edit_course_syllabus_form.on('edit', () => {
    $edit_course_syllabus_form.show()
    $edit_syllabus_link.hide()
    $course_syllabus.hide()
    $course_syllabus_details.hide()
    easy_student_view.hide()
    $course_syllabus_body = RichContentEditor.freshNode($course_syllabus_body)
    $course_syllabus_body.val($course_syllabus.data('syllabus_body'))
    RichContentEditor.loadNewEditor($course_syllabus_body, {
      focus: true,
      manageParent: true
    })

    $('.jump_to_today_link').focus()
  }) // a11y: Set focus so it doesn't get lost.

  function recreateCourseSyllabusBody() {
    $('#tinymce-parent-of-course_syllabus_body').append($course_syllabus_body)
  }

  $edit_course_syllabus_form.on('hide_edit', () => {
    $edit_course_syllabus_form.hide()
    $edit_syllabus_link.show()
    $course_syllabus.show()
    easy_student_view.show()
    const text = $.trim($course_syllabus.html())
    $course_syllabus_details.showIf(!text)
    RichContentEditor.destroyRCE($course_syllabus_body)
    recreateCourseSyllabusBody()
    resetToggleLinks()
    $edit_syllabus_link.focus()
  })

  $edit_syllabus_link.on('click', ev => {
    ev.preventDefault()
    $edit_course_syllabus_form.triggerHandler('edit')
  })

  $edit_course_syllabus_form.on('click', '.toggle_views_link', ev => {
    ev.preventDefault()
    RichContentEditor.callOnRCE($course_syllabus_body, 'toggle')
    // hide the clicked link, and show the other toggle link.
    // todo: replace .andSelf with .addBack when JQuery is upgraded.
    $(ev.currentTarget)
      .siblings('.toggle_views_link')
      .andSelf()
      .toggle()
      .focus()
  })

  $edit_course_syllabus_form.on('click', '.cancel_button', ev => {
    ev.preventDefault()
    RichContentEditor.closeRCE($course_syllabus_body)
    $edit_course_syllabus_form.triggerHandler('hide_edit')
  })

  return $edit_course_syllabus_form.formSubmit({
    object_name: 'course',

    processData(data) {
      RichContentEditor.closeRCE($course_syllabus_body) // I'd like to wait until success, but by then the RCE is gone
      const syllabus_body = RichContentEditor.callOnRCE($course_syllabus_body, 'get_code')
      data['course[syllabus_body]'] = syllabus_body
      return data
    },

    beforeSubmit(data) {
      $edit_course_syllabus_form.triggerHandler('hide_edit')
      $course_syllabus_details.hide()
      $course_syllabus.loadingImage()
    },

    success(data) {
      if (data.course.settings.syllabus_course_summary !== course_summary_enabled) {
        return window.location.reload()
      }
      /*
      xsslint safeString.property syllabus_body
      */
      $course_syllabus.loadingImage('remove').html(data.course.syllabus_body)
      $course_syllabus.data('syllabus_body', data.course.syllabus_body)
      $course_syllabus_details.hide()
      if (!ENV.FEATURES.new_math_equation_handling) {
        if (mathml.isMathMLOnPage()) {
          if (mathml.isMathJaxLoaded()) {
            mathml.reloadElement('content')
          } else {
            mathml.loadMathJax(undefined)
          }
        }
      }
    },

    error(data) {
      return $edit_course_syllabus_form.triggerHandler('edit').formErrors(data)
    }
  })
}

export default {
  bindToEditSyllabus,
  bindToMiniCalendar,
  bindToSyllabus
}
<|endoftext|>


<commit_before>/**
 * Provides the AJAX status overlay.
 *
 * @author	Alexander Ebert
 * @copyright	2001-2018 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @module	WoltLabSuite/Core/Ajax/Status
 */
define(['Language'], function(Language) {
	"use strict";

	var _activeRequests = 0;
	var _overlay = null;
	var _timeoutShow = null;

	/**
	 * @exports	WoltLabSuite/Core/Ajax/Status
	 */
	var AjaxStatus = {
		/**
		 * Initializes the status overlay on first usage.
		 */
		_init: function() {
			_overlay = elCreate('div');
			_overlay.classList.add('spinner');

			var icon = elCreate('span');
			icon.className = 'icon icon48 fa-spinner';
			_overlay.appendChild(icon);

			var title = elCreate('span');
			title.textContent = Language.get('wcf.global.loading');
			_overlay.appendChild(title);

			document.body.appendChild(_overlay);
		},

		/**
		 * Shows the loading overlay.
		 */
		show: function() {
			if (_overlay === null) {
				this._init();
			}

			_activeRequests++;

			if (_timeoutShow === null) {
				_timeoutShow = window.setTimeout(function() {
					if (_activeRequests) {
						_overlay.classList.add('active');
					}

					_timeoutShow = null;
				}, 250);
			}
		},

		/**
		 * Hides the loading overlay.
		 */
		hide: function() {
			_activeRequests--;

			if (_activeRequests === 0) {
				if (_timeoutShow !== null) {
					window.clearTimeout(_timeoutShow);
				}

				_overlay.classList.remove('active');
			}
		}
	};

	return AjaxStatus;
});
<commit_msg>Incorrect reset of the timer to show the loading indicator<commit_after>/**
 * Provides the AJAX status overlay.
 *
 * @author	Alexander Ebert
 * @copyright	2001-2018 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @module	WoltLabSuite/Core/Ajax/Status
 */
define(['Language'], function(Language) {
	"use strict";

	var _activeRequests = 0;
	var _overlay = null;
	var _timeoutShow = null;

	/**
	 * @exports	WoltLabSuite/Core/Ajax/Status
	 */
	var AjaxStatus = {
		/**
		 * Initializes the status overlay on first usage.
		 */
		_init: function() {
			_overlay = elCreate('div');
			_overlay.classList.add('spinner');

			var icon = elCreate('span');
			icon.className = 'icon icon48 fa-spinner';
			_overlay.appendChild(icon);

			var title = elCreate('span');
			title.textContent = Language.get('wcf.global.loading');
			_overlay.appendChild(title);

			document.body.appendChild(_overlay);
		},

		/**
		 * Shows the loading overlay.
		 */
		show: function() {
			if (_overlay === null) {
				this._init();
			}

			_activeRequests++;

			if (_timeoutShow === null) {
				_timeoutShow = window.setTimeout(function() {
					if (_activeRequests) {
						_overlay.classList.add('active');
					}

					_timeoutShow = null;
				}, 250);
			}
		},

		/**
		 * Hides the loading overlay.
		 */
		hide: function() {
			_activeRequests--;

			if (_activeRequests === 0) {
				if (_timeoutShow !== null) {
					window.clearTimeout(_timeoutShow);
					_timeoutShow = null;
				}

				_overlay.classList.remove('active');
			}
		}
	};

	return AjaxStatus;
});
<|endoftext|>


<commit_before>/**
 * Flexible UI element featuring both a list of items and an input field with suggestion support.
 *
 * @author	Alexander Ebert
 * @copyright	2001-2017 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @module	WoltLabSuite/Core/Ui/ItemList
 */
define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSuite/Core/Ui/Suggestion'], function(Core, Dictionary, Language, DomTraverse, EventKey, UiSuggestion) {
	"use strict";

	var _activeId = '';
	var _data = new Dictionary();
	var _didInit = false;

	var _callbackKeyDown = null;
	var _callbackKeyPress = null;
	var _callbackKeyUp = null;
	var _callbackRemoveItem = null;

	/**
	 * @exports	WoltLabSuite/Core/Ui/ItemList
	 */
	return {
		/**
		 * Initializes an item list.
		 *
		 * The `values` argument must be empty or contain a list of strings or object, e.g.
		 * `['foo', 'bar']` or `[{ objectId: 1337, value: 'baz'}, {...}]`
		 *
		 * @param	{string}	elementId	input element id
		 * @param	{Array}		values		list of existing values
		 * @param	{Object}	options		option list
		 */
		init: function(elementId, values, options) {
			var element = elById(elementId);
			if (element === null) {
				throw new Error("Expected a valid element id, '" + elementId + "' is invalid.");
			}

			options = Core.extend({
				// search parameters for suggestions
				ajax: {
					actionName: 'getSearchResultList',
					className: '',
					data: {}
				},

				// list of excluded string values, e.g. `['ignore', 'these strings', 'when', 'searching']`
				excludedSearchValues: [],
				// maximum number of items this list may contain, `-1` for infinite
				maxItems: -1,
				// maximum length of an item value, `-1` for infinite
				maxLength: -1,
				// disallow custom values, only values offered by the suggestion dropdown are accepted
				restricted: false,

				// initial value will be interpreted as comma separated value and submitted as such
				isCSV: false,

				// will be invoked whenever the items change, receives the element id first and list of values second
				callbackChange: null,
				// callback once the form is about to be submitted
				callbackSubmit: null,
				// value may contain the placeholder `{$objectId}`
				submitFieldName: ''
			}, options);

			var form = DomTraverse.parentByTag(element, 'FORM');
			if (form !== null) {
				if (options.isCSV === false) {
					if (!options.submitFieldName.length && typeof options.callbackSubmit !== 'function') {
						throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");
					}

					form.addEventListener('submit', (function() {
						var values = this.getValues(elementId);
						if (options.submitFieldName.length) {
							var input;
							for (var i = 0, length = values.length; i < length; i++) {
								input = elCreate('input');
								input.type = 'hidden';
								input.name = options.submitFieldName.replace(/{$objectId}/, values[i].objectId);
								input.value = values[i].value;

								form.appendChild(input);
							}
						}
						else {
							options.callbackSubmit(form, values);
						}
					}).bind(this));
				}
			}

			this._setup();

			var data = this._createUI(element, options);
			//noinspection JSUnresolvedVariable
			var suggestion = new UiSuggestion(elementId, {
				ajax: options.ajax,
				callbackSelect: this._addItem.bind(this),
				excludedSearchValues: options.excludedSearchValues
			});

			_data.set(elementId, {
				dropdownMenu: null,
				element: data.element,
				list: data.list,
				listItem: data.element.parentNode,
				options: options,
				shadow: data.shadow,
				suggestion: suggestion
			});

			values = (data.values.length) ? data.values : values;
			if (Array.isArray(values)) {
				var value;
				for (var i = 0, length = values.length; i < length; i++) {
					value = values[i];
					if (typeof value === 'string') {
						value = { objectId: 0, value: value };
					}

					this._addItem(elementId, value);
				}
			}
		},

		/**
		 * Returns the list of current values.
		 *
		 * @param	{string}	elementId	input element id
		 * @return	{Array}		list of objects containing object id and value
		 */
		getValues: function(elementId) {
			if (!_data.has(elementId)) {
				throw new Error("Element id '" + elementId + "' is unknown.");
			}

			var data = _data.get(elementId);
			var items = DomTraverse.childrenByClass(data.list, 'item');
			var values = [], value, item;
			for (var i = 0, length = items.length; i < length; i++) {
				item = items[i];
				value = {
					objectId: elData(item, 'object-id'),
					value: DomTraverse.childByTag(item, 'SPAN').textContent
				};

				values.push(value);
			}

			return values;
		},

		/**
		 * Sets the list of current values.
		 *
		 * @param	{string}	elementId	input element id
		 * @param	{Array}		values		list of objects containing object id and value
		 */
		setValues: function(elementId, values) {
			if (!_data.has(elementId)) {
				throw new Error("Element id '" + elementId + "' is unknown.");
			}

			var data = _data.get(elementId);

			// remove all existing items first
			var i, length;
			var items = DomTraverse.childrenByClass(data.list, 'item');
			for (i = 0, length = items.length; i < length; i++) {
				this._removeItem(null, items[i], true);
			}

			// add new items
			for (i = 0, length = values.length; i < length; i++) {
				this._addItem(elementId, values[i]);
			}
		},

		/**
		 * Binds static event listeners.
		 */
		_setup: function() {
			if (_didInit) {
				return;
			}

			_didInit = true;

			_callbackKeyDown = this._keyDown.bind(this);
			_callbackKeyPress = this._keyPress.bind(this);
			_callbackKeyUp = this._keyUp.bind(this);
			_callbackRemoveItem = this._removeItem.bind(this);
		},

		/**
		 * Creates the DOM structure for target element. If `element` is a `<textarea>`
		 * it will be automatically replaced with an `<input>` element.
		 *
		 * @param	{Element}	element		input element
		 * @param	{Object}	options		option list
		 */
		_createUI: function(element, options) {
			var list = elCreate('ol');
			list.className = 'inputItemList';
			elData(list, 'element-id', element.id);
			list.addEventListener(WCF_CLICK_EVENT, function(event) {
				if (event.target === list) {
					//noinspection JSUnresolvedFunction
					element.focus();
				}
			});

			var listItem = elCreate('li');
			listItem.className = 'input';
			list.appendChild(listItem);

			element.addEventListener('keydown', _callbackKeyDown);
			element.addEventListener('keypress', _callbackKeyPress);
			element.addEventListener('keyup', _callbackKeyUp);

			element.parentNode.insertBefore(list, element);
			listItem.appendChild(element);

			if (options.maxLength !== -1) {
				elAttr(element, 'maxLength', options.maxLength);
			}

			var shadow = null, values = [];
			if (options.isCSV) {
				shadow = elCreate('input');
				shadow.className = 'itemListInputShadow';
				shadow.type = 'hidden';
				//noinspection JSUnresolvedVariable
				shadow.name = element.name;
				element.removeAttribute('name');

				list.parentNode.insertBefore(shadow, list);

				//noinspection JSUnresolvedVariable
				var value, tmp = element.value.split(',');
				for (var i = 0, length = tmp.length; i < length; i++) {
					value = tmp[i].trim();
					if (value.length) {
						values.push(value);
					}
				}

				if (element.nodeName === 'TEXTAREA') {
					var inputElement = elCreate('input');
					inputElement.type = 'text';
					element.parentNode.insertBefore(inputElement, element);
					inputElement.id = element.id;

					elRemove(element);
					element = inputElement;
				}
			}

			return {
				element: element,
				list: list,
				shadow: shadow,
				values: values
			};
		},

		/**
		 * Enforces the maximum number of items.
		 *
		 * @param	{string}	elementId	input element id
		 */
		_handleLimit: function(elementId) {
			var data = _data.get(elementId);
			if (data.options.maxItems === -1) {
				return;
			}

			if (data.list.childElementCount - 1 < data.options.maxItems) {
				if (data.element.disabled) {
					data.element.disabled = false;
					data.element.removeAttribute('placeholder');
				}
			}
			else if (!data.element.disabled) {
				data.element.disabled = true;
				elAttr(data.element, 'placeholder', Language.get('wcf.global.form.input.maxItems'));
			}
		},

		/**
		 * Sets the active item list id and handles keyboard access to remove an existing item.
		 *
		 * @param	{object}	event		event object
		 */
		_keyDown: function(event) {
			var input = event.currentTarget;
			var lastItem = input.parentNode.previousElementSibling;

			_activeId = input.id;

			if (event.keyCode === 8) {
				// 8 = [BACKSPACE]
				if (input.value.length === 0) {
					if (lastItem !== null) {
						if (lastItem.classList.contains('active')) {
							this._removeItem(null, lastItem);
						}
						else {
							lastItem.classList.add('active');
						}
					}
				}
			}
			else if (event.keyCode === 27) {
				// 27 = [ESC]
				if (lastItem !== null && lastItem.classList.contains('active')) {
					lastItem.classList.remove('active');
				}
			}
		},

		/**
		 * Handles the `[ENTER]` and `[,]` key to add an item to the list unless it is restricted.
		 *
		 * @param	{object}	event		event object
		 */
		_keyPress: function(event) {
			if (EventKey.Enter(event) || EventKey.Comma(event)) {
				event.preventDefault();

				if (_data.get(event.currentTarget.id).options.restricted) {
					// restricted item lists only allow results from the dropdown to be picked
					return;
				}

				var value = event.currentTarget.value.trim();
				if (value.length) {
					this._addItem(event.currentTarget.id, { objectId: 0, value: value });
				}
			}
		},

		/**
		 * Handles the keyup event to unmark an item for deletion.
		 *
		 * @param	{object}	event		event object
		 */
		_keyUp: function(event) {
			var input = event.currentTarget;

			if (input.value.length > 0) {
				var lastItem = input.parentNode.previousElementSibling;
				if (lastItem !== null) {
					lastItem.classList.remove('active');
				}
			}
		},

		/**
		 * Adds an item to the list.
		 *
		 * @param	{string}	elementId	input element id
		 * @param	{object}	value		item value
		 */
		_addItem: function(elementId, value) {
			var data = _data.get(elementId);

			var listItem = elCreate('li');
			listItem.className = 'item';

			var content = elCreate('span');
			content.className = 'content';
			elData(content, 'object-id', value.objectId);
			content.textContent = value.value;

			var button = elCreate('a');
			button.className = 'icon icon16 fa-times';
			button.addEventListener(WCF_CLICK_EVENT, _callbackRemoveItem);
			listItem.appendChild(content);
			listItem.appendChild(button);

			data.list.insertBefore(listItem, data.listItem);
			data.suggestion.addExcludedValue(value.value);
			data.element.value = '';

			this._handleLimit(elementId);
			var values = this._syncShadow(data);

			if (typeof data.options.callbackChange === 'function') {
				if (values === null) values = this.getValues(elementId);
				data.options.callbackChange(elementId, values);
			}
		},

		/**
		 * Removes an item from the list.
		 *
		 * @param	{?object}	event		event object
		 * @param	{Element?}	item		list item
		 * @param	{boolean?}	noFocus		input element will not be focused if true
		 */
		_removeItem: function(event, item, noFocus) {
			item = (event === null) ? item : event.currentTarget.parentNode;

			var parent = item.parentNode;
			//noinspection JSCheckFunctionSignatures
			var elementId = elData(parent, 'element-id');
			var data = _data.get(elementId);

			data.suggestion.removeExcludedValue(item.children[0].textContent);
			parent.removeChild(item);
			if (!noFocus) data.element.focus();

			this._handleLimit(elementId);
			var values = this._syncShadow(data);

			if (typeof data.options.callbackChange === 'function') {
				if (values === null) values = this.getValues(elementId);
				data.options.callbackChange(elementId, values);
			}
		},

		/**
		 * Synchronizes the shadow input field with the current list item values.
		 *
		 * @param	{object}	data		element data
		 */
		_syncShadow: function(data) {
			if (!data.options.isCSV) return null;

			var value = '', values = this.getValues(data.element.id);
			for (var i = 0, length = values.length; i < length; i++) {
				value += (value.length ? ',' : '') + values[i].value;
			}

			data.shadow.value = value;

			return values;
		}
	};
});
<commit_msg>UiItemList was only partially destructed<commit_after>/**
 * Flexible UI element featuring both a list of items and an input field with suggestion support.
 *
 * @author	Alexander Ebert
 * @copyright	2001-2017 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @module	WoltLabSuite/Core/Ui/ItemList
 */
define(['Core', 'Dictionary', 'Language', 'Dom/Traverse', 'EventKey', 'WoltLabSuite/Core/Ui/Suggestion', 'Ui/SimpleDropdown'], function(Core, Dictionary, Language, DomTraverse, EventKey, UiSuggestion, UiSimpleDropdown) {
	"use strict";

	var _activeId = '';
	var _data = new Dictionary();
	var _didInit = false;

	var _callbackKeyDown = null;
	var _callbackKeyPress = null;
	var _callbackKeyUp = null;
	var _callbackRemoveItem = null;

	/**
	 * @exports	WoltLabSuite/Core/Ui/ItemList
	 */
	return {
		/**
		 * Initializes an item list.
		 *
		 * The `values` argument must be empty or contain a list of strings or object, e.g.
		 * `['foo', 'bar']` or `[{ objectId: 1337, value: 'baz'}, {...}]`
		 *
		 * @param	{string}	elementId	input element id
		 * @param	{Array}		values		list of existing values
		 * @param	{Object}	options		option list
		 */
		init: function(elementId, values, options) {
			var element = elById(elementId);
			if (element === null) {
				throw new Error("Expected a valid element id, '" + elementId + "' is invalid.");
			}

			// remove data from previous instance
			if (_data.has(elementId)) {
				var tmp = _data.get(elementId);

				for (var key in tmp) {
					if (tmp.hasOwnProperty(key)) {
						var el = tmp[key];
						if (el instanceof Element && el.parentNode) {
							elRemove(el);
						}
					}
				}

				UiSimpleDropdown.destroy(elementId);
				_data.delete(elementId);
			}

			options = Core.extend({
				// search parameters for suggestions
				ajax: {
					actionName: 'getSearchResultList',
					className: '',
					data: {}
				},

				// list of excluded string values, e.g. `['ignore', 'these strings', 'when', 'searching']`
				excludedSearchValues: [],
				// maximum number of items this list may contain, `-1` for infinite
				maxItems: -1,
				// maximum length of an item value, `-1` for infinite
				maxLength: -1,
				// disallow custom values, only values offered by the suggestion dropdown are accepted
				restricted: false,

				// initial value will be interpreted as comma separated value and submitted as such
				isCSV: false,

				// will be invoked whenever the items change, receives the element id first and list of values second
				callbackChange: null,
				// callback once the form is about to be submitted
				callbackSubmit: null,
				// value may contain the placeholder `{$objectId}`
				submitFieldName: ''
			}, options);

			var form = DomTraverse.parentByTag(element, 'FORM');
			if (form !== null) {
				if (options.isCSV === false) {
					if (!options.submitFieldName.length && typeof options.callbackSubmit !== 'function') {
						throw new Error("Expected a valid function for option 'callbackSubmit', a non-empty value for option 'submitFieldName' or enabling the option 'submitFieldCSV'.");
					}

					form.addEventListener('submit', (function() {
						var values = this.getValues(elementId);
						if (options.submitFieldName.length) {
							var input;
							for (var i = 0, length = values.length; i < length; i++) {
								input = elCreate('input');
								input.type = 'hidden';
								input.name = options.submitFieldName.replace(/{$objectId}/, values[i].objectId);
								input.value = values[i].value;

								form.appendChild(input);
							}
						}
						else {
							options.callbackSubmit(form, values);
						}
					}).bind(this));
				}
			}

			this._setup();

			var data = this._createUI(element, options);
			//noinspection JSUnresolvedVariable
			var suggestion = new UiSuggestion(elementId, {
				ajax: options.ajax,
				callbackSelect: this._addItem.bind(this),
				excludedSearchValues: options.excludedSearchValues
			});

			_data.set(elementId, {
				dropdownMenu: null,
				element: data.element,
				list: data.list,
				listItem: data.element.parentNode,
				options: options,
				shadow: data.shadow,
				suggestion: suggestion
			});

			values = (data.values.length) ? data.values : values;
			if (Array.isArray(values)) {
				var value;
				for (var i = 0, length = values.length; i < length; i++) {
					value = values[i];
					if (typeof value === 'string') {
						value = { objectId: 0, value: value };
					}

					this._addItem(elementId, value);
				}
			}
		},

		/**
		 * Returns the list of current values.
		 *
		 * @param	{string}	elementId	input element id
		 * @return	{Array}		list of objects containing object id and value
		 */
		getValues: function(elementId) {
			if (!_data.has(elementId)) {
				throw new Error("Element id '" + elementId + "' is unknown.");
			}

			var data = _data.get(elementId);
			var items = DomTraverse.childrenByClass(data.list, 'item');
			var values = [], value, item;
			for (var i = 0, length = items.length; i < length; i++) {
				item = items[i];
				value = {
					objectId: elData(item, 'object-id'),
					value: DomTraverse.childByTag(item, 'SPAN').textContent
				};

				values.push(value);
			}

			return values;
		},

		/**
		 * Sets the list of current values.
		 *
		 * @param	{string}	elementId	input element id
		 * @param	{Array}		values		list of objects containing object id and value
		 */
		setValues: function(elementId, values) {
			if (!_data.has(elementId)) {
				throw new Error("Element id '" + elementId + "' is unknown.");
			}

			var data = _data.get(elementId);

			// remove all existing items first
			var i, length;
			var items = DomTraverse.childrenByClass(data.list, 'item');
			for (i = 0, length = items.length; i < length; i++) {
				this._removeItem(null, items[i], true);
			}

			// add new items
			for (i = 0, length = values.length; i < length; i++) {
				this._addItem(elementId, values[i]);
			}
		},

		/**
		 * Binds static event listeners.
		 */
		_setup: function() {
			if (_didInit) {
				return;
			}

			_didInit = true;

			_callbackKeyDown = this._keyDown.bind(this);
			_callbackKeyPress = this._keyPress.bind(this);
			_callbackKeyUp = this._keyUp.bind(this);
			_callbackRemoveItem = this._removeItem.bind(this);
		},

		/**
		 * Creates the DOM structure for target element. If `element` is a `<textarea>`
		 * it will be automatically replaced with an `<input>` element.
		 *
		 * @param	{Element}	element		input element
		 * @param	{Object}	options		option list
		 */
		_createUI: function(element, options) {
			var list = elCreate('ol');
			list.className = 'inputItemList';
			elData(list, 'element-id', element.id);
			list.addEventListener(WCF_CLICK_EVENT, function(event) {
				if (event.target === list) {
					//noinspection JSUnresolvedFunction
					element.focus();
				}
			});

			var listItem = elCreate('li');
			listItem.className = 'input';
			list.appendChild(listItem);

			element.addEventListener('keydown', _callbackKeyDown);
			element.addEventListener('keypress', _callbackKeyPress);
			element.addEventListener('keyup', _callbackKeyUp);

			element.parentNode.insertBefore(list, element);
			listItem.appendChild(element);

			if (options.maxLength !== -1) {
				elAttr(element, 'maxLength', options.maxLength);
			}

			var shadow = null, values = [];
			if (options.isCSV) {
				shadow = elCreate('input');
				shadow.className = 'itemListInputShadow';
				shadow.type = 'hidden';
				//noinspection JSUnresolvedVariable
				shadow.name = element.name;
				element.removeAttribute('name');

				list.parentNode.insertBefore(shadow, list);

				//noinspection JSUnresolvedVariable
				var value, tmp = element.value.split(',');
				for (var i = 0, length = tmp.length; i < length; i++) {
					value = tmp[i].trim();
					if (value.length) {
						values.push(value);
					}
				}

				if (element.nodeName === 'TEXTAREA') {
					var inputElement = elCreate('input');
					inputElement.type = 'text';
					element.parentNode.insertBefore(inputElement, element);
					inputElement.id = element.id;

					elRemove(element);
					element = inputElement;
				}
			}

			return {
				element: element,
				list: list,
				shadow: shadow,
				values: values
			};
		},

		/**
		 * Enforces the maximum number of items.
		 *
		 * @param	{string}	elementId	input element id
		 */
		_handleLimit: function(elementId) {
			var data = _data.get(elementId);
			if (data.options.maxItems === -1) {
				return;
			}

			if (data.list.childElementCount - 1 < data.options.maxItems) {
				if (data.element.disabled) {
					data.element.disabled = false;
					data.element.removeAttribute('placeholder');
				}
			}
			else if (!data.element.disabled) {
				data.element.disabled = true;
				elAttr(data.element, 'placeholder', Language.get('wcf.global.form.input.maxItems'));
			}
		},

		/**
		 * Sets the active item list id and handles keyboard access to remove an existing item.
		 *
		 * @param	{object}	event		event object
		 */
		_keyDown: function(event) {
			var input = event.currentTarget;
			var lastItem = input.parentNode.previousElementSibling;

			_activeId = input.id;

			if (event.keyCode === 8) {
				// 8 = [BACKSPACE]
				if (input.value.length === 0) {
					if (lastItem !== null) {
						if (lastItem.classList.contains('active')) {
							this._removeItem(null, lastItem);
						}
						else {
							lastItem.classList.add('active');
						}
					}
				}
			}
			else if (event.keyCode === 27) {
				// 27 = [ESC]
				if (lastItem !== null && lastItem.classList.contains('active')) {
					lastItem.classList.remove('active');
				}
			}
		},

		/**
		 * Handles the `[ENTER]` and `[,]` key to add an item to the list unless it is restricted.
		 *
		 * @param	{object}	event		event object
		 */
		_keyPress: function(event) {
			if (EventKey.Enter(event) || EventKey.Comma(event)) {
				event.preventDefault();

				if (_data.get(event.currentTarget.id).options.restricted) {
					// restricted item lists only allow results from the dropdown to be picked
					return;
				}

				var value = event.currentTarget.value.trim();
				if (value.length) {
					this._addItem(event.currentTarget.id, { objectId: 0, value: value });
				}
			}
		},

		/**
		 * Handles the keyup event to unmark an item for deletion.
		 *
		 * @param	{object}	event		event object
		 */
		_keyUp: function(event) {
			var input = event.currentTarget;

			if (input.value.length > 0) {
				var lastItem = input.parentNode.previousElementSibling;
				if (lastItem !== null) {
					lastItem.classList.remove('active');
				}
			}
		},

		/**
		 * Adds an item to the list.
		 *
		 * @param	{string}	elementId	input element id
		 * @param	{object}	value		item value
		 */
		_addItem: function(elementId, value) {
			var data = _data.get(elementId);

			var listItem = elCreate('li');
			listItem.className = 'item';

			var content = elCreate('span');
			content.className = 'content';
			elData(content, 'object-id', value.objectId);
			content.textContent = value.value;

			var button = elCreate('a');
			button.className = 'icon icon16 fa-times';
			button.addEventListener(WCF_CLICK_EVENT, _callbackRemoveItem);
			listItem.appendChild(content);
			listItem.appendChild(button);

			data.list.insertBefore(listItem, data.listItem);
			data.suggestion.addExcludedValue(value.value);
			data.element.value = '';

			this._handleLimit(elementId);
			var values = this._syncShadow(data);

			if (typeof data.options.callbackChange === 'function') {
				if (values === null) values = this.getValues(elementId);
				data.options.callbackChange(elementId, values);
			}
		},

		/**
		 * Removes an item from the list.
		 *
		 * @param	{?object}	event		event object
		 * @param	{Element?}	item		list item
		 * @param	{boolean?}	noFocus		input element will not be focused if true
		 */
		_removeItem: function(event, item, noFocus) {
			item = (event === null) ? item : event.currentTarget.parentNode;

			var parent = item.parentNode;
			//noinspection JSCheckFunctionSignatures
			var elementId = elData(parent, 'element-id');
			var data = _data.get(elementId);

			data.suggestion.removeExcludedValue(item.children[0].textContent);
			parent.removeChild(item);
			if (!noFocus) data.element.focus();

			this._handleLimit(elementId);
			var values = this._syncShadow(data);

			if (typeof data.options.callbackChange === 'function') {
				if (values === null) values = this.getValues(elementId);
				data.options.callbackChange(elementId, values);
			}
		},

		/**
		 * Synchronizes the shadow input field with the current list item values.
		 *
		 * @param	{object}	data		element data
		 */
		_syncShadow: function(data) {
			if (!data.options.isCSV) return null;

			var value = '', values = this.getValues(data.element.id);
			for (var i = 0, length = values.length; i < length; i++) {
				value += (value.length ? ',' : '') + values[i].value;
			}

			data.shadow.value = value;

			return values;
		}
	};
});
<|endoftext|>


<commit_before>// global namespace
var BEEwb = BEEwb || {};

BEEwb.events = {};

/**
 * OnWindowResize event function
 */
BEEwb.events.onWindowResize = function() {
    BEEwb.main.camera.aspect = BEEwb.main.container.clientWidth / BEEwb.main.container.clientHeight;
    BEEwb.main.camera.updateProjectionMatrix();

    BEEwb.main.renderer.setSize( window.innerWidth, window.innerHeight / 1.5 );

    BEEwb.main.render();
};

/**
 * OnMouseDown event function
 */
BEEwb.events.onMouseDown = function( e ) {

    // Records the first click position
    BEEwb.main.mouseVector.x = 2 * ( (e.clientX - BEEwb.main.containerWidthOffset) /
        BEEwb.main.renderer.domElement.clientWidth) - 1;

    BEEwb.main.mouseVector.y = 1 - 2 * ( (e.clientY - BEEwb.main.containerHeightOffset - BEEwb.main.topPanelVerticalOffset) /
        BEEwb.main.renderer.domElement.clientHeight );

    BEEwb.main.mouseVector.z = 0.5;
};

/**
 * OnMouseUp event function
 */
BEEwb.events.onMouseUp = function( e ) {

    var prevMouseVector = BEEwb.main.mouseVector.clone();

    BEEwb.main.mouseVector.x = 2 * ( (e.clientX - BEEwb.main.containerWidthOffset) /
        BEEwb.main.renderer.domElement.clientWidth) - 1;
    BEEwb.main.mouseVector.y = 1 - 2 * ( (e.clientY - BEEwb.main.containerHeightOffset - BEEwb.main.topPanelVerticalOffset) /
        BEEwb.main.renderer.domElement.clientHeight );
    BEEwb.main.mouseVector.z = 0.5;

    BEEwb.main.raycaster.setFromCamera( BEEwb.main.mouseVector.clone(), BEEwb.main.camera );

    var intersects = BEEwb.main.raycaster.intersectObjects( BEEwb.main.objects.children );

    // Selects the first found intersection
    if (intersects.length > 0) {

        var intersection = intersects[ 0 ];
        var model = intersection.object;

        if (BEEwb.main.selectedObject !== model)
            BEEwb.main.selectModel(model);

    } else if (prevMouseVector.x == BEEwb.main.mouseVector.x
        && prevMouseVector.y == BEEwb.main.mouseVector.y
        && prevMouseVector.z == BEEwb.main.mouseVector.z) {
        // It means the scene wasn't dragged and so we should remove all selections

        BEEwb.main.removeAllSelections();
    }

    // Updates the size/scale/rotation input boxes
    if (BEEwb.transformOps.selectedMode == 'translate') {
        BEEwb.transformOps.updatePositionInputs();
    }

    if (BEEwb.transformOps.selectedMode == 'scale') {
        if ($('#scaleby-per').is(':checked')) {
            BEEwb.transformOps.updateScaleSizeInputsByPercentage();
        } else {
            BEEwb.transformOps.updateScaleSizeInputs();
        }
    }

    if (BEEwb.transformOps.selectedMode == 'rotate') {
        BEEwb.transformOps.updateRotationInputs();
    }
};

/**
 * OnKeyDown event function
 */
BEEwb.events.onKeyDown = function( event ) {

    switch ( event.keyCode ) {

        case 46: // Delete model
            BEEwb.transformOps.removeSelected();
            break;

        case 77: // M - Move
            BEEwb.transformOps.activateMove();
            break;

        case 82: // R - Rotate
            BEEwb.transformOps.activateRotate();
            break;

        case 83: // S - Scale
            BEEwb.transformOps.activateScale();
            break;
    }
};
<commit_msg>BEEWEB-109 when onMouseUp is trigger on top of unselected model, deselects the previous model and selects the unselected<commit_after>// global namespace
var BEEwb = BEEwb || {};

BEEwb.events = {};

/**
 * OnWindowResize event function
 */
BEEwb.events.onWindowResize = function() {
    BEEwb.main.camera.aspect = BEEwb.main.container.clientWidth / BEEwb.main.container.clientHeight;
    BEEwb.main.camera.updateProjectionMatrix();

    BEEwb.main.renderer.setSize( window.innerWidth, window.innerHeight / 1.5 );

    BEEwb.main.render();
};

/**
 * OnMouseDown event function
 */
BEEwb.events.onMouseDown = function( e ) {

    // Records the first click position
    BEEwb.main.mouseVector.x = 2 * ( (e.clientX - BEEwb.main.containerWidthOffset) /
        BEEwb.main.renderer.domElement.clientWidth) - 1;

    BEEwb.main.mouseVector.y = 1 - 2 * ( (e.clientY - BEEwb.main.containerHeightOffset - BEEwb.main.topPanelVerticalOffset) /
        BEEwb.main.renderer.domElement.clientHeight );

    BEEwb.main.mouseVector.z = 0.5;
};

/**
 * OnMouseUp event function
 */
BEEwb.events.onMouseUp = function( e ) {

    var prevMouseVector = BEEwb.main.mouseVector.clone();

    BEEwb.main.mouseVector.x = 2 * ( (e.clientX - BEEwb.main.containerWidthOffset) /
        BEEwb.main.renderer.domElement.clientWidth) - 1;
    BEEwb.main.mouseVector.y = 1 - 2 * ( (e.clientY - BEEwb.main.containerHeightOffset - BEEwb.main.topPanelVerticalOffset) /
        BEEwb.main.renderer.domElement.clientHeight );
    BEEwb.main.mouseVector.z = 0.5;

    BEEwb.main.raycaster.setFromCamera( BEEwb.main.mouseVector.clone(), BEEwb.main.camera );

    var intersects = BEEwb.main.raycaster.intersectObjects( BEEwb.main.objects.children );

    // Selects the first found intersection
    if (intersects.length > 0) {
        BEEwb.main.removeAllSelections();

        var intersection = intersects[ 0 ];
        var model = intersection.object;

        if (BEEwb.main.selectedObject !== model)
            BEEwb.main.selectModel(model);

    } else if (prevMouseVector.x == BEEwb.main.mouseVector.x
        && prevMouseVector.y == BEEwb.main.mouseVector.y
        && prevMouseVector.z == BEEwb.main.mouseVector.z) {
        // It means the scene wasn't dragged and so we should remove all selections

        BEEwb.main.removeAllSelections();
    }

    // Updates the size/scale/rotation input boxes
    if (BEEwb.transformOps.selectedMode == 'translate') {
        BEEwb.transformOps.updatePositionInputs();
    }

    if (BEEwb.transformOps.selectedMode == 'scale') {
        if ($('#scaleby-per').is(':checked')) {
            BEEwb.transformOps.updateScaleSizeInputsByPercentage();
        } else {
            BEEwb.transformOps.updateScaleSizeInputs();
        }
    }

    if (BEEwb.transformOps.selectedMode == 'rotate') {
        BEEwb.transformOps.updateRotationInputs();
    }
};

/**
 * OnKeyDown event function
 */
BEEwb.events.onKeyDown = function( event ) {

    switch ( event.keyCode ) {

        case 46: // Delete model
            BEEwb.transformOps.removeSelected();
            break;

        case 77: // M - Move
            BEEwb.transformOps.activateMove();
            break;

        case 82: // R - Rotate
            BEEwb.transformOps.activateRotate();
            break;

        case 83: // S - Scale
            BEEwb.transformOps.activateScale();
            break;
    }
};
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import moment from 'moment'
import Typography from '@material-ui/core/Typography'
import LinearProgress from '@material-ui/core/LinearProgress'
import AppleIcon from 'mdi-material-ui/Apple'
import CountdownClock from 'js/components/Campaign/CountdownClockComponent'
import InviteFriend from 'js/components/Settings/Profile/InviteFriendContainer'
import { abbreviateNumber } from 'js/utils/utils'
import Link from 'js/components/General/Link'
import CampaignWrapper from 'js/components/Campaign/CampaignWrapper'

const Nov2021Campaign = ({ app, user, campaign, onDismiss }) => {
  const {
    recruits: { recruitsWithAtLeastOneTab = {} },
  } = user
  const { time, goal } = campaign
  // const hasCampaignEnded = moment().isAfter(time.end)
  const hasCampaignEnded = true
  const { currentNumber: numNewUsers } = app.campaign.goal
  const progressNumAbbreviated = abbreviateNumber(numNewUsers)
  const goalNumAbbreviated = abbreviateNumber(goal)
  const progress = (100 * numNewUsers) / goal
  const kidsWord = numNewUsers === 1 ? 'kid' : 'kids'

  const ACCENT_COLOR = '#C41E3A'
  const goalIconStyle = {
    height: 60,
    width: 60,
  }
  const completedGoalStyle = Object.assign({}, goalIconStyle, {
    color: ACCENT_COLOR,
  })
  const incompletedGoalStyle = Object.assign({}, goalIconStyle, {
    color: '#BBB',
  })

  return (
    <CampaignWrapper
      onDismiss={onDismiss}
      customTheme={{ mainColor: ACCENT_COLOR, lightColor: '#94989e' }}
    >
      <Typography
        variant={'h6'}
        style={{
          textAlign: 'center',
          marginTop: 4,
        }}
      >
        {hasCampaignEnded
          ? 'Thank you for keeping kids fed!'
          : 'Recruit a friend, feed 10 children in need'}
      </Typography>

          {hasCampaignEnded ? null : (

                  {recruitsWithAtLeastOneTab > 0 ? (
                    <AppleIcon style={completedGoalStyle} />
                  ) : (
                    <AppleIcon style={incompletedGoalStyle} />
                  )}
                  {recruitsWithAtLeastOneTab > 1 ? (
                    <AppleIcon style={completedGoalStyle} />
                  ) : (
                    <AppleIcon style={incompletedGoalStyle} />
                  )}
                  {recruitsWithAtLeastOneTab > 2 ? (
                    <AppleIcon style={completedGoalStyle} />
                  ) : (
                    <AppleIcon style={incompletedGoalStyle} />
                  )}

                <Typography variant={'body2'} gutterBottom>
                  {recruitsWithAtLeastOneTab * 10}/30 kids fed
                </Typography>

          )}
          {hasCampaignEnded ? null : (

              <InviteFriend
                user={user}
                InputProps={{ style: { fontSize: 14 } }}
                label={'Share this link with a friend'}
                helperText={"and you'll feed 10 kids when they join!"}
                style={{
                  minWidth: 280,
                }}
              />

          )}

            {hasCampaignEnded ? (
              <>
                <Typography variant={'body2'} paragraph>
                  Thank you for joining us in keeping kids fed through{' '}
                  <Link
                    to="https://www.nokidhungry.org/"
                    target="_blank"
                    rel="noopener noreferrer"
                    style={{ color: ACCENT_COLOR }}
                  >
                    No Kid Hungry
                  </Link>
                  ! Each meal handed out works to ensure all children have the
                  opportunity to grow up strong, happy, and healthy.
                </Typography>
                <Typography variant={'body2'} gutterBottom={false}>
                  Happy holidays & happy tabbing!
                </Typography>
              </>
            ) : (
              <>
                <Typography variant={'body2'} paragraph>
                  Every child needs three meals a day to grow up strong, happy,
                  and healthy. With{' '}
                  <Link
                    to="https://www.nokidhungry.org/"
                    target="_blank"
                    rel="noopener noreferrer"
                    style={{ color: ACCENT_COLOR }}
                  >
                    No Kid Hungry
                  </Link>
                  , this can be a reality thanks to their school and early
                  childhood meal programs.
                </Typography>
                <Typography variant={'body2'} paragraph>
                  Now through December 6, we’re feeding 10 children for
                  every person you recruit to Tab for a Cause! Maximize your
                  impact by getting at least 3 friends on board today. ❤
                </Typography>
              </>
            )}

            <Typography variant={'caption'}>
              {progressNumAbbreviated} {kidsWord} fed
            </Typography>
            <Typography variant={'caption'}>
              Goal: {goalNumAbbreviated}
            </Typography>

          <LinearProgress
            color={'primary'}
            variant={'determinate'}
            value={progress}
          />

        {hasCampaignEnded ? null : (
          <Typography variant={'caption'}>
            <CountdownClock
              campaignStartDatetime={time.start}
              campaignEndDatetime={time.end}
            />{' '}
            remaining
          </Typography>
        )}

    </CampaignWrapper>
  )
}

Nov2021Campaign.propTypes = {
  app: PropTypes.shape({
    campaign: PropTypes.shape({
      goal: PropTypes.shape({
        currentNumber: PropTypes.number.isRequired,
      }),
    }).isRequired,
  }),
  campaign: PropTypes.shape({
    time: PropTypes.shape({
      start: PropTypes.instanceOf(moment).isRequired,
      end: PropTypes.instanceOf(moment).isRequired,
    }),
    goal: PropTypes.number.isRequired,
  }).isRequired,
  user: PropTypes.shape({
    recruits: PropTypes.shape({
      recruitsWithAtLeastOneTab: PropTypes.number.isRequired,
    }).isRequired,
  }).isRequired,
  onDismiss: PropTypes.func.isRequired,
}

export default Nov2021Campaign
<commit_msg>WIP - copy<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import moment from 'moment'
import Typography from '@material-ui/core/Typography'
import LinearProgress from '@material-ui/core/LinearProgress'
import AppleIcon from 'mdi-material-ui/Apple'
import CountdownClock from 'js/components/Campaign/CountdownClockComponent'
import InviteFriend from 'js/components/Settings/Profile/InviteFriendContainer'
import { abbreviateNumber, commaFormatted } from 'js/utils/utils'
import Link from 'js/components/General/Link'
import CampaignWrapper from 'js/components/Campaign/CampaignWrapper'

const Nov2021Campaign = ({ app, user, campaign, onDismiss }) => {
  const {
    recruits: { recruitsWithAtLeastOneTab = {} },
  } = user
  const { time, goal } = campaign
  const hasCampaignEnded = moment().isAfter(time.end)
  // const hasCampaignEnded = true
  const { currentNumber: numNewUsers } = app.campaign.goal
  const progressNumAbbreviated = abbreviateNumber(numNewUsers)
  const progressNumCommaFormatted = commaFormatted(numNewUsers)
  const goalNumAbbreviated = abbreviateNumber(goal)
  const progress = (100 * numNewUsers) / goal
  const kidsWord = numNewUsers === 1 ? 'kid' : 'kids'

  const ACCENT_COLOR = '#C41E3A'
  const goalIconStyle = {
    height: 60,
    width: 60,
  }
  const completedGoalStyle = Object.assign({}, goalIconStyle, {
    color: ACCENT_COLOR,
  })
  const incompletedGoalStyle = Object.assign({}, goalIconStyle, {
    color: '#BBB',
  })

  return (
    <CampaignWrapper
      onDismiss={onDismiss}
      customTheme={{ mainColor: ACCENT_COLOR, lightColor: '#94989e' }}
    >
      <Typography
        variant={'h6'}
        style={{
          textAlign: 'center',
          marginTop: 4,
        }}
      >
        {hasCampaignEnded
          ? 'Thank you for keeping kids fed!'
          : 'Recruit a friend, feed 10 children in need'}
      </Typography>

          {hasCampaignEnded ? null : (

                  {recruitsWithAtLeastOneTab > 0 ? (
                    <AppleIcon style={completedGoalStyle} />
                  ) : (
                    <AppleIcon style={incompletedGoalStyle} />
                  )}
                  {recruitsWithAtLeastOneTab > 1 ? (
                    <AppleIcon style={completedGoalStyle} />
                  ) : (
                    <AppleIcon style={incompletedGoalStyle} />
                  )}
                  {recruitsWithAtLeastOneTab > 2 ? (
                    <AppleIcon style={completedGoalStyle} />
                  ) : (
                    <AppleIcon style={incompletedGoalStyle} />
                  )}

                <Typography variant={'body2'} gutterBottom>
                  {recruitsWithAtLeastOneTab * 10}/30 kids fed
                </Typography>

          )}
          {hasCampaignEnded ? null : (

              <InviteFriend
                user={user}
                InputProps={{ style: { fontSize: 14 } }}
                label={'Share this link with a friend'}
                helperText={"and you'll feed 10 kids when they join!"}
                style={{
                  minWidth: 280,
                }}
              />

          )}

            {hasCampaignEnded ? (
              <>
                <Typography variant={'body2'} paragraph>
                  Thank you for joining us in keeping kids fed through{' '}
                  <Link
                    to="https://www.nokidhungry.org/"
                    target="_blank"
                    rel="noopener noreferrer"
                    style={{ color: ACCENT_COLOR }}
                  >
                    No Kid Hungry
                  </Link>
                  ! Together,{' '}

                    our community provided {progressNumCommaFormatted} meals to
                    kids in need.
                  {' '}
                  Each meal handed out works to ensure all children have the
                  opportunity to grow up strong, happy, and healthy.
                </Typography>
                <Typography variant={'body2'} paragraph>
                  Your help was critical: rather than spending our marketing
                  budget on ads, we’re able to turn it into thousands of meals
                  for kids, thanks to you.
                </Typography>
                <Typography variant={'body2'} gutterBottom={false}>
                  Happy holidays & happy tabbing!
                </Typography>
              </>
            ) : (
              <>
                <Typography variant={'body2'} paragraph>
                  Every child needs three meals a day to grow up strong, happy,
                  and healthy. With{' '}
                  <Link
                    to="https://www.nokidhungry.org/"
                    target="_blank"
                    rel="noopener noreferrer"
                    style={{ color: ACCENT_COLOR }}
                  >
                    No Kid Hungry
                  </Link>
                  , this can be a reality thanks to their school and early
                  childhood meal programs.
                </Typography>
                <Typography variant={'body2'} paragraph>
                  Now until December 6,{' '}

                    we're feeding 10 children for every person you recruit to
                    Tab for a Cause!
                  {' '}
                  Quadruple your impact by getting at least 3 friends on board
                  today. ❤
                </Typography>
              </>
            )}

            <Typography variant={'caption'}>
              {progressNumAbbreviated} {kidsWord} fed
            </Typography>
            <Typography variant={'caption'}>
              Goal: {goalNumAbbreviated}
            </Typography>

          <LinearProgress
            color={'primary'}
            variant={'determinate'}
            value={progress}
          />

        {hasCampaignEnded ? null : (
          <Typography variant={'caption'}>
            <CountdownClock
              campaignStartDatetime={time.start}
              campaignEndDatetime={time.end}
            />{' '}
            remaining
          </Typography>
        )}

    </CampaignWrapper>
  )
}

Nov2021Campaign.propTypes = {
  app: PropTypes.shape({
    campaign: PropTypes.shape({
      goal: PropTypes.shape({
        currentNumber: PropTypes.number.isRequired,
      }),
    }).isRequired,
  }),
  campaign: PropTypes.shape({
    time: PropTypes.shape({
      start: PropTypes.instanceOf(moment).isRequired,
      end: PropTypes.instanceOf(moment).isRequired,
    }),
    goal: PropTypes.number.isRequired,
  }).isRequired,
  user: PropTypes.shape({
    recruits: PropTypes.shape({
      recruitsWithAtLeastOneTab: PropTypes.number.isRequired,
    }).isRequired,
  }).isRequired,
  onDismiss: PropTypes.func.isRequired,
}

export default Nov2021Campaign
<|endoftext|>