diff --git a/public/index.html b/public/index.html index 7e7e157d1..afed83cab 100644 --- a/public/index.html +++ b/public/index.html @@ -6641,7 +6641,7 @@ - + diff --git a/public/lib/moment-with-locales.min.js b/public/lib/moment-with-locales.min.js new file mode 100644 index 000000000..8a6c9b475 --- /dev/null +++ b/public/lib/moment-with-locales.min.js @@ -0,0 +1,2 @@ +!function(e,a){"object"==typeof exports&&"undefined"!=typeof module?module.exports=a():"function"==typeof define&&define.amd?define(a):e.moment=a()}(this,function(){"use strict";var E;function c(){return E.apply(null,arguments)}function F(e){return e instanceof Array||"[object Array]"===Object.prototype.toString.call(e)}function z(e){return null!=e&&"[object Object]"===Object.prototype.toString.call(e)}function l(e,a){return Object.prototype.hasOwnProperty.call(e,a)}function N(e){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(e).length;for(var a in e)if(l(e,a))return;return 1}function L(e){return void 0===e}function J(e){return"number"==typeof e||"[object Number]"===Object.prototype.toString.call(e)}function R(e){return e instanceof Date||"[object Date]"===Object.prototype.toString.call(e)}function C(e,a){for(var t=[],s=e.length,n=0;n>>0,s=0;sFe(e)?(r=e+1,a-Fe(e)):(r=e,a);return{year:r,dayOfYear:t}}function aa(e,a,t){var s,n,r=Xe(e.year(),a,t),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+ta(n=e.year()-1,a,t):r>ta(e.year(),a,t)?(s=r-ta(e.year(),a,t),n=e.year()+1):(n=e.year(),s=r),{week:s,year:n}}function ta(e,a,t){var s=Xe(e,a,t),a=Xe(e+1,a,t);return(Fe(e)-s+a)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),h("w",r,u),h("ww",r,a),h("W",r,u),h("WW",r,a),Se(["w","ww","W","WW"],function(e,a,t,s){a[s.substr(0,1)]=f(e)});function sa(e,a){return e.slice(a,7).concat(e.slice(0,a))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),h("d",r),h("e",r),h("E",r),h("dd",function(e,a){return a.weekdaysMinRegex(e)}),h("ddd",function(e,a){return a.weekdaysShortRegex(e)}),h("dddd",function(e,a){return a.weekdaysRegex(e)}),Se(["dd","ddd","dddd"],function(e,a,t,s){s=t._locale.weekdaysParse(e,s,t._strict);null!=s?a.d=s:Y(t).invalidWeekday=e}),Se(["d","e","E"],function(e,a,t,s){a[s]=f(e)});var na="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ra="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),da="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),_a=m,ia=m,oa=m;function ma(){function e(e,a){return a.length-e.length}for(var a,t,s,n=[],r=[],d=[],_=[],i=0;i<7;i++)s=U([2e3,1]).day(i),a=we(this.weekdaysMin(s,"")),t=we(this.weekdaysShort(s,"")),s=we(this.weekdays(s,"")),n.push(a),r.push(t),d.push(s),_.push(a),_.push(t),_.push(s);n.sort(e),r.sort(e),d.sort(e),_.sort(e),this._weekdaysRegex=new RegExp("^("+_.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+d.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+n.join("|")+")","i")}function ua(){return this.hours()%12||12}function la(e,a){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),a)})}function Ma(e,a){return a._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,ua),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+ua.apply(this)+de(this.minutes(),2)}),s("hmmss",0,0,function(){return""+ua.apply(this)+de(this.minutes(),2)+de(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+de(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+de(this.minutes(),2)+de(this.seconds(),2)}),la("a",!0),la("A",!1),h("a",Ma),h("A",Ma),h("H",r,M),h("h",r,u),h("k",r,u),h("HH",r,a),h("hh",r,a),h("kk",r,a),h("hmm",ye),h("hmmss",_),h("Hmm",ye),h("Hmmss",_),k(["H","HH"],D),k(["k","kk"],function(e,a,t){e=f(e);a[D]=24===e?0:e}),k(["a","A"],function(e,a,t){t._isPm=t._locale.isPM(e),t._meridiem=e}),k(["h","hh"],function(e,a,t){a[D]=f(e),Y(t).bigHour=!0}),k("hmm",function(e,a,t){var s=e.length-2;a[D]=f(e.substr(0,s)),a[Pe]=f(e.substr(s)),Y(t).bigHour=!0}),k("hmmss",function(e,a,t){var s=e.length-4,n=e.length-2;a[D]=f(e.substr(0,s)),a[Pe]=f(e.substr(s,2)),a[Oe]=f(e.substr(n)),Y(t).bigHour=!0}),k("Hmm",function(e,a,t){var s=e.length-2;a[D]=f(e.substr(0,s)),a[Pe]=f(e.substr(s))}),k("Hmmss",function(e,a,t){var s=e.length-4,n=e.length-2;a[D]=f(e.substr(0,s)),a[Pe]=f(e.substr(s,2)),a[Oe]=f(e.substr(n))});m=Ne("Hours",!0);var ha,ca={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ie,monthsShort:Ue,week:{dow:0,doy:6},weekdays:na,weekdaysMin:da,weekdaysShort:ra,meridiemParse:/[ap]\.?m?\.?/i},g={},La={};function Ya(e){return e&&e.toLowerCase().replace("_","-")}function ya(e){for(var a,t,s,n,r=0;r=a&&function(e,a){for(var t=Math.min(e.length,a.length),s=0;s=a-1)break;a--}r++}return ha}function fa(a){var e,t;if(void 0===g[a]&&"undefined"!=typeof module&&module&&module.exports&&(t=a)&&t.match("^[^/\\\\]*$"))try{e=ha._abbr,require("./locale/"+a),ka(e)}catch(e){g[a]=null}return g[a]}function ka(e,a){return e&&((a=L(a)?Da(e):pa(e,a))?ha=a:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),ha._abbr}function pa(e,a){if(null===a)return delete g[e],null;var t,s=ca;if(a.abbr=e,null!=g[e])ae("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=g[e]._config;else if(null!=a.parentLocale)if(null!=g[a.parentLocale])s=g[a.parentLocale]._config;else{if(null==(t=fa(a.parentLocale)))return La[a.parentLocale]||(La[a.parentLocale]=[]),La[a.parentLocale].push({name:e,config:a}),null;s=t._config}return g[e]=new ne(se(s,a)),La[e]&&La[e].forEach(function(e){pa(e.name,e.config)}),ka(e),g[e]}function Da(e){var a;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return ha;if(!F(e)){if(a=fa(e))return a;e=[e]}return ya(e)}function Ta(e){var a=e._a;return a&&-2===Y(e).overflow&&(a=a[je]<0||11Ce(a[p],a[je])?xe:a[D]<0||24ta(r,i,o)?Y(s)._overflowWeeks=!0:null!=m?Y(s)._overflowWeekday=!0:(u=ea(r,d,_,i,o),s._a[p]=u.year,s._dayOfYear=u.dayOfYear)),null!=e._dayOfYear&&(n=Aa(e._a[p],t[p]),(e._dayOfYear>Fe(n)||0===e._dayOfYear)&&(Y(e)._overflowDayOfYear=!0),m=Qe(n,0,e._dayOfYear),e._a[je]=m.getUTCMonth(),e._a[xe]=m.getUTCDate()),a=0;a<3&&null==e._a[a];++a)e._a[a]=l[a]=t[a];for(;a<7;a++)e._a[a]=l[a]=null==e._a[a]?2===a?1:0:e._a[a];24===e._a[D]&&0===e._a[Pe]&&0===e._a[Oe]&&0===e._a[We]&&(e._nextDay=!0,e._a[D]=0),e._d=(e._useUTC?Qe:$e).apply(null,l),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[D]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(Y(e).weekdayMismatch=!0)}}function Fa(e){if(e._f===c.ISO_8601)Pa(e);else if(e._f===c.RFC_2822)Wa(e);else{e._a=[],Y(e).empty=!0;for(var a,t,s,n,r,d=""+e._i,_=d.length,i=0,o=le(e._f,e._locale).match(_e)||[],m=o.length,u=0;ue.valueOf():e.valueOf()"}),u.toJSON=function(){return this.isValid()?this.toISOString():null},u.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},u.unix=function(){return Math.floor(this.valueOf()/1e3)},u.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},u.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},u.eraName=function(){for(var e,a=this.localeData().eras(),t=0,s=a.length;tthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},u.isLocal=function(){return!!this.isValid()&&!this._isUTC},u.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},u.isUtc=Za,u.isUTC=Za,u.zoneAbbr=function(){return this._isUTC?"UTC":""},u.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},u.dates=e("dates accessor is deprecated. Use date instead.",i),u.months=e("months accessor is deprecated. Use month instead",Ke),u.years=e("years accessor is deprecated. Use year instead",ze),u.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,a){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,a),this):-this.utcOffset()}),u.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){var e,a;return L(this._isDSTShifted)&&(Z(e={},this),(e=za(e))._a?(a=(e._isUTC?U:w)(e._a),this._isDSTShifted=this.isValid()&&0>>0,s=0;sAe(e)?(r=e+1,t-Ae(e)):(r=e,t);return{year:r,dayOfYear:n}}function qe(e,t,n){var s,i,r=ze(e.year(),t,n),r=Math.floor((e.dayOfYear()-r-1)/7)+1;return r<1?s=r+P(i=e.year()-1,t,n):r>P(e.year(),t,n)?(s=r-P(e.year(),t,n),i=e.year()+1):(i=e.year(),s=r),{week:s,year:i}}function P(e,t,n){var s=ze(e,t,n),t=ze(e+1,t,n);return(Ae(e)-s+t)/7}s("w",["ww",2],"wo","week"),s("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),v("w",p),v("ww",p,w),v("W",p),v("WW",p,w),Te(["w","ww","W","WW"],function(e,t,n,s){t[s.substr(0,1)]=g(e)});function Be(e,t){return e.slice(t,7).concat(e.slice(0,t))}s("d",0,"do","day"),s("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),s("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),s("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),s("e",0,0,"weekday"),s("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),v("d",p),v("e",p),v("E",p),v("dd",function(e,t){return t.weekdaysMinRegex(e)}),v("ddd",function(e,t){return t.weekdaysShortRegex(e)}),v("dddd",function(e,t){return t.weekdaysRegex(e)}),Te(["dd","ddd","dddd"],function(e,t,n,s){s=n._locale.weekdaysParse(e,s,n._strict);null!=s?t.d=s:m(n).invalidWeekday=e}),Te(["d","e","E"],function(e,t,n,s){t[s]=g(e)});var Je="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Qe="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Xe="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Ke=k,et=k,tt=k;function nt(){function e(e,t){return t.length-e.length}for(var t,n,s,i=[],r=[],a=[],o=[],u=0;u<7;u++)s=l([2e3,1]).day(u),t=M(this.weekdaysMin(s,"")),n=M(this.weekdaysShort(s,"")),s=M(this.weekdays(s,"")),i.push(t),r.push(n),a.push(s),o.push(t),o.push(n),o.push(s);i.sort(e),r.sort(e),a.sort(e),o.sort(e),this._weekdaysRegex=new RegExp("^("+o.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+r.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+i.join("|")+")","i")}function st(){return this.hours()%12||12}function it(e,t){s(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function rt(e,t){return t._meridiemParse}s("H",["HH",2],0,"hour"),s("h",["hh",2],0,st),s("k",["kk",2],0,function(){return this.hours()||24}),s("hmm",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)}),s("hmmss",0,0,function(){return""+st.apply(this)+r(this.minutes(),2)+r(this.seconds(),2)}),s("Hmm",0,0,function(){return""+this.hours()+r(this.minutes(),2)}),s("Hmmss",0,0,function(){return""+this.hours()+r(this.minutes(),2)+r(this.seconds(),2)}),it("a",!0),it("A",!1),t("hour","h"),n("hour",13),v("a",rt),v("A",rt),v("H",p),v("h",p),v("k",p),v("HH",p,w),v("hh",p,w),v("kk",p,w),v("hmm",ge),v("hmmss",we),v("Hmm",ge),v("Hmmss",we),D(["H","HH"],x),D(["k","kk"],function(e,t,n){e=g(e);t[x]=24===e?0:e}),D(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e),n._meridiem=e}),D(["h","hh"],function(e,t,n){t[x]=g(e),m(n).bigHour=!0}),D("hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s)),m(n).bigHour=!0}),D("hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i)),m(n).bigHour=!0}),D("Hmm",function(e,t,n){var s=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s))}),D("Hmmss",function(e,t,n){var s=e.length-4,i=e.length-2;t[x]=g(e.substr(0,s)),t[T]=g(e.substr(s,2)),t[N]=g(e.substr(i))});k=de("Hours",!0);var at,ot={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Ce,monthsShort:Ue,week:{dow:0,doy:6},weekdays:Je,weekdaysMin:Xe,weekdaysShort:Qe,meridiemParse:/[ap]\.?m?\.?/i},R={},ut={};function lt(e){return e&&e.toLowerCase().replace("_","-")}function ht(e){for(var t,n,s,i,r=0;r=t&&function(e,t){for(var n=Math.min(e.length,t.length),s=0;s=t-1)break;t--}r++}return at}function dt(t){var e;if(void 0===R[t]&&"undefined"!=typeof module&&module&&module.exports&&null!=t.match("^[^/\\\\]*$"))try{e=at._abbr,require("./locale/"+t),ct(e)}catch(e){R[t]=null}return R[t]}function ct(e,t){return e&&((t=o(t)?mt(e):ft(e,t))?at=t:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+e+" not found. Did you forget to load it?")),at._abbr}function ft(e,t){if(null===t)return delete R[e],null;var n,s=ot;if(t.abbr=e,null!=R[e])Q("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),s=R[e]._config;else if(null!=t.parentLocale)if(null!=R[t.parentLocale])s=R[t.parentLocale]._config;else{if(null==(n=dt(t.parentLocale)))return ut[t.parentLocale]||(ut[t.parentLocale]=[]),ut[t.parentLocale].push({name:e,config:t}),null;s=n._config}return R[e]=new K(X(s,t)),ut[e]&&ut[e].forEach(function(e){ft(e.name,e.config)}),ct(e),R[e]}function mt(e){var t;if(!(e=e&&e._locale&&e._locale._abbr?e._locale._abbr:e))return at;if(!a(e)){if(t=dt(e))return t;e=[e]}return ht(e)}function _t(e){var t=e._a;return t&&-2===m(e).overflow&&(t=t[O]<0||11We(t[Y],t[O])?b:t[x]<0||24P(r,u,l)?m(s)._overflowWeeks=!0:null!=h?m(s)._overflowWeekday=!0:(d=$e(r,a,o,u,l),s._a[Y]=d.year,s._dayOfYear=d.dayOfYear)),null!=e._dayOfYear&&(i=bt(e._a[Y],n[Y]),(e._dayOfYear>Ae(i)||0===e._dayOfYear)&&(m(e)._overflowDayOfYear=!0),h=Ze(i,0,e._dayOfYear),e._a[O]=h.getUTCMonth(),e._a[b]=h.getUTCDate()),t=0;t<3&&null==e._a[t];++t)e._a[t]=c[t]=n[t];for(;t<7;t++)e._a[t]=c[t]=null==e._a[t]?2===t?1:0:e._a[t];24===e._a[x]&&0===e._a[T]&&0===e._a[N]&&0===e._a[Ne]&&(e._nextDay=!0,e._a[x]=0),e._d=(e._useUTC?Ze:je).apply(null,c),r=e._useUTC?e._d.getUTCDay():e._d.getDay(),null!=e._tzm&&e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm),e._nextDay&&(e._a[x]=24),e._w&&void 0!==e._w.d&&e._w.d!==r&&(m(e).weekdayMismatch=!0)}}function Tt(e){if(e._f===f.ISO_8601)St(e);else if(e._f===f.RFC_2822)Ot(e);else{e._a=[],m(e).empty=!0;for(var t,n,s,i,r,a=""+e._i,o=a.length,u=0,l=ae(e._f,e._locale).match(te)||[],h=l.length,d=0;de.valueOf():e.valueOf()"}),i.toJSON=function(){return this.isValid()?this.toISOString():null},i.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},i.unix=function(){return Math.floor(this.valueOf()/1e3)},i.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},i.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},i.eraName=function(){for(var e,t=this.localeData().eras(),n=0,s=t.length;nthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},i.isLocal=function(){return!!this.isValid()&&!this._isUTC},i.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},i.isUtc=At,i.isUTC=At,i.zoneAbbr=function(){return this._isUTC?"UTC":""},i.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},i.dates=e("dates accessor is deprecated. Use date instead.",ke),i.months=e("months accessor is deprecated. Use month instead",Ge),i.years=e("years accessor is deprecated. Use year instead",Ie),i.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",function(e,t){return null!=e?(this.utcOffset(e="string"!=typeof e?-e:e,t),this):-this.utcOffset()}),i.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",function(){if(!o(this._isDSTShifted))return this._isDSTShifted;var e,t={};return $(t,this),(t=Nt(t))._a?(e=(t._isUTC?l:W)(t._a),this._isDSTShifted=this.isValid()&&0 0, чтобы включить DRY. Определяет величину штрафа для кратчайшей \"штрафуемой\" строки.", + "DRY_Repetition_Penalty_desc": "DRY налагает штраф на токены, генерация которых приведёт к появлению строки, которая уже была в тексте раньше. Установите множитель = 0, чтобы отключить.", + "Base": "Основание", + "DRY_Base_desc": "Определяет, насколько быстро возрастает штраф с увеличением длины строки.", + "Allowed Length": "Допустимая длина", + "DRY_Allowed_Length_desc": "Длина повторяющейся строки, при превышении которой DRY начинает налагать штраф." } diff --git a/public/script.js b/public/script.js index f2176fe39..ec68a916f 100644 --- a/public/script.js +++ b/public/script.js @@ -294,10 +294,17 @@ DOMPurify.addHook('afterSanitizeAttributes', function (node) { } }); -DOMPurify.addHook('uponSanitizeAttribute', (_, data, config) => { +DOMPurify.addHook('uponSanitizeAttribute', (node, data, config) => { if (!config['MESSAGE_SANITIZE']) { return; } + + /* Retain the classes on UI elements of messages that interact with the main UI */ + const permittedNodeTypes = ['BUTTON', 'DIV']; + if (config['MESSAGE_ALLOW_SYSTEM_UI'] && node.classList.contains('menu_button') && permittedNodeTypes.includes(node.nodeName)) { + return; + } + switch (data.attrName) { case 'class': { if (data.attrValue) { @@ -385,8 +392,8 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { if (localStorage.getItem(warningShownKey) === null) { const warningToast = toastr.warning( - 'Use the "Ext. Media" button to allow it. Click on this message to dismiss.', - 'External media has been blocked', + t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`, + t`External media has been blocked`, { timeOut: 0, preventDuplicates: true, @@ -651,6 +658,7 @@ async function getSystemMessages() { force_avatar: system_avatar, is_user: false, is_system: true, + uses_system_ui: true, mes: await renderTemplateAsync('welcome', { displayVersion }), }, group: { @@ -926,7 +934,7 @@ async function firstLoadInit() { token = tokenData.token; } catch { hideLoader(); - toastr.error('Couldn\'t get CSRF token. Please refresh the page.', 'Error', { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true }); + toastr.error(t`Couldn't get CSRF token. Please refresh the page.`, t`Error`, { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true }); throw new Error('Initialization failed'); } @@ -1142,7 +1150,7 @@ async function getStatusKobold() { // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress. if (online_status === 'no_connection' && data.response) { - toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true }); + toastr.error(data.response, t`API Error`, { timeOut: 5000, preventDuplicates: true }); } } catch (err) { console.error('Error getting status', err); @@ -1228,7 +1236,7 @@ async function getStatusTextgen() { // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress. if (online_status === 'no_connection' && data.response) { - toastr.error(data.response, 'API Error', { timeOut: 5000, preventDuplicates: true }); + toastr.error(data.response, t`API Error`, { timeOut: 5000, preventDuplicates: true }); } } catch (err) { if (err instanceof AbortReason) { @@ -1280,7 +1288,7 @@ export async function selectCharacterById(id) { } if (isChatSaving) { - toastr.info('Please wait until the chat is saved before switching characters.', 'Your chat is still saving...'); + toastr.info(t`Please wait until the chat is saved before switching characters.`, t`Your chat is still saving...`); return; } @@ -1634,7 +1642,7 @@ export async function getOneCharacter(avatarUrl) { if (indexOf !== -1) { characters[indexOf] = getData; } else { - toastr.error(`Character ${avatarUrl} not found in the list`, 'Error', { timeOut: 5000, preventDuplicates: true }); + toastr.error(t`Character ${avatarUrl} not found in the list`, t`Error`, { timeOut: 5000, preventDuplicates: true }); } } } @@ -1917,9 +1925,10 @@ export async function sendTextareaMessage() { * @param {boolean} isSystem If the message was sent by the system * @param {boolean} isUser If the message was sent by the user * @param {number} messageId Message index in chat array + * @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides * @returns {string} HTML string */ -export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) { +export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}) { if (!mes) { return ''; } @@ -1994,15 +2003,33 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) { }); } - mes = mes.replace(/```[\s\S]*?```|``[\s\S]*?``|`[\s\S]*?`|(".+?")|(\u201C.+?\u201D)/gm, function (match, p1, p2) { - if (p1) { - return '"' + p1.replace(/"/g, '') + '"'; - } else if (p2) { - return '“' + p2.replace(/\u201C|\u201D/g, '') + '”'; - } else { - return match; + mes = mes.replace( + /```[\s\S]*?```|``[\s\S]*?``|`[\s\S]*?`|(".*?")|(\u201C.*?\u201D)|(\u00AB.*?\u00BB)|(\u300C.*?\u300D)|(\u300E.*?\u300F)|(\uFF02.*?\uFF02)/gm, + function (match, p1, p2, p3, p4, p5, p6) { + if (p1) { + // English double quotes + return `"${p1.slice(1, -1)}"`; + } else if (p2) { + // Curly double quotes “ ” + return `“${p2.slice(1, -1)}”`; + } else if (p3) { + // Guillemets « » + return `«${p3.slice(1, -1)}»`; + } else if (p4) { + // Corner brackets 「 」 + return `「${p4.slice(1, -1)}」`; + } else if (p5) { + // White corner brackets 『 』 + return `『${p5.slice(1, -1)}』`; + } else if (p6) { + // Fullwidth quotes " " + return `"${p6.slice(1, -1)}"`; + } else { + // Return the original match if no quotes are found + return match; + } } - }); + ); // Restore double quotes in tags if (!power_user.encode_tags) { @@ -2030,7 +2057,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId) { } /** @type {any} */ - const config = { MESSAGE_SANITIZE: true, ADD_TAGS: ['custom-style'] }; + const config = { MESSAGE_SANITIZE: true, ADD_TAGS: ['custom-style'], ...sanitizerOverrides }; mes = encodeStyleTags(mes); mes = DOMPurify.sanitize(mes, config); mes = decodeStyleTags(mes); @@ -2235,6 +2262,18 @@ export function addCopyToCodeBlocks(messageElement) { } +/** + * Adds a single message to the chat. + * @param {object} mes Message object + * @param {object} [options] Options + * @param {string} [options.type='normal'] Message type + * @param {number} [options.insertAfter=null] Message ID to insert the new message after + * @param {boolean} [options.scroll=true] Whether to scroll to the new message + * @param {number} [options.insertBefore=null] Message ID to insert the new message before + * @param {number} [options.forceId=null] Force the message ID + * @param {boolean} [options.showSwipes=true] Whether to show swipe buttons + * @returns {void} + */ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true } = {}) { let messageText = mes['mes']; const momentDate = timestampToMoment(mes.send_date); @@ -2263,7 +2302,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll } else if (this_chid === undefined) { avatarImg = system_avatar; } else { - if (characters[this_chid].avatar != 'none') { + if (characters[this_chid].avatar !== 'none') { avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar); } else { avatarImg = default_avatar; @@ -2278,12 +2317,16 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll avatarImg = mes['force_avatar']; } + // if mes.uses_system_ui is true, set an override on the sanitizer options + const sanitizerOverrides = mes.uses_system_ui ? { MESSAGE_ALLOW_SYSTEM_UI: true } : {}; + messageText = messageFormatting( messageText, mes.name, isSystem, mes.is_user, chat.indexOf(mes), + sanitizerOverrides, ); const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1); let bookmarkLink = mes?.extra?.bookmark_link ?? ''; @@ -2335,7 +2378,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll } //shows or hides the Prompt display button - let mesIdToFind = type == 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId')); + let mesIdToFind = type === 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId')); //if we have itemized messages, and the array isn't null.. if (params.isUser === false && Array.isArray(itemizedPrompts) && itemizedPrompts.length > 0) { @@ -2656,7 +2699,7 @@ export function sendSystemMessage(type, text, extra = {}) { newMessage.mes = text; } - if (type == system_message_types.SLASH_COMMANDS) { + if (type === system_message_types.SLASH_COMMANDS) { newMessage.mes = getSlashCommandsHelp(); } @@ -2670,7 +2713,7 @@ export function sendSystemMessage(type, text, extra = {}) { chat.push(newMessage); addOneMessage(newMessage); is_send_press = false; - if (type == system_message_types.SLASH_COMMANDS) { + if (type === system_message_types.SLASH_COMMANDS) { const browser = new SlashCommandBrowser(); const spinner = document.querySelector('#chat .last_mes .custom-slashHelp'); const parent = spinner.parentElement; @@ -3398,7 +3441,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro await eventSource.emit(event_types.GENERATION_AFTER_COMMANDS, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun); if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) { - toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true }); + toastr.error(t`Streaming is enabled, but the version of Kobold used does not support token streaming.`, undefined, { timeOut: 10000, preventDuplicates: true }); unblockGeneration(type); return Promise.resolve(); } @@ -3407,7 +3450,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro textgen_settings.streaming && textgen_settings.legacy_api && textgen_settings.type === OOBA) { - toastr.error('Streaming is not supported for the Legacy API. Update Ooba and use new API to enable streaming.', undefined, { timeOut: 10000, preventDuplicates: true }); + toastr.error(t`Streaming is not supported for the Legacy API. Update Ooba and use new API to enable streaming.`, undefined, { timeOut: 10000, preventDuplicates: true }); unblockGeneration(type); return Promise.resolve(); } @@ -3423,7 +3466,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro if (!pingResult) { unblockGeneration(type); - toastr.error('Verify that the server is running and accessible.', 'ST Server cannot be reached'); + toastr.error(t`Verify that the server is running and accessible.`, t`ST Server cannot be reached`); throw new Error('Server unreachable'); } @@ -4476,7 +4519,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro generatedPromptCache = ''; if (data?.response) { - toastr.error(data.response, 'API Error', { preventDuplicates: true }); + toastr.error(data.response, t`API Error`, { preventDuplicates: true }); } throw new Error(data?.response); } @@ -4585,7 +4628,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro function onError(exception) { if (typeof exception?.error?.message === 'string') { - toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 }); + toastr.error(exception.error.message, t`Error`, { timeOut: 10000, extendedTimeOut: 20000 }); } generatedPromptCache = ''; @@ -4989,7 +5032,7 @@ function addChatsSeparator(mesSendString) { async function duplicateCharacter() { if (!this_chid) { - toastr.warning('You must first select a character to duplicate!'); + toastr.warning(t`You must first select a character to duplicate!`); return ''; } @@ -5008,7 +5051,7 @@ async function duplicateCharacter() { body: JSON.stringify(body), }); if (response.ok) { - toastr.success('Character Duplicated'); + toastr.success(t`Character Duplicated`); const data = await response.json(); await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path }); await getCharacters(); @@ -5855,23 +5898,23 @@ export function setSendButtonState(value) { export async function renameCharacter(name = null, { silent = false, renameChats = null } = {}) { if (!name && silent) { - toastr.warning('No character name provided.', 'Rename Character'); + toastr.warning(t`No character name provided.`, t`Rename Character`); return false; } if (this_chid === undefined) { - toastr.warning('No character selected.', 'Rename Character'); + toastr.warning(t`No character selected.`, t`Rename Character`); return false; } const oldAvatar = characters[this_chid].avatar; - const newValue = name || await callGenericPopup('

New name:

', POPUP_TYPE.INPUT, characters[this_chid].name); + const newValue = name || await callGenericPopup('

' + t`New name:` + '

', POPUP_TYPE.INPUT, characters[this_chid].name); if (!newValue) { - toastr.warning('No character name provided.', 'Rename Character'); + toastr.warning(t`No character name provided.`, t`Rename Character`); return false; } if (newValue === characters[this_chid].name) { - toastr.info('Same character name provided, so name did not change.', 'Rename Character'); + toastr.info(t`Same character name provided, so name did not change.`, t`Rename Character`); return false; } @@ -5918,9 +5961,9 @@ export async function renameCharacter(name = null, { silent = false, renameChats if (renamePastChatsConfirm) { await renamePastChats(newAvatar, newValue); await reloadCurrentChat(); - toastr.success('Character renamed and past chats updated!', 'Rename Character'); + toastr.success(t`Character renamed and past chats updated!`, t`Rename Character`); } else { - toastr.success('Character renamed!', 'Rename Character'); + toastr.success(t`Character renamed!`, t`Rename Character`); } } else { @@ -5933,8 +5976,8 @@ export async function renameCharacter(name = null, { silent = false, renameChats } catch (error) { // Reloading to prevent data corruption - if (!silent) await callPopup('Something went wrong. The page will be reloaded.', 'text'); - else toastr.error('Something went wrong. The page will be reloaded.', 'Rename Character'); + if (!silent) await callPopup(t`Something went wrong. The page will be reloaded.`, 'text'); + else toastr.error(t`Something went wrong. The page will be reloaded.`, t`Rename Character`); console.log('Renaming character error:', error); location.reload(); @@ -5991,7 +6034,7 @@ async function renamePastChats(newAvatar, newValue) { } } } catch (error) { - toastr.error(`Past chat could not be updated: ${file_name}`); + toastr.error(t`Past chat could not be updated: ${file_name}`); console.error(error); } } @@ -6040,7 +6083,7 @@ export async function saveChat(chatName, withMetadata, mesId) { characters[this_chid]['date_last_chat'] = Date.now(); chat.forEach(function (item, i) { if (item['is_group']) { - toastr.error('Trying to save group chat with regular saveChat function. Aborting to prevent corruption.'); + toastr.error(t`Trying to save group chat with regular saveChat function. Aborting to prevent corruption.`); throw new Error('Group chat saved from saveChat'); } /* @@ -6085,7 +6128,7 @@ export async function saveChat(chatName, withMetadata, mesId) { contentType: 'application/json', success: function (data) { }, error: function (jqXHR, exception) { - toastr.error('Check the server connection and reload the page to prevent data loss.', 'Chat could not be saved'); + toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`); console.log(exception); console.log(jqXHR); }, @@ -6482,7 +6525,7 @@ export async function getSettings() { if (!response.ok) { reloadLoop(); - toastr.error('Settings could not be loaded after multiple attempts. Please try again later.'); + toastr.error(t`Settings could not be loaded after multiple attempts. Please try again later.`); throw new Error('Error getting settings'); } @@ -6709,7 +6752,7 @@ export async function saveSettings(type) { eventSource.emit(event_types.SETTINGS_UPDATED); }, error: function (jqXHR, exception) { - toastr.error('Check the server connection and reload the page to prevent data loss.', 'Settings could not be saved'); + toastr.error(t`Check the server connection and reload the page to prevent data loss.t`, t`Settings could not be saved`); console.log(exception); console.log(jqXHR); }, @@ -6975,7 +7018,7 @@ export async function displayPastChats() { const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats()); if (!data) { - toastr.error('Could not load chat data. Try reloading the page.'); + toastr.error(t`Could not load chat data. Try reloading the page.`); return; } @@ -7108,7 +7151,7 @@ export function selectRightMenuWithAnimation(selectedMenuId) { export function select_rm_info(type, charId, previousCharId = null) { if (!type) { - toastr.error('Invalid process (no \'type\')'); + toastr.error(t`Invalid process (no 'type')`); return; } if (type !== 'group_create') { @@ -7116,20 +7159,20 @@ export function select_rm_info(type, charId, previousCharId = null) { } if (type === 'char_delete') { - toastr.warning(`Character Deleted: ${displayName}`); + toastr.warning(t`Character Deleted: ${displayName}`); } if (type === 'char_create') { - toastr.success(`Character Created: ${displayName}`); + toastr.success(t`Character Created: ${displayName}`); } if (type === 'group_create') { - toastr.success('Group Created'); + toastr.success(t`Group Created`); } if (type === 'group_delete') { - toastr.warning('Group Deleted'); + toastr.warning(t`Group Deleted`); } if (type === 'char_import') { - toastr.success(`Character Imported: ${displayName}`); + toastr.success(t`Character Imported: ${displayName}`); } selectRightMenuWithAnimation('rm_characters_block'); @@ -7587,25 +7630,25 @@ export function hideSwipeButtons() { */ export async function deleteSwipe(swipeId = null) { if (swipeId && (isNaN(swipeId) || swipeId < 0)) { - toastr.warning(`Invalid swipe ID: ${swipeId + 1}`); + toastr.warning(t`Invalid swipe ID: ${swipeId + 1}`); return; } const lastMessage = chat[chat.length - 1]; if (!lastMessage || !Array.isArray(lastMessage.swipes) || !lastMessage.swipes.length) { - toastr.warning('No messages to delete swipes from.'); + toastr.warning(t`No messages to delete swipes from.`); return; } if (lastMessage.swipes.length <= 1) { - toastr.warning('Can\'t delete the last swipe.'); + toastr.warning(t`Can't delete the last swipe.`); return; } swipeId = swipeId ?? lastMessage.swipe_id; if (swipeId < 0 || swipeId >= lastMessage.swipes.length) { - toastr.warning(`Invalid swipe ID: ${swipeId + 1}`); + toastr.warning(t`Invalid swipe ID: ${swipeId + 1}`); return; } @@ -7744,7 +7787,7 @@ export function setGenerationProgress(progress) { function isHordeGenerationNotAllowed() { if (main_api == 'koboldhorde' && preset_settings == 'gui') { - toastr.error('GUI Settings preset is not supported for Horde. Please select another preset.'); + toastr.error(t`GUI Settings preset is not supported for Horde. Please select another preset.`); return true; } @@ -7793,7 +7836,7 @@ function openCharacterWorldPopup() { } $('#character_json_data').val(JSON.stringify(data)); - toastr.info('Embedded lorebook will be removed from this character.'); + toastr.info(t`Embedded lorebook will be removed from this character.`); } catch { console.error('Failed to parse character JSON data.'); } @@ -7984,11 +8027,11 @@ async function createOrEditCharacter(e) { if ($('#form_create').attr('actiontype') == 'createcharacter') { if (String($('#character_name_pole').val()).length === 0) { - toastr.error('Name is required'); + toastr.error(t`Name is required`); return; } if (is_group_generating || is_send_press) { - toastr.error('Cannot create characters while generating. Stop the request and try again.', 'Creation aborted'); + toastr.error(t`Cannot create characters while generating. Stop the request and try again.`, t`Creation aborted`); return; } try { @@ -8072,7 +8115,7 @@ async function createOrEditCharacter(e) { } catch (error) { console.error('Error creating character', error); - toastr.error('Failed to create character'); + toastr.error(t`Failed to create character`); } } else { try { @@ -8131,7 +8174,7 @@ async function createOrEditCharacter(e) { } } catch (error) { console.log(error); - toastr.error('Something went wrong while saving the character, or the image file provided was in an invalid format. Double check that the image is not a webp.'); + toastr.error(t`Something went wrong while saving the character, or the image file provided was in an invalid format. Double check that the image is not a webp.`); } } } @@ -8641,7 +8684,7 @@ async function selectContextCallback(args, name) { const result = fuse.search(name); if (result.length === 0) { - !quiet && toastr.warning(`Context template "${name}" not found`); + !quiet && toastr.warning(t`Context template '${name}' not found`); return ''; } @@ -8661,7 +8704,7 @@ async function selectInstructCallback(args, name) { const result = fuse.search(name); if (result.length === 0) { - !quiet && toastr.warning(`Instruct template "${name}" not found`); + !quiet && toastr.warning(t`Instruct template '${name}' not found`); return ''; } @@ -8713,7 +8756,7 @@ async function connectAPISlash(args, text) { const apiConfig = CONNECT_API_MAP[text.toLowerCase()]; if (!apiConfig) { - toastr.error(`Error: ${text} is not a valid API`); + toastr.error(t`Error: ${text} is not a valid API`); return ''; } @@ -8742,7 +8785,7 @@ async function connectAPISlash(args, text) { } const quiet = isTrueBoolean(args?.quiet); - const toast = quiet ? jQuery() : toastr.info(`API set to ${text}, trying to connect..`); + const toast = quiet ? jQuery() : toastr.info(t`API set to ${text}, trying to connect..`); try { await waitUntilCondition(() => online_status !== 'no_connection', 10000, 100); @@ -8781,7 +8824,7 @@ export async function processDroppedFiles(files, data = new Map()) { const preservedName = data instanceof Map && data.get(file); await importCharacter(file, preservedName); } else { - toastr.warning('Unsupported file type: ' + file.name); + toastr.warning(t`Unsupported file type: ` + file.name); } } } @@ -8794,7 +8837,7 @@ export async function processDroppedFiles(files, data = new Map()) { */ async function importCharacter(file, preserveFileName = '') { if (is_group_generating || is_send_press) { - toastr.error('Cannot import characters while generating. Stop the request and try again.', 'Import aborted'); + toastr.error(t`Cannot import characters while generating. Stop the request and try again.`, t`Import aborted`); throw new Error('Cannot import character while generating'); } @@ -8821,7 +8864,7 @@ async function importCharacter(file, preserveFileName = '') { }); if (data.error) { - toastr.error('The file is likely invalid or corrupted.', 'Could not import character'); + toastr.error(t`The file is likely invalid or corrupted.`, t`Could not import character`); return; } @@ -8874,7 +8917,7 @@ async function doImpersonate(args, prompt) { await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100); } catch { console.warn('Timeout waiting for generation unlock'); - toastr.warning('Cannot run /impersonate command while the reply is being generated.'); + toastr.warning(t`Cannot run /impersonate command while the reply is being generated.`); return ''; } @@ -8936,19 +8979,19 @@ async function doDeleteChat() { async function doRenameChat(_, chatName) { if (!chatName) { - toastr.warning('Name must be provided as an argument to rename this chat.'); + toastr.warning(t`Name must be provided as an argument to rename this chat.`); return ''; } const currentChatName = getCurrentChatId(); if (!currentChatName) { - toastr.warning('No chat selected that can be renamed.'); + toastr.warning(t`No chat selected that can be renamed.`); return ''; } await renameChat(currentChatName, chatName); - toastr.success(`Successfully renamed chat to: ${chatName}`); + toastr.success(t`Successfully renamed chat to: ${chatName}`); return ''; } @@ -9063,7 +9106,7 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {}) for (const key of characterKey) { const character = characters.find(x => x.avatar == key); if (!character) { - toastr.warning(`Character ${key} not found. Skipping deletion.`); + toastr.warning(t`Character ${key} not found. Skipping deletion.`); continue; } @@ -9080,7 +9123,7 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {}) }); if (!response.ok) { - toastr.error(`${response.status} ${response.statusText}`, 'Failed to delete character'); + toastr.error(`${response.status} ${response.statusText}`, t`Failed to delete character`); continue; } @@ -9132,6 +9175,90 @@ function doTogglePanels() { return ''; } +/** + * Event handler to open a navbar drawer when a drawer open button is clicked. + * Handles click events on .drawer-opener elements. + * Opens the drawer associated with the clicked button according to the data-target attribute. + * @returns {void} + */ +function doDrawerOpenClick() { + const targetDrawerID = $(this).attr('data-target'); + const drawer = $(`#${targetDrawerID}`); + const drawerToggle = drawer.find('.drawer-toggle'); + const drawerWasOpenAlready = drawerToggle.parent().find('.drawer-content').hasClass('openDrawer'); + if (drawerWasOpenAlready || drawer.hasClass('resizing')) { return; } + doNavbarIconClick.call(drawerToggle); +} + +/** + * Event handler to open or close a navbar drawer when a navbar icon is clicked. + * Handles click events on .drawer-toggle elements. + * @returns {void} + */ +function doNavbarIconClick() { + var icon = $(this).find('.drawer-icon'); + var drawer = $(this).parent().find('.drawer-content'); + if (drawer.hasClass('resizing')) { return; } + var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer'); + let targetDrawerID = $(this).parent().find('.drawer-content').attr('id'); + const pinnedDrawerClicked = drawer.hasClass('pinnedOpen'); + + if (!drawerWasOpenAlready) { //to open the drawer + $('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () { + await delay(50); $(this).closest('.drawer-content').removeClass('resizing'); + }); + $('.openIcon').toggleClass('closedIcon openIcon'); + $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer'); + icon.toggleClass('openIcon closedIcon'); + drawer.toggleClass('openDrawer closedDrawer'); + + //console.log(targetDrawerID); + if (targetDrawerID === 'right-nav-panel') { + $(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({ + duration: 200, + easing: 'swing', + start: function () { + jQuery(this).css('display', 'flex'); //flex needed to make charlist scroll + }, + complete: async function () { + favsToHotswap(); + await delay(50); + $(this).closest('.drawer-content').removeClass('resizing'); + $('#rm_print_characters_block').trigger('scroll'); + }, + }); + } else { + $(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, 'swing', async function () { + await delay(50); $(this).closest('.drawer-content').removeClass('resizing'); + }); + } + + // Set the height of "autoSetHeight" textareas within the drawer to their scroll height + if (!CSS.supports('field-sizing', 'content')) { + $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () { + await resetScrollHeight($(this)); + return; + }); + } + + } else if (drawerWasOpenAlready) { //to close manually + icon.toggleClass('closedIcon openIcon'); + + if (pinnedDrawerClicked) { + $(drawer).addClass('resizing').slideToggle(200, 'swing', async function () { + await delay(50); $(this).removeClass('resizing'); + }); + } + else { + $('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () { + await delay(50); $(this).closest('.drawer-content').removeClass('resizing'); + }); + } + + drawer.toggleClass('closedDrawer openDrawer'); + } +} + function addDebugFunctions() { const doBackfill = async () => { for (const message of chat) { @@ -9755,12 +9882,7 @@ jQuery(async function () { let deleteChats = false; - const confirm = await Popup.show.confirm('Delete the character?', ` - THIS IS PERMANENT!

-
`, { + const confirm = await Popup.show.confirm(t`Delete the character?`, await renderTemplateAsync('deleteConfirm'), { onClose: () => deleteChats = !!$('#del_char_checkbox').prop('checked'), }); if (!confirm) { @@ -10721,69 +10843,8 @@ jQuery(async function () { stopScriptExecution(); }); - $('.drawer-toggle').on('click', function () { - var icon = $(this).find('.drawer-icon'); - var drawer = $(this).parent().find('.drawer-content'); - if (drawer.hasClass('resizing')) { return; } - var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer'); - let targetDrawerID = $(this).parent().find('.drawer-content').attr('id'); - const pinnedDrawerClicked = drawer.hasClass('pinnedOpen'); - - if (!drawerWasOpenAlready) { //to open the drawer - $('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () { - await delay(50); $(this).closest('.drawer-content').removeClass('resizing'); - }); - $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon'); - $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer'); - icon.toggleClass('openIcon closedIcon'); - drawer.toggleClass('openDrawer closedDrawer'); - - //console.log(targetDrawerID); - if (targetDrawerID === 'right-nav-panel') { - $(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle({ - duration: 200, - easing: 'swing', - start: function () { - jQuery(this).css('display', 'flex'); //flex needed to make charlist scroll - }, - complete: async function () { - favsToHotswap(); - await delay(50); - $(this).closest('.drawer-content').removeClass('resizing'); - $('#rm_print_characters_block').trigger('scroll'); - }, - }); - } else { - $(this).closest('.drawer').find('.drawer-content').addClass('resizing').slideToggle(200, 'swing', async function () { - await delay(50); $(this).closest('.drawer-content').removeClass('resizing'); - }); - } - - // Set the height of "autoSetHeight" textareas within the drawer to their scroll height - if (!CSS.supports('field-sizing', 'content')) { - $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () { - await resetScrollHeight($(this)); - return; - }); - } - - } else if (drawerWasOpenAlready) { //to close manually - icon.toggleClass('closedIcon openIcon'); - - if (pinnedDrawerClicked) { - $(drawer).addClass('resizing').slideToggle(200, 'swing', async function () { - await delay(50); $(this).removeClass('resizing'); - }); - } - else { - $('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () { - await delay(50); $(this).closest('.drawer-content').removeClass('resizing'); - }); - } - - drawer.toggleClass('closedDrawer openDrawer'); - } - }); + $(document).on('click', '.drawer-opener', doDrawerOpenClick); + $('.drawer-toggle').on('click', doNavbarIconClick); $('html').on('touchstart mousedown', function (e) { var clickTarget = $(e.target); diff --git a/public/scripts/PromptManager.js b/public/scripts/PromptManager.js index 551b78328..00a34c86b 100644 --- a/public/scripts/PromptManager.js +++ b/public/scripts/PromptManager.js @@ -8,6 +8,7 @@ import { debounce, waitUntilCondition, escapeHtml } from './utils.js'; import { debounce_timeout } from './constants.js'; import { renderTemplateAsync } from './templates.js'; import { Popup } from './popup.js'; +import { t } from './i18n.js'; function debouncePromise(func, delay) { let timeoutId; @@ -455,7 +456,7 @@ class PromptManager { // Delete selected prompt from list form and close edit form this.handleDeletePrompt = async (event) => { - Popup.show.confirm('Are you sure you want to delete this prompt?', null).then((userChoice) => { + Popup.show.confirm(t`Are you sure you want to delete this prompt?`, null).then((userChoice) => { if (!userChoice) return; const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value; const prompt = this.getPromptById(promptID); @@ -531,7 +532,7 @@ class PromptManager { // Import prompts for the selected character this.handleImport = () => { - Popup.show.confirm('Existing prompts with the same ID will be overridden. Do you want to proceed?', null) + Popup.show.confirm(t`Existing prompts with the same ID will be overridden. Do you want to proceed?`, null) .then(userChoice => { if (!userChoice) return; @@ -552,7 +553,7 @@ class PromptManager { const data = JSON.parse(fileContent); this.import(data); } catch (err) { - toastr.error('An error occurred while importing prompts. More info available in console.'); + toastr.error(t`An error occurred while importing prompts. More info available in console.`); console.log('An error occurred while importing prompts'); console.log(err.toString()); } @@ -567,7 +568,7 @@ class PromptManager { // Restore default state of a characters prompt order this.handleCharacterReset = () => { - Popup.show.confirm('This will reset the prompt order for this character. You will not lose any prompts.', null) + Popup.show.confirm(t`This will reset the prompt order for this character. You will not lose any prompts.`, null) .then(userChoice => { if (!userChoice) return; @@ -1649,7 +1650,7 @@ class PromptManager { }; if (false === this.validateObject(controlObj, importData)) { - toastr.warning('Could not import prompts. Export failed validation.'); + toastr.warning(t`Could not import prompts. Export failed validation.`); return; } @@ -1672,7 +1673,7 @@ class PromptManager { throw new Error('Prompt order strategy not supported.'); } - toastr.success('Prompt import complete.'); + toastr.success(t`Prompt import complete.`); this.saveServiceSettings().then(() => this.render()); } diff --git a/public/scripts/authors-note.js b/public/scripts/authors-note.js index 31a5719e1..5e1cf44a3 100644 --- a/public/scripts/authors-note.js +++ b/public/scripts/authors-note.js @@ -16,6 +16,7 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; export { MODULE_NAME as NOTE_MODULE_NAME }; +import { t } from './i18n.js'; const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory @@ -37,7 +38,7 @@ const chara_note_position = { function setNoteTextCommand(_, text) { $('#extension_floating_prompt').val(text).trigger('input'); - toastr.success('Author\'s Note text updated'); + toastr.success(t`Author's Note text updated`); return ''; } @@ -45,12 +46,12 @@ function setNoteDepthCommand(_, text) { const value = Number(text); if (Number.isNaN(value)) { - toastr.error('Not a valid number'); + toastr.error(t`Not a valid number`); return; } $('#extension_floating_depth').val(Math.abs(value)).trigger('input'); - toastr.success('Author\'s Note depth updated'); + toastr.success(t`Author's Note depth updated`); return ''; } @@ -58,12 +59,12 @@ function setNoteIntervalCommand(_, text) { const value = Number(text); if (Number.isNaN(value)) { - toastr.error('Not a valid number'); + toastr.error(t`Not a valid number`); return; } $('#extension_floating_interval').val(Math.abs(value)).trigger('input'); - toastr.success('Author\'s Note frequency updated'); + toastr.success(t`Author's Note frequency updated`); return ''; } @@ -76,12 +77,12 @@ function setNotePositionCommand(_, text) { const position = validPositions[text?.trim()]; if (Number.isNaN(position)) { - toastr.error('Not a valid position'); + toastr.error(t`Not a valid position`); return; } $(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input'); - toastr.info('Author\'s Note position updated'); + toastr.info(t`Author's Note position updated`); return ''; } @@ -206,7 +207,7 @@ function onExtensionFloatingCharaPromptInput() { extension_settings.note.chara.push(tempCharaNote); } else { console.log('Character author\'s note error: No avatar name key could be found.'); - toastr.error('Something went wrong. Could not save character\'s author\'s note.'); + toastr.error(t`Something went wrong. Could not save character's author's note.`); // Don't save settings if something went wrong return; @@ -397,7 +398,7 @@ function onANMenuItemClick() { //because this listener takes priority $('#options').stop().fadeOut(animation_duration); } else { - toastr.warning('Select a character before trying to use Author\'s Note', '', { timeOut: 2000 }); + toastr.warning(t`Select a character before trying to use Author's Note`, '', { timeOut: 2000 }); } } diff --git a/public/scripts/chats.js b/public/scripts/chats.js index 75ad746b9..d8f1e8dad 100644 --- a/public/scripts/chats.js +++ b/public/scripts/chats.js @@ -40,6 +40,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { ScraperManager } from './scrapers.js'; import { DragAndDropHandler } from './dragdrop.js'; import { renderTemplateAsync } from './templates.js'; +import { t } from './i18n.js'; /** * @typedef {Object} FileAttachment @@ -206,7 +207,7 @@ export async function populateFileAttachment(message, inputId = 'file_form_input const fileText = await converter(file); base64Data = window.btoa(unescape(encodeURIComponent(fileText))); } catch (error) { - toastr.error(String(error), 'Could not convert file'); + toastr.error(String(error), t`Could not convert file`); console.error('Could not convert file', error); } } @@ -257,7 +258,7 @@ export async function uploadFileAttachment(fileName, base64Data) { const responseData = await result.json(); return responseData.path; } catch (error) { - toastr.error(String(error), 'Could not upload file'); + toastr.error(String(error), t`Could not upload file`); console.error('Could not upload file', error); } } @@ -283,7 +284,7 @@ export async function getFileAttachment(url) { const text = await result.text(); return text; } catch (error) { - toastr.error(error, 'Could not download file'); + toastr.error(error, t`Could not download file`); console.error('Could not download file', error); } } @@ -299,13 +300,13 @@ async function validateFile(file) { const isBinary = /^[\x00-\x08\x0E-\x1F\x7F-\xFF]*$/.test(fileText); if (!isImage && file.size > fileSizeLimit) { - toastr.error(`File is too big. Maximum size is ${humanFileSize(fileSizeLimit)}.`); + toastr.error(t`File is too big. Maximum size is ${humanFileSize(fileSizeLimit)}.`); return false; } // If file is binary if (isBinary && !isImage && !isConvertible(file.type)) { - toastr.error('Binary files are not supported. Select a text file or image.'); + toastr.error(t`Binary files are not supported. Select a text file or image.`); return false; } @@ -521,7 +522,7 @@ async function openExternalMediaOverridesDialog() { const entityId = getCurrentEntityId(); if (!entityId) { - toastr.info('No character or group selected'); + toastr.info(t`No character or group selected`); return; } @@ -646,7 +647,7 @@ async function deleteFileFromServer(url, silent = false) { await eventSource.emit(event_types.FILE_ATTACHMENT_DELETED, url); return true; } catch (error) { - toastr.error(String(error), 'Could not delete file'); + toastr.error(String(error), t`Could not delete file`); console.error('Could not delete file', error); return false; } @@ -1054,7 +1055,7 @@ async function openAttachmentManager() { const selectedAttachments = document.querySelectorAll('.attachmentListItemCheckboxContainer .attachmentListItemCheckbox:checked'); if (selectedAttachments.length === 0) { - toastr.info('No attachments selected.', 'Data Bank'); + toastr.info(t`No attachments selected.`, t`Data Bank`); return; } @@ -1168,7 +1169,7 @@ async function runScraper(scraperId, target, callback) { if (files.length === 0) { console.warn('Scraping returned no files'); - toastr.info('No files were scraped.', 'Data Bank'); + toastr.info(t`No files were scraped.`, t`Data Bank`); return; } @@ -1176,12 +1177,12 @@ async function runScraper(scraperId, target, callback) { await uploadFileAttachmentToServer(file, target); } - toastr.success(`Scraped ${files.length} files from ${scraperId} to ${target}.`, 'Data Bank'); + toastr.success(t`Scraped ${files.length} files from ${scraperId} to ${target}.`, t`Data Bank`); callback(); } catch (error) { console.error('Scraping failed', error); - toastr.error('Check browser console for details.', 'Scraping failed'); + toastr.error(t`Check browser console for details.`, t`Scraping failed`); } } @@ -1208,7 +1209,7 @@ export async function uploadFileAttachmentToServer(file, target) { const fileText = await converter(file); base64Data = window.btoa(unescape(encodeURIComponent(fileText))); } catch (error) { - toastr.error(String(error), 'Could not convert file'); + toastr.error(String(error), t`Could not convert file`); console.error('Could not convert file', error); } } else { diff --git a/public/scripts/extensions/regex/editor.html b/public/scripts/extensions/regex/editor.html index 5552504e1..aa207ffd9 100644 --- a/public/scripts/extensions/regex/editor.html +++ b/public/scripts/extensions/regex/editor.html @@ -132,7 +132,7 @@ Ephemerality -