first commit
This commit is contained in:
@ -0,0 +1,418 @@
|
||||
!(function(Date){
|
||||
'use strict';
|
||||
|
||||
var localNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
|
||||
/****************** Gregorian dates ********************/
|
||||
/** Constants used for time computations */
|
||||
Date.SECOND = 1000 /* milliseconds */;
|
||||
Date.MINUTE = 60 * Date.SECOND;
|
||||
Date.HOUR = 60 * Date.MINUTE;
|
||||
Date.DAY = 24 * Date.HOUR;
|
||||
Date.WEEK = 7 * Date.DAY;
|
||||
|
||||
/** MODIFY ONLY THE MARKED PARTS OF THE METHODS **/
|
||||
/************ START *************/
|
||||
/** INTERFACE METHODS FOR THE CALENDAR PICKER **/
|
||||
|
||||
/********************** *************************/
|
||||
/**************** SETTERS ***********************/
|
||||
/********************** *************************/
|
||||
|
||||
/** Sets the date for the current date without h/m/s. */
|
||||
Date.prototype.setLocalDateOnly = function (dateType, date) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
var tmp = new Date(date);
|
||||
this.setDate(1);
|
||||
this.setFullYear(tmp.getFullYear());
|
||||
this.setMonth(tmp.getMonth());
|
||||
this.setDate(tmp.getDate());
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the full date for the current date. */
|
||||
Date.prototype.setLocalDate = function (dateType, d) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
return this.setDate(d);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the month for the current date. */
|
||||
Date.prototype.setLocalMonth = function (dateType, m, d) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
if (d == undefined) this.getDate();
|
||||
return this.setMonth(m);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the year for the current date. */
|
||||
Date.prototype.setOtherFullYear = function(dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
var date = new Date(this);
|
||||
date.setFullYear(y);
|
||||
if (date.getMonth() != this.getMonth()) this.setDate(28);
|
||||
return this.setUTCFullYear(y);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the year for the current date. */
|
||||
Date.prototype.setLocalFullYear = function (dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
var date = new Date(this);
|
||||
date.setFullYear(y);
|
||||
if (date.getMonth() != this.getMonth()) this.setDate(28);
|
||||
return this.setFullYear(y);
|
||||
}
|
||||
};
|
||||
|
||||
/********************** *************************/
|
||||
/**************** GETTERS ***********************/
|
||||
/********************** *************************/
|
||||
|
||||
/** The number of days per week **/
|
||||
Date.prototype.getLocalWeekDays = function (dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return 6;
|
||||
} else {
|
||||
return 6; // 7 days per week
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the year for the current date. */
|
||||
Date.prototype.getOtherFullYear = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
return this.getFullYear();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the year for the current date. */
|
||||
Date.prototype.getLocalFullYear = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
return this.getFullYear();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the month the date. */
|
||||
Date.prototype.getLocalMonth = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
return this.getMonth();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the date. */
|
||||
Date.prototype.getLocalDate = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
return this.getDate();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of day in the year. */
|
||||
Date.prototype.getLocalDay = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
return '';
|
||||
} else {
|
||||
return this.getDay();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of days in the current month */
|
||||
Date.prototype.getLocalMonthDays = function(dateType, month) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
var year = this.getFullYear();
|
||||
if (typeof month == "undefined") {
|
||||
month = this.getMonth();
|
||||
}
|
||||
if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
|
||||
return 29;
|
||||
} else {
|
||||
return [31,28,31,30,31,30,31,31,30,31,30,31][month];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the week number for the current date. */
|
||||
Date.prototype.getLocalWeekNumber = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
|
||||
var DoW = d.getDay();
|
||||
d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
|
||||
var ms = d.valueOf(); // GMT
|
||||
d.setMonth(0);
|
||||
d.setDate(4); // Thu in Week 1
|
||||
return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of day in the year. */
|
||||
Date.prototype.getLocalDayOfYear = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
return '';
|
||||
} else {
|
||||
var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
|
||||
var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
|
||||
var time = now - then;
|
||||
return Math.floor(time / Date.DAY);
|
||||
}
|
||||
};
|
||||
|
||||
/** Checks date and time equality */
|
||||
Date.prototype.equalsTo = function(date) {
|
||||
return ((this.getFullYear() == date.getFullYear()) &&
|
||||
(this.getMonth() == date.getMonth()) &&
|
||||
(this.getDate() == date.getDate()) &&
|
||||
(this.getHours() == date.getHours()) &&
|
||||
(this.getMinutes() == date.getMinutes()));
|
||||
};
|
||||
|
||||
/** Converts foreign date to gregorian date. */
|
||||
Date.localCalToGregorian = function(y, m, d) {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return'';
|
||||
};
|
||||
|
||||
/** Converts gregorian date to foreign date. */
|
||||
Date.gregorianToLocalCal = function(y, m, d) {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
};
|
||||
|
||||
/** Method to convert numbers to local symbols. */
|
||||
Date.convertNumbers = function(str) {
|
||||
str = str.toString();
|
||||
|
||||
for (var i = 0, l = localNumbers.length; i < l; i++) {
|
||||
str = str.replace(new RegExp(i, 'g'), localNumbers[i]);
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
/** Translates to english numbers a string. */
|
||||
Date.toEnglish = function(str) {
|
||||
str = this.toString();
|
||||
var nums = [0,1,2,3,4,5,6,7,8,9];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
str = str.replace(new RegExp(nums[i], 'g'), i);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
/** Order the months from Gergorian to the calendar order */
|
||||
Date.monthsToLocalOrder = function(months) {
|
||||
return months;
|
||||
};
|
||||
|
||||
/** INTERFACE METHODS FOR THE CALENDAR PICKER **/
|
||||
/************* END **************/
|
||||
|
||||
/** Method to parse a string and return a date. **/
|
||||
Date.parseFieldDate = function(str, fmt, dateType, localStrings) {
|
||||
if (dateType != 'gregorian')
|
||||
str = Date.toEnglish(str);
|
||||
|
||||
var today = new Date();
|
||||
var y = 0;
|
||||
var m = -1;
|
||||
var d = 0;
|
||||
var a = str.split(/\W+/);
|
||||
var b = fmt.match(/%./g);
|
||||
var i = 0, j = 0;
|
||||
var hr = 0;
|
||||
var min = 0;
|
||||
var sec = 0;
|
||||
for (i = 0; i < a.length; ++i) {
|
||||
if (!a[i])
|
||||
continue;
|
||||
switch (b[i]) {
|
||||
case "%d":
|
||||
case "%e":
|
||||
d = parseInt(a[i], 10);
|
||||
break;
|
||||
|
||||
case "%m":
|
||||
m = parseInt(a[i], 10) - 1;
|
||||
break;
|
||||
|
||||
case "%Y":
|
||||
case "%y":
|
||||
y = parseInt(a[i], 10);
|
||||
(y < 100) && (y += (y > 29) ? 1900 : 2000);
|
||||
break;
|
||||
|
||||
case "%b":
|
||||
case "%B":
|
||||
for (j = 0; j < 12; ++j) {
|
||||
if (localStrings.months[j].substr(0, a[i].length).toLowerCase() === a[i].toLowerCase()) {
|
||||
m = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "%H":
|
||||
case "%I":
|
||||
case "%k":
|
||||
case "%l":
|
||||
hr = parseInt(a[i], 10);
|
||||
break;
|
||||
|
||||
case "%P":
|
||||
case "%p":
|
||||
if (/pm/i.test(a[i]) && hr < 12)
|
||||
hr += 12;
|
||||
else if (/am/i.test(a[i]) && hr >= 12)
|
||||
hr -= 12;
|
||||
break;
|
||||
|
||||
case "%M":
|
||||
min = parseInt(a[i], 10);
|
||||
break;
|
||||
case "%S":
|
||||
sec = parseInt(a[i], 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isNaN(y)) y = today.getFullYear();
|
||||
if (isNaN(m)) m = today.getMonth();
|
||||
if (isNaN(d)) d = today.getDate();
|
||||
if (isNaN(hr)) hr = today.getHours();
|
||||
if (isNaN(min)) min = today.getMinutes();
|
||||
if (isNaN(sec)) sec = today.getSeconds();
|
||||
if (y != 0 && m != -1 && d != 0)
|
||||
return new Date(y, m, d, hr, min, sec);
|
||||
y = 0; m = -1; d = 0;
|
||||
for (i = 0; i < a.length; ++i) {
|
||||
if (a[i].search(/[a-zA-Z]+/) != -1) {
|
||||
var t = -1;
|
||||
for (j = 0; j < 12; ++j) {
|
||||
if (localStrings.months[j].substr(0, a[i].length).toLowerCase() === a[i].toLowerCase()) {
|
||||
t = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (t != -1) {
|
||||
if (m != -1) {
|
||||
d = m+1;
|
||||
}
|
||||
m = t;
|
||||
}
|
||||
} else if (parseInt(a[i], 10) <= 12 && m == -1) {
|
||||
m = a[i]-1;
|
||||
} else if (parseInt(a[i], 10) > 31 && y == 0) {
|
||||
y = parseInt(a[i], 10);
|
||||
(y < 100) && (y += (y > 29) ? 1900 : 2000);
|
||||
} else if (d == 0) {
|
||||
d = a[i];
|
||||
}
|
||||
}
|
||||
if (y == 0)
|
||||
y = today.getFullYear();
|
||||
if (m != -1 && d != 0)
|
||||
return new Date(y, m, d, hr, min, sec);
|
||||
return today;
|
||||
};
|
||||
|
||||
/** Prints the date in a string according to the given format. */
|
||||
Date.prototype.print = function (str, dateType, translate, localStrings) {
|
||||
/** Handle calendar type **/
|
||||
if (typeof dateType !== 'string') str = '';
|
||||
if (!dateType) dateType = 'gregorian';
|
||||
|
||||
/** Handle wrong format **/
|
||||
if (typeof str !== 'string') str = '';
|
||||
if (!str) return '';
|
||||
|
||||
if (this.getLocalDate(dateType) == 'NaN' || !this.getLocalDate(dateType)) return '';
|
||||
var m = this.getLocalMonth(dateType);
|
||||
var d = this.getLocalDate(dateType);
|
||||
var y = this.getLocalFullYear(dateType);
|
||||
var wn = this.getLocalWeekNumber(dateType);
|
||||
var w = this.getDay();
|
||||
var s = {};
|
||||
var hr = this.getHours();
|
||||
var pm = (hr >= 12);
|
||||
var ir = (pm) ? (hr - 12) : hr;
|
||||
var dy = this.getLocalDayOfYear(dateType);
|
||||
if (ir == 0)
|
||||
ir = 12;
|
||||
var min = this.getMinutes();
|
||||
var sec = this.getSeconds();
|
||||
s["%a"] = localStrings.shortDays[w]; // abbreviated weekday name
|
||||
s["%A"] = localStrings.days[w]; // full weekday name
|
||||
s["%b"] = localStrings.shortMonths[m]; // abbreviated month name
|
||||
s["%B"] = localStrings.months[m]; // full month name
|
||||
// FIXME: %c : preferred date and time representation for the current locale
|
||||
s["%C"] = 1 + Math.floor(y / 100); // the century number
|
||||
s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
|
||||
s["%e"] = d; // the day of the month (range 1 to 31)
|
||||
// FIXME: %D : american date style: %m/%d/%y
|
||||
// FIXME: %E, %F, %G, %g, %h (man strftime)
|
||||
s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
|
||||
s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
|
||||
s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
|
||||
s["%k"] = hr; // hour, range 0 to 23 (24h format)
|
||||
s["%l"] = ir; // hour, range 1 to 12 (12h format)
|
||||
s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
|
||||
s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
|
||||
s["%n"] = "\n"; // a newline character
|
||||
s["%p"] = pm ? localStrings.pm.toUpperCase() : localStrings.am.toUpperCase();
|
||||
s["%P"] = pm ? localStrings.pm : localStrings.am;
|
||||
// FIXME: %r : the time in am/pm notation %I:%M:%S %p
|
||||
// FIXME: %R : the time in 24-hour notation %H:%M
|
||||
s["%s"] = Math.floor(this.getTime() / 1000);
|
||||
s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
|
||||
s["%t"] = "\t"; // a tab character
|
||||
// FIXME: %T : the time in 24-hour notation (%H:%M:%S)
|
||||
s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
|
||||
s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
|
||||
s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
|
||||
// FIXME: %x : preferred date representation for the current locale without the time
|
||||
// FIXME: %X : preferred time representation for the current locale without the date
|
||||
s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
|
||||
s["%Y"] = y; // year with the century
|
||||
s["%%"] = "%"; // a literal '%' character
|
||||
|
||||
var re = /%./g;
|
||||
|
||||
var tmpDate = str.replace(re, function (par) { return s[par] || par; });
|
||||
if (dateType != 'gregorian' && translate) {
|
||||
tmpDate = Date.convertNumbers(tmpDate);
|
||||
}
|
||||
|
||||
return tmpDate;
|
||||
};
|
||||
})(Date);
|
||||
1
media/system/js/fields/calendar-locales/date/gregorian/date-helper.min.js
vendored
Normal file
1
media/system/js/fields/calendar-locales/date/gregorian/date-helper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1,658 @@
|
||||
!(function(Date){
|
||||
'use strict';
|
||||
|
||||
var localNumbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
||||
|
||||
/** BEGIN: DATE OBJECT PATCHES **/
|
||||
/** Adds the number of days array to the Date object. */
|
||||
Date.gregorian_MD = [31,28,31,30,31,30,31,31,30,31,30,31];
|
||||
Date.local_MD = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
|
||||
|
||||
/** Constants used for time computations */
|
||||
Date.SECOND = 1000 /* milliseconds */;
|
||||
Date.MINUTE = 60 * Date.SECOND;
|
||||
Date.HOUR = 60 * Date.MINUTE;
|
||||
Date.DAY = 24 * Date.HOUR;
|
||||
Date.WEEK = 7 * Date.DAY;
|
||||
|
||||
/** MODIFY ONLY THE MARKED PARTS OF THE METHODS **/
|
||||
/************ START *************/
|
||||
/** INTERFACE METHODS FOR THE CALENDAR PICKER **/
|
||||
|
||||
/********************** *************************/
|
||||
/**************** SETTERS ***********************/
|
||||
/********************** *************************/
|
||||
|
||||
/** Sets the date for the current date without h/m/s. */
|
||||
Date.prototype.setLocalDateOnly = function (dateType, date) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
var tmp = new Date(date);
|
||||
this.setDate(1);
|
||||
this.setFullYear(tmp.getFullYear());
|
||||
this.setMonth(tmp.getMonth());
|
||||
this.setDate(tmp.getDate());
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the full date for the current date. */
|
||||
Date.prototype.setLocalDate = function (dateType, d) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.setJalaliDate(d);
|
||||
} else {
|
||||
return this.setDate(d);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the month for the current date. */
|
||||
Date.prototype.setLocalMonth = function (dateType, m, d) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.setJalaliMonth(m, d);
|
||||
} else {
|
||||
if (d == undefined) this.getDate();
|
||||
return this.setMonth(m);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the year for the current date. */
|
||||
Date.prototype.setOtherFullYear = function(dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
var date = new Date(this);
|
||||
date.setLocalFullYear(y);
|
||||
if (date.getLocalMonth('jalali') != this.getLocalMonth('jalali')) this.setLocalDate('jalali', 29);
|
||||
return this.setLocalFullYear('jalali', y);
|
||||
} else {
|
||||
var date = new Date(this);
|
||||
date.setFullYear(y);
|
||||
if (date.getMonth() != this.getMonth()) this.setDate(28);
|
||||
return this.setUTCFullYear(y);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the year for the current date. */
|
||||
Date.prototype.setLocalFullYear = function (dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.setJalaliFullYear(y);
|
||||
} else {
|
||||
var date = new Date(this);
|
||||
date.setFullYear(y);
|
||||
if (date.getMonth() != this.getMonth()) this.setDate(28);
|
||||
return this.setFullYear(y);
|
||||
}
|
||||
};
|
||||
|
||||
/********************** *************************/
|
||||
/**************** GETTERS ***********************/
|
||||
/********************** *************************/
|
||||
|
||||
/** The number of days per week **/
|
||||
Date.prototype.getLocalWeekDays = function (dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return 6;
|
||||
} else {
|
||||
return 6; // 7 days per week
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the year for the current date. */
|
||||
Date.prototype.getOtherFullYear = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.getJalaliFullYear();
|
||||
} else {
|
||||
return this.getFullYear();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the year for the current date. */
|
||||
Date.prototype.getLocalFullYear = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.getJalaliFullYear();
|
||||
} else {
|
||||
return this.getFullYear();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the month the date. */
|
||||
Date.prototype.getLocalMonth = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.getJalaliMonth();
|
||||
} else {
|
||||
return this.getMonth();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the date. */
|
||||
Date.prototype.getLocalDate = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.getJalaliDate();
|
||||
} else {
|
||||
return this.getDate();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of day in the year. */
|
||||
Date.prototype.getLocalDay = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
return this.getJalaliDay();
|
||||
} else {
|
||||
return this.getDay();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of days in the current month */
|
||||
Date.prototype.getLocalMonthDays = function(dateType, month) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
var year = this.getLocalFullYear('jalali');
|
||||
if (typeof month == "undefined") {
|
||||
month = this.getLocalMonth('jalali');
|
||||
}
|
||||
if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
|
||||
return 29;
|
||||
} else {
|
||||
return Date.local_MD[month];
|
||||
}
|
||||
} else {
|
||||
var year = this.getFullYear();
|
||||
if (typeof month == "undefined") {
|
||||
month = this.getMonth();
|
||||
}
|
||||
if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
|
||||
return 29;
|
||||
} else {
|
||||
return Date.gregorian_MD[month];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the week number for the current date. */
|
||||
Date.prototype.getLocalWeekNumber = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
|
||||
var DoW = d.getDay();
|
||||
d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
|
||||
var ms = d.valueOf(); // GMT
|
||||
d.setMonth(0);
|
||||
d.setDate(4); // Thu in Week 1
|
||||
return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
|
||||
} else {
|
||||
var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
|
||||
var DoW = d.getDay();
|
||||
d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
|
||||
var ms = d.valueOf(); // GMT
|
||||
d.setMonth(0);
|
||||
d.setDate(4); // Thu in Week 1
|
||||
return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of day in the year. */
|
||||
Date.prototype.getLocalDayOfYear = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
var now = new Date(this.getOtherFullYear(dateType), this.getLocalMonth(dateType), this.getLocalDate(dateType), 0, 0, 0);
|
||||
var then = new Date(this.getOtherFullYear(dateType), 0, 0, 0, 0, 0);
|
||||
var time = now - then;
|
||||
return Math.floor(time / Date.DAY);
|
||||
} else {
|
||||
var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
|
||||
var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
|
||||
var time = now - then;
|
||||
return Math.floor(time / Date.DAY);
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of days in the current month */
|
||||
Date.prototype.getMonthDays = function(month) {
|
||||
var year = this.getFullYear();
|
||||
if (typeof month == "undefined") {
|
||||
month = this.getMonth();
|
||||
}
|
||||
if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
|
||||
return 29;
|
||||
} else {
|
||||
if (Date.dateType != 'gregorian') {
|
||||
return Date.local_MD[month];
|
||||
} else {
|
||||
return Date.gregorian_MD[month];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Checks date and time equality */
|
||||
Date.prototype.equalsTo = function(date) {
|
||||
return ((this.getFullYear() == date.getFullYear()) &&
|
||||
(this.getMonth() == date.getMonth()) &&
|
||||
(this.getDate() == date.getDate()) &&
|
||||
(this.getHours() == date.getHours()) &&
|
||||
(this.getMinutes() == date.getMinutes()));
|
||||
};
|
||||
|
||||
/** Converts foreign date to gregorian date. */
|
||||
Date.localCalToGregorian = function(y, m, d) {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return JalaliDate.jalaliToGregorian(y, m, d);
|
||||
};
|
||||
|
||||
/** Converts gregorian date to foreign date. */
|
||||
Date.gregorianToLocalCal = function(y, m, d) {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return JalaliDate.gregorianToJalali(y, m, d);
|
||||
};
|
||||
|
||||
/** Method to convert numbers from local symbols to English numbers. */
|
||||
Date.numbersToIso = function(str) {
|
||||
var i, nums = [0,1,2,3,4,5,6,7,8,9];
|
||||
str = str.toString();
|
||||
|
||||
|
||||
for (i = 0; i < nums.length; i++) {
|
||||
str = str.replace(new RegExp(localNumbers[i], 'g'), nums[i]);
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
/** Method to convert numbers to local symbols. */
|
||||
Date.convertNumbers = function(str) {
|
||||
str = str.toString();
|
||||
|
||||
for (var i = 0, l = localNumbers.length; i < l; i++) {
|
||||
str = str.replace(new RegExp(i, 'g'), localNumbers[i]);
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
/** Translates to english numbers a string. */
|
||||
Date.toEnglish = function(str) {
|
||||
str = this.toString();
|
||||
var nums = [0,1,2,3,4,5,6,7,8,9];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
str = str.replace(new RegExp(nums[i], 'g'), i);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
/** Order the days from Gergorian to calendar order */
|
||||
Date.monthsToLocalOrder = function(months, dateType) {
|
||||
if (dateType === 'jalali'){
|
||||
months.push(months.shift()); // January to the end
|
||||
months.push(months.shift()); // February to the end
|
||||
|
||||
return months;
|
||||
} else {
|
||||
return months;
|
||||
}
|
||||
};
|
||||
|
||||
/** INTERFACE METHODS FOR THE CALENDAR PICKER **/
|
||||
/************* END **************/
|
||||
|
||||
/** Prints the date in a string according to the given format. */
|
||||
Date.prototype.print = function (str, dateType, translate, localStrings) {
|
||||
/** Handle calendar type **/
|
||||
if (typeof dateType !== 'string') str = '';
|
||||
if (!dateType) dateType = 'gregorian';
|
||||
|
||||
/** Handle wrong format **/
|
||||
if (typeof str !== 'string') str = '';
|
||||
if (!str) return '';
|
||||
|
||||
|
||||
if (this.getLocalDate(dateType) == 'NaN' || !this.getLocalDate(dateType)) return '';
|
||||
var m = this.getLocalMonth(dateType);
|
||||
var d = this.getLocalDate(dateType);
|
||||
var y = this.getLocalFullYear(dateType);
|
||||
var wn = this.getLocalWeekNumber(dateType);
|
||||
var w = this.getLocalDay(dateType);
|
||||
var s = {};
|
||||
var hr = this.getHours();
|
||||
var pm = (hr >= 12);
|
||||
var ir = (pm) ? (hr - 12) : hr;
|
||||
var dy = this.getLocalDayOfYear(dateType);
|
||||
if (ir == 0)
|
||||
ir = 12;
|
||||
var min = this.getMinutes();
|
||||
var sec = this.getSeconds();
|
||||
s["%a"] = localStrings.shortDays[w]; // abbreviated weekday name
|
||||
s["%A"] = localStrings.days[w]; // full weekday name
|
||||
s["%b"] = localStrings.shortMonths[m]; // abbreviated month name
|
||||
s["%B"] = localStrings.months[m]; // full month name
|
||||
// FIXME: %c : preferred date and time representation for the current locale
|
||||
s["%C"] = 1 + Math.floor(y / 100); // the century number
|
||||
s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
|
||||
s["%e"] = d; // the day of the month (range 1 to 31)
|
||||
// FIXME: %D : american date style: %m/%d/%y
|
||||
// FIXME: %E, %F, %G, %g, %h (man strftime)
|
||||
s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
|
||||
s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
|
||||
s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
|
||||
s["%k"] = hr; // hour, range 0 to 23 (24h format)
|
||||
s["%l"] = ir; // hour, range 1 to 12 (12h format)
|
||||
s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
|
||||
s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
|
||||
s["%n"] = "\n"; // a newline character
|
||||
s["%p"] = pm ? localStrings.pm.toUpperCase() : localStrings.am.toUpperCase();
|
||||
s["%P"] = pm ? localStrings.pm : localStrings.am;
|
||||
// FIXME: %r : the time in am/pm notation %I:%M:%S %p
|
||||
// FIXME: %R : the time in 24-hour notation %H:%M
|
||||
s["%s"] = Math.floor(this.getTime() / 1000);
|
||||
s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
|
||||
s["%t"] = "\t"; // a tab character
|
||||
// FIXME: %T : the time in 24-hour notation (%H:%M:%S)
|
||||
s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
|
||||
s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
|
||||
s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
|
||||
// FIXME: %x : preferred date representation for the current locale without the time
|
||||
// FIXME: %X : preferred time representation for the current locale without the date
|
||||
s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
|
||||
s["%Y"] = y; // year with the century
|
||||
s["%%"] = "%"; // a literal '%' character
|
||||
|
||||
var re = /%./g;
|
||||
|
||||
var tmpDate = str.replace(re, function (par) { return s[par] || par; });
|
||||
if (translate) {
|
||||
tmpDate = Date.convertNumbers(tmpDate);
|
||||
}
|
||||
|
||||
return tmpDate;
|
||||
};
|
||||
|
||||
Date.parseFieldDate = function(str, fmt, dateType, localStrings) {
|
||||
str = Date.numbersToIso(str);
|
||||
|
||||
var today = new Date();
|
||||
var y = 0;
|
||||
var m = -1;
|
||||
var d = 0;
|
||||
var a = str.split(/\W+/);
|
||||
var b = fmt.match(/%./g);
|
||||
var i = 0, j = 0;
|
||||
var hr = 0;
|
||||
var min = 0;
|
||||
var sec = 0;
|
||||
for (i = 0; i < a.length; ++i) {
|
||||
if (!a[i])
|
||||
continue;
|
||||
switch (b[i]) {
|
||||
case "%d":
|
||||
case "%e":
|
||||
d = parseInt(a[i], 10);
|
||||
break;
|
||||
|
||||
case "%m":
|
||||
m = parseInt(a[i], 10) - 1;
|
||||
break;
|
||||
|
||||
case "%Y":
|
||||
case "%y":
|
||||
y = parseInt(a[i], 10);
|
||||
(y < 100) && (y += (y > 29) ? 1900 : 2000);
|
||||
break;
|
||||
|
||||
case "%b":
|
||||
case "%B":
|
||||
for (j = 0; j < 12; ++j) {
|
||||
if (localStrings.months[j].substr(0, a[i].length).toLowerCase() === a[i].toLowerCase()) { m = j; break; }
|
||||
}
|
||||
break;
|
||||
|
||||
case "%H":
|
||||
case "%I":
|
||||
case "%k":
|
||||
case "%l":
|
||||
hr = parseInt(a[i], 10);
|
||||
break;
|
||||
|
||||
case "%P":
|
||||
case "%p":
|
||||
if (/pm/i.test(a[i]) && hr < 12)
|
||||
hr += 12;
|
||||
else if (/am/i.test(a[i]) && hr >= 12)
|
||||
hr -= 12;
|
||||
break;
|
||||
|
||||
case "%M":
|
||||
min = parseInt(a[i], 10);
|
||||
break;
|
||||
case "%S":
|
||||
sec = parseInt(a[i], 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isNaN(y)) y = today.getFullYear();
|
||||
if (isNaN(m)) m = today.getMonth();
|
||||
if (isNaN(d)) d = today.getDate();
|
||||
if (isNaN(hr)) hr = today.getHours();
|
||||
if (isNaN(min)) min = today.getMinutes();
|
||||
if (y != 0 && m != -1 && d != 0)
|
||||
return new Date(y, m, d, hr, min, 0);
|
||||
y = 0; m = -1; d = 0;
|
||||
for (i = 0; i < a.length; ++i) {
|
||||
if (a[i].search(/[a-zA-Z]+/) != -1) {
|
||||
var t = -1;
|
||||
for (j = 0; j < 12; ++j) {
|
||||
if (localStrings.months[j].substr(0, a[i].length).toLowerCase() === a[i].toLowerCase()) { t = j; break; }
|
||||
}
|
||||
if (t != -1) {
|
||||
if (m != -1) {
|
||||
d = m+1;
|
||||
}
|
||||
m = t;
|
||||
}
|
||||
} else if (parseInt(a[i], 10) <= 12 && m == -1) {
|
||||
m = a[i]-1;
|
||||
} else if (parseInt(a[i], 10) > 31 && y == 0) {
|
||||
y = parseInt(a[i], 10);
|
||||
(y < 100) && (y += (y > 29) ? 1900 : 2000);
|
||||
} else if (d == 0) {
|
||||
d = a[i];
|
||||
}
|
||||
}
|
||||
if (y == 0)
|
||||
y = today.getFullYear();
|
||||
if (m != -1 && d != 0)
|
||||
return new Date(y, m, d, hr, min, 0);
|
||||
return today;
|
||||
};
|
||||
|
||||
/*
|
||||
* JalaliJSCalendar - Jalali Extension for Date Object
|
||||
* Copyright (c) 2008 Ali Farhadi (http://farhadi.ir/)
|
||||
* Released under the terms of the GNU General Public License.
|
||||
* See the GPL for details (http://www.gnu.org/licenses/gpl.html).
|
||||
*
|
||||
* Based on code from http://farsiweb.info
|
||||
*/
|
||||
|
||||
var JalaliDate = {
|
||||
g_days_in_month: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||
j_days_in_month: [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]
|
||||
};
|
||||
|
||||
JalaliDate.jalaliToGregorian = function(j_y, j_m, j_d)
|
||||
{
|
||||
j_y = parseInt(j_y);
|
||||
j_m = parseInt(j_m);
|
||||
j_d = parseInt(j_d);
|
||||
var jy = j_y-979;
|
||||
var jm = j_m-1;
|
||||
var jd = j_d-1;
|
||||
|
||||
var j_day_no = 365*jy + parseInt(jy / 33)*8 + parseInt((jy%33+3) / 4);
|
||||
for (var i=0; i < jm; ++i) j_day_no += JalaliDate.j_days_in_month[i];
|
||||
|
||||
j_day_no += jd;
|
||||
|
||||
var g_day_no = j_day_no+79;
|
||||
|
||||
var gy = 1600 + 400 * parseInt(g_day_no / 146097); /* 146097 = 365*400 + 400/4 - 400/100 + 400/400 */
|
||||
g_day_no = g_day_no % 146097;
|
||||
|
||||
var leap = true;
|
||||
if (g_day_no >= 36525) /* 36525 = 365*100 + 100/4 */
|
||||
{
|
||||
g_day_no--;
|
||||
gy += 100*parseInt(g_day_no/ 36524); /* 36524 = 365*100 + 100/4 - 100/100 */
|
||||
g_day_no = g_day_no % 36524;
|
||||
|
||||
if (g_day_no >= 365)
|
||||
g_day_no++;
|
||||
else
|
||||
leap = false;
|
||||
}
|
||||
|
||||
gy += 4*parseInt(g_day_no/ 1461); /* 1461 = 365*4 + 4/4 */
|
||||
g_day_no %= 1461;
|
||||
|
||||
if (g_day_no >= 366) {
|
||||
leap = false;
|
||||
|
||||
g_day_no--;
|
||||
gy += parseInt(g_day_no/ 365);
|
||||
g_day_no = g_day_no % 365;
|
||||
}
|
||||
|
||||
for (var i = 0; g_day_no >= JalaliDate.g_days_in_month[i] + (i == 1 && leap); i++)
|
||||
g_day_no -= JalaliDate.g_days_in_month[i] + (i == 1 && leap);
|
||||
var gm = i+1;
|
||||
var gd = g_day_no+1;
|
||||
|
||||
return [gy, gm, gd];
|
||||
};
|
||||
|
||||
JalaliDate.checkDate = function(j_y, j_m, j_d)
|
||||
{
|
||||
return !(j_y < 0 || j_y > 32767 || j_m < 1 || j_m > 12 || j_d < 1 || j_d >
|
||||
(JalaliDate.j_days_in_month[j_m-1] + (j_m == 12 && !((j_y-979)%33%4))));
|
||||
};
|
||||
|
||||
JalaliDate.gregorianToJalali = function(g_y, g_m, g_d)
|
||||
{
|
||||
g_y = parseInt(g_y);
|
||||
g_m = parseInt(g_m);
|
||||
g_d = parseInt(g_d);
|
||||
var gy = g_y-1600;
|
||||
var gm = g_m-1;
|
||||
var gd = g_d-1;
|
||||
|
||||
var g_day_no = 365*gy+parseInt((gy+3) / 4)-parseInt((gy+99)/100)+parseInt((gy+399)/400);
|
||||
|
||||
for (var i=0; i < gm; ++i)
|
||||
g_day_no += JalaliDate.g_days_in_month[i];
|
||||
if (gm>1 && ((gy%4==0 && gy%100!=0) || (gy%400==0)))
|
||||
/* leap and after Feb */
|
||||
++g_day_no;
|
||||
g_day_no += gd;
|
||||
|
||||
var j_day_no = g_day_no-79;
|
||||
|
||||
var j_np = parseInt(j_day_no/ 12053);
|
||||
j_day_no %= 12053;
|
||||
|
||||
var jy = 979+33*j_np+4*parseInt(j_day_no/1461);
|
||||
|
||||
j_day_no %= 1461;
|
||||
|
||||
if (j_day_no >= 366) {
|
||||
jy += parseInt((j_day_no-1)/ 365);
|
||||
j_day_no = (j_day_no-1)%365;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 11 && j_day_no >= JalaliDate.j_days_in_month[i]; ++i) {
|
||||
j_day_no -= JalaliDate.j_days_in_month[i];
|
||||
}
|
||||
var jm = i+1;
|
||||
var jd = j_day_no+1;
|
||||
|
||||
|
||||
return [jy, jm, jd];
|
||||
};
|
||||
|
||||
Date.prototype.setJalaliFullYear = function(y, m, d) {
|
||||
var gd = this.getDate();
|
||||
var gm = this.getMonth();
|
||||
var gy = this.getFullYear();
|
||||
var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
|
||||
if (y < 100) y += 1300;
|
||||
j[0] = y;
|
||||
if (m != undefined) {
|
||||
if (m > 11) {
|
||||
j[0] += Math.floor(m / 12);
|
||||
m = m % 12;
|
||||
}
|
||||
j[1] = m + 1;
|
||||
}
|
||||
if (d != undefined) j[2] = d;
|
||||
var g = JalaliDate.jalaliToGregorian(j[0], j[1], j[2]);
|
||||
return this.setFullYear(g[0], g[1]-1, g[2]);
|
||||
};
|
||||
|
||||
Date.prototype.setJalaliMonth = function(m, d) {
|
||||
var gd = this.getDate();
|
||||
var gm = this.getMonth();
|
||||
var gy = this.getFullYear();
|
||||
var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
|
||||
if (m > 11) {
|
||||
j[0] += Math.floor(m / 12);
|
||||
m = m % 12;
|
||||
}
|
||||
j[1] = m+1;
|
||||
if (d != undefined) j[2] = d;
|
||||
var g = JalaliDate.jalaliToGregorian(j[0], j[1], j[2]);
|
||||
return this.setFullYear(g[0], g[1]-1, g[2]);
|
||||
};
|
||||
|
||||
Date.prototype.setJalaliDate = function(d) {
|
||||
var gd = this.getDate();
|
||||
var gm = this.getMonth();
|
||||
var gy = this.getFullYear();
|
||||
var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
|
||||
j[2] = d;
|
||||
var g = JalaliDate.jalaliToGregorian(j[0], j[1], j[2]);
|
||||
return this.setFullYear(g[0], g[1]-1, g[2]);
|
||||
};
|
||||
|
||||
Date.prototype.getJalaliFullYear = function() {
|
||||
var gd = this.getDate();
|
||||
var gm = this.getMonth();
|
||||
var gy = this.getFullYear();
|
||||
var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
|
||||
return j[0];
|
||||
};
|
||||
|
||||
Date.prototype.getJalaliMonth = function() {
|
||||
var gd = this.getDate();
|
||||
var gm = this.getMonth();
|
||||
var gy = this.getFullYear();
|
||||
var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
|
||||
return j[1]-1;
|
||||
};
|
||||
|
||||
Date.prototype.getJalaliDate = function() {
|
||||
var gd = this.getDate();
|
||||
var gm = this.getMonth();
|
||||
var gy = this.getFullYear();
|
||||
var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
|
||||
return j[2];
|
||||
};
|
||||
|
||||
Date.prototype.getJalaliDay = function() {
|
||||
var day = this.getDay();
|
||||
day = (day) % 7;
|
||||
return day;
|
||||
};
|
||||
|
||||
})(Date);
|
||||
1
media/system/js/fields/calendar-locales/date/jalali/date-helper.min.js
vendored
Normal file
1
media/system/js/fields/calendar-locales/date/jalali/date-helper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
1229
media/system/js/fields/calendar.js
Normal file
1229
media/system/js/fields/calendar.js
Normal file
File diff suppressed because it is too large
Load Diff
1
media/system/js/fields/calendar.min.js
vendored
Normal file
1
media/system/js/fields/calendar.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/calendar.min.js.gz
Normal file
BIN
media/system/js/fields/calendar.min.js.gz
Normal file
Binary file not shown.
38
media/system/js/fields/color-field-adv-init.js
Normal file
38
media/system/js/fields/color-field-adv-init.js
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
!(function(document, $) {
|
||||
"use strict";
|
||||
|
||||
function initMinicolorsField (event) {
|
||||
$(event.target).find('.minicolors').each(function() {
|
||||
$(this).minicolors({
|
||||
control: $(this).attr('data-control') || 'hue',
|
||||
format: $(this).attr('data-validate') === 'color'
|
||||
? 'hex'
|
||||
: ($(this).attr('data-format') === 'rgba'
|
||||
? 'rgb'
|
||||
: $(this).attr('data-format'))
|
||||
|| 'hex',
|
||||
keywords: $(this).attr('data-keywords') || '',
|
||||
opacity: $(this).attr('data-format') === 'rgba',
|
||||
position: $(this).attr('data-position') || 'default',
|
||||
swatches: $(this).attr('data-colors') ? $(this).attr('data-colors').split(",") : [],
|
||||
theme: 'bootstrap'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize at an initial page load
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", initMinicolorsField);
|
||||
|
||||
/**
|
||||
* Initialize when a part of the page was updated
|
||||
*/
|
||||
document.addEventListener("joomla:updated", initMinicolorsField);
|
||||
|
||||
})(document, jQuery);
|
||||
1
media/system/js/fields/color-field-adv-init.min.js
vendored
Normal file
1
media/system/js/fields/color-field-adv-init.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(t,a){"use strict";function o(t){a(t.target).find(".minicolors").each((function(){a(this).minicolors({control:a(this).attr("data-control")||"hue",format:"color"===a(this).attr("data-validate")?"hex":("rgba"===a(this).attr("data-format")?"rgb":a(this).attr("data-format"))||"hex",keywords:a(this).attr("data-keywords")||"",opacity:"rgba"===a(this).attr("data-format"),position:a(this).attr("data-position")||"default",swatches:a(this).attr("data-colors")?a(this).attr("data-colors").split(","):[],theme:"bootstrap"})}))}t.addEventListener("DOMContentLoaded",o),t.addEventListener("joomla:updated",o)}(document,jQuery);
|
||||
BIN
media/system/js/fields/color-field-adv-init.min.js.gz
Normal file
BIN
media/system/js/fields/color-field-adv-init.min.js.gz
Normal file
Binary file not shown.
599
media/system/js/fields/joomla-field-color-slider.js
Normal file
599
media/system/js/fields/joomla-field-color-slider.js
Normal file
@ -0,0 +1,599 @@
|
||||
/**
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["rgbToHex", "hslToRgb"] }] */
|
||||
|
||||
(document => {
|
||||
|
||||
/**
|
||||
* Regex for hex values e.g. #FF3929
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const hexRegex = /^#([a-z0-9]{1,2})([a-z0-9]{1,2})([a-z0-9]{1,2})$/i;
|
||||
|
||||
/**
|
||||
* Regex for rgb values e.g. rgba(255, 0, 24, 0.5);
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const rgbRegex = /^rgba?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)(?:[\D]+([0-9](?:.\d+)?))?\)$/i;
|
||||
|
||||
/**
|
||||
* Regex for hsl values e.g. hsl(255,0,24);
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const hslRegex = /^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$/i;
|
||||
|
||||
/**
|
||||
* Regex for saturation and lightness of hsl - only accepts 1 or 0 or 0.4 or 40
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const hslNumberRegex = /^(([0-1])|(0\\.[0-9]+)|([0-9]{1,2})|(100))$/;
|
||||
|
||||
/**
|
||||
* Regex for hue values - one to three numbers
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const hueRegex = /^[0-9]{1,3}$/;
|
||||
|
||||
/**
|
||||
* Creates a slider for the color values hue, saturation and light.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class JoomlaFieldColorSlider {
|
||||
/**
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
constructor(element) {
|
||||
// Elements
|
||||
this.messageSpan = element.querySelector('.form-control-feedback');
|
||||
this.mainInput = element.querySelector('.color-input');
|
||||
this.input = element.querySelector('#slider-input');
|
||||
this.sliders = element.querySelectorAll('.color-slider');
|
||||
this.hueSlider = element.querySelector('#hue-slider');
|
||||
this.saturationSlider = element.querySelector('#saturation-slider');
|
||||
this.lightSlider = element.querySelector('#light-slider');
|
||||
this.alphaSlider = element.querySelector('#alpha-slider');
|
||||
|
||||
// Attributes
|
||||
this.color = element.dataset.color || '';
|
||||
this.default = element.dataset.default || '';
|
||||
this.format = this.input.dataset.format || 'hex';
|
||||
this.saveFormat = this.mainInput.dataset.format || 'hex';
|
||||
this.preview = element.dataset.preview === 'true';
|
||||
this.setAlpha = this.format === 'hsla' || this.format === 'rgba';
|
||||
this.hue = 360;
|
||||
this.saturation = 1;
|
||||
this.light = 1;
|
||||
this.alpha = 1;
|
||||
this.defaultHsl = [this.hue, this.saturation, this.light, this.alpha];
|
||||
this.setInitValue();
|
||||
this.setBackground();
|
||||
|
||||
// Hide preview field, when selected value should not be visible
|
||||
if (!this.preview) {
|
||||
this.input.classList.add('hidden');
|
||||
} else {
|
||||
this.setInputPattern();
|
||||
}
|
||||
|
||||
// Always hide main input field (value saved in database)
|
||||
this.mainInput.classList.add('hidden');
|
||||
Array.prototype.forEach.call(this.sliders, slider => {
|
||||
slider.addEventListener('change', () => this.updateValue(slider));
|
||||
});
|
||||
this.input.addEventListener('change', () => this.changeInput(this.input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selected value into input field and set it as its background-color.
|
||||
*/
|
||||
updateValue(slider) {
|
||||
this.showError('');
|
||||
const hsl = this.getSliderValueAsHsl(slider.value, slider.dataset.type);
|
||||
const rgb = this.hslToRgb(hsl);
|
||||
[this.hue, this.saturation, this.light, this.alpha] = hsl;
|
||||
this.input.style.border = `2px solid ${this.getRgbString(rgb)}`;
|
||||
this.setSliderValues(hsl, slider.dataset.type);
|
||||
this.setInputValue(hsl);
|
||||
this.setBackground(slider);
|
||||
}
|
||||
|
||||
/**
|
||||
* React on user changing input value
|
||||
*
|
||||
* @param {HTMLElement} inputField
|
||||
*/
|
||||
changeInput(inputField) {
|
||||
let hsl = [this.hue, this.saturation, this.light, this.alpha];
|
||||
if (!inputField.value) {
|
||||
this.mainInput.value = '';
|
||||
this.showError('');
|
||||
return;
|
||||
}
|
||||
if (!this.checkValue(inputField.value)) {
|
||||
this.showError('JFIELD_COLOR_ERROR_WRONG_FORMAT');
|
||||
this.setInputValue(this.defaultHsl);
|
||||
} else {
|
||||
this.showError('');
|
||||
switch (this.format) {
|
||||
case 'hue':
|
||||
hsl[0] = inputField.value;
|
||||
this.hue = inputField.value;
|
||||
break;
|
||||
case 'saturation':
|
||||
hsl[1] = inputField.value;
|
||||
this.saturation = inputField.value;
|
||||
break;
|
||||
case 'light':
|
||||
hsl[2] = inputField.value;
|
||||
this.light = inputField.value;
|
||||
break;
|
||||
case 'alpha':
|
||||
hsl[3] = inputField.value;
|
||||
this.alpha = inputField.value;
|
||||
break;
|
||||
default:
|
||||
hsl = this.getHsl(inputField.value);
|
||||
}
|
||||
this.setSliderValues(hsl);
|
||||
this.setInputValue(hsl, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check validity of value
|
||||
*
|
||||
* @param {number|string} value to check
|
||||
* @param {string=false} format for which the value gets tested
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkValue(value, format) {
|
||||
const test = format || this.format;
|
||||
switch (test) {
|
||||
case 'hue':
|
||||
return value <= 360 && hueRegex.test(value);
|
||||
case 'saturation':
|
||||
case 'light':
|
||||
case 'alpha':
|
||||
return hslNumberRegex.test(value);
|
||||
case 'hsl':
|
||||
case 'hsla':
|
||||
return hslRegex.test(value);
|
||||
case 'hex':
|
||||
return hexRegex.test(value);
|
||||
case 'rgb':
|
||||
case 'rgba':
|
||||
return rgbRegex.test(value);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set validation pattern on input field
|
||||
*/
|
||||
setInputPattern() {
|
||||
let pattern;
|
||||
|
||||
// RegExp has '/' at start and end
|
||||
switch (this.format) {
|
||||
case 'hue':
|
||||
pattern = hueRegex.source.slice(1, -1);
|
||||
break;
|
||||
case 'saturation':
|
||||
case 'light':
|
||||
case 'alpha':
|
||||
pattern = hslNumberRegex.source.slice(1, -1);
|
||||
break;
|
||||
case 'hsl':
|
||||
case 'hsla':
|
||||
pattern = hslRegex.source.slice(1, -1);
|
||||
break;
|
||||
case 'rgb':
|
||||
pattern = rgbRegex.source.slice(1, -1);
|
||||
break;
|
||||
case 'hex':
|
||||
default:
|
||||
pattern = hexRegex.source.slice(1, -1);
|
||||
}
|
||||
this.input.setAttribute('pattern', pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set linear gradient for slider background
|
||||
* @param {HTMLInputElement} [exceptSlider]
|
||||
*/
|
||||
setBackground(exceptSlider) {
|
||||
Array.prototype.forEach.call(this.sliders, slider => {
|
||||
// Jump over changed slider
|
||||
if (exceptSlider === slider) {
|
||||
return;
|
||||
}
|
||||
let colors = [];
|
||||
let endValue = 100;
|
||||
|
||||
// Longer start color so slider selection matches displayed colors
|
||||
colors.push(this.getSliderValueAsRgb(0, slider.dataset.type));
|
||||
if (slider.dataset.type === 'hue') {
|
||||
const steps = Math.floor(360 / 20);
|
||||
endValue = 360;
|
||||
for (let i = 0; i <= 360; i += steps) {
|
||||
colors.push(this.getSliderValueAsRgb(i, slider.dataset.type));
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i <= 100; i += 10) {
|
||||
colors.push(this.getSliderValueAsRgb(i, slider.dataset.type));
|
||||
}
|
||||
}
|
||||
|
||||
// Longer end color so slider selection matches displayed colors
|
||||
colors.push(this.getSliderValueAsRgb(endValue, slider.dataset.type));
|
||||
colors = colors.map(value => this.getRgbString(value));
|
||||
slider.style.background = `linear-gradient(90deg, ${colors.join(',')})`;
|
||||
slider.style.webkitAppearance = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert given color into hue, saturation and light
|
||||
*/
|
||||
setInitValue() {
|
||||
// The initial value can be also a color defined in css
|
||||
const cssValue = window.getComputedStyle(this.input).getPropertyValue(this.default);
|
||||
this.default = cssValue || this.default;
|
||||
if (this.color === '' || typeof this.color === 'undefined') {
|
||||
// Unable to get hsl with empty value
|
||||
this.input.value = '';
|
||||
this.mainInput.value = '';
|
||||
return;
|
||||
}
|
||||
const value = this.checkValue(this.color, this.saveFormat) ? this.color : this.default;
|
||||
if (!value) {
|
||||
this.showError('JFIELD_COLOR_ERROR_NO_COLOUR');
|
||||
return;
|
||||
}
|
||||
let hsl = [];
|
||||
// When given value is a number, use it as defined format and get rest from default value
|
||||
if (/^[0-9]+$/.test(value)) {
|
||||
hsl = this.default && this.getHsl(this.default);
|
||||
if (this.format === 'hue') {
|
||||
hsl[0] = value;
|
||||
}
|
||||
if (this.format === 'saturation') {
|
||||
hsl[1] = value > 1 ? value / 100 : value;
|
||||
}
|
||||
if (this.format === 'light') {
|
||||
hsl[2] = value > 1 ? value / 100 : value;
|
||||
}
|
||||
if (this.format === 'alpha') {
|
||||
hsl[3] = value > 1 ? value / 100 : value;
|
||||
}
|
||||
} else {
|
||||
hsl = this.getHsl(value);
|
||||
}
|
||||
[this.hue, this.saturation, this.light] = hsl;
|
||||
this.alpha = hsl[4] || this.alpha;
|
||||
this.defaultHsl = this.default ? this.getHsl(this.default) : hsl;
|
||||
this.setSliderValues(hsl);
|
||||
this.setInputValue(hsl);
|
||||
this.input.style.border = `2px solid ${this.getRgbString(this.hslToRgb(hsl))}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert message into error message span
|
||||
* Message gets handled with Joomla.Text or as empty string
|
||||
*
|
||||
* @param {string} msg
|
||||
*/
|
||||
showError(msg) {
|
||||
this.messageSpan.innerText = msg ? Joomla.Text._(msg) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert value into HSLa e.g. #003E7C => [210, 100, 24]
|
||||
* @param {array|number|string} value
|
||||
* @returns {array}
|
||||
*/
|
||||
getHsl(value) {
|
||||
let hsl = [];
|
||||
if (Array.isArray(value)) {
|
||||
hsl = value;
|
||||
} else if (hexRegex.test(value)) {
|
||||
hsl = this.hexToHsl(value);
|
||||
} else if (rgbRegex.test(value)) {
|
||||
hsl = this.rgbToHsl(value);
|
||||
} else if (hslRegex.test(value)) {
|
||||
const matches = value.match(hslRegex);
|
||||
hsl = [matches[1], matches[2], matches[3], matches[4]];
|
||||
} else {
|
||||
this.showError('JFIELD_COLOR_ERROR_CONVERT_HSL');
|
||||
return this.defaultHsl;
|
||||
}
|
||||
|
||||
// Convert saturation etc. values from e.g. 40 to 0.4
|
||||
let i;
|
||||
for (i = 1; i < hsl.length; i += 1) {
|
||||
hsl[i] = hsl[i] > 1 ? hsl[i] / 100 : hsl[i];
|
||||
}
|
||||
return hsl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HSL value from color slider value
|
||||
* @params {int} value convert this value
|
||||
* @params {string} type type of value: hue, saturation, light or alpha
|
||||
* @returns array
|
||||
*/
|
||||
getSliderValueAsHsl(value, type) {
|
||||
let h = this.hue;
|
||||
let s = this.saturation;
|
||||
let l = this.light;
|
||||
let a = this.alpha;
|
||||
switch (type) {
|
||||
case 'alpha':
|
||||
a = value;
|
||||
break;
|
||||
case 'saturation':
|
||||
s = value;
|
||||
break;
|
||||
case 'light':
|
||||
l = value;
|
||||
break;
|
||||
case 'hue':
|
||||
default:
|
||||
h = value;
|
||||
}
|
||||
|
||||
// Percentage light and saturation
|
||||
if (l > 1) {
|
||||
l /= 100;
|
||||
}
|
||||
if (s > 1) {
|
||||
s /= 100;
|
||||
}
|
||||
if (a > 1) {
|
||||
a /= 100;
|
||||
}
|
||||
return [h, s, l, a];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates RGB value from color slider value
|
||||
* @params {int} value convert this value
|
||||
* @params {string} type type of value: hue, saturation, light or alpha
|
||||
* @returns array
|
||||
*/
|
||||
getSliderValueAsRgb(value, type) {
|
||||
return this.hslToRgb(this.getSliderValueAsHsl(value, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value in all sliders
|
||||
* @param {array} [hsla]
|
||||
* @param {string} [except]
|
||||
*/
|
||||
setSliderValues([h, s, l, a], except) {
|
||||
if (this.hueSlider && except !== 'hue') {
|
||||
this.hueSlider.value = Math.round(h);
|
||||
}
|
||||
if (this.saturationSlider && except !== 'saturation') {
|
||||
this.saturationSlider.value = Math.round(s * 100);
|
||||
}
|
||||
if (this.lightSlider && except !== 'light') {
|
||||
this.lightSlider.value = Math.round(l * 100);
|
||||
}
|
||||
if (a && this.alphaSlider && except !== 'alpha') {
|
||||
this.alphaSlider.value = Math.round(a * 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value in text input fields depending on their format
|
||||
* @param {array} hsl
|
||||
* @param {boolean=false} onlyMain indicates to change mainInput only
|
||||
*/
|
||||
setInputValue(hsl, onlyMain) {
|
||||
const inputs = [this.mainInput];
|
||||
if (!onlyMain) {
|
||||
inputs.push(this.input);
|
||||
}
|
||||
inputs.forEach(input => {
|
||||
let value;
|
||||
switch (input.dataset.format) {
|
||||
case 'hsl':
|
||||
value = this.getHslString(hsl);
|
||||
break;
|
||||
case 'hsla':
|
||||
value = this.getHslString(hsl, true);
|
||||
break;
|
||||
case 'rgb':
|
||||
value = this.getRgbString(this.hslToRgb(hsl));
|
||||
break;
|
||||
case 'rgba':
|
||||
value = this.getRgbString(this.hslToRgb(hsl), true);
|
||||
break;
|
||||
case 'hex':
|
||||
value = this.rgbToHex(this.hslToRgb(hsl));
|
||||
break;
|
||||
case 'alpha':
|
||||
value = Math.round(hsl[3] * 100);
|
||||
break;
|
||||
case 'saturation':
|
||||
value = Math.round(hsl[1] * 100);
|
||||
break;
|
||||
case 'light':
|
||||
value = Math.round(hsl[2] * 100);
|
||||
break;
|
||||
case 'hue':
|
||||
default:
|
||||
value = Math.round(hsl[0]);
|
||||
break;
|
||||
}
|
||||
input.value = value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Put RGB values into a string like 'rgb(<R>, <G>, <B>)'
|
||||
* @params {array} rgba
|
||||
* @params {boolean=false} withAlpha
|
||||
* @return {string}
|
||||
*/
|
||||
getRgbString([r, g, b, a], withAlpha) {
|
||||
if (withAlpha || this.setAlpha) {
|
||||
const alpha = typeof a === 'undefined' ? this.alpha : a;
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put HSL values into a string like 'hsl(<H>, <S>%, <L>%, <a>)'
|
||||
* @params {array} values
|
||||
* @params {boolean=false} withAlpha
|
||||
* @return {string}
|
||||
*/
|
||||
getHslString(values, withAlpha) {
|
||||
let [h, s, l, a] = values;
|
||||
s *= 100;
|
||||
l *= 100;
|
||||
[h, s, l] = [h, s, l].map(value => Math.round(value));
|
||||
if (withAlpha || this.setAlpha) {
|
||||
a = a || this.alpha;
|
||||
return `hsla(${h}, ${s}%, ${l}%, ${a})`;
|
||||
}
|
||||
return `hsl(${h}, ${s}%, ${l}%)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hsl values out of hex
|
||||
* @param {array} rgb
|
||||
* @return {string}
|
||||
*/
|
||||
rgbToHex(rgb) {
|
||||
let r = rgb[0].toString(16).toUpperCase();
|
||||
let g = rgb[1].toString(16).toUpperCase();
|
||||
let b = rgb[2].toString(16).toUpperCase();
|
||||
|
||||
// Double value for hex with '#' and 6 chars
|
||||
r = r.length === 1 ? `${r}${r}` : r;
|
||||
g = g.length === 1 ? `${g}${g}` : g;
|
||||
b = b.length === 1 ? `${b}${b}` : b;
|
||||
return `#${r}${g}${b}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hsl values out of rgb
|
||||
* @param {string|array} values
|
||||
* @return {array}
|
||||
*/
|
||||
rgbToHsl(values) {
|
||||
let rgb = values;
|
||||
if (typeof values === 'string') {
|
||||
const parts = values.match(rgbRegex);
|
||||
rgb = [parts[1], parts[2], parts[3], parts[4]];
|
||||
}
|
||||
const [r, g, b] = rgb.map(value => value > 1 ? value / 255 : value);
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const l = (max + min) / 2;
|
||||
const d = max - min;
|
||||
let h = 0;
|
||||
let s = 0;
|
||||
let a = rgb[3] || values[3] || this.alpha;
|
||||
if (max !== min) {
|
||||
if (max === 0) {
|
||||
s = max;
|
||||
} else if (min === 1) {
|
||||
s = min;
|
||||
} else {
|
||||
s = (max - l) / Math.min(l, 1 - l);
|
||||
}
|
||||
switch (max) {
|
||||
case r:
|
||||
h = 60 * (g - b) / d;
|
||||
break;
|
||||
case g:
|
||||
h = 60 * (2 + (b - r) / d);
|
||||
break;
|
||||
case b:
|
||||
default:
|
||||
h = 60 * (4 + (r - g) / d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
h = h < 0 ? h + 360 : h;
|
||||
a = a > 1 ? a / 100 : a;
|
||||
return [h, s, l, a];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hsl values out of hex
|
||||
* @param {string} hex
|
||||
* @return {array}
|
||||
*/
|
||||
hexToHsl(hex) {
|
||||
const parts = hex.match(hexRegex);
|
||||
const r = parts[1];
|
||||
const g = parts[2];
|
||||
const b = parts[3];
|
||||
const rgb = [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
|
||||
return this.rgbToHsl(rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSLa values into RGBa
|
||||
* @param {array} hsla
|
||||
* @returns {number[]}
|
||||
*/
|
||||
hslToRgb([h, sat, light, alpha]) {
|
||||
let r = 1;
|
||||
let g = 1;
|
||||
let b = 1;
|
||||
|
||||
// Saturation and light were calculated as 0.24 instead of 24%
|
||||
const s = sat > 1 ? sat / 100 : sat;
|
||||
const l = light > 1 ? light / 100 : light;
|
||||
const a = alpha > 1 ? alpha / 100 : alpha;
|
||||
if (h < 0 || h > 360 || s < 0 || s > 1 || l < 0 || l > 1) {
|
||||
this.showError('JFIELD_COLOR_ERROR_CONVERT_HSL');
|
||||
return this.hslToRgb(this.defaultHsl);
|
||||
}
|
||||
const c = (1 - Math.abs(2 * l - 1)) * s;
|
||||
const hi = h / 60;
|
||||
const x = c * (1 - Math.abs(hi % 2 - 1));
|
||||
const m = l - c / 2;
|
||||
if (h >= 0 && h < 60) {
|
||||
[r, g, b] = [c, x, 0];
|
||||
} else if (h >= 60 && h < 120) {
|
||||
[r, g, b] = [x, c, 0];
|
||||
} else if (h >= 120 && h < 180) {
|
||||
[r, g, b] = [0, c, x];
|
||||
} else if (h >= 180 && h < 240) {
|
||||
[r, g, b] = [0, x, c];
|
||||
} else if (h >= 240 && h < 300) {
|
||||
[r, g, b] = [x, 0, c];
|
||||
} else if (h >= 300 && h <= 360) {
|
||||
[r, g, b] = [c, 0, x];
|
||||
} else {
|
||||
this.showError('JFIELD_COLOR_ERROR_CONVERT_HUE');
|
||||
return this.hslToRgb(this.defaultHsl);
|
||||
}
|
||||
const rgb = [r, g, b].map(value => Math.round((value + m) * 255));
|
||||
rgb.push(a);
|
||||
return rgb;
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const fields = document.querySelectorAll('.color-slider-wrapper');
|
||||
if (fields) {
|
||||
Array.prototype.forEach.call(fields, slider => {
|
||||
// eslint-disable-next-line no-new
|
||||
new JoomlaFieldColorSlider(slider);
|
||||
});
|
||||
}
|
||||
});
|
||||
})(document);
|
||||
1
media/system/js/fields/joomla-field-color-slider.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-color-slider.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-color-slider.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-color-slider.min.js.gz
Normal file
Binary file not shown.
393
media/system/js/fields/joomla-field-fancy-select.js
Normal file
393
media/system/js/fields/joomla-field-fancy-select.js
Normal file
@ -0,0 +1,393 @@
|
||||
/**
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fancy select field, which use Choices.js
|
||||
*
|
||||
* Example:
|
||||
* <joomla-field-fancy-select ...attributes>
|
||||
* <select>...</select>
|
||||
* </joomla-field-fancy-select>
|
||||
*
|
||||
* Possible attributes:
|
||||
*
|
||||
* allow-custom Whether allow User to dynamically add a new value.
|
||||
* new-item-prefix="" Prefix for a dynamically added value.
|
||||
*
|
||||
* remote-search Enable remote search.
|
||||
* url="" Url for remote search.
|
||||
* term-key="term" Variable key name for searched term, will be appended to Url.
|
||||
*
|
||||
* min-term-length="1" The minimum length a search value should be before choices are searched.
|
||||
* placeholder="" The value of the inputs placeholder.
|
||||
* search-placeholder="" The value of the search inputs placeholder.
|
||||
*
|
||||
* data-max-results="30" The maximum amount of search results to be displayed.
|
||||
* data-max-render="30" The maximum amount of items to be rendered, critical for large lists.
|
||||
*/
|
||||
window.customElements.define('joomla-field-fancy-select', class extends HTMLElement {
|
||||
// Attributes to monitor
|
||||
get allowCustom() {
|
||||
return this.hasAttribute('allow-custom');
|
||||
}
|
||||
get remoteSearch() {
|
||||
return this.hasAttribute('remote-search');
|
||||
}
|
||||
get url() {
|
||||
return this.getAttribute('url');
|
||||
}
|
||||
get termKey() {
|
||||
return this.getAttribute('term-key') || 'term';
|
||||
}
|
||||
get minTermLength() {
|
||||
return parseInt(this.getAttribute('min-term-length'), 10) || 1;
|
||||
}
|
||||
get newItemPrefix() {
|
||||
return this.getAttribute('new-item-prefix') || '';
|
||||
}
|
||||
get placeholder() {
|
||||
return this.getAttribute('placeholder');
|
||||
}
|
||||
get searchPlaceholder() {
|
||||
return this.getAttribute('search-placeholder');
|
||||
}
|
||||
get value() {
|
||||
return this.choicesInstance.getValue(true);
|
||||
}
|
||||
set value($val) {
|
||||
this.choicesInstance.setChoiceByValue($val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Keycodes
|
||||
this.keyCode = {
|
||||
ENTER: 13
|
||||
};
|
||||
if (!Joomla) {
|
||||
throw new Error('Joomla API is not properly initiated');
|
||||
}
|
||||
if (!window.Choices) {
|
||||
throw new Error('JoomlaFieldFancySelect requires Choices.js to work');
|
||||
}
|
||||
this.choicesCache = {};
|
||||
this.activeXHR = null;
|
||||
this.choicesInstance = null;
|
||||
this.isDisconnected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle
|
||||
*/
|
||||
connectedCallback() {
|
||||
// Make sure Choices are loaded
|
||||
if (window.Choices || document.readyState === 'complete') {
|
||||
this.doConnect();
|
||||
} else {
|
||||
const callback = () => {
|
||||
this.doConnect();
|
||||
window.removeEventListener('load', callback);
|
||||
};
|
||||
window.addEventListener('load', callback);
|
||||
}
|
||||
}
|
||||
doConnect() {
|
||||
// Get a <select> element
|
||||
this.select = this.querySelector('select');
|
||||
if (!this.select) {
|
||||
throw new Error('JoomlaFieldFancySelect requires <select> element to work');
|
||||
}
|
||||
|
||||
// The element was already initialised previously and perhaps was detached from DOM
|
||||
if (this.choicesInstance) {
|
||||
if (this.isDisconnected) {
|
||||
// Re init previous instance
|
||||
this.choicesInstance.init();
|
||||
this.isDisconnected = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.isDisconnected = false;
|
||||
|
||||
// Add placeholder option for multiple mode,
|
||||
// Because it not supported as parameter by Choices for <select> https://github.com/jshjohnson/Choices#placeholder
|
||||
if (this.select.multiple && this.placeholder) {
|
||||
const option = document.createElement('option');
|
||||
option.setAttribute('placeholder', '');
|
||||
option.textContent = this.placeholder;
|
||||
this.select.appendChild(option);
|
||||
}
|
||||
|
||||
// Init Choices
|
||||
// eslint-disable-next-line no-undef
|
||||
this.choicesInstance = new Choices(this.select, {
|
||||
placeholderValue: this.placeholder,
|
||||
searchPlaceholderValue: this.searchPlaceholder,
|
||||
removeItemButton: true,
|
||||
searchFloor: this.minTermLength,
|
||||
searchResultLimit: parseInt(this.select.dataset.maxResults, 10) || 10,
|
||||
renderChoiceLimit: parseInt(this.select.dataset.maxRender, 10) || -1,
|
||||
shouldSort: false,
|
||||
fuseOptions: {
|
||||
threshold: 0.3 // Strict search
|
||||
},
|
||||
|
||||
noResultsText: Joomla.Text._('JGLOBAL_SELECT_NO_RESULTS_MATCH', 'No results found'),
|
||||
noChoicesText: Joomla.Text._('JGLOBAL_SELECT_NO_RESULTS_MATCH', 'No results found'),
|
||||
itemSelectText: Joomla.Text._('JGLOBAL_SELECT_PRESS_TO_SELECT', 'Press to select'),
|
||||
// Redefine some classes
|
||||
classNames: {
|
||||
button: 'choices__button_joomla' // It is need because an original styling use unavailable Icon.svg file
|
||||
}
|
||||
});
|
||||
|
||||
// Handle typing of custom Term
|
||||
if (this.allowCustom) {
|
||||
// START Work around for issue https://github.com/joomla/joomla-cms/issues/29459
|
||||
// The choices.js always auto-highlights the first element
|
||||
// in the dropdown that not allow to add a custom Term.
|
||||
//
|
||||
// This workaround can be removed when choices.js
|
||||
// will have an option that allow to disable it.
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle, prefer-destructuring
|
||||
const _highlightChoice = this.choicesInstance._highlightChoice;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
this.choicesInstance._highlightChoice = el => {
|
||||
// Prevent auto-highlight of first element, if nothing actually highlighted
|
||||
if (!el) return;
|
||||
|
||||
// Call original highlighter
|
||||
_highlightChoice.call(this.choicesInstance, el);
|
||||
};
|
||||
|
||||
// Unhighlight any highlighted items, when mouse leave the dropdown
|
||||
this.addEventListener('mouseleave', () => {
|
||||
if (!this.choicesInstance.dropdown.isActive) {
|
||||
return;
|
||||
}
|
||||
const highlighted = Array.from(this.choicesInstance.dropdown.element.querySelectorAll(`.${this.choicesInstance.config.classNames.highlightedState}`));
|
||||
highlighted.forEach(choice => {
|
||||
choice.classList.remove(this.choicesInstance.config.classNames.highlightedState);
|
||||
choice.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
this.choicesInstance._highlightPosition = 0;
|
||||
});
|
||||
// END workaround for issue #29459
|
||||
|
||||
// Add custom term on ENTER keydown
|
||||
this.addEventListener('keydown', event => {
|
||||
if (event.keyCode !== this.keyCode.ENTER || event.target !== this.choicesInstance.input.element) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
if (this.choicesInstance._highlightPosition || !event.target.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure nothing is highlighted
|
||||
const highlighted = this.choicesInstance.dropdown.element.querySelector(`.${this.choicesInstance.config.classNames.highlightedState}`);
|
||||
if (highlighted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if value already exist
|
||||
const lowerValue = event.target.value.toLowerCase();
|
||||
let valueInCache = false;
|
||||
|
||||
// Check if value in existing choices
|
||||
this.choicesInstance.config.choices.some(choiceItem => {
|
||||
if (choiceItem.value.toLowerCase() === lowerValue || choiceItem.label.toLowerCase() === lowerValue) {
|
||||
valueInCache = choiceItem.value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (valueInCache === false) {
|
||||
// Check if value in cache
|
||||
Object.keys(this.choicesCache).some(key => {
|
||||
if (key.toLowerCase() === lowerValue || this.choicesCache[key].toLowerCase() === lowerValue) {
|
||||
valueInCache = key;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Make choice based on existing value
|
||||
if (valueInCache !== false) {
|
||||
this.choicesInstance.setChoiceByValue(valueInCache);
|
||||
event.target.value = null;
|
||||
this.choicesInstance.hideDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create and add new
|
||||
this.choicesInstance.setChoices([{
|
||||
value: this.newItemPrefix + event.target.value,
|
||||
label: event.target.value,
|
||||
selected: true,
|
||||
customProperties: {
|
||||
value: event.target.value // Store real value, just in case
|
||||
}
|
||||
}], 'value', 'label', false);
|
||||
this.choicesCache[event.target.value] = event.target.value;
|
||||
event.target.value = null;
|
||||
this.choicesInstance.hideDropdown();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle remote search
|
||||
if (this.remoteSearch && this.url) {
|
||||
// Cache existing
|
||||
this.choicesInstance.config.choices.forEach(choiceItem => {
|
||||
this.choicesCache[choiceItem.value] = choiceItem.label;
|
||||
});
|
||||
const lookupDelay = 300;
|
||||
let lookupTimeout = null;
|
||||
this.select.addEventListener('search', () => {
|
||||
clearTimeout(lookupTimeout);
|
||||
lookupTimeout = setTimeout(this.requestLookup.bind(this), lookupDelay);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle
|
||||
*/
|
||||
disconnectedCallback() {
|
||||
// Destroy Choices instance, to unbind event listeners
|
||||
if (this.choicesInstance) {
|
||||
this.choicesInstance.destroy();
|
||||
this.isDisconnected = true;
|
||||
}
|
||||
if (this.activeXHR) {
|
||||
this.activeXHR.abort();
|
||||
this.activeXHR = null;
|
||||
}
|
||||
}
|
||||
requestLookup() {
|
||||
let {
|
||||
url
|
||||
} = this;
|
||||
url += url.indexOf('?') === -1 ? '?' : '&';
|
||||
url += `${encodeURIComponent(this.termKey)}=${encodeURIComponent(this.choicesInstance.input.value)}`;
|
||||
|
||||
// Stop previous request if any
|
||||
if (this.activeXHR) {
|
||||
this.activeXHR.abort();
|
||||
}
|
||||
this.activeXHR = Joomla.request({
|
||||
url,
|
||||
onSuccess: response => {
|
||||
this.activeXHR = null;
|
||||
const items = response ? JSON.parse(response) : [];
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove duplications
|
||||
let item;
|
||||
// eslint-disable-next-line no-plusplus
|
||||
for (let i = items.length - 1; i >= 0; i--) {
|
||||
// The loop must be form the end !!!
|
||||
item = items[i];
|
||||
// eslint-disable-next-line prefer-template
|
||||
item.value = '' + item.value; // Make sure the value is a string, choices.js expect a string.
|
||||
|
||||
if (this.choicesCache[item.value]) {
|
||||
items.splice(i, 1);
|
||||
} else {
|
||||
this.choicesCache[item.value] = item.text;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new options to field, assume that each item is object, eg {value: "foo", text: "bar"}
|
||||
if (items.length) {
|
||||
this.choicesInstance.setChoices(items, 'value', 'text', false);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
this.activeXHR = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
disableAllOptions() {
|
||||
// Choices.js does not offer a public API for accessing the choices
|
||||
// So we have to access the private store => don't eslint
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const {
|
||||
choices
|
||||
} = this.choicesInstance._store;
|
||||
choices.forEach((elem, index) => {
|
||||
choices[index].disabled = true;
|
||||
choices[index].selected = false;
|
||||
});
|
||||
this.choicesInstance.clearStore();
|
||||
this.choicesInstance.setChoices(choices, 'value', 'label', true);
|
||||
}
|
||||
enableAllOptions() {
|
||||
// Choices.js does not offer a public API for accessing the choices
|
||||
// So we have to access the private store => don't eslint
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const {
|
||||
choices
|
||||
} = this.choicesInstance._store;
|
||||
const values = this.choicesInstance.getValue(true);
|
||||
choices.forEach((elem, index) => {
|
||||
choices[index].disabled = false;
|
||||
});
|
||||
this.choicesInstance.clearStore();
|
||||
this.choicesInstance.setChoices(choices, 'value', 'label', true);
|
||||
this.value = values;
|
||||
}
|
||||
disableByValue($val) {
|
||||
// Choices.js does not offer a public API for accessing the choices
|
||||
// So we have to access the private store => don't eslint
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const {
|
||||
choices
|
||||
} = this.choicesInstance._store;
|
||||
const values = this.choicesInstance.getValue(true);
|
||||
choices.forEach((elem, index) => {
|
||||
if (elem.value === $val) {
|
||||
choices[index].disabled = true;
|
||||
choices[index].selected = false;
|
||||
}
|
||||
});
|
||||
const index = values.indexOf($val);
|
||||
if (index > -1) {
|
||||
values.slice(index, 1);
|
||||
}
|
||||
this.choicesInstance.clearStore();
|
||||
this.choicesInstance.setChoices(choices, 'value', 'label', true);
|
||||
this.value = values;
|
||||
}
|
||||
enableByValue($val) {
|
||||
// Choices.js does not offer a public API for accessing the choices
|
||||
// So we have to access the private store => don't eslint
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const {
|
||||
choices
|
||||
} = this.choicesInstance._store;
|
||||
const values = this.choicesInstance.getValue(true);
|
||||
choices.forEach((elem, index) => {
|
||||
if (elem.value === $val) {
|
||||
choices[index].disabled = false;
|
||||
}
|
||||
});
|
||||
this.choicesInstance.clearStore();
|
||||
this.choicesInstance.setChoices(choices, 'value', 'label', true);
|
||||
this.value = values;
|
||||
}
|
||||
});
|
||||
1
media/system/js/fields/joomla-field-fancy-select.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-fancy-select.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-fancy-select.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-fancy-select.min.js.gz
Normal file
Binary file not shown.
369
media/system/js/fields/joomla-field-media.js
Normal file
369
media/system/js/fields/joomla-field-media.js
Normal file
@ -0,0 +1,369 @@
|
||||
/**
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
if (!Joomla) {
|
||||
throw new Error('Joomla API is not properly initiated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the extensions
|
||||
*
|
||||
* @param {*} path
|
||||
* @returns {string}
|
||||
*/
|
||||
const getExtension = path => {
|
||||
const parts = path.split(/[#]/);
|
||||
if (parts.length > 1) {
|
||||
return parts[1].split(/[?]/)[0].split('.').pop().trim();
|
||||
}
|
||||
return path.split(/[#?]/)[0].split('.').pop().trim();
|
||||
};
|
||||
class JoomlaFieldMedia extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.onSelected = this.onSelected.bind(this);
|
||||
this.show = this.show.bind(this);
|
||||
this.clearValue = this.clearValue.bind(this);
|
||||
this.modalClose = this.modalClose.bind(this);
|
||||
this.setValue = this.setValue.bind(this);
|
||||
this.updatePreview = this.updatePreview.bind(this);
|
||||
this.validateValue = this.validateValue.bind(this);
|
||||
this.markValid = this.markValid.bind(this);
|
||||
this.markInvalid = this.markInvalid.bind(this);
|
||||
this.mimeType = '';
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ['type', 'base-path', 'root-folder', 'url', 'modal-container', 'modal-width', 'modal-height', 'input', 'button-select', 'button-clear', 'button-save-selected', 'preview', 'preview-width', 'preview-height'];
|
||||
}
|
||||
get type() {
|
||||
return this.getAttribute('type');
|
||||
}
|
||||
set type(value) {
|
||||
this.setAttribute('type', value);
|
||||
}
|
||||
get basePath() {
|
||||
return this.getAttribute('base-path');
|
||||
}
|
||||
set basePath(value) {
|
||||
this.setAttribute('base-path', value);
|
||||
}
|
||||
get rootFolder() {
|
||||
return this.getAttribute('root-folder');
|
||||
}
|
||||
set rootFolder(value) {
|
||||
this.setAttribute('root-folder', value);
|
||||
}
|
||||
get url() {
|
||||
return this.getAttribute('url');
|
||||
}
|
||||
set url(value) {
|
||||
this.setAttribute('url', value);
|
||||
}
|
||||
get modalContainer() {
|
||||
return this.getAttribute('modal-container');
|
||||
}
|
||||
set modalContainer(value) {
|
||||
this.setAttribute('modal-container', value);
|
||||
}
|
||||
get input() {
|
||||
return this.getAttribute('input');
|
||||
}
|
||||
set input(value) {
|
||||
this.setAttribute('input', value);
|
||||
}
|
||||
get buttonSelect() {
|
||||
return this.getAttribute('button-select');
|
||||
}
|
||||
set buttonSelect(value) {
|
||||
this.setAttribute('button-select', value);
|
||||
}
|
||||
get buttonClear() {
|
||||
return this.getAttribute('button-clear');
|
||||
}
|
||||
set buttonClear(value) {
|
||||
this.setAttribute('button-clear', value);
|
||||
}
|
||||
get buttonSaveSelected() {
|
||||
return this.getAttribute('button-save-selected');
|
||||
}
|
||||
set buttonSaveSelected(value) {
|
||||
this.setAttribute('button-save-selected', value);
|
||||
}
|
||||
get modalWidth() {
|
||||
return parseInt(this.getAttribute('modal-width'), 10);
|
||||
}
|
||||
set modalWidth(value) {
|
||||
this.setAttribute('modal-width', value);
|
||||
}
|
||||
get modalHeight() {
|
||||
return parseInt(this.getAttribute('modal-height'), 10);
|
||||
}
|
||||
set modalHeight(value) {
|
||||
this.setAttribute('modal-height', value);
|
||||
}
|
||||
get previewWidth() {
|
||||
return parseInt(this.getAttribute('preview-width'), 10);
|
||||
}
|
||||
set previewWidth(value) {
|
||||
this.setAttribute('preview-width', value);
|
||||
}
|
||||
get previewHeight() {
|
||||
return parseInt(this.getAttribute('preview-height'), 10);
|
||||
}
|
||||
set previewHeight(value) {
|
||||
this.setAttribute('preview-height', value);
|
||||
}
|
||||
get preview() {
|
||||
return this.getAttribute('preview');
|
||||
}
|
||||
set preview(value) {
|
||||
this.setAttribute('preview', value);
|
||||
}
|
||||
get previewContainer() {
|
||||
return this.getAttribute('preview-container');
|
||||
}
|
||||
|
||||
// attributeChangedCallback(attr, oldValue, newValue) {}
|
||||
|
||||
connectedCallback() {
|
||||
this.button = this.querySelector(this.buttonSelect);
|
||||
this.inputElement = this.querySelector(this.input);
|
||||
this.buttonClearEl = this.querySelector(this.buttonClear);
|
||||
this.modalElement = this.querySelector('.joomla-modal');
|
||||
this.buttonSaveSelectedElement = this.querySelector(this.buttonSaveSelected);
|
||||
this.previewElement = this.querySelector('.field-media-preview');
|
||||
if (!this.button || !this.inputElement || !this.buttonClearEl || !this.modalElement || !this.buttonSaveSelectedElement) {
|
||||
throw new Error('Misconfiguaration...');
|
||||
}
|
||||
this.button.addEventListener('click', this.show);
|
||||
|
||||
// Bootstrap modal init
|
||||
if (this.modalElement && window.bootstrap && window.bootstrap.Modal && !window.bootstrap.Modal.getInstance(this.modalElement)) {
|
||||
Joomla.initialiseModal(this.modalElement, {
|
||||
isJoomla: true
|
||||
});
|
||||
}
|
||||
if (this.buttonClearEl) {
|
||||
this.buttonClearEl.addEventListener('click', this.clearValue);
|
||||
}
|
||||
this.supportedExtensions = Joomla.getOptions('media-picker', {});
|
||||
if (!Object.keys(this.supportedExtensions).length) {
|
||||
throw new Error('Joomla API is not properly initiated');
|
||||
}
|
||||
this.inputElement.removeAttribute('readonly');
|
||||
this.inputElement.addEventListener('change', this.validateValue);
|
||||
this.updatePreview();
|
||||
}
|
||||
disconnectedCallback() {
|
||||
if (this.button) {
|
||||
this.button.removeEventListener('click', this.show);
|
||||
}
|
||||
if (this.buttonClearEl) {
|
||||
this.buttonClearEl.removeEventListener('click', this.clearValue);
|
||||
}
|
||||
if (this.inputElement) {
|
||||
this.inputElement.removeEventListener('change', this.validateValue);
|
||||
}
|
||||
}
|
||||
onSelected(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.modalClose();
|
||||
return false;
|
||||
}
|
||||
show() {
|
||||
this.modalElement.open();
|
||||
Joomla.selectedMediaFile = {};
|
||||
this.buttonSaveSelectedElement.addEventListener('click', this.onSelected);
|
||||
}
|
||||
async modalClose() {
|
||||
try {
|
||||
await Joomla.getMedia(Joomla.selectedMediaFile, this.inputElement, this);
|
||||
} catch (err) {
|
||||
Joomla.renderMessages({
|
||||
error: [Joomla.Text._('JLIB_APPLICATION_ERROR_SERVER')]
|
||||
});
|
||||
}
|
||||
Joomla.selectedMediaFile = {};
|
||||
Joomla.Modal.getCurrent().close();
|
||||
}
|
||||
setValue(value) {
|
||||
this.inputElement.value = value;
|
||||
this.validatedUrl = value;
|
||||
this.mimeType = Joomla.selectedMediaFile.fileType;
|
||||
this.updatePreview();
|
||||
|
||||
// trigger change event both on the input and on the custom element
|
||||
this.inputElement.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
value
|
||||
},
|
||||
bubbles: true
|
||||
}));
|
||||
}
|
||||
async validateValue(event) {
|
||||
let {
|
||||
value
|
||||
} = event.target;
|
||||
if (this.validatedUrl === value || value === '') return;
|
||||
if (/^(http(s)?:\/\/).+$/.test(value)) {
|
||||
try {
|
||||
fetch(value).then(response => {
|
||||
if (response.status === 200) {
|
||||
this.validatedUrl = value;
|
||||
this.markValid();
|
||||
} else {
|
||||
this.validatedUrl = value;
|
||||
this.markInvalid();
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
this.validatedUrl = value;
|
||||
this.markInvalid();
|
||||
}
|
||||
} else {
|
||||
if (/^\//.test(value)) {
|
||||
value = value.substring(1);
|
||||
}
|
||||
const hashedUrl = value.split('#');
|
||||
const urlParts = hashedUrl[0].split('/');
|
||||
const rest = urlParts.slice(1);
|
||||
fetch(`${Joomla.getOptions('system.paths').rootFull}/${value}`).then(response => response.blob()).then(blob => {
|
||||
if (blob.type.includes('image')) {
|
||||
const img = new Image();
|
||||
img.src = URL.createObjectURL(blob);
|
||||
img.onload = () => {
|
||||
this.inputElement.value = `${urlParts[0]}/${rest.join('/')}#joomlaImage://local-${urlParts[0]}/${rest.join('/')}?width=${img.width}&height=${img.height}`;
|
||||
this.validatedUrl = `${urlParts[0]}/${rest.join('/')}#joomlaImage://local-${urlParts[0]}/${rest.join('/')}?width=${img.width}&height=${img.height}`;
|
||||
this.markValid();
|
||||
};
|
||||
} else if (blob.type.includes('audio')) {
|
||||
this.mimeType = blob.type;
|
||||
this.inputElement.value = value;
|
||||
this.validatedUrl = value;
|
||||
this.markValid();
|
||||
} else if (blob.type.includes('video')) {
|
||||
this.mimeType = blob.type;
|
||||
this.inputElement.value = value;
|
||||
this.validatedUrl = value;
|
||||
this.markValid();
|
||||
} else if (blob.type.includes('application/pdf')) {
|
||||
this.mimeType = blob.type;
|
||||
this.inputElement.value = value;
|
||||
this.validatedUrl = value;
|
||||
this.markValid();
|
||||
} else {
|
||||
this.validatedUrl = value;
|
||||
this.markInvalid();
|
||||
}
|
||||
}).catch(() => {
|
||||
this.setValue(value);
|
||||
this.validatedUrl = value;
|
||||
this.markInvalid();
|
||||
});
|
||||
}
|
||||
}
|
||||
markValid() {
|
||||
this.inputElement.removeAttribute('required');
|
||||
this.inputElement.removeAttribute('pattern');
|
||||
if (document.formvalidator) {
|
||||
document.formvalidator.validate(this.inputElement);
|
||||
}
|
||||
}
|
||||
markInvalid() {
|
||||
this.inputElement.setAttribute('required', '');
|
||||
this.inputElement.setAttribute('pattern', '/^(http://INVALID/).+$/');
|
||||
if (document.formvalidator) {
|
||||
document.formvalidator.validate(this.inputElement);
|
||||
}
|
||||
}
|
||||
clearValue() {
|
||||
this.setValue('');
|
||||
this.validatedUrl = '';
|
||||
this.inputElement.removeAttribute('required');
|
||||
this.inputElement.removeAttribute('pattern');
|
||||
if (document.formvalidator) {
|
||||
document.formvalidator.validate(this.inputElement);
|
||||
}
|
||||
}
|
||||
updatePreview() {
|
||||
if (['true', 'static'].indexOf(this.preview) === -1 || this.preview === 'false' || !this.previewElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset preview
|
||||
if (this.preview) {
|
||||
const {
|
||||
value
|
||||
} = this.inputElement;
|
||||
const {
|
||||
supportedExtensions
|
||||
} = this;
|
||||
if (!value) {
|
||||
this.buttonClearEl.style.display = 'none';
|
||||
this.previewElement.innerHTML = Joomla.sanitizeHtml('<span class="field-media-preview-icon"></span>');
|
||||
} else {
|
||||
let type;
|
||||
this.buttonClearEl.style.display = '';
|
||||
this.previewElement.innerHTML = '';
|
||||
const ext = getExtension(value).toLowerCase();
|
||||
if (supportedExtensions.images.includes(ext)) type = 'images';
|
||||
if (supportedExtensions.audios.includes(ext)) type = 'audios';
|
||||
if (supportedExtensions.videos.includes(ext)) type = 'videos';
|
||||
if (supportedExtensions.documents.includes(ext)) type = 'documents';
|
||||
let previewElement;
|
||||
const mediaType = {
|
||||
images: () => {
|
||||
if (supportedExtensions.images.includes(ext)) {
|
||||
previewElement = new Image();
|
||||
previewElement.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
|
||||
previewElement.setAttribute('alt', '');
|
||||
}
|
||||
},
|
||||
audios: () => {
|
||||
if (supportedExtensions.audios.includes(ext)) {
|
||||
previewElement = document.createElement('audio');
|
||||
previewElement.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
|
||||
previewElement.setAttribute('controls', '');
|
||||
}
|
||||
},
|
||||
videos: () => {
|
||||
if (supportedExtensions.videos.includes(ext)) {
|
||||
previewElement = document.createElement('video');
|
||||
const previewElementSource = document.createElement('source');
|
||||
previewElementSource.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
|
||||
previewElementSource.type = this.mimeType;
|
||||
previewElement.setAttribute('controls', '');
|
||||
previewElement.setAttribute('width', this.previewWidth);
|
||||
previewElement.setAttribute('height', this.previewHeight);
|
||||
previewElement.appendChild(previewElementSource);
|
||||
}
|
||||
},
|
||||
documents: () => {
|
||||
if (supportedExtensions.documents.includes(ext)) {
|
||||
previewElement = document.createElement('object');
|
||||
previewElement.data = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
|
||||
previewElement.type = this.mimeType;
|
||||
previewElement.setAttribute('width', this.previewWidth);
|
||||
previewElement.setAttribute('height', this.previewHeight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// @todo more checks
|
||||
if (this.givenType && ['images', 'audios', 'videos', 'documents'].includes(this.givenType)) {
|
||||
mediaType[this.givenType]();
|
||||
} else if (type && ['images', 'audios', 'videos', 'documents'].includes(type)) {
|
||||
mediaType[type]();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.previewElement.style.width = this.previewWidth;
|
||||
this.previewElement.appendChild(previewElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-media', JoomlaFieldMedia);
|
||||
1
media/system/js/fields/joomla-field-media.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-media.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-media.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-media.min.js.gz
Normal file
Binary file not shown.
122
media/system/js/fields/joomla-field-module-order.js
Normal file
122
media/system/js/fields/joomla-field-module-order.js
Normal file
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @package Joomla.JavaScript
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
customElements.define('joomla-field-module-order', class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.linkedFieldSelector = '';
|
||||
this.linkedFieldElement = '';
|
||||
this.originalPosition = '';
|
||||
this.writeDynaList.bind(this);
|
||||
this.getNewOrder.bind(this);
|
||||
}
|
||||
connectedCallback() {
|
||||
this.linkedFieldSelector = this.getAttribute('data-linked-field') || 'jform_position';
|
||||
if (!this.linkedFieldSelector) {
|
||||
throw new Error('No linked field defined!');
|
||||
}
|
||||
this.linkedFieldElement = document.getElementById(this.linkedFieldSelector);
|
||||
if (!this.linkedFieldElement) {
|
||||
throw new Error('No linked field defined!');
|
||||
}
|
||||
const that = this;
|
||||
this.originalPosition = this.linkedFieldElement.value;
|
||||
|
||||
/** Initialize the field * */
|
||||
this.getNewOrder(this.originalPosition);
|
||||
|
||||
/** Watch for changes on the linked field * */
|
||||
this.linkedFieldElement.addEventListener('change', () => {
|
||||
that.originalPosition = that.linkedFieldElement.value;
|
||||
that.getNewOrder(that.linkedFieldElement.value);
|
||||
});
|
||||
}
|
||||
writeDynaList(selectProperties, source, originalPositionName, originalPositionValue) {
|
||||
let i = 0;
|
||||
const selectNode = document.createElement('select');
|
||||
if (this.hasAttribute('disabled')) {
|
||||
selectNode.setAttribute('disabled', '');
|
||||
}
|
||||
if (this.getAttribute('onchange')) {
|
||||
selectNode.setAttribute('onchange', this.getAttribute('onchange'));
|
||||
}
|
||||
if (this.getAttribute('size')) {
|
||||
selectNode.setAttribute('size', this.getAttribute('size'));
|
||||
}
|
||||
selectNode.classList.add(selectProperties.itemClass);
|
||||
selectNode.setAttribute('name', selectProperties.name);
|
||||
selectNode.id = selectProperties.id;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const x in source) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!source.hasOwnProperty(x)) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
const node = document.createElement('option');
|
||||
const item = source[x];
|
||||
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
node.value = item[1];
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
node.innerHTML = Joomla.sanitizeHtml(item[2]);
|
||||
if (originalPositionName && originalPositionValue === item[1] || !originalPositionName && i === 0) {
|
||||
node.setAttribute('selected', 'selected');
|
||||
}
|
||||
selectNode.appendChild(node);
|
||||
i += 1;
|
||||
}
|
||||
this.innerHTML = '';
|
||||
this.appendChild(selectNode);
|
||||
}
|
||||
getNewOrder(originalPosition) {
|
||||
const url = this.getAttribute('data-url');
|
||||
const clientId = this.getAttribute('data-client-id');
|
||||
const originalOrder = this.getAttribute('data-ordering');
|
||||
const name = this.getAttribute('data-name');
|
||||
const attr = this.getAttribute('data-client-attr') ? this.getAttribute('data-client-attr') : 'form-select';
|
||||
const id = `${this.getAttribute('data-id')}`;
|
||||
const moduleId = `${this.getAttribute('data-module-id')}`;
|
||||
const orders = [];
|
||||
const that = this;
|
||||
Joomla.request({
|
||||
url: `${url}&client_id=${clientId}&position=${originalPosition}&module_id=${moduleId}`,
|
||||
method: 'GET',
|
||||
perform: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
onSuccess(resp) {
|
||||
if (resp) {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(resp);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
/** Check if everything is OK * */
|
||||
if (response.data.length > 0) {
|
||||
for (let i = 0; i < response.data.length; i += 1) {
|
||||
orders[i] = response.data[i].split(',');
|
||||
}
|
||||
that.writeDynaList({
|
||||
name,
|
||||
id,
|
||||
itemClass: attr
|
||||
}, orders, that.originalPosition, originalOrder);
|
||||
}
|
||||
}
|
||||
|
||||
/** Render messages, if any. There are only message in case of errors. * */
|
||||
if (typeof resp.messages === 'object' && resp.messages !== null) {
|
||||
Joomla.renderMessages(resp.messages);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
1
media/system/js/fields/joomla-field-module-order.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-module-order.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
customElements.define("joomla-field-module-order",class extends HTMLElement{constructor(){super(),this.linkedFieldSelector="",this.linkedFieldElement="",this.originalPosition="",this.writeDynaList.bind(this),this.getNewOrder.bind(this)}connectedCallback(){if(this.linkedFieldSelector=this.getAttribute("data-linked-field")||"jform_position",!this.linkedFieldSelector)throw new Error("No linked field defined!");if(this.linkedFieldElement=document.getElementById(this.linkedFieldSelector),!this.linkedFieldElement)throw new Error("No linked field defined!");const e=this;this.originalPosition=this.linkedFieldElement.value,this.getNewOrder(this.originalPosition),this.linkedFieldElement.addEventListener("change",(()=>{e.originalPosition=e.linkedFieldElement.value,e.getNewOrder(e.linkedFieldElement.value)}))}writeDynaList(e,t,i,s){let n=0;const l=document.createElement("select");this.hasAttribute("disabled")&&l.setAttribute("disabled",""),this.getAttribute("onchange")&&l.setAttribute("onchange",this.getAttribute("onchange")),this.getAttribute("size")&&l.setAttribute("size",this.getAttribute("size")),l.classList.add(e.itemClass),l.setAttribute("name",e.name),l.id=e.id;for(const e in t){if(!t.hasOwnProperty(e))continue;const d=document.createElement("option"),r=t[e];d.value=r[1],d.innerHTML=Joomla.sanitizeHtml(r[2]),(i&&s===r[1]||!i&&0===n)&&d.setAttribute("selected","selected"),l.appendChild(d),n+=1}this.innerHTML="",this.appendChild(l)}getNewOrder(e){const t=this.getAttribute("data-url"),i=this.getAttribute("data-client-id"),s=this.getAttribute("data-ordering"),n=this.getAttribute("data-name"),l=this.getAttribute("data-client-attr")?this.getAttribute("data-client-attr"):"form-select",d=`${this.getAttribute("data-id")}`,r=`${this.getAttribute("data-module-id")}`,o=[],a=this;Joomla.request({url:`${t}&client_id=${i}&position=${e}&module_id=${r}`,method:"GET",perform:!0,headers:{"Content-Type":"application/x-www-form-urlencoded"},onSuccess(e){if(e){let t;try{t=JSON.parse(e)}catch(e){console.error(e)}if(t.data.length>0){for(let e=0;e<t.data.length;e+=1)o[e]=t.data[e].split(",");a.writeDynaList({name:n,id:d,itemClass:l},o,a.originalPosition,s)}}"object"==typeof e.messages&&null!==e.messages&&Joomla.renderMessages(e.messages)}})}});
|
||||
BIN
media/system/js/fields/joomla-field-module-order.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-module-order.min.js.gz
Normal file
Binary file not shown.
166
media/system/js/fields/joomla-field-permissions.js
Normal file
166
media/system/js/fields/joomla-field-permissions.js
Normal file
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
window.customElements.define('joomla-field-permissions', class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
if (!Joomla) {
|
||||
throw new Error('Joomla API is not properly initiated');
|
||||
}
|
||||
if (!this.getAttribute('data-uri')) {
|
||||
throw new Error('No valid url for validation');
|
||||
}
|
||||
this.query = window.location.search.substring(1);
|
||||
this.buttons = '';
|
||||
this.buttonDataSelector = 'data-onchange-task';
|
||||
this.onDropdownChange = this.onDropdownChange.bind(this);
|
||||
this.getUrlParam = this.getUrlParam.bind(this);
|
||||
this.component = this.getUrlParam('component');
|
||||
this.extension = this.getUrlParam('extension');
|
||||
this.option = this.getUrlParam('option');
|
||||
this.view = this.getUrlParam('view');
|
||||
this.asset = 'not';
|
||||
this.context = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle
|
||||
*/
|
||||
connectedCallback() {
|
||||
this.buttons = [].slice.call(document.querySelectorAll(`[${this.buttonDataSelector}]`));
|
||||
if (this.buttons) {
|
||||
this.buttons.forEach(button => {
|
||||
button.addEventListener('change', this.onDropdownChange);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle
|
||||
*/
|
||||
disconnectedCallback() {
|
||||
if (this.buttons) {
|
||||
this.buttons.forEach(button => {
|
||||
button.removeEventListener('change', this.onDropdownChange);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle
|
||||
*/
|
||||
onDropdownChange(event) {
|
||||
event.preventDefault();
|
||||
const task = event.target.getAttribute(this.buttonDataSelector);
|
||||
if (task === 'permissions.apply') {
|
||||
this.sendPermissions(event);
|
||||
}
|
||||
}
|
||||
sendPermissions(event) {
|
||||
const {
|
||||
target
|
||||
} = event;
|
||||
|
||||
// Set the icon while storing the values
|
||||
const icon = document.getElementById(`icon_${target.id}`);
|
||||
icon.removeAttribute('class');
|
||||
icon.setAttribute('class', 'joomla-icon joomla-field-permissions__spinner');
|
||||
|
||||
// Get values add prepare GET-Parameter
|
||||
const {
|
||||
value
|
||||
} = target;
|
||||
if (document.getElementById('jform_context')) {
|
||||
this.context = document.getElementById('jform_context').value;
|
||||
[this.context] = this.context.split('.');
|
||||
}
|
||||
if (this.option === 'com_config' && !this.component && !this.extension) {
|
||||
this.asset = 'root.1';
|
||||
} else if (!this.extension && this.view === 'component') {
|
||||
this.asset = this.component;
|
||||
} else if (this.context) {
|
||||
if (this.view === 'group') {
|
||||
this.asset = `${this.context}.fieldgroup.${this.getUrlParam('id')}`;
|
||||
} else {
|
||||
this.asset = `${this.context}.field.{this.getUrlParam('id')}`;
|
||||
}
|
||||
this.title = document.getElementById('jform_title').value;
|
||||
} else if (this.extension && this.view) {
|
||||
this.asset = `${this.extension}.${this.view}.${this.getUrlParam('id')}`;
|
||||
this.title = document.getElementById('jform_title').value;
|
||||
} else if (!this.extension && this.view) {
|
||||
this.asset = `${this.option}.${this.view}.${this.getUrlParam('id')}`;
|
||||
this.title = document.getElementById('jform_title').value;
|
||||
}
|
||||
const id = target.id.replace('jform_rules_', '');
|
||||
const lastUnderscoreIndex = id.lastIndexOf('_');
|
||||
const permissionData = {
|
||||
comp: this.asset,
|
||||
action: id.substring(0, lastUnderscoreIndex),
|
||||
rule: id.substring(lastUnderscoreIndex + 1),
|
||||
value,
|
||||
title: this.title
|
||||
};
|
||||
|
||||
// Remove JS messages, if they exist.
|
||||
Joomla.removeMessages();
|
||||
|
||||
// Ajax request
|
||||
Joomla.request({
|
||||
url: this.getAttribute('data-uri'),
|
||||
method: 'POST',
|
||||
data: JSON.stringify(permissionData),
|
||||
perform: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
onSuccess: data => {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(data);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
icon.removeAttribute('class');
|
||||
|
||||
// Check if everything is OK
|
||||
if (response.data && response.data.result) {
|
||||
icon.setAttribute('class', 'joomla-icon joomla-field-permissions__allowed');
|
||||
const badgeSpan = target.parentNode.parentNode.nextElementSibling.querySelector('span');
|
||||
badgeSpan.removeAttribute('class');
|
||||
badgeSpan.setAttribute('class', response.data.class);
|
||||
badgeSpan.innerHTML = Joomla.sanitizeHtml(response.data.text);
|
||||
}
|
||||
|
||||
// Render messages, if any. There are only message in case of errors.
|
||||
if (typeof response.messages === 'object' && response.messages !== null) {
|
||||
Joomla.renderMessages(response.messages);
|
||||
if (response.data && response.data.result) {
|
||||
icon.setAttribute('class', 'joomla-icon joomla-field-permissions__allowed');
|
||||
} else {
|
||||
icon.setAttribute('class', 'joomla-icon joomla-field-permissions__denied');
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: xhr => {
|
||||
// Remove the spinning icon.
|
||||
icon.removeAttribute('style');
|
||||
Joomla.renderMessages(Joomla.ajaxErrorsMessages(xhr, xhr.statusText));
|
||||
icon.setAttribute('class', 'joomla-icon joomla-field-permissions__denied');
|
||||
}
|
||||
});
|
||||
}
|
||||
getUrlParam(variable) {
|
||||
const vars = this.query.split('&');
|
||||
let i = 0;
|
||||
for (i; i < vars.length; i += 1) {
|
||||
const pair = vars[i].split('=');
|
||||
if (pair[0] === variable) {
|
||||
return pair[1];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
1
media/system/js/fields/joomla-field-permissions.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-permissions.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
window.customElements.define("joomla-field-permissions",class extends HTMLElement{constructor(){if(super(),!Joomla)throw new Error("Joomla API is not properly initiated");if(!this.getAttribute("data-uri"))throw new Error("No valid url for validation");this.query=window.location.search.substring(1),this.buttons="",this.buttonDataSelector="data-onchange-task",this.onDropdownChange=this.onDropdownChange.bind(this),this.getUrlParam=this.getUrlParam.bind(this),this.component=this.getUrlParam("component"),this.extension=this.getUrlParam("extension"),this.option=this.getUrlParam("option"),this.view=this.getUrlParam("view"),this.asset="not",this.context=""}connectedCallback(){this.buttons=[].slice.call(document.querySelectorAll(`[${this.buttonDataSelector}]`)),this.buttons&&this.buttons.forEach((t=>{t.addEventListener("change",this.onDropdownChange)}))}disconnectedCallback(){this.buttons&&this.buttons.forEach((t=>{t.removeEventListener("change",this.onDropdownChange)}))}onDropdownChange(t){t.preventDefault();"permissions.apply"===t.target.getAttribute(this.buttonDataSelector)&&this.sendPermissions(t)}sendPermissions(t){const{target:e}=t,s=document.getElementById(`icon_${e.id}`);s.removeAttribute("class"),s.setAttribute("class","joomla-icon joomla-field-permissions__spinner");const{value:o}=e;document.getElementById("jform_context")&&(this.context=document.getElementById("jform_context").value,[this.context]=this.context.split(".")),"com_config"!==this.option||this.component||this.extension?this.extension||"component"!==this.view?this.context?("group"===this.view?this.asset=`${this.context}.fieldgroup.${this.getUrlParam("id")}`:this.asset=`${this.context}.field.{this.getUrlParam('id')}`,this.title=document.getElementById("jform_title").value):this.extension&&this.view?(this.asset=`${this.extension}.${this.view}.${this.getUrlParam("id")}`,this.title=document.getElementById("jform_title").value):!this.extension&&this.view&&(this.asset=`${this.option}.${this.view}.${this.getUrlParam("id")}`,this.title=document.getElementById("jform_title").value):this.asset=this.component:this.asset="root.1";const i=e.id.replace("jform_rules_",""),n=i.lastIndexOf("_"),a={comp:this.asset,action:i.substring(0,n),rule:i.substring(n+1),value:o,title:this.title};Joomla.removeMessages(),Joomla.request({url:this.getAttribute("data-uri"),method:"POST",data:JSON.stringify(a),perform:!0,headers:{"Content-Type":"application/json"},onSuccess:t=>{let o;try{o=JSON.parse(t)}catch(t){console.error(t)}if(s.removeAttribute("class"),o.data&&o.data.result){s.setAttribute("class","joomla-icon joomla-field-permissions__allowed");const t=e.parentNode.parentNode.nextElementSibling.querySelector("span");t.removeAttribute("class"),t.setAttribute("class",o.data.class),t.innerHTML=Joomla.sanitizeHtml(o.data.text)}"object"==typeof o.messages&&null!==o.messages&&(Joomla.renderMessages(o.messages),o.data&&o.data.result?s.setAttribute("class","joomla-icon joomla-field-permissions__allowed"):s.setAttribute("class","joomla-icon joomla-field-permissions__denied"))},onError:t=>{s.removeAttribute("style"),Joomla.renderMessages(Joomla.ajaxErrorsMessages(t,t.statusText)),s.setAttribute("class","joomla-icon joomla-field-permissions__denied")}})}getUrlParam(t){const e=this.query.split("&");let s=0;for(;s<e.length;s+=1){const o=e[s].split("=");if(o[0]===t)return o[1]}return!1}});
|
||||
BIN
media/system/js/fields/joomla-field-permissions.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-permissions.min.js.gz
Normal file
Binary file not shown.
74
media/system/js/fields/joomla-field-send-test-mail.js
Normal file
74
media/system/js/fields/joomla-field-send-test-mail.js
Normal file
@ -0,0 +1,74 @@
|
||||
((customElements, Joomla) => {
|
||||
class JoomlaFieldSendTestMail extends HTMLElement {
|
||||
// attributeChangedCallback(attr, oldValue, newValue) {}
|
||||
constructor() {
|
||||
super();
|
||||
if (!Joomla) {
|
||||
throw new Error('Joomla API is not properly initiated');
|
||||
}
|
||||
if (!this.getAttribute('uri')) {
|
||||
throw new Error('No valid url for validation');
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
const self = this;
|
||||
const button = document.getElementById('sendtestmail');
|
||||
if (button) {
|
||||
button.addEventListener('click', () => {
|
||||
self.sendTestMail(self);
|
||||
});
|
||||
}
|
||||
}
|
||||
sendTestMail() {
|
||||
const emailData = {
|
||||
smtpauth: document.getElementById('jform_smtpauth1').checked ? 1 : 0,
|
||||
smtpuser: this.querySelector('[name="jform[smtpuser]"]').value,
|
||||
smtphost: this.querySelector('[name="jform[smtphost]"]').value,
|
||||
smtpsecure: this.querySelector('[name="jform[smtpsecure]"]').value,
|
||||
smtpport: this.querySelector('[name="jform[smtpport]"]').value,
|
||||
mailfrom: this.querySelector('[name="jform[mailfrom]"]').value,
|
||||
fromname: this.querySelector('[name="jform[fromname]"]').value,
|
||||
mailer: this.querySelector('[name="jform[mailer]"]').value,
|
||||
mailonline: document.getElementById('jform_mailonline1').checked ? 1 : 0
|
||||
};
|
||||
const smtppass = this.querySelector('[name="jform[smtppass]"]');
|
||||
if (smtppass.disabled === false) {
|
||||
emailData.smtppass = smtppass.value;
|
||||
}
|
||||
|
||||
// Remove js messages, if they exist.
|
||||
Joomla.removeMessages();
|
||||
Joomla.request({
|
||||
url: this.getAttribute('uri'),
|
||||
method: 'POST',
|
||||
data: JSON.stringify(emailData),
|
||||
perform: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
onSuccess: resp => {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(resp);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
if (typeof response.messages === 'object' && response.messages !== null) {
|
||||
Joomla.renderMessages(response.messages);
|
||||
}
|
||||
document.body.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
},
|
||||
onError: xhr => {
|
||||
Joomla.renderMessages(Joomla.ajaxErrorsMessages(xhr));
|
||||
document.body.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-send-test-mail', JoomlaFieldSendTestMail);
|
||||
})(customElements, Joomla);
|
||||
1
media/system/js/fields/joomla-field-send-test-mail.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-send-test-mail.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
((e,t)=>{class s extends HTMLElement{constructor(){if(super(),!t)throw new Error("Joomla API is not properly initiated");if(!this.getAttribute("uri"))throw new Error("No valid url for validation")}connectedCallback(){const e=this,t=document.getElementById("sendtestmail");t&&t.addEventListener("click",(()=>{e.sendTestMail(e)}))}sendTestMail(){const e={smtpauth:document.getElementById("jform_smtpauth1").checked?1:0,smtpuser:this.querySelector('[name="jform[smtpuser]"]').value,smtphost:this.querySelector('[name="jform[smtphost]"]').value,smtpsecure:this.querySelector('[name="jform[smtpsecure]"]').value,smtpport:this.querySelector('[name="jform[smtpport]"]').value,mailfrom:this.querySelector('[name="jform[mailfrom]"]').value,fromname:this.querySelector('[name="jform[fromname]"]').value,mailer:this.querySelector('[name="jform[mailer]"]').value,mailonline:document.getElementById("jform_mailonline1").checked?1:0},s=this.querySelector('[name="jform[smtppass]"]');!1===s.disabled&&(e.smtppass=s.value),t.removeMessages(),t.request({url:this.getAttribute("uri"),method:"POST",data:JSON.stringify(e),perform:!0,headers:{"Content-Type":"application/json"},onSuccess:e=>{let s;try{s=JSON.parse(e)}catch(e){console.error(e)}"object"==typeof s.messages&&null!==s.messages&&t.renderMessages(s.messages),document.body.scrollIntoView({behavior:"smooth"})},onError:e=>{t.renderMessages(t.ajaxErrorsMessages(e)),document.body.scrollIntoView({behavior:"smooth"})}})}}e.define("joomla-field-send-test-mail",s)})(customElements,Joomla);
|
||||
BIN
media/system/js/fields/joomla-field-send-test-mail.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-send-test-mail.min.js.gz
Normal file
Binary file not shown.
410
media/system/js/fields/joomla-field-simple-color.js
Normal file
410
media/system/js/fields/joomla-field-simple-color.js
Normal file
@ -0,0 +1,410 @@
|
||||
/**
|
||||
* Based on:
|
||||
* Very simple jQuery Color Picker
|
||||
* Copyright (C) 2012 Tanguy Krotoff
|
||||
* Licensed under the MIT license
|
||||
*
|
||||
* ADAPTED BY: Dimitris Grammatikogiannis
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
(customElements => {
|
||||
const KEYCODE = {
|
||||
TAB: 9,
|
||||
ESC: 27
|
||||
};
|
||||
const colorNames = {
|
||||
aliceblue: '#f0f8ff',
|
||||
antiquewhite: '#faebd7',
|
||||
aqua: '#00ffff',
|
||||
aquamarine: '#7fffd4',
|
||||
azure: '#f0ffff',
|
||||
beige: '#f5f5dc',
|
||||
bisque: '#ffe4c4',
|
||||
black: '#000000',
|
||||
blanchedalmond: '#ffebcd',
|
||||
blue: '#0000ff',
|
||||
blueviolet: '#8a2be2',
|
||||
brown: '#a52a2a',
|
||||
burlywood: '#deb887',
|
||||
cadetblue: '#5f9ea0',
|
||||
chartreuse: '#7fff00',
|
||||
chocolate: '#d2691e',
|
||||
coral: '#ff7f50',
|
||||
cornflowerblue: '#6495ed',
|
||||
cornsilk: '#fff8dc',
|
||||
crimson: '#dc143c',
|
||||
cyan: '#00ffff',
|
||||
darkblue: '#00008b',
|
||||
darkcyan: '#008b8b',
|
||||
darkgoldenrod: '#b8860b',
|
||||
darkgray: '#a9a9a9',
|
||||
darkgreen: '#006400',
|
||||
darkgrey: '#a9a9a9',
|
||||
darkkhaki: '#bdb76b',
|
||||
darkmagenta: '#8b008b',
|
||||
darkolivegreen: '#556b2f',
|
||||
darkorange: '#ff8c00',
|
||||
darkorchid: '#9932cc',
|
||||
darkred: '#8b0000',
|
||||
darksalmon: '#e9967a',
|
||||
darkseagreen: '#8fbc8f',
|
||||
darkslateblue: '#483d8b',
|
||||
darkslategray: '#2f4f4f',
|
||||
darkslategrey: '#2f4f4f',
|
||||
darkturquoise: '#00ced1',
|
||||
darkviolet: '#9400d3',
|
||||
deeppink: '#ff1493',
|
||||
deepskyblue: '#00bfff',
|
||||
dimgray: '#696969',
|
||||
dimgrey: '#696969',
|
||||
dodgerblue: '#1e90ff',
|
||||
firebrick: '#b22222',
|
||||
floralwhite: '#fffaf0',
|
||||
forestgreen: '#228b22',
|
||||
fuchsia: '#ff00ff',
|
||||
gainsboro: '#dcdcdc',
|
||||
ghostwhite: '#f8f8ff',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
grey: '#808080',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
lawngreen: '#7cfc00',
|
||||
lemonchiffon: '#fffacd',
|
||||
lightblue: '#add8e6',
|
||||
lightcoral: '#f08080',
|
||||
lightcyan: '#e0ffff',
|
||||
lightgoldenrodyellow: '#fafad2',
|
||||
lightgray: '#d3d3d3',
|
||||
lightgreen: '#90ee90',
|
||||
lightgrey: '#d3d3d3',
|
||||
lightpink: '#ffb6c1',
|
||||
lightsalmon: '#ffa07a',
|
||||
lightseagreen: '#20b2aa',
|
||||
lightskyblue: '#87cefa',
|
||||
lightslategray: '#778899',
|
||||
lightslategrey: '#778899',
|
||||
lightsteelblue: '#b0c4de',
|
||||
lightyellow: '#ffffe0',
|
||||
lime: '#00ff00',
|
||||
limegreen: '#32cd32',
|
||||
linen: '#faf0e6',
|
||||
magenta: '#ff00ff',
|
||||
maroon: '#800000',
|
||||
mediumaquamarine: '#66cdaa',
|
||||
mediumblue: '#0000cd',
|
||||
mediumorchid: '#ba55d3',
|
||||
mediumpurple: '#9370db',
|
||||
mediumseagreen: '#3cb371',
|
||||
mediumslateblue: '#7b68ee',
|
||||
mediumspringgreen: '#00fa9a',
|
||||
mediumturquoise: '#48d1cc',
|
||||
mediumvioletred: '#c71585',
|
||||
midnightblue: '#191970',
|
||||
mintcream: '#f5fffa',
|
||||
mistyrose: '#ffe4e1',
|
||||
moccasin: '#ffe4b5',
|
||||
navajowhite: '#ffdead',
|
||||
navy: '#000080',
|
||||
oldlace: '#fdf5e6',
|
||||
olive: '#808000',
|
||||
olivedrab: '#6b8e23',
|
||||
orange: '#ffa500',
|
||||
orangered: '#ff4500',
|
||||
orchid: '#da70d6',
|
||||
palegoldenrod: '#eee8aa',
|
||||
palegreen: '#98fb98',
|
||||
paleturquoise: '#afeeee',
|
||||
palevioletred: '#db7093',
|
||||
papayawhip: '#ffefd5',
|
||||
peachpuff: '#ffdab9',
|
||||
peru: '#cd853f',
|
||||
pink: '#ffc0cb',
|
||||
plum: '#dda0dd',
|
||||
powderblue: '#b0e0e6',
|
||||
purple: '#800080',
|
||||
red: '#ff0000',
|
||||
rosybrown: '#bc8f8f',
|
||||
royalblue: '#4169e1',
|
||||
saddlebrown: '#8b4513',
|
||||
salmon: '#fa8072',
|
||||
sandybrown: '#f4a460',
|
||||
seagreen: '#2e8b57',
|
||||
seashell: '#fff5ee',
|
||||
sienna: '#a0522d',
|
||||
silver: '#c0c0c0',
|
||||
skyblue: '#87ceeb',
|
||||
slateblue: '#6a5acd',
|
||||
slategray: '#708090',
|
||||
slategrey: '#708090',
|
||||
snow: '#fffafa',
|
||||
springgreen: '#00ff7f',
|
||||
steelblue: '#4682b4',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
tomato: '#ff6347',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
wheat: '#f5deb3',
|
||||
white: '#ffffff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ffff00',
|
||||
yellowgreen: '#9acd32'
|
||||
};
|
||||
class JoomlaFieldSimpleColor extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Define some variables
|
||||
this.select = '';
|
||||
this.options = [];
|
||||
this.icon = '';
|
||||
this.panel = '';
|
||||
this.buttons = [];
|
||||
this.focusableElements = null;
|
||||
this.focusableSelectors = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])'];
|
||||
}
|
||||
connectedCallback() {
|
||||
this.select = this.querySelector('select');
|
||||
if (!this.select) {
|
||||
throw new Error('Simple color field requires a select element');
|
||||
}
|
||||
this.options = [].slice.call(this.select.querySelectorAll('option'));
|
||||
this.select.classList.add('hidden');
|
||||
|
||||
// Build the pop up
|
||||
this.options.forEach(option => {
|
||||
let color = option.value;
|
||||
let clss = 'swatch';
|
||||
if (color === 'none') {
|
||||
clss += ' nocolor';
|
||||
color = 'transparent';
|
||||
}
|
||||
if (option.selected) {
|
||||
clss += ' active';
|
||||
}
|
||||
const el = document.createElement('button');
|
||||
el.setAttribute('class', clss);
|
||||
el.style.backgroundColor = color;
|
||||
el.setAttribute('type', 'button');
|
||||
const a11yColor = color === 'transparent' ? this.textTransp : this.getColorName(color);
|
||||
el.innerHTML = Joomla.sanitizeHtml(`<span class="visually-hidden">${a11yColor}</span>`);
|
||||
this.buttons.push(el);
|
||||
});
|
||||
|
||||
// Add a close button
|
||||
const close = document.createElement('button');
|
||||
close.setAttribute('class', 'btn-close');
|
||||
close.setAttribute('type', 'button');
|
||||
close.innerHTML = Joomla.sanitizeHtml(this.textClose);
|
||||
this.buttons.push(close);
|
||||
let color = this.select.value;
|
||||
let clss = '';
|
||||
if (color === 'none') {
|
||||
clss += ' nocolor';
|
||||
color = 'transparent';
|
||||
}
|
||||
this.icon = document.createElement('button');
|
||||
if (clss) {
|
||||
this.icon.setAttribute('class', clss);
|
||||
}
|
||||
const uniqueId = `simple-color-${Math.random().toString(36).substr(2, 10)}`;
|
||||
this.icon.setAttribute('type', 'button');
|
||||
this.icon.setAttribute('tabindex', '0');
|
||||
this.icon.style.backgroundColor = color;
|
||||
this.icon.innerHTML = Joomla.sanitizeHtml(`<span class="visually-hidden">${this.textSelect}</span>`);
|
||||
this.icon.id = uniqueId;
|
||||
this.select.insertAdjacentElement('beforebegin', this.icon);
|
||||
this.icon.addEventListener('click', this.show.bind(this));
|
||||
this.panel = document.createElement('div');
|
||||
this.panel.classList.add('simplecolors-panel');
|
||||
this.panel.setAttribute('aria-labelledby', uniqueId);
|
||||
this.hide = this.hide.bind(this);
|
||||
this.colorSelect = this.colorSelect.bind(this);
|
||||
this.buttons.forEach(el => {
|
||||
if (el.classList.contains('btn-close')) {
|
||||
el.addEventListener('click', this.hide);
|
||||
} else {
|
||||
el.addEventListener('click', this.colorSelect);
|
||||
}
|
||||
this.panel.insertAdjacentElement('beforeend', el);
|
||||
});
|
||||
this.appendChild(this.panel);
|
||||
this.focusableElements = [].slice.call(this.panel.querySelectorAll(this.focusableSelectors.join()));
|
||||
this.keys = this.keys.bind(this);
|
||||
this.hide = this.hide.bind(this);
|
||||
this.mousedown = this.mousedown.bind(this);
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ['text-select', 'text-color', 'text-close', 'text-transparent'];
|
||||
}
|
||||
get textSelect() {
|
||||
return this.getAttribute('text-select');
|
||||
}
|
||||
get textColor() {
|
||||
return this.getAttribute('text-color');
|
||||
}
|
||||
get textClose() {
|
||||
return this.getAttribute('text-close');
|
||||
}
|
||||
get textTransp() {
|
||||
return this.getAttribute('text-transparent');
|
||||
}
|
||||
|
||||
// Show the panel
|
||||
show() {
|
||||
document.addEventListener('mousedown', this.hide);
|
||||
this.addEventListener('keydown', this.keys);
|
||||
this.panel.addEventListener('mousedown', this.mousedown);
|
||||
this.panel.setAttribute('data-open', '');
|
||||
const focused = this.panel.querySelector('button');
|
||||
if (focused) {
|
||||
focused.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Hide panel
|
||||
hide() {
|
||||
document.removeEventListener('mousedown', this.hide, false);
|
||||
this.removeEventListener('keydown', this.keys);
|
||||
if (this.panel.hasAttribute('data-open')) {
|
||||
this.panel.removeAttribute('data-open');
|
||||
}
|
||||
this.icon.focus();
|
||||
}
|
||||
colorSelect(e) {
|
||||
let color = '';
|
||||
let bgcolor = '';
|
||||
let clss = '';
|
||||
if (e.target.classList.contains('nocolor')) {
|
||||
color = 'none';
|
||||
bgcolor = 'transparent';
|
||||
clss = 'nocolor';
|
||||
} else {
|
||||
color = this.rgb2hex(e.target.style.backgroundColor);
|
||||
bgcolor = color;
|
||||
}
|
||||
|
||||
// Reset the active class
|
||||
this.buttons.forEach(el => {
|
||||
if (el.classList.contains('active')) {
|
||||
el.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Add the active class to the selected button
|
||||
e.target.classList.add('active');
|
||||
this.icon.classList.remove('nocolor');
|
||||
this.icon.setAttribute('class', clss);
|
||||
this.icon.style.backgroundColor = bgcolor;
|
||||
|
||||
// trigger change event both on the select and on the custom element
|
||||
this.select.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
value: color
|
||||
},
|
||||
bubbles: true
|
||||
}));
|
||||
|
||||
// Hide the panel
|
||||
this.hide();
|
||||
|
||||
// Change select value
|
||||
this.options.forEach(el => {
|
||||
if (el.selected) {
|
||||
el.removeAttribute('selected');
|
||||
}
|
||||
if (el.value === bgcolor) {
|
||||
el.setAttribute('selected', '');
|
||||
}
|
||||
});
|
||||
}
|
||||
keys(e) {
|
||||
if (e.keyCode === KEYCODE.ESC) {
|
||||
this.hide();
|
||||
}
|
||||
if (e.keyCode === KEYCODE.TAB) {
|
||||
// Get the index of the current active element
|
||||
const focusedIndex = this.focusableElements.indexOf(document.activeElement);
|
||||
|
||||
// If first element is focused and shiftkey is in use, focus last item within modal
|
||||
if (e.shiftKey && (focusedIndex === 0 || focusedIndex === -1)) {
|
||||
this.focusableElements[this.focusableElements.length - 1].focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
// If last element is focused and shiftkey is not in use, focus first item within modal
|
||||
if (!e.shiftKey && focusedIndex === this.focusableElements.length - 1) {
|
||||
this.focusableElements[0].focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevents the mousedown event from "eating" the click event.
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
mousedown(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
getColorName(value) {
|
||||
// Expand any short code
|
||||
let newValue = value;
|
||||
if (value.length === 4) {
|
||||
const tmpValue = value.split('');
|
||||
newValue = tmpValue[0] + tmpValue[1] + tmpValue[1] + tmpValue[2] + tmpValue[2] + tmpValue[3] + tmpValue[3];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const color in colorNames) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (colorNames.hasOwnProperty(color) && newValue.toLowerCase() === colorNames[color]) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
return `${this.textColor} ${value.replace('#', '').split('').join(', ')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a RGB color to its hexadecimal value.
|
||||
* See http://stackoverflow.com/questions/1740700/get-hex-value-rather-than-rgb-value-using-$
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
rgb2hex(rgb) {
|
||||
const hex = x => `0${parseInt(x, 10).toString(16)}`.slice(-2);
|
||||
const matches = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
return `#${hex(matches[1])}${hex(matches[2])}${hex(matches[3])}`;
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-simple-color', JoomlaFieldSimpleColor);
|
||||
})(customElements);
|
||||
1
media/system/js/fields/joomla-field-simple-color.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-simple-color.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-simple-color.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-simple-color.min.js.gz
Normal file
Binary file not shown.
558
media/system/js/fields/joomla-field-subform.js
Normal file
558
media/system/js/fields/joomla-field-subform.js
Normal file
@ -0,0 +1,558 @@
|
||||
/**
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
(customElements => {
|
||||
|
||||
const KEYCODE = {
|
||||
SPACE: 32,
|
||||
ESC: 27,
|
||||
ENTER: 13
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper for testing whether a selection modifier is pressed
|
||||
* @param {Event} event
|
||||
*
|
||||
* @returns {boolean|*}
|
||||
*/
|
||||
function hasModifier(event) {
|
||||
return event.ctrlKey || event.metaKey || event.shiftKey;
|
||||
}
|
||||
class JoomlaFieldSubform extends HTMLElement {
|
||||
// Attribute getters
|
||||
get buttonAdd() {
|
||||
return this.getAttribute('button-add');
|
||||
}
|
||||
get buttonRemove() {
|
||||
return this.getAttribute('button-remove');
|
||||
}
|
||||
get buttonMove() {
|
||||
return this.getAttribute('button-move');
|
||||
}
|
||||
get rowsContainer() {
|
||||
return this.getAttribute('rows-container');
|
||||
}
|
||||
get repeatableElement() {
|
||||
return this.getAttribute('repeatable-element');
|
||||
}
|
||||
get minimum() {
|
||||
return this.getAttribute('minimum');
|
||||
}
|
||||
get maximum() {
|
||||
return this.getAttribute('maximum');
|
||||
}
|
||||
get name() {
|
||||
return this.getAttribute('name');
|
||||
}
|
||||
set name(value) {
|
||||
// Update the template
|
||||
this.template = this.template.replace(new RegExp(` name="${this.name.replace(/[[\]]/g, '\\$&')}`, 'g'), ` name="${value}`);
|
||||
this.setAttribute('name', value);
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
const that = this;
|
||||
|
||||
// Get the rows container
|
||||
this.containerWithRows = this;
|
||||
if (this.rowsContainer) {
|
||||
const allContainers = this.querySelectorAll(this.rowsContainer);
|
||||
|
||||
// Find closest, and exclude nested
|
||||
Array.from(allContainers).forEach(container => {
|
||||
if (container.closest('joomla-field-subform') === this) {
|
||||
this.containerWithRows = container;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Keep track of row index, this is important to avoid a name duplication
|
||||
// Note: php side should reset the indexes each time, eg: $value = array_values($value);
|
||||
this.lastRowIndex = this.getRows().length - 1;
|
||||
|
||||
// Template for the repeating group
|
||||
this.template = '';
|
||||
|
||||
// Prepare a row template, and find available field names
|
||||
this.prepareTemplate();
|
||||
|
||||
// Bind buttons
|
||||
if (this.buttonAdd || this.buttonRemove) {
|
||||
this.addEventListener('click', event => {
|
||||
let btnAdd = null;
|
||||
let btnRem = null;
|
||||
if (that.buttonAdd) {
|
||||
btnAdd = event.target.matches(that.buttonAdd) ? event.target : event.target.closest(that.buttonAdd);
|
||||
}
|
||||
if (that.buttonRemove) {
|
||||
btnRem = event.target.matches(that.buttonRemove) ? event.target : event.target.closest(that.buttonRemove);
|
||||
}
|
||||
|
||||
// Check active, with extra check for nested joomla-field-subform
|
||||
if (btnAdd && btnAdd.closest('joomla-field-subform') === that) {
|
||||
let row = btnAdd.closest(that.repeatableElement);
|
||||
row = row && row.closest('joomla-field-subform') === that ? row : null;
|
||||
that.addRow(row);
|
||||
event.preventDefault();
|
||||
} else if (btnRem && btnRem.closest('joomla-field-subform') === that) {
|
||||
const row = btnRem.closest(that.repeatableElement);
|
||||
that.removeRow(row);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
this.addEventListener('keydown', event => {
|
||||
if (event.keyCode !== KEYCODE.SPACE) return;
|
||||
const isAdd = that.buttonAdd && event.target.matches(that.buttonAdd);
|
||||
const isRem = that.buttonRemove && event.target.matches(that.buttonRemove);
|
||||
if ((isAdd || isRem) && event.target.closest('joomla-field-subform') === that) {
|
||||
let row = event.target.closest(that.repeatableElement);
|
||||
row = row && row.closest('joomla-field-subform') === that ? row : null;
|
||||
if (isRem && row) {
|
||||
that.removeRow(row);
|
||||
} else if (isAdd) {
|
||||
that.addRow(row);
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sorting
|
||||
if (this.buttonMove) {
|
||||
this.setUpDragSort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for existing rows
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
getRows() {
|
||||
const rows = Array.from(this.containerWithRows.children);
|
||||
const result = [];
|
||||
|
||||
// Filter out the rows
|
||||
rows.forEach(row => {
|
||||
if (row.matches(this.repeatableElement)) {
|
||||
result.push(row);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a row template
|
||||
*/
|
||||
prepareTemplate() {
|
||||
const tmplElement = [].slice.call(this.children).filter(el => el.classList.contains('subform-repeatable-template-section'));
|
||||
if (tmplElement[0]) {
|
||||
this.template = tmplElement[0].innerHTML;
|
||||
}
|
||||
if (!this.template) {
|
||||
throw new Error('The row template is required for the subform element to work');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new row
|
||||
* @param {HTMLElement} after
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
addRow(after) {
|
||||
// Count how many we already have
|
||||
const count = this.getRows().length;
|
||||
if (count >= this.maximum) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make a new row from the template
|
||||
let tmpEl;
|
||||
if (this.containerWithRows.nodeName === 'TBODY' || this.containerWithRows.nodeName === 'TABLE') {
|
||||
tmpEl = document.createElement('tbody');
|
||||
} else {
|
||||
tmpEl = document.createElement('div');
|
||||
}
|
||||
tmpEl.innerHTML = this.template;
|
||||
const row = tmpEl.children[0];
|
||||
|
||||
// Add to container
|
||||
if (after) {
|
||||
after.parentNode.insertBefore(row, after.nextSibling);
|
||||
} else {
|
||||
this.containerWithRows.append(row);
|
||||
}
|
||||
|
||||
// Add draggable attributes
|
||||
if (this.buttonMove) {
|
||||
row.setAttribute('draggable', 'false');
|
||||
row.setAttribute('aria-grabbed', 'false');
|
||||
row.setAttribute('tabindex', '0');
|
||||
}
|
||||
|
||||
// Marker that it is new
|
||||
row.setAttribute('data-new', '1');
|
||||
// Fix names and ids, and reset values
|
||||
this.fixUniqueAttributes(row, count);
|
||||
|
||||
// Tell about the new row
|
||||
this.dispatchEvent(new CustomEvent('subform-row-add', {
|
||||
detail: {
|
||||
row
|
||||
},
|
||||
bubbles: true
|
||||
}));
|
||||
row.dispatchEvent(new CustomEvent('joomla:updated', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the row
|
||||
* @param {HTMLElement} row
|
||||
*/
|
||||
removeRow(row) {
|
||||
// Count how much we have
|
||||
const count = this.getRows().length;
|
||||
if (count <= this.minimum) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell about the row will be removed
|
||||
this.dispatchEvent(new CustomEvent('subform-row-remove', {
|
||||
detail: {
|
||||
row
|
||||
},
|
||||
bubbles: true
|
||||
}));
|
||||
row.dispatchEvent(new CustomEvent('joomla:removed', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
row.parentNode.removeChild(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix name and id for fields that are in the row
|
||||
* @param {HTMLElement} row
|
||||
* @param {Number} count
|
||||
*/
|
||||
fixUniqueAttributes(row, count) {
|
||||
const countTmp = count || 0;
|
||||
const group = row.getAttribute('data-group'); // current group name
|
||||
const basename = row.getAttribute('data-base-name');
|
||||
const countnew = Math.max(this.lastRowIndex, countTmp);
|
||||
const groupnew = basename + countnew; // new group name
|
||||
|
||||
this.lastRowIndex = countnew + 1;
|
||||
row.setAttribute('data-group', groupnew);
|
||||
|
||||
// Fix inputs that have a "name" attribute
|
||||
let haveName = row.querySelectorAll('[name]');
|
||||
const ids = {}; // Collect id for fix checkboxes and radio
|
||||
|
||||
// Filter out nested
|
||||
haveName = [].slice.call(haveName).filter(el => {
|
||||
if (el.nodeName === 'JOOMLA-FIELD-SUBFORM') {
|
||||
// Skip self in .closest() call
|
||||
return el.parentElement.closest('joomla-field-subform') === this;
|
||||
}
|
||||
return el.closest('joomla-field-subform') === this;
|
||||
});
|
||||
haveName.forEach(elem => {
|
||||
const $el = elem;
|
||||
const name = $el.getAttribute('name');
|
||||
const aria = $el.getAttribute('aria-describedby');
|
||||
const id = name.replace(/(\[\]$)/g, '').replace(/(\]\[)/g, '__').replace(/\[/g, '_').replace(/\]/g, ''); // id from name
|
||||
const nameNew = name.replace(`[${group}][`, `[${groupnew}][`); // New name
|
||||
let idNew = id.replace(group, groupnew).replace(/\W/g, '_'); // Count new id
|
||||
let countMulti = 0; // count for multiple radio/checkboxes
|
||||
let forOldAttr = id; // Fix "for" in the labels
|
||||
|
||||
if ($el.type === 'checkbox' && name.match(/\[\]$/)) {
|
||||
// <input type="checkbox" name="name[]"> fix
|
||||
// Recount id
|
||||
countMulti = ids[id] ? ids[id].length : 0;
|
||||
if (!countMulti) {
|
||||
// Set the id for fieldset and group label
|
||||
const fieldset = $el.closest('fieldset.checkboxes');
|
||||
const elLbl = row.querySelector(`label[for="${id}"]`);
|
||||
if (fieldset) {
|
||||
fieldset.setAttribute('id', idNew);
|
||||
}
|
||||
if (elLbl) {
|
||||
elLbl.setAttribute('for', idNew);
|
||||
elLbl.setAttribute('id', `${idNew}-lbl`);
|
||||
}
|
||||
}
|
||||
forOldAttr += countMulti;
|
||||
idNew += countMulti;
|
||||
} else if ($el.type === 'radio') {
|
||||
// <input type="radio"> fix
|
||||
// Recount id
|
||||
countMulti = ids[id] ? ids[id].length : 0;
|
||||
if (!countMulti) {
|
||||
// Set the id for fieldset and group label
|
||||
const fieldset = $el.closest('fieldset.radio');
|
||||
const elLbl = row.querySelector(`label[for="${id}"]`);
|
||||
if (fieldset) {
|
||||
fieldset.setAttribute('id', idNew);
|
||||
}
|
||||
if (elLbl) {
|
||||
elLbl.setAttribute('for', idNew);
|
||||
elLbl.setAttribute('id', `${idNew}-lbl`);
|
||||
}
|
||||
}
|
||||
forOldAttr += countMulti;
|
||||
idNew += countMulti;
|
||||
}
|
||||
|
||||
// Cache already used id
|
||||
if (ids[id]) {
|
||||
ids[id].push(true);
|
||||
} else {
|
||||
ids[id] = [true];
|
||||
}
|
||||
|
||||
// Replace the name to new one
|
||||
$el.name = nameNew;
|
||||
if ($el.id) {
|
||||
$el.id = idNew;
|
||||
}
|
||||
if (aria) {
|
||||
$el.setAttribute('aria-describedby', `${nameNew}-desc`);
|
||||
}
|
||||
|
||||
// Check if there is a label for this input
|
||||
const lbl = row.querySelector(`label[for="${forOldAttr}"]`);
|
||||
if (lbl) {
|
||||
lbl.setAttribute('for', idNew);
|
||||
lbl.setAttribute('id', `${idNew}-lbl`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use of HTML Drag and Drop API
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
|
||||
* https://www.sitepoint.com/accessible-drag-drop/
|
||||
*/
|
||||
setUpDragSort() {
|
||||
const that = this; // Self reference
|
||||
let item = null; // Storing the selected item
|
||||
let touched = false; // We have a touch events
|
||||
|
||||
// Find all existing rows and add draggable attributes
|
||||
const rows = Array.from(this.getRows());
|
||||
rows.forEach(row => {
|
||||
row.setAttribute('draggable', 'false');
|
||||
row.setAttribute('aria-grabbed', 'false');
|
||||
row.setAttribute('tabindex', '0');
|
||||
});
|
||||
|
||||
// Helper method to test whether Handler was clicked
|
||||
function getMoveHandler(element) {
|
||||
return !element.form // This need to test whether the element is :input
|
||||
&& element.matches(that.buttonMove) ? element : element.closest(that.buttonMove);
|
||||
}
|
||||
|
||||
// Helper method to move row to selected position
|
||||
function switchRowPositions(src, dest) {
|
||||
let isRowBefore = false;
|
||||
if (src.parentNode === dest.parentNode) {
|
||||
for (let cur = src; cur; cur = cur.previousSibling) {
|
||||
if (cur === dest) {
|
||||
isRowBefore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isRowBefore) {
|
||||
dest.parentNode.insertBefore(src, dest);
|
||||
} else {
|
||||
dest.parentNode.insertBefore(src, dest.nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch interaction:
|
||||
*
|
||||
* - a touch of "move button" marks a row draggable / "selected",
|
||||
* or deselect previous selected
|
||||
*
|
||||
* - a touch of "move button" in the destination row will move
|
||||
* a selected row to a new position
|
||||
*/
|
||||
this.addEventListener('touchstart', event => {
|
||||
touched = true;
|
||||
|
||||
// Check for .move button
|
||||
const handler = getMoveHandler(event.target);
|
||||
const row = handler ? handler.closest(that.repeatableElement) : null;
|
||||
if (!row || row.closest('joomla-field-subform') !== that) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First selection
|
||||
if (!item) {
|
||||
row.setAttribute('draggable', 'true');
|
||||
row.setAttribute('aria-grabbed', 'true');
|
||||
item = row;
|
||||
} else {
|
||||
// Second selection
|
||||
// Move to selected position
|
||||
if (row !== item) {
|
||||
switchRowPositions(item, row);
|
||||
}
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Mouse interaction
|
||||
// - mouse down, enable "draggable" and allow to drag the row,
|
||||
// - mouse up, disable "draggable"
|
||||
this.addEventListener('mousedown', ({
|
||||
target
|
||||
}) => {
|
||||
if (touched) return;
|
||||
|
||||
// Check for .move button
|
||||
const handler = getMoveHandler(target);
|
||||
const row = handler ? handler.closest(that.repeatableElement) : null;
|
||||
if (!row || row.closest('joomla-field-subform') !== that) {
|
||||
return;
|
||||
}
|
||||
row.setAttribute('draggable', 'true');
|
||||
row.setAttribute('aria-grabbed', 'true');
|
||||
item = row;
|
||||
});
|
||||
this.addEventListener('mouseup', () => {
|
||||
if (item && !touched) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard interaction
|
||||
// - "tab" to navigate to needed row,
|
||||
// - modifier (ctr,alt,shift) + "space" select the row,
|
||||
// - "tab" to select destination,
|
||||
// - "enter" to place selected row in to destination
|
||||
// - "esc" to cancel selection
|
||||
this.addEventListener('keydown', event => {
|
||||
if (event.keyCode !== KEYCODE.ESC && event.keyCode !== KEYCODE.SPACE && event.keyCode !== KEYCODE.ENTER || event.target.form || !event.target.matches(that.repeatableElement)) {
|
||||
return;
|
||||
}
|
||||
const row = event.target;
|
||||
|
||||
// Make sure we handle correct children
|
||||
if (!row || row.closest('joomla-field-subform') !== that) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Space is the selection or unselection keystroke
|
||||
if (event.keyCode === KEYCODE.SPACE && hasModifier(event)) {
|
||||
// Unselect previously selected
|
||||
if (row.getAttribute('aria-grabbed') === 'true') {
|
||||
row.setAttribute('draggable', 'false');
|
||||
row.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
} else {
|
||||
// Select new
|
||||
// If there was previously selected
|
||||
if (item) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
|
||||
// Mark new selection
|
||||
row.setAttribute('draggable', 'true');
|
||||
row.setAttribute('aria-grabbed', 'true');
|
||||
item = row;
|
||||
}
|
||||
|
||||
// Prevent default to suppress any native actions
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Escape is the cancel keystroke (for any target element)
|
||||
if (event.keyCode === KEYCODE.ESC && item) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
|
||||
// Enter, to place selected item in selected position
|
||||
if (event.keyCode === KEYCODE.ENTER && item) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
|
||||
// Do nothing here
|
||||
if (row === item) {
|
||||
item = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the item to selected position
|
||||
switchRowPositions(item, row);
|
||||
event.preventDefault();
|
||||
item = null;
|
||||
}
|
||||
});
|
||||
|
||||
// dragstart event to initiate mouse dragging
|
||||
this.addEventListener('dragstart', ({
|
||||
dataTransfer
|
||||
}) => {
|
||||
if (item) {
|
||||
// We going to move the row
|
||||
dataTransfer.effectAllowed = 'move';
|
||||
|
||||
// This need to work in Firefox and IE10+
|
||||
dataTransfer.setData('text', '');
|
||||
}
|
||||
});
|
||||
this.addEventListener('dragover', event => {
|
||||
if (item) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle drag action, move element to hovered position
|
||||
this.addEventListener('dragenter', ({
|
||||
target
|
||||
}) => {
|
||||
// Make sure the target in the correct container
|
||||
if (!item || target.parentElement.closest('joomla-field-subform') !== that) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a hovered row
|
||||
const row = target.closest(that.repeatableElement);
|
||||
|
||||
// One more check for correct parent
|
||||
if (!row || row.closest('joomla-field-subform') !== that) return;
|
||||
switchRowPositions(item, row);
|
||||
});
|
||||
|
||||
// dragend event to clean-up after drop or cancelation
|
||||
// which fires whether or not the drop target was valid
|
||||
this.addEventListener('dragend', () => {
|
||||
if (item) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-subform', JoomlaFieldSubform);
|
||||
})(customElements);
|
||||
1
media/system/js/fields/joomla-field-subform.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-subform.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-subform.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-subform.min.js.gz
Normal file
Binary file not shown.
149
media/system/js/fields/joomla-field-user.js
Normal file
149
media/system/js/fields/joomla-field-user.js
Normal file
@ -0,0 +1,149 @@
|
||||
import JoomlaDialog from 'joomla.dialog';
|
||||
|
||||
/**
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
class JoomlaFieldUser extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.onUserSelect = '';
|
||||
this.onchangeStr = '';
|
||||
|
||||
// Bind context
|
||||
this.modalClose = this.modalClose.bind(this);
|
||||
this.setValue = this.setValue.bind(this);
|
||||
this.modalOpen = this.modalOpen.bind(this);
|
||||
}
|
||||
static get observedAttributes() {
|
||||
return ['url', 'modal', 'modal-width', 'modal-height', 'modal-title', 'input', 'input-name', 'button-select'];
|
||||
}
|
||||
get url() {
|
||||
return this.getAttribute('url');
|
||||
}
|
||||
set url(value) {
|
||||
this.setAttribute('url', value);
|
||||
}
|
||||
get modalWidth() {
|
||||
return this.getAttribute('modal-width');
|
||||
}
|
||||
set modalWidth(value) {
|
||||
this.setAttribute('modal-width', value);
|
||||
}
|
||||
get modalTitle() {
|
||||
return this.getAttribute('modal-title');
|
||||
}
|
||||
set modalTitle(value) {
|
||||
this.setAttribute('modal-title', value);
|
||||
}
|
||||
get modalHeight() {
|
||||
return this.getAttribute('modal-height');
|
||||
}
|
||||
set modalHeight(value) {
|
||||
this.setAttribute('modal-height', value);
|
||||
}
|
||||
get inputId() {
|
||||
return this.getAttribute('input');
|
||||
}
|
||||
set inputId(value) {
|
||||
this.setAttribute('input', value);
|
||||
}
|
||||
get inputNameClass() {
|
||||
return this.getAttribute('input-name');
|
||||
}
|
||||
set inputNameClass(value) {
|
||||
this.setAttribute('input-name', value);
|
||||
}
|
||||
get buttonSelectClass() {
|
||||
return this.getAttribute('button-select');
|
||||
}
|
||||
set buttonSelectClass(value) {
|
||||
this.setAttribute('button-select', value);
|
||||
}
|
||||
connectedCallback() {
|
||||
// Set up elements
|
||||
this.input = this.querySelector(this.inputId);
|
||||
this.inputName = this.querySelector(this.inputNameClass);
|
||||
this.buttonSelect = this.querySelector(this.buttonSelectClass);
|
||||
if (this.buttonSelect) {
|
||||
this.buttonSelect.addEventListener('click', this.modalOpen.bind(this));
|
||||
// this.modal.addEventListener('hide', this.removeIframe.bind(this));
|
||||
|
||||
// Check for onchange callback,
|
||||
this.onchangeStr = this.input.getAttribute('data-onchange');
|
||||
if (this.onchangeStr) {
|
||||
// eslint-disable-next-line no-new-func
|
||||
this.onUserSelect = new Function(this.onchangeStr);
|
||||
this.input.addEventListener('change', this.onUserSelect);
|
||||
}
|
||||
}
|
||||
}
|
||||
disconnectedCallback() {
|
||||
if (this.onUserSelect && this.input) {
|
||||
this.input.removeEventListener('change', this.onUserSelect);
|
||||
}
|
||||
if (this.buttonSelect) {
|
||||
this.buttonSelect.removeEventListener('click', this.modalOpen);
|
||||
}
|
||||
if (this.modal) {
|
||||
this.modal.removeEventListener('hide', this);
|
||||
}
|
||||
}
|
||||
|
||||
// Opens the modal
|
||||
modalOpen() {
|
||||
// Create and show the dialog
|
||||
const dialog = new JoomlaDialog({
|
||||
popupType: 'iframe',
|
||||
src: this.url,
|
||||
textHeader: this.modalTitle,
|
||||
width: this.modalWidth,
|
||||
height: this.modalHeight
|
||||
});
|
||||
dialog.classList.add('joomla-dialog-user-field');
|
||||
dialog.show();
|
||||
|
||||
// Wait for message
|
||||
const msgListener = event => {
|
||||
// Avoid cross origins
|
||||
if (event.origin !== window.location.origin) return;
|
||||
// Check message type
|
||||
if (event.data.messageType === 'joomla:content-select') {
|
||||
this.setValue(event.data.id, event.data.name);
|
||||
dialog.close();
|
||||
} else if (event.data.messageType === 'joomla:cancel') {
|
||||
dialog.close();
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', msgListener);
|
||||
dialog.addEventListener('joomla-dialog:close', () => {
|
||||
window.removeEventListener('message', msgListener);
|
||||
dialog.destroy();
|
||||
this.dialog = null;
|
||||
});
|
||||
this.dialog = dialog;
|
||||
}
|
||||
|
||||
// Closes the modal
|
||||
modalClose() {
|
||||
if (this.dialog) {
|
||||
this.dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the value
|
||||
setValue(value, name) {
|
||||
this.input.setAttribute('value', value);
|
||||
this.inputName.setAttribute('value', name || value);
|
||||
// trigger change event both on the input and on the custom element
|
||||
this.input.dispatchEvent(new CustomEvent('change'));
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
detail: {
|
||||
value,
|
||||
name
|
||||
},
|
||||
bubbles: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-user', JoomlaFieldUser);
|
||||
1
media/system/js/fields/joomla-field-user.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-user.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import JoomlaDialog from"joomla.dialog";class JoomlaFieldUser extends HTMLElement{constructor(){super(),this.onUserSelect="",this.onchangeStr="",this.modalClose=this.modalClose.bind(this),this.setValue=this.setValue.bind(this),this.modalOpen=this.modalOpen.bind(this)}static get observedAttributes(){return["url","modal","modal-width","modal-height","modal-title","input","input-name","button-select"]}get url(){return this.getAttribute("url")}set url(t){this.setAttribute("url",t)}get modalWidth(){return this.getAttribute("modal-width")}set modalWidth(t){this.setAttribute("modal-width",t)}get modalTitle(){return this.getAttribute("modal-title")}set modalTitle(t){this.setAttribute("modal-title",t)}get modalHeight(){return this.getAttribute("modal-height")}set modalHeight(t){this.setAttribute("modal-height",t)}get inputId(){return this.getAttribute("input")}set inputId(t){this.setAttribute("input",t)}get inputNameClass(){return this.getAttribute("input-name")}set inputNameClass(t){this.setAttribute("input-name",t)}get buttonSelectClass(){return this.getAttribute("button-select")}set buttonSelectClass(t){this.setAttribute("button-select",t)}connectedCallback(){this.input=this.querySelector(this.inputId),this.inputName=this.querySelector(this.inputNameClass),this.buttonSelect=this.querySelector(this.buttonSelectClass),this.buttonSelect&&(this.buttonSelect.addEventListener("click",this.modalOpen.bind(this)),this.onchangeStr=this.input.getAttribute("data-onchange"),this.onchangeStr&&(this.onUserSelect=new Function(this.onchangeStr),this.input.addEventListener("change",this.onUserSelect)))}disconnectedCallback(){this.onUserSelect&&this.input&&this.input.removeEventListener("change",this.onUserSelect),this.buttonSelect&&this.buttonSelect.removeEventListener("click",this.modalOpen),this.modal&&this.modal.removeEventListener("hide",this)}modalOpen(){const t=new JoomlaDialog({popupType:"iframe",src:this.url,textHeader:this.modalTitle,width:this.modalWidth,height:this.modalHeight});t.classList.add("joomla-dialog-user-field"),t.show();const e=e=>{e.origin===window.location.origin&&("joomla:content-select"===e.data.messageType?(this.setValue(e.data.id,e.data.name),t.close()):"joomla:cancel"===e.data.messageType&&t.close())};window.addEventListener("message",e),t.addEventListener("joomla-dialog:close",(()=>{window.removeEventListener("message",e),t.destroy(),this.dialog=null})),this.dialog=t}modalClose(){this.dialog&&this.dialog.close()}setValue(t,e){this.input.setAttribute("value",t),this.inputName.setAttribute("value",e||t),this.input.dispatchEvent(new CustomEvent("change")),this.dispatchEvent(new CustomEvent("change",{detail:{value:t,name:e},bubbles:!0}))}}customElements.define("joomla-field-user",JoomlaFieldUser);
|
||||
BIN
media/system/js/fields/joomla-field-user.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-user.min.js.gz
Normal file
Binary file not shown.
558
media/system/js/fields/joomla-media-select.js
Normal file
558
media/system/js/fields/joomla-media-select.js
Normal file
@ -0,0 +1,558 @@
|
||||
/**
|
||||
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
if (!Joomla) {
|
||||
throw new Error('Joomla API is not properly initiated');
|
||||
}
|
||||
|
||||
/**
|
||||
* An object holding all the information of the selected image in media manager
|
||||
* eg:
|
||||
* {
|
||||
* extension: "png"
|
||||
* fileType: "image/png"
|
||||
* height: 44
|
||||
* path: "local-images:/powered_by.png"
|
||||
* thumb: undefined
|
||||
* width: 294
|
||||
* }
|
||||
*/
|
||||
Joomla.selectedMediaFile = {};
|
||||
const supportedExtensions = Joomla.getOptions('media-picker', {});
|
||||
if (!Object.keys(supportedExtensions).length) {
|
||||
throw new Error('No supported extensions provided');
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Listener that updates the Joomla.selectedMediaFile
|
||||
* to the selected file in the media manager
|
||||
*/
|
||||
document.addEventListener('onMediaFileSelected', async e => {
|
||||
Joomla.selectedMediaFile = e.detail;
|
||||
const currentModal = Joomla.Modal.getCurrent();
|
||||
const container = currentModal.querySelector('.modal-body');
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
const optionsEl = container.querySelector('joomla-field-mediamore');
|
||||
if (optionsEl) {
|
||||
optionsEl.parentNode.removeChild(optionsEl);
|
||||
}
|
||||
|
||||
// No extra attributes (lazy, alt) for fields
|
||||
if (container.closest('joomla-field-media')) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
images,
|
||||
audios,
|
||||
videos,
|
||||
documents
|
||||
} = supportedExtensions;
|
||||
if (Joomla.selectedMediaFile.path) {
|
||||
let type;
|
||||
if (images.includes(Joomla.selectedMediaFile.extension.toLowerCase())) {
|
||||
type = 'images';
|
||||
} else if (audios.includes(Joomla.selectedMediaFile.extension.toLowerCase())) {
|
||||
type = 'audios';
|
||||
} else if (videos.includes(Joomla.selectedMediaFile.extension.toLowerCase())) {
|
||||
type = 'videos';
|
||||
} else if (documents.includes(Joomla.selectedMediaFile.extension.toLowerCase())) {
|
||||
type = 'documents';
|
||||
}
|
||||
if (type) {
|
||||
container.insertAdjacentHTML('afterbegin', `<joomla-field-mediamore
|
||||
parent-id="${currentModal.id}"
|
||||
type="${type}"
|
||||
summary-label="${Joomla.Text._('JFIELD_MEDIA_SUMMARY_LABEL')}"
|
||||
lazy-label="${Joomla.Text._('JFIELD_MEDIA_LAZY_LABEL')}"
|
||||
alt-label="${Joomla.Text._('JFIELD_MEDIA_ALT_LABEL')}"
|
||||
alt-check-label="${Joomla.Text._('JFIELD_MEDIA_ALT_CHECK_LABEL')}"
|
||||
alt-check-desc-label="${Joomla.Text._('JFIELD_MEDIA_ALT_CHECK_DESC_LABEL')}"
|
||||
classes-label="${Joomla.Text._('JFIELD_MEDIA_CLASS_LABEL')}"
|
||||
figure-classes-label="${Joomla.Text._('JFIELD_MEDIA_FIGURE_CLASS_LABEL')}"
|
||||
figure-caption-label="${Joomla.Text._('JFIELD_MEDIA_FIGURE_CAPTION_LABEL')}"
|
||||
embed-check-label="${Joomla.Text._('JFIELD_MEDIA_EMBED_CHECK_LABEL')}"
|
||||
embed-check-desc-label="${Joomla.Text._('JFIELD_MEDIA_EMBED_CHECK_DESC_LABEL')}"
|
||||
download-check-label="${Joomla.Text._('JFIELD_MEDIA_DOWNLOAD_CHECK_LABEL')}"
|
||||
download-check-desc-label="${Joomla.Text._('JFIELD_MEDIA_DOWNLOAD_CHECK_DESC_LABEL')}"
|
||||
title-label="${Joomla.Text._('JFIELD_MEDIA_TITLE_LABEL')}"
|
||||
width-label="${Joomla.Text._('JFIELD_MEDIA_WIDTH_LABEL')}"
|
||||
height-label="${Joomla.Text._('JFIELD_MEDIA_HEIGHT_LABEL')}"
|
||||
></joomla-field-mediamore>
|
||||
`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Method to check if passed param is HTMLElement
|
||||
*
|
||||
* @param o {string|HTMLElement} Element to be checked
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isElement = o => typeof HTMLElement === 'object' ? o instanceof HTMLElement : o && typeof o === 'object' && o.nodeType === 1 && typeof o.nodeName === 'string';
|
||||
|
||||
/**
|
||||
* Method to return the image size
|
||||
*
|
||||
* @param url {string}
|
||||
*
|
||||
* @returns {bool}
|
||||
*/
|
||||
const getImageSize = url => new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = () => {
|
||||
Joomla.selectedMediaFile.width = img.width;
|
||||
Joomla.selectedMediaFile.height = img.height;
|
||||
resolve(true);
|
||||
};
|
||||
img.onerror = () => {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
reject(false);
|
||||
};
|
||||
});
|
||||
const insertAsImage = async (media, editor, fieldClass) => {
|
||||
if (media.url) {
|
||||
const {
|
||||
rootFull
|
||||
} = Joomla.getOptions('system.paths');
|
||||
const parts = media.url.split(rootFull);
|
||||
if (parts.length > 1) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
Joomla.selectedMediaFile.url = parts[1];
|
||||
if (media.thumb_path) {
|
||||
Joomla.selectedMediaFile.thumb = media.thumb_path;
|
||||
} else {
|
||||
Joomla.selectedMediaFile.thumb = false;
|
||||
}
|
||||
} else if (media.thumb_path) {
|
||||
Joomla.selectedMediaFile.url = media.url;
|
||||
Joomla.selectedMediaFile.thumb = media.thumb_path;
|
||||
}
|
||||
} else {
|
||||
Joomla.selectedMediaFile.url = false;
|
||||
}
|
||||
if (Joomla.selectedMediaFile.url) {
|
||||
let attribs;
|
||||
let isLazy = '';
|
||||
let alt = '';
|
||||
let appendAlt = '';
|
||||
let classes = '';
|
||||
let figClasses = '';
|
||||
let figCaption = '';
|
||||
let imageElement = '';
|
||||
if (!isElement(editor)) {
|
||||
const currentModal = fieldClass.closest('.modal-content');
|
||||
attribs = currentModal.querySelector('joomla-field-mediamore');
|
||||
if (attribs) {
|
||||
if (attribs.getAttribute('alt-check') === 'true') {
|
||||
appendAlt = ' alt=""';
|
||||
}
|
||||
alt = attribs.getAttribute('alt-value') ? ` alt="${attribs.getAttribute('alt-value')}"` : appendAlt;
|
||||
classes = attribs.getAttribute('img-classes') ? ` class="${attribs.getAttribute('img-classes')}"` : '';
|
||||
figClasses = attribs.getAttribute('fig-classes') ? ` class="image ${attribs.getAttribute('fig-classes')}"` : ' class="image"';
|
||||
figCaption = attribs.getAttribute('fig-caption') ? `${attribs.getAttribute('fig-caption')}` : '';
|
||||
if (attribs.getAttribute('is-lazy') === 'true') {
|
||||
isLazy = ` loading="lazy" width="${Joomla.selectedMediaFile.width}" height="${Joomla.selectedMediaFile.height}"`;
|
||||
if (Joomla.selectedMediaFile.width === 0 || Joomla.selectedMediaFile.height === 0) {
|
||||
try {
|
||||
await getImageSize(Joomla.selectedMediaFile.url);
|
||||
isLazy = ` loading="lazy" width="${Joomla.selectedMediaFile.width}" height="${Joomla.selectedMediaFile.height}"`;
|
||||
} catch (err) {
|
||||
isLazy = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (figCaption) {
|
||||
imageElement = `<figure${figClasses}><img src="${Joomla.selectedMediaFile.url}"${classes}${isLazy}${alt} data-path="${Joomla.selectedMediaFile.path}"/><figcaption>${figCaption}</figcaption></figure>`;
|
||||
} else {
|
||||
imageElement = `<img src="${Joomla.selectedMediaFile.url}"${classes}${isLazy}${alt} data-path="${Joomla.selectedMediaFile.path}"/>`;
|
||||
}
|
||||
if (attribs) {
|
||||
attribs.parentNode.removeChild(attribs);
|
||||
}
|
||||
Joomla.editors.instances[editor].replaceSelection(imageElement);
|
||||
} else {
|
||||
if (Joomla.selectedMediaFile.width === 0 || Joomla.selectedMediaFile.height === 0) {
|
||||
try {
|
||||
await getImageSize(Joomla.selectedMediaFile.url);
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (err) {
|
||||
Joomla.selectedMediaFile.height = 0;
|
||||
Joomla.selectedMediaFile.width = 0;
|
||||
}
|
||||
}
|
||||
fieldClass.markValid();
|
||||
fieldClass.setValue(`${Joomla.selectedMediaFile.url}#joomlaImage://${media.path.replace(':', '')}?width=${Joomla.selectedMediaFile.width}&height=${Joomla.selectedMediaFile.height}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
const insertAsOther = (media, editor, fieldClass, type) => {
|
||||
if (media.url) {
|
||||
const {
|
||||
rootFull
|
||||
} = Joomla.getOptions('system.paths');
|
||||
const parts = media.url.split(rootFull);
|
||||
if (parts.length > 1) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
Joomla.selectedMediaFile.url = parts[1];
|
||||
} else {
|
||||
Joomla.selectedMediaFile.url = media.url;
|
||||
}
|
||||
} else {
|
||||
Joomla.selectedMediaFile.url = false;
|
||||
}
|
||||
let attribs;
|
||||
if (Joomla.selectedMediaFile.url) {
|
||||
// Available Only inside an editor
|
||||
if (!isElement(editor)) {
|
||||
let outputText;
|
||||
const currentModal = fieldClass.closest('.modal-content');
|
||||
attribs = currentModal.querySelector('joomla-field-mediamore');
|
||||
if (attribs) {
|
||||
const embedable = attribs.getAttribute('embed-it');
|
||||
if (embedable && embedable === 'true') {
|
||||
if (type === 'audios') {
|
||||
outputText = `<audio controls src="${Joomla.selectedMediaFile.url}"></audio>`;
|
||||
}
|
||||
if (type === 'documents') {
|
||||
// @todo use ${Joomla.selectedMediaFile.filetype} in type
|
||||
const title = attribs.getAttribute('title');
|
||||
outputText = `<object type="application/${Joomla.selectedMediaFile.extension}" data="${Joomla.selectedMediaFile.url}" ${title ? `title="${title}"` : ''} width="${attribs.getAttribute('width')}" height="${attribs.getAttribute('height')}">
|
||||
${Joomla.Text._('JFIELD_MEDIA_UNSUPPORTED').replace('{tag}', `<a download href="${Joomla.selectedMediaFile.url}">`).replace(/{extension}/g, Joomla.selectedMediaFile.extension)}
|
||||
</object>`;
|
||||
}
|
||||
if (type === 'videos') {
|
||||
outputText = `<video controls width="${attribs.getAttribute('width')}" height="${attribs.getAttribute('height')}">
|
||||
<source src="${Joomla.selectedMediaFile.url}" type="${Joomla.selectedMediaFile.fileType}">
|
||||
</video>`;
|
||||
}
|
||||
} else if (Joomla.editors.instances[editor].getSelection() !== '') {
|
||||
outputText = `<a download href="${Joomla.selectedMediaFile.url}">${Joomla.editors.instances[editor].getSelection()}</a>`;
|
||||
} else {
|
||||
const name = /([\w-]+)\./.exec(Joomla.selectedMediaFile.url);
|
||||
outputText = `<a download href="${Joomla.selectedMediaFile.url}">${Joomla.Text._('JFIELD_MEDIA_DOWNLOAD_FILE').replace('{file}', name[1])}</a>`;
|
||||
}
|
||||
}
|
||||
if (attribs) {
|
||||
attribs.parentNode.removeChild(attribs);
|
||||
}
|
||||
Joomla.editors.instances[editor].replaceSelection(outputText);
|
||||
} else {
|
||||
fieldClass.markValid();
|
||||
fieldClass.givenType = type;
|
||||
fieldClass.setValue(Joomla.selectedMediaFile.url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to append the image in an editor or a field
|
||||
*
|
||||
* @param {{}} resp
|
||||
* @param {string|HTMLElement} editor
|
||||
* @param {string} fieldClass
|
||||
*/
|
||||
const execTransform = async (resp, editor, fieldClass) => {
|
||||
if (resp.success === true) {
|
||||
const media = resp.data[0];
|
||||
const {
|
||||
images,
|
||||
audios,
|
||||
videos,
|
||||
documents
|
||||
} = supportedExtensions;
|
||||
if (Joomla.selectedMediaFile.extension && images.includes(media.extension.toLowerCase())) {
|
||||
return insertAsImage(media, editor, fieldClass);
|
||||
}
|
||||
if (Joomla.selectedMediaFile.extension && audios.includes(media.extension.toLowerCase())) {
|
||||
return insertAsOther(media, editor, fieldClass, 'audios');
|
||||
}
|
||||
if (Joomla.selectedMediaFile.extension && documents.includes(media.extension.toLowerCase())) {
|
||||
return insertAsOther(media, editor, fieldClass, 'documents');
|
||||
}
|
||||
if (Joomla.selectedMediaFile.extension && videos.includes(media.extension.toLowerCase())) {
|
||||
return insertAsOther(media, editor, fieldClass, 'videos');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Method that resolves the real url for the selected media file
|
||||
*
|
||||
* @param data {object} The data for the detail
|
||||
* @param editor {string|object} The data for the detail
|
||||
* @param fieldClass {HTMLElement} The fieldClass for the detail
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
Joomla.getMedia = (data, editor, fieldClass) => new Promise((resolve, reject) => {
|
||||
if (!data || typeof data === 'object' && (!data.path || data.path === '')) {
|
||||
Joomla.selectedMediaFile = {};
|
||||
resolve({
|
||||
resp: {
|
||||
success: false
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Compile the url
|
||||
const url = new URL(Joomla.getOptions('media-picker-api').apiBaseUrl ? Joomla.getOptions('media-picker-api').apiBaseUrl : `${Joomla.getOptions('system.paths').baseFull}index.php?option=com_media&format=json`);
|
||||
url.searchParams.append('task', 'api.files');
|
||||
url.searchParams.append('url', true);
|
||||
url.searchParams.append('path', data.path);
|
||||
url.searchParams.append('mediatypes', '0,1,2,3');
|
||||
url.searchParams.append(Joomla.getOptions('csrf.token'), 1);
|
||||
fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}).then(response => response.json()).then(async response => resolve(await execTransform(response, editor, fieldClass))).catch(error => reject(error));
|
||||
});
|
||||
|
||||
// For B/C purposes
|
||||
Joomla.getImage = Joomla.getMedia;
|
||||
|
||||
/**
|
||||
* A simple Custom Element for adding alt text and controlling
|
||||
* the lazy loading on a selected image
|
||||
*
|
||||
* Will be rendered only for editor content images
|
||||
* Attributes:
|
||||
* - parent-id: the id of the parent media field {string}
|
||||
* - lazy-label: The text for the checkbox label {string}
|
||||
* - alt-label: The text for the alt label {string}
|
||||
* - is-lazy: The value for the lazyloading (calculated, defaults to 'true') {string}
|
||||
* - alt-value: The value for the alt text (calculated, defaults to '') {string}
|
||||
*/
|
||||
class JoomlaFieldMediaOptions extends HTMLElement {
|
||||
get type() {
|
||||
return this.getAttribute('type');
|
||||
}
|
||||
get parentId() {
|
||||
return this.getAttribute('parent-id');
|
||||
}
|
||||
get lazytext() {
|
||||
return this.getAttribute('lazy-label');
|
||||
}
|
||||
get alttext() {
|
||||
return this.getAttribute('alt-label');
|
||||
}
|
||||
get altchecktext() {
|
||||
return this.getAttribute('alt-check-label');
|
||||
}
|
||||
get altcheckdesctext() {
|
||||
return this.getAttribute('alt-check-desc-label');
|
||||
}
|
||||
get embedchecktext() {
|
||||
return this.getAttribute('embed-check-label');
|
||||
}
|
||||
get embedcheckdesctext() {
|
||||
return this.getAttribute('embed-check-desc-label');
|
||||
}
|
||||
get downloadchecktext() {
|
||||
return this.getAttribute('download-check-label');
|
||||
}
|
||||
get downloadcheckdesctext() {
|
||||
return this.getAttribute('download-check-desc-label');
|
||||
}
|
||||
get classestext() {
|
||||
return this.getAttribute('classes-label');
|
||||
}
|
||||
get figclassestext() {
|
||||
return this.getAttribute('figure-classes-label');
|
||||
}
|
||||
get figcaptiontext() {
|
||||
return this.getAttribute('figure-caption-label');
|
||||
}
|
||||
get summarytext() {
|
||||
return this.getAttribute('summary-label');
|
||||
}
|
||||
get widthtext() {
|
||||
return this.getAttribute('width-label');
|
||||
}
|
||||
get heighttext() {
|
||||
return this.getAttribute('height-label');
|
||||
}
|
||||
get titletext() {
|
||||
return this.getAttribute('title-label');
|
||||
}
|
||||
connectedCallback() {
|
||||
if (this.type === 'images') {
|
||||
this.innerHTML = `<details open>
|
||||
<summary>${this.summarytext}</summary>
|
||||
<div class="">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-alt">${this.alttext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-alt" data-is="alt-value" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="${this.parentId}-alt-check">
|
||||
<label class="form-check-label" for="${this.parentId}-alt-check">${this.altchecktext}</label>
|
||||
<div><small class="form-text">${this.altcheckdesctext}</small></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="${this.parentId}-lazy" checked>
|
||||
<label class="form-check-label" for="${this.parentId}-lazy">${this.lazytext}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-classes">${this.classestext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-classes" data-is="img-classes"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-figclasses">${this.figclassestext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-figclasses" data-is="fig-classes"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-figcaption">${this.figcaptiontext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-figcaption" data-is="fig-caption"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>`;
|
||||
this.lazyInputFn = this.lazyInputFn.bind(this);
|
||||
this.altCheckFn = this.altCheckFn.bind(this);
|
||||
this.inputFn = this.inputFn.bind(this);
|
||||
|
||||
// Add event listeners
|
||||
this.lazyInput = this.querySelector(`#${this.parentId}-lazy`);
|
||||
this.lazyInput.addEventListener('change', this.lazyInputFn);
|
||||
this.altCheck = this.querySelector(`#${this.parentId}-alt-check`);
|
||||
this.altCheck.addEventListener('input', this.altCheckFn);
|
||||
[].slice.call(this.querySelectorAll('input[type="text"]')).map(el => {
|
||||
el.addEventListener('input', this.inputFn);
|
||||
const {
|
||||
is
|
||||
} = el.dataset;
|
||||
if (is) {
|
||||
this.setAttribute(is, el.value.replace(/"/g, '"'));
|
||||
}
|
||||
return el;
|
||||
});
|
||||
|
||||
// Set initial values
|
||||
this.setAttribute('is-lazy', !!this.lazyInput.checked);
|
||||
this.setAttribute('alt-check', false);
|
||||
} else if (['audios', 'videos', 'documents'].includes(this.type)) {
|
||||
this.innerHTML = `<details open>
|
||||
<summary>${this.summarytext}</summary>
|
||||
<div class="">
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input radio" type="radio" name="flexRadioDefault" id="${this.parentId}-embed-check-2" value="0" checked>
|
||||
<label class="form-check-label" for="${this.parentId}-embed-check-2">
|
||||
${this.downloadchecktext}
|
||||
<div><small class="form-text">${this.downloadcheckdesctext}</small></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input radio" type="radio" name="flexRadioDefault" id="${this.parentId}-embed-check-1" value="1">
|
||||
<label class="form-check-label" for="${this.parentId}-embed-check-1">
|
||||
${this.embedchecktext}
|
||||
<div><small class="form-text">${this.embedcheckdesctext}</small></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggable-parts" style="display: none">
|
||||
<div style="display: ${this.type === 'audios' ? 'none' : 'block'}">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-width">${this.widthtext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-width" value="800" data-is="width"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-height">${this.heighttext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-height" value="600" data-is="height"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: ${this.type === 'document' ? 'block' : 'none'}">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-title">${this.titletext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-title" value="" data-is="title"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>`;
|
||||
this.embedInputFn = this.embedInputFn.bind(this);
|
||||
this.inputFn = this.inputFn.bind(this);
|
||||
[].slice.call(this.querySelectorAll('.form-check-input.radio')).map(el => el.addEventListener('input', this.embedInputFn));
|
||||
this.setAttribute('embed-it', false);
|
||||
[].slice.call(this.querySelectorAll('input[type="text"]')).map(el => {
|
||||
el.addEventListener('input', this.inputFn);
|
||||
const {
|
||||
is
|
||||
} = el.dataset;
|
||||
if (is) {
|
||||
this.setAttribute(is, el.value.replace(/"/g, '"'));
|
||||
}
|
||||
return el;
|
||||
});
|
||||
}
|
||||
}
|
||||
disconnectedCallback() {
|
||||
if (this.type === 'image') {
|
||||
this.lazyInput.removeEventListener('input', this.lazyInputFn);
|
||||
this.altInput.removeEventListener('input', this.inputFn);
|
||||
this.altCheck.removeEventListener('input', this.altCheckFn);
|
||||
}
|
||||
if (['audio', 'video', 'document'].includes(this.type)) {
|
||||
[].slice.call(this.querySelectorAll('.form-check-input.radio')).map(el => el.removeEventListener('input', this.embedInputFn));
|
||||
[].slice.call(this.querySelectorAll('input[type="text"]')).map(el => el.removeEventListener('input', this.embedInputFn));
|
||||
}
|
||||
this.innerHTML = '';
|
||||
}
|
||||
lazyInputFn(e) {
|
||||
this.setAttribute('is-lazy', !!e.target.checked);
|
||||
}
|
||||
altCheckFn(e) {
|
||||
this.setAttribute('alt-check', !!e.target.checked);
|
||||
}
|
||||
inputFn(e) {
|
||||
const {
|
||||
is
|
||||
} = e.target.dataset;
|
||||
if (is) {
|
||||
this.setAttribute(is, e.target.value.replace(/"/g, '"'));
|
||||
}
|
||||
}
|
||||
embedInputFn(e) {
|
||||
const {
|
||||
value
|
||||
} = e.target;
|
||||
this.setAttribute('embed-it', value !== '0');
|
||||
const toggable = this.querySelector('.toggable-parts');
|
||||
if (toggable) {
|
||||
if (toggable.style.display !== 'block') {
|
||||
toggable.style.display = 'block';
|
||||
} else {
|
||||
toggable.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-mediamore', JoomlaFieldMediaOptions);
|
||||
1
media/system/js/fields/joomla-media-select.min.js
vendored
Normal file
1
media/system/js/fields/joomla-media-select.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-media-select.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-media-select.min.js.gz
Normal file
Binary file not shown.
166
media/system/js/fields/modal-content-select-field.js
Normal file
166
media/system/js/fields/modal-content-select-field.js
Normal file
@ -0,0 +1,166 @@
|
||||
import JoomlaDialog from 'joomla.dialog';
|
||||
|
||||
/**
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper method to set values on the fields, and trigger "change" event
|
||||
*
|
||||
* @param {object} data
|
||||
* @param {HTMLInputElement} inputValue
|
||||
* @param {HTMLInputElement} inputTitle
|
||||
*/
|
||||
const setValues = (data, inputValue, inputTitle) => {
|
||||
const value = `${data.id || data.value || ''}`;
|
||||
const isChanged = inputValue.value !== value;
|
||||
inputValue.value = value;
|
||||
if (inputTitle) {
|
||||
inputTitle.value = data.title || inputValue.value;
|
||||
}
|
||||
if (isChanged) {
|
||||
inputValue.dispatchEvent(new CustomEvent('change', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show Select dialog
|
||||
*
|
||||
* @param {HTMLInputElement} inputValue
|
||||
* @param {HTMLInputElement} inputTitle
|
||||
* @param {Object} dialogConfig
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const doSelect = (inputValue, inputTitle, dialogConfig) => {
|
||||
// Use a JoomlaExpectingPostMessage flag to be able to distinct legacy methods
|
||||
// @TODO: This should be removed after full transition to postMessage()
|
||||
window.JoomlaExpectingPostMessage = true;
|
||||
// Create and show the dialog
|
||||
const dialog = new JoomlaDialog(dialogConfig);
|
||||
dialog.classList.add('joomla-dialog-content-select-field');
|
||||
dialog.show();
|
||||
return new Promise(resolve => {
|
||||
const msgListener = event => {
|
||||
// Avoid cross origins
|
||||
if (event.origin !== window.location.origin) return;
|
||||
// Check message type
|
||||
if (event.data.messageType === 'joomla:content-select') {
|
||||
setValues(event.data, inputValue, inputTitle);
|
||||
dialog.close();
|
||||
} else if (event.data.messageType === 'joomla:cancel') {
|
||||
dialog.close();
|
||||
}
|
||||
};
|
||||
|
||||
// Clear all when dialog is closed
|
||||
dialog.addEventListener('joomla-dialog:close', () => {
|
||||
delete window.JoomlaExpectingPostMessage;
|
||||
window.removeEventListener('message', msgListener);
|
||||
dialog.destroy();
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Wait for message
|
||||
window.addEventListener('message', msgListener);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update view, depending if value is selected or not
|
||||
*
|
||||
* @param {HTMLInputElement} inputValue
|
||||
* @param {HTMLElement} container
|
||||
*/
|
||||
const updateView = (inputValue, container) => {
|
||||
const hasValue = !!inputValue.value;
|
||||
container.querySelectorAll('[data-show-when-value]').forEach(el => {
|
||||
if (el.dataset.showWhenValue) {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
hasValue ? el.removeAttribute('hidden') : el.setAttribute('hidden', '');
|
||||
} else {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
hasValue ? el.setAttribute('hidden', '') : el.removeAttribute('hidden');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise the field
|
||||
* @param {HTMLElement} container
|
||||
*/
|
||||
const setupField = container => {
|
||||
const inputValue = container ? container.querySelector('.js-input-value') : null;
|
||||
const inputTitle = container ? container.querySelector('.js-input-title') : null;
|
||||
if (!container || !inputValue) {
|
||||
throw new Error('Incomplete markup of Content dialog field');
|
||||
}
|
||||
container.addEventListener('change', () => {
|
||||
updateView(inputValue, container);
|
||||
});
|
||||
|
||||
// Bind the buttons
|
||||
container.addEventListener('click', event => {
|
||||
const button = event.target.closest('[data-button-action]');
|
||||
if (!button) return;
|
||||
event.preventDefault();
|
||||
|
||||
// Extract the data
|
||||
const action = button.dataset.buttonAction;
|
||||
const dialogConfig = button.dataset.modalConfig ? JSON.parse(button.dataset.modalConfig) : {};
|
||||
|
||||
// Handle requested action
|
||||
let handle;
|
||||
switch (action) {
|
||||
case 'select':
|
||||
case 'create':
|
||||
handle = doSelect(inputValue, inputTitle, dialogConfig);
|
||||
break;
|
||||
case 'edit':
|
||||
{
|
||||
// Update current value in the URL
|
||||
const url = dialogConfig.src.indexOf('http') === 0 ? new URL(dialogConfig.src) : new URL(dialogConfig.src, window.location.origin);
|
||||
url.searchParams.set('id', inputValue.value);
|
||||
dialogConfig.src = url.toString();
|
||||
handle = doSelect(inputValue, inputTitle, dialogConfig);
|
||||
break;
|
||||
}
|
||||
case 'clear':
|
||||
handle = (async () => setValues({
|
||||
id: '',
|
||||
title: ''
|
||||
}, inputValue, inputTitle))();
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown action ${action} for Modal select field`);
|
||||
}
|
||||
handle.then(() => {
|
||||
// Perform checkin when needed
|
||||
if (button.dataset.checkinUrl) {
|
||||
const chckUrl = button.dataset.checkinUrl;
|
||||
const url = chckUrl.indexOf('http') === 0 ? new URL(chckUrl) : new URL(chckUrl, window.location.origin);
|
||||
// Add value to request
|
||||
url.searchParams.set('id', inputValue.value);
|
||||
url.searchParams.set('cid[]', inputValue.value);
|
||||
// Also add value to POST, because Controller may expect it from there
|
||||
const data = new FormData();
|
||||
data.append('id', inputValue.value);
|
||||
data.append('cid[]', inputValue.value);
|
||||
Joomla.request({
|
||||
url: url.toString(),
|
||||
method: 'POST',
|
||||
promise: true,
|
||||
data
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
const setup = container => {
|
||||
container.querySelectorAll('.js-modal-content-select-field').forEach(el => setupField(el));
|
||||
};
|
||||
document.addEventListener('DOMContentLoaded', () => setup(document));
|
||||
document.addEventListener('joomla:updated', event => setup(event.target));
|
||||
1
media/system/js/fields/modal-content-select-field.min.js
vendored
Normal file
1
media/system/js/fields/modal-content-select-field.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import JoomlaDialog from"joomla.dialog";const setValues=(e,t,a)=>{const o=`${e.id||e.value||""}`,n=t.value!==o;t.value=o,a&&(a.value=e.title||t.value),n&&t.dispatchEvent(new CustomEvent("change",{bubbles:!0,cancelable:!0}))},doSelect=(e,t,a)=>{window.JoomlaExpectingPostMessage=!0;const o=new JoomlaDialog(a);return o.classList.add("joomla-dialog-content-select-field"),o.show(),new Promise((a=>{const n=a=>{a.origin===window.location.origin&&("joomla:content-select"===a.data.messageType?(setValues(a.data,e,t),o.close()):"joomla:cancel"===a.data.messageType&&o.close())};o.addEventListener("joomla-dialog:close",(()=>{delete window.JoomlaExpectingPostMessage,window.removeEventListener("message",n),o.destroy(),a()})),window.addEventListener("message",n)}))},updateView=(e,t)=>{const a=!!e.value;t.querySelectorAll("[data-show-when-value]").forEach((e=>{e.dataset.showWhenValue?a?e.removeAttribute("hidden"):e.setAttribute("hidden",""):a?e.setAttribute("hidden",""):e.removeAttribute("hidden")}))},setupField=e=>{const t=e?e.querySelector(".js-input-value"):null,a=e?e.querySelector(".js-input-title"):null;if(!e||!t)throw new Error("Incomplete markup of Content dialog field");e.addEventListener("change",(()=>{updateView(t,e)})),e.addEventListener("click",(e=>{const o=e.target.closest("[data-button-action]");if(!o)return;e.preventDefault();const n=o.dataset.buttonAction,s=o.dataset.modalConfig?JSON.parse(o.dataset.modalConfig):{};let l;switch(n){case"select":case"create":l=doSelect(t,a,s);break;case"edit":{const e=0===s.src.indexOf("http")?new URL(s.src):new URL(s.src,window.location.origin);e.searchParams.set("id",t.value),s.src=e.toString(),l=doSelect(t,a,s);break}case"clear":l=(async()=>setValues({id:"",title:""},t,a))();break;default:throw new Error(`Unknown action ${n} for Modal select field`)}l.then((()=>{if(o.dataset.checkinUrl){const e=o.dataset.checkinUrl,a=0===e.indexOf("http")?new URL(e):new URL(e,window.location.origin);a.searchParams.set("id",t.value),a.searchParams.set("cid[]",t.value);const n=new FormData;n.append("id",t.value),n.append("cid[]",t.value),Joomla.request({url:a.toString(),method:"POST",promise:!0,data:n})}}))}))},setup=e=>{e.querySelectorAll(".js-modal-content-select-field").forEach((e=>setupField(e)))};document.addEventListener("DOMContentLoaded",(()=>setup(document))),document.addEventListener("joomla:updated",(e=>setup(e.target)));
|
||||
BIN
media/system/js/fields/modal-content-select-field.min.js.gz
Normal file
BIN
media/system/js/fields/modal-content-select-field.min.js.gz
Normal file
Binary file not shown.
216
media/system/js/fields/modal-fields.js
Normal file
216
media/system/js/fields/modal-fields.js
Normal file
@ -0,0 +1,216 @@
|
||||
/**
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Process modal fields in parent.
|
||||
*
|
||||
* @param string fieldPrefix The fields to be updated prefix.
|
||||
* @param string id The new id for the item.
|
||||
* @param string title The new title for the item.
|
||||
* @param string catid Future usage.
|
||||
* @param object object Future usage.
|
||||
* @param string url Future usage.
|
||||
* @param string language Future usage.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
window.processModalParent = function (fieldPrefix, id, title, catid, url, language, object)
|
||||
{
|
||||
var fieldId = document.getElementById(fieldPrefix + '_id') || document.getElementById(fieldPrefix + '_value'),
|
||||
fieldTitle = document.getElementById(fieldPrefix + '_name') || document.getElementById(fieldPrefix);
|
||||
|
||||
// Default values.
|
||||
id = id || '';
|
||||
title = title || '';
|
||||
catid = catid || '';
|
||||
object = object || '';
|
||||
url = url || '';
|
||||
language = language || '';
|
||||
|
||||
var isChanged = fieldId.value !== id;
|
||||
|
||||
if (id)
|
||||
{
|
||||
fieldId.value = id;
|
||||
fieldTitle.value = title;
|
||||
|
||||
if (document.getElementById(fieldPrefix + '_select'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_select').classList.add('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_new'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_new').classList.add('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_edit'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_edit').classList.remove('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_clear'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_clear').classList.remove('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_propagate'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_propagate').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldId.value = '';
|
||||
fieldTitle.value = fieldId.getAttribute('data-text');
|
||||
|
||||
if (document.getElementById(fieldPrefix + '_select'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_select').classList.remove('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_new'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_new').classList.remove('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_edit'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_edit').classList.add('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_clear'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_clear').classList.add('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_propagate'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_propagate').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (isChanged) {
|
||||
fieldId.dispatchEvent(new CustomEvent('change', { bubbles: true, cancelable: true }));
|
||||
}
|
||||
|
||||
if (fieldId.getAttribute('data-required') == '1')
|
||||
{
|
||||
document.formvalidator.validate(fieldId);
|
||||
document.formvalidator.validate(fieldTitle);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Process new/edit modal fields in child.
|
||||
*
|
||||
* @param object element The modal footer button element.
|
||||
* @param string fieldPrefix The fields to be updated prefix.
|
||||
* @param string action Modal action (add, edit).
|
||||
* @param string itemType The item type (Article, Contact, etc).
|
||||
* @param string task Task to be done (apply, save, cancel).
|
||||
* @param string formId Id of the form field (defaults to itemtype-form).
|
||||
* @param string idFieldId Id of the id field (defaults to jform_id).
|
||||
* @param string titleFieldId Id of the title field (defaults to jform_title).
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
window.processModalEdit = function (element, fieldPrefix, action, itemType, task, formId, idFieldId, titleFieldId)
|
||||
{
|
||||
formId = formId || itemType.toLowerCase() + '-form';
|
||||
idFieldId = idFieldId || 'jform_id';
|
||||
titleFieldId = titleFieldId || 'jform_title';
|
||||
|
||||
var modalId = element.parentNode.parentNode.parentNode.parentNode.id, submittedTask = task;
|
||||
var iframe = document.getElementById(modalId).getElementsByTagName('iframe')[0];
|
||||
|
||||
// Set frame id.
|
||||
iframe.id = 'Frame_' + modalId;
|
||||
|
||||
var iframeDocument = iframe.contentDocument;
|
||||
|
||||
// If Close (cancel task), close the modal.
|
||||
if (task === 'cancel')
|
||||
{
|
||||
// Submit button on child iframe so we can check out.
|
||||
iframe.contentWindow.Joomla.submitbutton(itemType.toLowerCase() + '.' + task);
|
||||
|
||||
Joomla.Modal.getCurrent().close();
|
||||
}
|
||||
// For Save (apply task) and Save & Close (save task).
|
||||
else
|
||||
{
|
||||
// Attach onload event to the iframe.
|
||||
iframe.addEventListener('load', function()
|
||||
{
|
||||
// Reload iframe document var value.
|
||||
iframeDocument = this.contentDocument;
|
||||
|
||||
// Validate the child form and update parent form.
|
||||
if (
|
||||
iframeDocument.getElementById(idFieldId)
|
||||
&& iframeDocument.getElementById(idFieldId).value != '0'
|
||||
&& [].slice.call(iframeDocument.querySelectorAll('joomla-alert[type="danger"]')).length == 0
|
||||
) {
|
||||
window.processModalParent(fieldPrefix, iframeDocument.getElementById(idFieldId).value, iframeDocument.getElementById(titleFieldId).value);
|
||||
|
||||
// If Save & Close (save task), submit the edit close action (so we don't have checked out items).
|
||||
if (task === 'save')
|
||||
{
|
||||
window.processModalEdit(element, fieldPrefix, 'edit', itemType, 'cancel', formId, idFieldId, titleFieldId);
|
||||
}
|
||||
}
|
||||
|
||||
// Show the iframe again for future modals or in case of error.
|
||||
iframe.classList.remove('visually-hidden');
|
||||
});
|
||||
|
||||
// Submit button on child iframe.
|
||||
if (iframeDocument.formvalidator.isValid(iframeDocument.getElementById(formId)))
|
||||
{
|
||||
// For Save & Close (save task) when creating we need to replace the task as apply because of redirects after submit and hide the iframe.
|
||||
if (task === 'save')
|
||||
{
|
||||
submittedTask = 'apply';
|
||||
iframe.classList.add('visually-hidden');
|
||||
}
|
||||
|
||||
iframe.contentWindow.Joomla.submitbutton(itemType.toLowerCase() + '.' + submittedTask);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Process select modal fields in child.
|
||||
*
|
||||
* @param string itemType The item type (Article, Contact, etc).
|
||||
* @param string fieldPrefix The fields to be updated prefix.
|
||||
* @param string id The new id for the item.
|
||||
* @param string title The new title for the item.
|
||||
* @param string catid Future usage.
|
||||
* @param object object Future usage.
|
||||
* @param string url Future usage.
|
||||
* @param string language Future usage.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
window.processModalSelect = function(itemType, fieldPrefix, id, title, catid, object, url, language) {
|
||||
window.processModalParent(fieldPrefix, id, title, catid, url, language, object);
|
||||
|
||||
// Close Modal only when necessary.
|
||||
if (Joomla.Modal.getCurrent())
|
||||
{
|
||||
Joomla.Modal.getCurrent().close();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
}());
|
||||
1
media/system/js/fields/modal-fields.min.js
vendored
Normal file
1
media/system/js/fields/modal-fields.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(){"use strict";window.processModalParent=function(e,t,d,n,o,a,l){var m=document.getElementById(e+"_id")||document.getElementById(e+"_value"),c=document.getElementById(e+"_name")||document.getElementById(e);t=t||"",d=d||"",n=n||"",l=l||"",o=o||"",a=a||"";var s=m.value!==t;return t?(m.value=t,c.value=d,document.getElementById(e+"_select")&&document.getElementById(e+"_select").classList.add("hidden"),document.getElementById(e+"_new")&&document.getElementById(e+"_new").classList.add("hidden"),document.getElementById(e+"_edit")&&document.getElementById(e+"_edit").classList.remove("hidden"),document.getElementById(e+"_clear")&&document.getElementById(e+"_clear").classList.remove("hidden"),document.getElementById(e+"_propagate")&&document.getElementById(e+"_propagate").classList.remove("hidden")):(m.value="",c.value=m.getAttribute("data-text"),document.getElementById(e+"_select")&&document.getElementById(e+"_select").classList.remove("hidden"),document.getElementById(e+"_new")&&document.getElementById(e+"_new").classList.remove("hidden"),document.getElementById(e+"_edit")&&document.getElementById(e+"_edit").classList.add("hidden"),document.getElementById(e+"_clear")&&document.getElementById(e+"_clear").classList.add("hidden"),document.getElementById(e+"_propagate")&&document.getElementById(e+"_propagate").classList.add("hidden")),s&&m.dispatchEvent(new CustomEvent("change",{bubbles:!0,cancelable:!0})),"1"==m.getAttribute("data-required")&&(document.formvalidator.validate(m),document.formvalidator.validate(c)),!1},window.processModalEdit=function(e,t,d,n,o,a,l,m){a=a||n.toLowerCase()+"-form",l=l||"jform_id",m=m||"jform_title";var c=e.parentNode.parentNode.parentNode.parentNode.id,s=o,i=document.getElementById(c).getElementsByTagName("iframe")[0];i.id="Frame_"+c;var r=i.contentDocument;return"cancel"===o?(i.contentWindow.Joomla.submitbutton(n.toLowerCase()+"."+o),Joomla.Modal.getCurrent().close()):(i.addEventListener("load",(function(){(r=this.contentDocument).getElementById(l)&&"0"!=r.getElementById(l).value&&0==[].slice.call(r.querySelectorAll('joomla-alert[type="danger"]')).length&&(window.processModalParent(t,r.getElementById(l).value,r.getElementById(m).value),"save"===o&&window.processModalEdit(e,t,"edit",n,"cancel",a,l,m)),i.classList.remove("visually-hidden")})),r.formvalidator.isValid(r.getElementById(a))&&("save"===o&&(s="apply",i.classList.add("visually-hidden")),i.contentWindow.Joomla.submitbutton(n.toLowerCase()+"."+s))),!1},window.processModalSelect=function(e,t,d,n,o,a,l,m){return window.processModalParent(t,d,n,o,l,m,a),Joomla.Modal.getCurrent()&&Joomla.Modal.getCurrent().close(),!1}}();
|
||||
BIN
media/system/js/fields/modal-fields.min.js.gz
Normal file
BIN
media/system/js/fields/modal-fields.min.js.gz
Normal file
Binary file not shown.
166
media/system/js/fields/passwordstrength.js
Normal file
166
media/system/js/fields/passwordstrength.js
Normal file
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* PasswordStrength script by Thomas Kjaergaard
|
||||
* License: MIT
|
||||
* Repo: https://github.com/tkjaergaard/Password-Strength
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014 Thomas Kjærgaard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
class PasswordStrength {
|
||||
constructor(settings) {
|
||||
this.lowercase = parseInt(settings.lowercase, 10) || 0;
|
||||
this.uppercase = parseInt(settings.uppercase, 10) || 0;
|
||||
this.numbers = parseInt(settings.numbers, 10) || 0;
|
||||
this.special = parseInt(settings.special, 10) || 0;
|
||||
this.length = parseInt(settings.length, 10) || 12;
|
||||
}
|
||||
getScore(value) {
|
||||
let score = 0;
|
||||
let mods = 0;
|
||||
const sets = ['lowercase', 'uppercase', 'numbers', 'special', 'length'];
|
||||
sets.forEach(set => {
|
||||
if (this[set] > 0) {
|
||||
mods += 1;
|
||||
}
|
||||
});
|
||||
score += this.constructor.calc(value, /[a-z]/g, this.lowercase, mods);
|
||||
score += this.constructor.calc(value, /[A-Z]/g, this.uppercase, mods);
|
||||
score += this.constructor.calc(value, /[0-9]/g, this.numbers, mods);
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
score += this.constructor.calc(value, /[$!#?=;:*\-_€%&()`´]/g, this.special, mods);
|
||||
if (mods === 1) {
|
||||
score += value.length > this.length ? 100 : 100 / this.length * value.length;
|
||||
} else {
|
||||
score += value.length > this.length ? 100 / mods : 100 / mods / this.length * value.length;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
static calc(value, pattern, length, mods) {
|
||||
const count = value.match(pattern);
|
||||
if (count && count.length > length && length !== 0) {
|
||||
return 100 / mods;
|
||||
}
|
||||
if (count && length > 0) {
|
||||
return 100 / mods / length * count.length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
((Joomla, document) => {
|
||||
// Method to check the input and set the meter
|
||||
const getMeter = element => {
|
||||
const meter = document.querySelector('meter');
|
||||
const minLength = element.getAttribute('data-min-length');
|
||||
const minIntegers = element.getAttribute('data-min-integers');
|
||||
const minSymbols = element.getAttribute('data-min-symbols');
|
||||
const minUppercase = element.getAttribute('data-min-uppercase');
|
||||
const minLowercase = element.getAttribute('data-min-lowercase');
|
||||
const strength = new PasswordStrength({
|
||||
lowercase: minLowercase || 0,
|
||||
uppercase: minUppercase || 0,
|
||||
numbers: minIntegers || 0,
|
||||
special: minSymbols || 0,
|
||||
length: minLength || 12
|
||||
});
|
||||
const score = strength.getScore(element.value);
|
||||
const i = meter.getAttribute('id').replace(/^\D+/g, '');
|
||||
const label = element.parentNode.parentNode.querySelector(`#password-${i}`);
|
||||
if (score === 100) {
|
||||
label.innerText = Joomla.Text._('JFIELD_PASSWORD_INDICATE_COMPLETE');
|
||||
} else {
|
||||
label.innerText = Joomla.Text._('JFIELD_PASSWORD_INDICATE_INCOMPLETE');
|
||||
}
|
||||
meter.value = score;
|
||||
if (!element.value.length) {
|
||||
label.innerText = '';
|
||||
element.setAttribute('required', '');
|
||||
}
|
||||
};
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const fields = [].slice.call(document.querySelectorAll('.js-password-strength'));
|
||||
|
||||
// Loop through the fields
|
||||
fields.forEach((field, index) => {
|
||||
let initialVal = '';
|
||||
if (!field.value.length) {
|
||||
initialVal = 0;
|
||||
}
|
||||
|
||||
// Create a progress meter and the label
|
||||
const meter = document.createElement('meter');
|
||||
meter.setAttribute('id', `progress-${index}`);
|
||||
meter.setAttribute('min', 0);
|
||||
meter.setAttribute('max', 100);
|
||||
meter.setAttribute('low', 40);
|
||||
meter.setAttribute('high', 99);
|
||||
meter.setAttribute('optimum', 100);
|
||||
meter.value = initialVal;
|
||||
const label = document.createElement('div');
|
||||
label.setAttribute('class', 'text-center');
|
||||
label.setAttribute('id', `password-${index}`);
|
||||
label.setAttribute('aria-live', 'polite');
|
||||
field.parentNode.insertAdjacentElement('afterEnd', label);
|
||||
field.parentNode.insertAdjacentElement('afterEnd', meter);
|
||||
|
||||
// Add a data attribute for the required
|
||||
if (field.value.length > 0) {
|
||||
field.setAttribute('required', true);
|
||||
}
|
||||
|
||||
// Add a listener for input data change
|
||||
field.addEventListener('keyup', ({
|
||||
target
|
||||
}) => {
|
||||
getMeter(target);
|
||||
});
|
||||
});
|
||||
|
||||
// Set a handler for the validation script
|
||||
if (fields[0]) {
|
||||
document.formvalidator.setHandler('password-strength', value => {
|
||||
const strengthElements = document.querySelectorAll('.js-password-strength');
|
||||
const minLength = strengthElements[0].getAttribute('data-min-length');
|
||||
const minIntegers = strengthElements[0].getAttribute('data-min-integers');
|
||||
const minSymbols = strengthElements[0].getAttribute('data-min-symbols');
|
||||
const minUppercase = strengthElements[0].getAttribute('data-min-uppercase');
|
||||
const minLowercase = strengthElements[0].getAttribute('data-min-lowercase');
|
||||
const strength = new PasswordStrength({
|
||||
lowercase: minLowercase || 0,
|
||||
uppercase: minUppercase || 0,
|
||||
numbers: minIntegers || 0,
|
||||
special: minSymbols || 0,
|
||||
length: minLength || 12
|
||||
});
|
||||
const score = strength.getScore(value);
|
||||
if (score === 100) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
})(Joomla, document);
|
||||
1
media/system/js/fields/passwordstrength.min.js
vendored
Normal file
1
media/system/js/fields/passwordstrength.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
class PasswordStrength{constructor(t){this.lowercase=parseInt(t.lowercase,10)||0,this.uppercase=parseInt(t.uppercase,10)||0,this.numbers=parseInt(t.numbers,10)||0,this.special=parseInt(t.special,10)||0,this.length=parseInt(t.length,10)||12}getScore(t){let e=0,r=0;return["lowercase","uppercase","numbers","special","length"].forEach((t=>{this[t]>0&&(r+=1)})),e+=this.constructor.calc(t,/[a-z]/g,this.lowercase,r),e+=this.constructor.calc(t,/[A-Z]/g,this.uppercase,r),e+=this.constructor.calc(t,/[0-9]/g,this.numbers,r),e+=this.constructor.calc(t,/[$!#?=;:*\-_€%&()`´]/g,this.special,r),e+=1===r?t.length>this.length?100:100/this.length*t.length:t.length>this.length?100/r:100/r/this.length*t.length,e}static calc(t,e,r,s){const a=t.match(e);return a&&a.length>r&&0!==r?100/s:a&&r>0?100/s/r*a.length:0}}((t,e)=>{e.addEventListener("DOMContentLoaded",(()=>{const r=[].slice.call(e.querySelectorAll(".js-password-strength"));r.forEach(((r,s)=>{let a="";r.value.length||(a=0);const n=e.createElement("meter");n.setAttribute("id",`progress-${s}`),n.setAttribute("min",0),n.setAttribute("max",100),n.setAttribute("low",40),n.setAttribute("high",99),n.setAttribute("optimum",100),n.value=a;const i=e.createElement("div");i.setAttribute("class","text-center"),i.setAttribute("id",`password-${s}`),i.setAttribute("aria-live","polite"),r.parentNode.insertAdjacentElement("afterEnd",i),r.parentNode.insertAdjacentElement("afterEnd",n),r.value.length>0&&r.setAttribute("required",!0),r.addEventListener("keyup",(({target:r})=>{(r=>{const s=e.querySelector("meter"),a=r.getAttribute("data-min-length"),n=r.getAttribute("data-min-integers"),i=r.getAttribute("data-min-symbols"),l=r.getAttribute("data-min-uppercase"),c=r.getAttribute("data-min-lowercase"),u=new PasswordStrength({lowercase:c||0,uppercase:l||0,numbers:n||0,special:i||0,length:a||12}).getScore(r.value),o=s.getAttribute("id").replace(/^\D+/g,""),g=r.parentNode.parentNode.querySelector(`#password-${o}`);g.innerText=100===u?t.Text._("JFIELD_PASSWORD_INDICATE_COMPLETE"):t.Text._("JFIELD_PASSWORD_INDICATE_INCOMPLETE"),s.value=u,r.value.length||(g.innerText="",r.setAttribute("required",""))})(r)}))})),r[0]&&e.formvalidator.setHandler("password-strength",(t=>{const r=e.querySelectorAll(".js-password-strength"),s=r[0].getAttribute("data-min-length"),a=r[0].getAttribute("data-min-integers"),n=r[0].getAttribute("data-min-symbols"),i=r[0].getAttribute("data-min-uppercase"),l=r[0].getAttribute("data-min-lowercase");return 100===new PasswordStrength({lowercase:l||0,uppercase:i||0,numbers:a||0,special:n||0,length:s||12}).getScore(t)}))}))})(Joomla,document);
|
||||
BIN
media/system/js/fields/passwordstrength.min.js.gz
Normal file
BIN
media/system/js/fields/passwordstrength.min.js.gz
Normal file
Binary file not shown.
76
media/system/js/fields/passwordview.js
Normal file
76
media/system/js/fields/passwordview.js
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
(document => {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
[].slice.call(document.querySelectorAll('input[type="password"]')).forEach(input => {
|
||||
const toggleButton = input.parentNode.querySelector('.input-password-toggle');
|
||||
if (toggleButton) {
|
||||
toggleButton.addEventListener('click', () => {
|
||||
const icon = toggleButton.firstElementChild;
|
||||
const srText = toggleButton.lastElementChild;
|
||||
if (input.type === 'password') {
|
||||
// Update the icon class
|
||||
icon.classList.remove('icon-eye');
|
||||
icon.classList.add('icon-eye-slash');
|
||||
|
||||
// Update the input type
|
||||
input.type = 'text';
|
||||
|
||||
// Focus the input field
|
||||
input.focus();
|
||||
|
||||
// Update the text for screenreaders
|
||||
srText.innerText = Joomla.Text._('JHIDEPASSWORD');
|
||||
} else if (input.type === 'text') {
|
||||
// Update the icon class
|
||||
icon.classList.add('icon-eye');
|
||||
icon.classList.remove('icon-eye-slash');
|
||||
|
||||
// Update the input type
|
||||
input.type = 'password';
|
||||
|
||||
// Focus the input field
|
||||
input.focus();
|
||||
|
||||
// Update the text for screenreaders
|
||||
srText.innerText = Joomla.Text._('JSHOWPASSWORD');
|
||||
}
|
||||
});
|
||||
}
|
||||
const modifyButton = input.parentNode.querySelector('.input-password-modify');
|
||||
if (modifyButton) {
|
||||
modifyButton.addEventListener('click', () => {
|
||||
const lock = !modifyButton.classList.contains('locked');
|
||||
if (lock === true) {
|
||||
// Add lock
|
||||
modifyButton.classList.add('locked');
|
||||
|
||||
// Reset value to empty string
|
||||
input.value = '';
|
||||
|
||||
// Disable the field
|
||||
input.setAttribute('disabled', '');
|
||||
|
||||
// Update the text
|
||||
modifyButton.innerText = Joomla.Text._('JMODIFY');
|
||||
} else {
|
||||
// Remove lock
|
||||
modifyButton.classList.remove('locked');
|
||||
|
||||
// Enable the field
|
||||
input.removeAttribute('disabled');
|
||||
|
||||
// Focus the input field
|
||||
input.focus();
|
||||
|
||||
// Update the text
|
||||
modifyButton.innerText = Joomla.Text._('JCANCEL');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
})(document);
|
||||
1
media/system/js/fields/passwordview.min.js
vendored
Normal file
1
media/system/js/fields/passwordview.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
(e=>{e.addEventListener("DOMContentLoaded",(()=>{[].slice.call(e.querySelectorAll('input[type="password"]')).forEach((e=>{const t=e.parentNode.querySelector(".input-password-toggle");t&&t.addEventListener("click",(()=>{const s=t.firstElementChild,o=t.lastElementChild;"password"===e.type?(s.classList.remove("icon-eye"),s.classList.add("icon-eye-slash"),e.type="text",e.focus(),o.innerText=Joomla.Text._("JHIDEPASSWORD")):"text"===e.type&&(s.classList.add("icon-eye"),s.classList.remove("icon-eye-slash"),e.type="password",e.focus(),o.innerText=Joomla.Text._("JSHOWPASSWORD"))}));const s=e.parentNode.querySelector(".input-password-modify");s&&s.addEventListener("click",(()=>{!0===!s.classList.contains("locked")?(s.classList.add("locked"),e.value="",e.setAttribute("disabled",""),s.innerText=Joomla.Text._("JMODIFY")):(s.classList.remove("locked"),e.removeAttribute("disabled"),e.focus(),s.innerText=Joomla.Text._("JCANCEL"))}))}))}))})(document);
|
||||
BIN
media/system/js/fields/passwordview.min.js.gz
Normal file
BIN
media/system/js/fields/passwordview.min.js.gz
Normal file
Binary file not shown.
44
media/system/js/fields/select-colour.js
Normal file
44
media/system/js/fields/select-colour.js
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
(() => {
|
||||
|
||||
const onChange = ({
|
||||
target
|
||||
}) => {
|
||||
const self = target;
|
||||
const value = parseInt(self.value, 10);
|
||||
self.classList.remove('form-select-success', 'form-select-danger');
|
||||
if (value === 1) {
|
||||
self.classList.add('form-select-success');
|
||||
} else if (value === 0 || value === -2) {
|
||||
self.classList.add('form-select-danger');
|
||||
}
|
||||
};
|
||||
const updateSelectboxColour = () => {
|
||||
const colourSelects = [].slice.call(document.querySelectorAll('.form-select-color-state'));
|
||||
colourSelects.forEach(colourSelect => {
|
||||
const value = parseInt(colourSelect.value, 10);
|
||||
|
||||
// Add class on page load
|
||||
if (value === 1) {
|
||||
colourSelect.classList.add('form-select-success');
|
||||
} else if (value === 0 || value === -2) {
|
||||
colourSelect.classList.add('form-select-danger');
|
||||
}
|
||||
|
||||
// Add class when value is changed
|
||||
colourSelect.addEventListener('change', onChange);
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
document.removeEventListener('DOMContentLoaded', updateSelectboxColour, true);
|
||||
};
|
||||
|
||||
// On document loaded
|
||||
document.addEventListener('DOMContentLoaded', updateSelectboxColour, true);
|
||||
|
||||
// On Joomla updated
|
||||
document.addEventListener('joomla:updated', updateSelectboxColour, true);
|
||||
})();
|
||||
1
media/system/js/fields/select-colour.min.js
vendored
Normal file
1
media/system/js/fields/select-colour.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
(()=>{const e=({target:e})=>{const s=e,t=parseInt(s.value,10);s.classList.remove("form-select-success","form-select-danger"),1===t?s.classList.add("form-select-success"):0!==t&&-2!==t||s.classList.add("form-select-danger")},s=()=>{[].slice.call(document.querySelectorAll(".form-select-color-state")).forEach((s=>{const t=parseInt(s.value,10);1===t?s.classList.add("form-select-success"):0!==t&&-2!==t||s.classList.add("form-select-danger"),s.addEventListener("change",e)})),document.removeEventListener("DOMContentLoaded",s,!0)};document.addEventListener("DOMContentLoaded",s,!0),document.addEventListener("joomla:updated",s,!0)})();
|
||||
BIN
media/system/js/fields/select-colour.min.js.gz
Normal file
BIN
media/system/js/fields/select-colour.min.js.gz
Normal file
Binary file not shown.
66
media/system/js/fields/tag.js
Normal file
66
media/system/js/fields/tag.js
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Field user
|
||||
*/
|
||||
jQuery(document).ready(function ($) {
|
||||
|
||||
if (Joomla.getOptions('field-tag-custom')) {
|
||||
|
||||
var options = Joomla.getOptions('field-tag-custom'),
|
||||
customTagPrefix = '#new#';
|
||||
|
||||
// Method to add tags pressing enter
|
||||
$(options.selector + '_chosen input').keyup(function(event) {
|
||||
|
||||
var tagOption;
|
||||
|
||||
// Tag is greater than the minimum required chars and enter pressed
|
||||
if (this.value && this.value.length >= options.minTermLength && (event.which === 13 || event.which === 188)) {
|
||||
|
||||
// Search a highlighted result
|
||||
var highlighted = $(options.selector + '_chosen').find('li.active-result.highlighted').first();
|
||||
|
||||
// Add the highlighted option
|
||||
if (event.which === 13 && highlighted.text() !== '')
|
||||
{
|
||||
// Extra check. If we have added a custom tag with this text remove it
|
||||
var customOptionValue = customTagPrefix + highlighted.text();
|
||||
$(options.selector + ' option').filter(function () { return $(this).val() == customOptionValue; }).remove();
|
||||
|
||||
// Select the highlighted result
|
||||
tagOption = $(options.selector + ' option').filter(function () { return $(this).html() == highlighted.text(); });
|
||||
tagOption.attr('selected', 'selected');
|
||||
}
|
||||
// Add the custom tag option
|
||||
else
|
||||
{
|
||||
var customTag = this.value;
|
||||
|
||||
// Extra check. Search if the custom tag already exists (typed faster than AJAX ready)
|
||||
tagOption = $(options.selector + ' option').filter(function () { return $(this).html() == customTag; });
|
||||
if (tagOption.text() !== '')
|
||||
{
|
||||
tagOption.attr('selected', 'selected');
|
||||
}
|
||||
else
|
||||
{
|
||||
var option = $('<option>');
|
||||
option.text(this.value).val(customTagPrefix + this.value);
|
||||
option.attr('selected','selected');
|
||||
|
||||
// Append the option and repopulate the chosen field
|
||||
$(options.selector).append(option);
|
||||
}
|
||||
}
|
||||
|
||||
this.value = '';
|
||||
$(options.selector).trigger('chosen:updated');
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
1
media/system/js/fields/tag.min.js
vendored
Normal file
1
media/system/js/fields/tag.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
jQuery(document).ready((function(e){if(Joomla.getOptions("field-tag-custom")){var t=Joomla.getOptions("field-tag-custom"),i="#new#";e(t.selector+"_chosen input").keyup((function(l){var r;if(this.value&&this.value.length>=t.minTermLength&&(13===l.which||188===l.which)){var o=e(t.selector+"_chosen").find("li.active-result.highlighted").first();if(13===l.which&&""!==o.text()){var s=i+o.text();e(t.selector+" option").filter((function(){return e(this).val()==s})).remove(),(r=e(t.selector+" option").filter((function(){return e(this).html()==o.text()}))).attr("selected","selected")}else{var n=this.value;if(""!==(r=e(t.selector+" option").filter((function(){return e(this).html()==n}))).text())r.attr("selected","selected");else{var c=e("<option>");c.text(this.value).val(i+this.value),c.attr("selected","selected"),e(t.selector).append(c)}}this.value="",e(t.selector).trigger("chosen:updated"),l.preventDefault()}}))}}));
|
||||
BIN
media/system/js/fields/tag.min.js.gz
Normal file
BIN
media/system/js/fields/tag.min.js.gz
Normal file
Binary file not shown.
728
media/system/js/fields/validate.js
Normal file
728
media/system/js/fields/validate.js
Normal file
@ -0,0 +1,728 @@
|
||||
/** Highest positive signed 32-bit float value */
|
||||
const maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
|
||||
|
||||
/** Bootstring parameters */
|
||||
const base = 36;
|
||||
const tMin = 1;
|
||||
const tMax = 26;
|
||||
const skew = 38;
|
||||
const damp = 700;
|
||||
const initialBias = 72;
|
||||
const initialN = 128; // 0x80
|
||||
const delimiter = '-'; // '\x2D'
|
||||
|
||||
/** Regular expressions */
|
||||
const regexPunycode = /^xn--/;
|
||||
const regexNonASCII = /[^\0-\x7F]/; // Note: U+007F DEL is excluded too.
|
||||
const regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
|
||||
|
||||
/** Error messages */
|
||||
const errors = {
|
||||
'overflow': 'Overflow: input needs wider integers to process',
|
||||
'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
|
||||
'invalid-input': 'Invalid input'
|
||||
};
|
||||
|
||||
/** Convenience shortcuts */
|
||||
const baseMinusTMin = base - tMin;
|
||||
const floor = Math.floor;
|
||||
const stringFromCharCode = String.fromCharCode;
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* A generic error utility function.
|
||||
* @private
|
||||
* @param {String} type The error type.
|
||||
* @returns {Error} Throws a `RangeError` with the applicable error message.
|
||||
*/
|
||||
function error(type) {
|
||||
throw new RangeError(errors[type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic `Array#map` utility function.
|
||||
* @private
|
||||
* @param {Array} array The array to iterate over.
|
||||
* @param {Function} callback The function that gets called for every array
|
||||
* item.
|
||||
* @returns {Array} A new array of values returned by the callback function.
|
||||
*/
|
||||
function map(array, callback) {
|
||||
const result = [];
|
||||
let length = array.length;
|
||||
while (length--) {
|
||||
result[length] = callback(array[length]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple `Array#map`-like wrapper to work with domain name strings or email
|
||||
* addresses.
|
||||
* @private
|
||||
* @param {String} domain The domain name or email address.
|
||||
* @param {Function} callback The function that gets called for every
|
||||
* character.
|
||||
* @returns {String} A new string of characters returned by the callback
|
||||
* function.
|
||||
*/
|
||||
function mapDomain(domain, callback) {
|
||||
const parts = domain.split('@');
|
||||
let result = '';
|
||||
if (parts.length > 1) {
|
||||
// In email addresses, only the domain name should be punycoded. Leave
|
||||
// the local part (i.e. everything up to `@`) intact.
|
||||
result = parts[0] + '@';
|
||||
domain = parts[1];
|
||||
}
|
||||
// Avoid `split(regex)` for IE8 compatibility. See #17.
|
||||
domain = domain.replace(regexSeparators, '\x2E');
|
||||
const labels = domain.split('.');
|
||||
const encoded = map(labels, callback).join('.');
|
||||
return result + encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array containing the numeric code points of each Unicode
|
||||
* character in the string. While JavaScript uses UCS-2 internally,
|
||||
* this function will convert a pair of surrogate halves (each of which
|
||||
* UCS-2 exposes as separate characters) into a single code point,
|
||||
* matching UTF-16.
|
||||
* @see `punycode.ucs2.encode`
|
||||
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
||||
* @memberOf punycode.ucs2
|
||||
* @name decode
|
||||
* @param {String} string The Unicode input string (UCS-2).
|
||||
* @returns {Array} The new array of code points.
|
||||
*/
|
||||
function ucs2decode(string) {
|
||||
const output = [];
|
||||
let counter = 0;
|
||||
const length = string.length;
|
||||
while (counter < length) {
|
||||
const value = string.charCodeAt(counter++);
|
||||
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
|
||||
// It's a high surrogate, and there is a next character.
|
||||
const extra = string.charCodeAt(counter++);
|
||||
if ((extra & 0xFC00) == 0xDC00) {
|
||||
// Low surrogate.
|
||||
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
|
||||
} else {
|
||||
// It's an unmatched surrogate; only append this code unit, in case the
|
||||
// next code unit is the high surrogate of a surrogate pair.
|
||||
output.push(value);
|
||||
counter--;
|
||||
}
|
||||
} else {
|
||||
output.push(value);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string based on an array of numeric code points.
|
||||
* @see `punycode.ucs2.decode`
|
||||
* @memberOf punycode.ucs2
|
||||
* @name encode
|
||||
* @param {Array} codePoints The array of numeric code points.
|
||||
* @returns {String} The new Unicode string (UCS-2).
|
||||
*/
|
||||
const ucs2encode = codePoints => String.fromCodePoint(...codePoints);
|
||||
|
||||
/**
|
||||
* Converts a basic code point into a digit/integer.
|
||||
* @see `digitToBasic()`
|
||||
* @private
|
||||
* @param {Number} codePoint The basic numeric code point value.
|
||||
* @returns {Number} The numeric value of a basic code point (for use in
|
||||
* representing integers) in the range `0` to `base - 1`, or `base` if
|
||||
* the code point does not represent a value.
|
||||
*/
|
||||
const basicToDigit = function basicToDigit(codePoint) {
|
||||
if (codePoint >= 0x30 && codePoint < 0x3A) {
|
||||
return 26 + (codePoint - 0x30);
|
||||
}
|
||||
if (codePoint >= 0x41 && codePoint < 0x5B) {
|
||||
return codePoint - 0x41;
|
||||
}
|
||||
if (codePoint >= 0x61 && codePoint < 0x7B) {
|
||||
return codePoint - 0x61;
|
||||
}
|
||||
return base;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a digit/integer into a basic code point.
|
||||
* @see `basicToDigit()`
|
||||
* @private
|
||||
* @param {Number} digit The numeric value of a basic code point.
|
||||
* @returns {Number} The basic code point whose value (when used for
|
||||
* representing integers) is `digit`, which needs to be in the range
|
||||
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
||||
* used; else, the lowercase form is used. The behavior is undefined
|
||||
* if `flag` is non-zero and `digit` has no uppercase form.
|
||||
*/
|
||||
const digitToBasic = function digitToBasic(digit, flag) {
|
||||
// 0..25 map to ASCII a..z or A..Z
|
||||
// 26..35 map to ASCII 0..9
|
||||
return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
|
||||
};
|
||||
|
||||
/**
|
||||
* Bias adaptation function as per section 3.4 of RFC 3492.
|
||||
* https://tools.ietf.org/html/rfc3492#section-3.4
|
||||
* @private
|
||||
*/
|
||||
const adapt = function adapt(delta, numPoints, firstTime) {
|
||||
let k = 0;
|
||||
delta = firstTime ? floor(delta / damp) : delta >> 1;
|
||||
delta += floor(delta / numPoints);
|
||||
for /* no initialization */
|
||||
(; delta > baseMinusTMin * tMax >> 1; k += base) {
|
||||
delta = floor(delta / baseMinusTMin);
|
||||
}
|
||||
return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a Punycode string of ASCII-only symbols to a string of Unicode
|
||||
* symbols.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The Punycode string of ASCII-only symbols.
|
||||
* @returns {String} The resulting string of Unicode symbols.
|
||||
*/
|
||||
const decode = function decode(input) {
|
||||
// Don't use UCS-2.
|
||||
const output = [];
|
||||
const inputLength = input.length;
|
||||
let i = 0;
|
||||
let n = initialN;
|
||||
let bias = initialBias;
|
||||
|
||||
// Handle the basic code points: let `basic` be the number of input code
|
||||
// points before the last delimiter, or `0` if there is none, then copy
|
||||
// the first basic code points to the output.
|
||||
|
||||
let basic = input.lastIndexOf(delimiter);
|
||||
if (basic < 0) {
|
||||
basic = 0;
|
||||
}
|
||||
for (let j = 0; j < basic; ++j) {
|
||||
// if it's not a basic code point
|
||||
if (input.charCodeAt(j) >= 0x80) {
|
||||
error('not-basic');
|
||||
}
|
||||
output.push(input.charCodeAt(j));
|
||||
}
|
||||
|
||||
// Main decoding loop: start just after the last delimiter if any basic code
|
||||
// points were copied; start at the beginning otherwise.
|
||||
|
||||
for /* no final expression */
|
||||
(let index = basic > 0 ? basic + 1 : 0; index < inputLength;) {
|
||||
// `index` is the index of the next character to be consumed.
|
||||
// Decode a generalized variable-length integer into `delta`,
|
||||
// which gets added to `i`. The overflow checking is easier
|
||||
// if we increase `i` as we go, then subtract off its starting
|
||||
// value at the end to obtain `delta`.
|
||||
const oldi = i;
|
||||
for /* no condition */
|
||||
(let w = 1, k = base;; k += base) {
|
||||
if (index >= inputLength) {
|
||||
error('invalid-input');
|
||||
}
|
||||
const digit = basicToDigit(input.charCodeAt(index++));
|
||||
if (digit >= base) {
|
||||
error('invalid-input');
|
||||
}
|
||||
if (digit > floor((maxInt - i) / w)) {
|
||||
error('overflow');
|
||||
}
|
||||
i += digit * w;
|
||||
const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
|
||||
if (digit < t) {
|
||||
break;
|
||||
}
|
||||
const baseMinusT = base - t;
|
||||
if (w > floor(maxInt / baseMinusT)) {
|
||||
error('overflow');
|
||||
}
|
||||
w *= baseMinusT;
|
||||
}
|
||||
const out = output.length + 1;
|
||||
bias = adapt(i - oldi, out, oldi == 0);
|
||||
|
||||
// `i` was supposed to wrap around from `out` to `0`,
|
||||
// incrementing `n` each time, so we'll fix that now:
|
||||
if (floor(i / out) > maxInt - n) {
|
||||
error('overflow');
|
||||
}
|
||||
n += floor(i / out);
|
||||
i %= out;
|
||||
|
||||
// Insert `n` at position `i` of the output.
|
||||
output.splice(i++, 0, n);
|
||||
}
|
||||
return String.fromCodePoint(...output);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a string of Unicode symbols (e.g. a domain name label) to a
|
||||
* Punycode string of ASCII-only symbols.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The string of Unicode symbols.
|
||||
* @returns {String} The resulting Punycode string of ASCII-only symbols.
|
||||
*/
|
||||
const encode = function encode(input) {
|
||||
const output = [];
|
||||
|
||||
// Convert the input in UCS-2 to an array of Unicode code points.
|
||||
input = ucs2decode(input);
|
||||
|
||||
// Cache the length.
|
||||
const inputLength = input.length;
|
||||
|
||||
// Initialize the state.
|
||||
let n = initialN;
|
||||
let delta = 0;
|
||||
let bias = initialBias;
|
||||
|
||||
// Handle the basic code points.
|
||||
for (const currentValue of input) {
|
||||
if (currentValue < 0x80) {
|
||||
output.push(stringFromCharCode(currentValue));
|
||||
}
|
||||
}
|
||||
const basicLength = output.length;
|
||||
let handledCPCount = basicLength;
|
||||
|
||||
// `handledCPCount` is the number of code points that have been handled;
|
||||
// `basicLength` is the number of basic code points.
|
||||
|
||||
// Finish the basic string with a delimiter unless it's empty.
|
||||
if (basicLength) {
|
||||
output.push(delimiter);
|
||||
}
|
||||
|
||||
// Main encoding loop:
|
||||
while (handledCPCount < inputLength) {
|
||||
// All non-basic code points < n have been handled already. Find the next
|
||||
// larger one:
|
||||
let m = maxInt;
|
||||
for (const currentValue of input) {
|
||||
if (currentValue >= n && currentValue < m) {
|
||||
m = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
|
||||
// but guard against overflow.
|
||||
const handledCPCountPlusOne = handledCPCount + 1;
|
||||
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
|
||||
error('overflow');
|
||||
}
|
||||
delta += (m - n) * handledCPCountPlusOne;
|
||||
n = m;
|
||||
for (const currentValue of input) {
|
||||
if (currentValue < n && ++delta > maxInt) {
|
||||
error('overflow');
|
||||
}
|
||||
if (currentValue === n) {
|
||||
// Represent delta as a generalized variable-length integer.
|
||||
let q = delta;
|
||||
for /* no condition */
|
||||
(let k = base;; k += base) {
|
||||
const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
|
||||
if (q < t) {
|
||||
break;
|
||||
}
|
||||
const qMinusT = q - t;
|
||||
const baseMinusT = base - t;
|
||||
output.push(stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)));
|
||||
q = floor(qMinusT / baseMinusT);
|
||||
}
|
||||
output.push(stringFromCharCode(digitToBasic(q, 0)));
|
||||
bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength);
|
||||
delta = 0;
|
||||
++handledCPCount;
|
||||
}
|
||||
}
|
||||
++delta;
|
||||
++n;
|
||||
}
|
||||
return output.join('');
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a Punycode string representing a domain name or an email address
|
||||
* to Unicode. Only the Punycoded parts of the input will be converted, i.e.
|
||||
* it doesn't matter if you call it on a string that has already been
|
||||
* converted to Unicode.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The Punycoded domain name or email address to
|
||||
* convert to Unicode.
|
||||
* @returns {String} The Unicode representation of the given Punycode
|
||||
* string.
|
||||
*/
|
||||
const toUnicode = function toUnicode(input) {
|
||||
return mapDomain(input, function (string) {
|
||||
return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a Unicode string representing a domain name or an email address to
|
||||
* Punycode. Only the non-ASCII parts of the domain name will be converted,
|
||||
* i.e. it doesn't matter if you call it with a domain that's already in
|
||||
* ASCII.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The domain name or email address to convert, as a
|
||||
* Unicode string.
|
||||
* @returns {String} The Punycode representation of the given domain name or
|
||||
* email address.
|
||||
*/
|
||||
const toASCII = function toASCII(input) {
|
||||
return mapDomain(input, function (string) {
|
||||
return regexNonASCII.test(string) ? 'xn--' + encode(string) : string;
|
||||
});
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/** Define the public API */
|
||||
const punycode = {
|
||||
/**
|
||||
* A string representing the current Punycode.js version number.
|
||||
* @memberOf punycode
|
||||
* @type String
|
||||
*/
|
||||
'version': '2.1.0',
|
||||
/**
|
||||
* An object of methods to convert from JavaScript's internal character
|
||||
* representation (UCS-2) to Unicode code points, and back.
|
||||
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
||||
* @memberOf punycode
|
||||
* @type Object
|
||||
*/
|
||||
'ucs2': {
|
||||
'decode': ucs2decode,
|
||||
'encode': ucs2encode
|
||||
},
|
||||
'decode': decode,
|
||||
'encode': encode,
|
||||
'toASCII': toASCII,
|
||||
'toUnicode': toUnicode
|
||||
};
|
||||
|
||||
/**
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
class JFormValidator {
|
||||
constructor() {
|
||||
this.customValidators = {};
|
||||
this.handlers = [];
|
||||
this.handlers = {};
|
||||
this.removeMarking = this.removeMarking.bind(this);
|
||||
this.inputEmail = () => {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'email');
|
||||
return input.type !== 'text';
|
||||
};
|
||||
|
||||
// Default handlers
|
||||
this.setHandler('username', value => {
|
||||
const regex = /[<|>|"|'|%|;|(|)|&]/i;
|
||||
return !regex.test(value);
|
||||
});
|
||||
this.setHandler('password', value => {
|
||||
const regex = /^\S[\S ]{2,98}\S$/;
|
||||
return regex.test(value);
|
||||
});
|
||||
this.setHandler('numeric', value => {
|
||||
const regex = /^(\d|-)?(\d|,)*\.?\d*$/;
|
||||
return regex.test(value);
|
||||
});
|
||||
this.setHandler('email', value => {
|
||||
const newValue = punycode.toASCII(value);
|
||||
const regex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
|
||||
return regex.test(newValue);
|
||||
});
|
||||
|
||||
// Attach all forms with a class 'form-validate'
|
||||
const forms = [].slice.call(document.querySelectorAll('form'));
|
||||
forms.forEach(form => {
|
||||
if (form.classList.contains('form-validate')) {
|
||||
this.attachToForm(form);
|
||||
}
|
||||
});
|
||||
}
|
||||
get custom() {
|
||||
return this.customValidators;
|
||||
}
|
||||
set custom(value) {
|
||||
this.customValidators = value;
|
||||
}
|
||||
setHandler(name, func, en) {
|
||||
const isEnabled = en === '' ? true : en;
|
||||
this.handlers[name] = {
|
||||
enabled: isEnabled,
|
||||
exec: func
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
markValid(element) {
|
||||
// Get a label
|
||||
const label = element.form.querySelector(`label[for="${element.id}"]`);
|
||||
let message;
|
||||
if (element.classList.contains('required') || element.getAttribute('required')) {
|
||||
if (label) {
|
||||
message = label.querySelector('span.form-control-feedback');
|
||||
}
|
||||
}
|
||||
element.classList.remove('form-control-danger');
|
||||
element.classList.remove('invalid');
|
||||
element.classList.add('form-control-success');
|
||||
element.parentNode.classList.remove('has-danger');
|
||||
element.parentNode.classList.add('has-success');
|
||||
element.setAttribute('aria-invalid', 'false');
|
||||
|
||||
// Remove message
|
||||
if (message) {
|
||||
message.parentNode.removeChild(message);
|
||||
}
|
||||
|
||||
// Restore Label
|
||||
if (label) {
|
||||
label.classList.remove('invalid');
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
markInvalid(element, empty) {
|
||||
// Get a label
|
||||
const label = element.form.querySelector(`label[for="${element.id}"]`);
|
||||
element.classList.remove('form-control-success');
|
||||
element.classList.remove('valid');
|
||||
element.classList.add('form-control-danger');
|
||||
element.classList.add('invalid');
|
||||
element.parentNode.classList.remove('has-success');
|
||||
element.parentNode.classList.add('has-danger');
|
||||
element.setAttribute('aria-invalid', 'true');
|
||||
|
||||
// Display custom message
|
||||
let mesgCont;
|
||||
const message = element.getAttribute('data-validation-text');
|
||||
if (label) {
|
||||
mesgCont = label.querySelector('span.form-control-feedback');
|
||||
}
|
||||
if (!mesgCont) {
|
||||
const elMsg = document.createElement('span');
|
||||
elMsg.classList.add('form-control-feedback');
|
||||
if (empty && empty === 'checkbox') {
|
||||
elMsg.innerHTML = message !== null ? Joomla.sanitizeHtml(message) : Joomla.sanitizeHtml(Joomla.Text._('JLIB_FORM_FIELD_REQUIRED_CHECK'));
|
||||
} else if (empty && empty === 'value') {
|
||||
elMsg.innerHTML = message !== null ? Joomla.sanitizeHtml(message) : Joomla.sanitizeHtml(Joomla.Text._('JLIB_FORM_FIELD_REQUIRED_VALUE'));
|
||||
} else {
|
||||
elMsg.innerHTML = message !== null ? Joomla.sanitizeHtml(message) : Joomla.sanitizeHtml(Joomla.Text._('JLIB_FORM_FIELD_INVALID_VALUE'));
|
||||
}
|
||||
if (label) {
|
||||
label.appendChild(elMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the Label as well
|
||||
if (label) {
|
||||
label.classList.add('invalid');
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
removeMarking(element) {
|
||||
// Get the associated label
|
||||
let message;
|
||||
const label = element.form.querySelector(`label[for="${element.id}"]`);
|
||||
if (label) {
|
||||
message = label.querySelector('span.form-control-feedback');
|
||||
}
|
||||
element.classList.remove('form-control-danger');
|
||||
element.classList.remove('form-control-success');
|
||||
element.classList.remove('invalid');
|
||||
element.classList.add('valid');
|
||||
element.parentNode.classList.remove('has-danger');
|
||||
element.parentNode.classList.remove('has-success');
|
||||
|
||||
// Remove message
|
||||
if (message) {
|
||||
if (label) {
|
||||
label.removeChild(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore Label
|
||||
if (label) {
|
||||
label.classList.remove('invalid');
|
||||
}
|
||||
}
|
||||
handleResponse(state, element, empty) {
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
|
||||
// Set the element and its label (if exists) invalid state
|
||||
if (tagName !== 'button' && element.value !== undefined || tagName === 'fieldset') {
|
||||
if (state === false) {
|
||||
this.markInvalid(element, empty);
|
||||
} else {
|
||||
this.markValid(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
validate(element) {
|
||||
let tagName;
|
||||
|
||||
// Ignore the element if its currently disabled,
|
||||
// because are not submitted for the http-request.
|
||||
// For those case return always true.
|
||||
if (element.getAttribute('disabled') === 'disabled' || element.getAttribute('display') === 'none') {
|
||||
this.handleResponse(true, element);
|
||||
return true;
|
||||
}
|
||||
// If the field is required make sure it has a value
|
||||
if (element.getAttribute('required') || element.classList.contains('required')) {
|
||||
tagName = element.tagName.toLowerCase();
|
||||
if (tagName === 'fieldset' && (element.classList.contains('radio') || element.classList.contains('checkboxes'))) {
|
||||
// No options are checked.
|
||||
if (element.querySelector('input:checked') === null) {
|
||||
this.handleResponse(false, element, 'checkbox');
|
||||
return false;
|
||||
}
|
||||
} else if (element.getAttribute('type') === 'checkbox' && element.checked !== true || tagName === 'select' && !element.value.length) {
|
||||
this.handleResponse(false, element, 'checkbox');
|
||||
return false;
|
||||
} else if (!element.value || element.classList.contains('placeholder')) {
|
||||
// If element has class placeholder that means it is empty.
|
||||
this.handleResponse(false, element, 'value');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Only validate the field if the validate class is set
|
||||
const handler = element.getAttribute('class') && element.getAttribute('class').match(/validate-([a-zA-Z0-9_-]+)/) ? element.getAttribute('class').match(/validate-([a-zA-Z0-9_-]+)/)[1] : '';
|
||||
if (element.getAttribute('pattern') && element.getAttribute('pattern') !== '') {
|
||||
if (element.value.length) {
|
||||
const isValid = new RegExp(`^${element.getAttribute('pattern')}$`).test(element.value);
|
||||
this.handleResponse(isValid, element, 'empty');
|
||||
return isValid;
|
||||
}
|
||||
if (element.hasAttribute('required') || element.classList.contains('required')) {
|
||||
this.handleResponse(false, element, 'empty');
|
||||
return false;
|
||||
}
|
||||
this.handleResponse(true, element);
|
||||
return true;
|
||||
}
|
||||
if (handler === '') {
|
||||
this.handleResponse(true, element);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check the additional validation types
|
||||
if (handler && handler !== 'none' && this.handlers[handler] && element.value) {
|
||||
// Execute the validation handler and return result
|
||||
if (this.handlers[handler].exec(element.value, element) !== true) {
|
||||
this.handleResponse(false, element, 'invalid_value');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Return validation state
|
||||
this.handleResponse(true, element);
|
||||
return true;
|
||||
}
|
||||
isValid(form) {
|
||||
let valid = true;
|
||||
let message;
|
||||
let error;
|
||||
let fields;
|
||||
const invalid = [];
|
||||
|
||||
// Validate form fields
|
||||
if (form.nodeName === 'FORM') {
|
||||
fields = [].slice.call(form.elements);
|
||||
} else {
|
||||
fields = [].slice.call(form.querySelectorAll('input, textarea, select, button, fieldset'));
|
||||
}
|
||||
fields.forEach(field => {
|
||||
if (this.validate(field) === false) {
|
||||
valid = false;
|
||||
invalid.push(field);
|
||||
}
|
||||
});
|
||||
|
||||
// Run custom form validators if present
|
||||
if (Object.keys(this.customValidators).length) {
|
||||
Object.keys(this.customValidators).foreach(key => {
|
||||
if (this.customValidators[key].exec() !== true) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!valid && invalid.length > 0) {
|
||||
if (form.getAttribute('data-validation-text')) {
|
||||
message = form.getAttribute('data-validation-text');
|
||||
} else {
|
||||
message = Joomla.Text._('JLIB_FORM_CONTAINS_INVALID_FIELDS');
|
||||
}
|
||||
error = {
|
||||
error: [message]
|
||||
};
|
||||
Joomla.renderMessages(error);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
attachToForm(form) {
|
||||
let elements;
|
||||
if (form.nodeName === 'FORM') {
|
||||
elements = [].slice.call(form.elements);
|
||||
} else {
|
||||
elements = [].slice.call(form.querySelectorAll('input, textarea, select, button, fieldset'));
|
||||
}
|
||||
|
||||
// Iterate through the form object and attach the validate method to all input fields.
|
||||
elements.forEach(element => {
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
if (['input', 'textarea', 'select', 'fieldset'].indexOf(tagName) > -1 && element.classList.contains('required')) {
|
||||
element.setAttribute('required', '');
|
||||
}
|
||||
|
||||
// Attach isValid method to submit button
|
||||
if ((tagName === 'input' || tagName === 'button') && (element.getAttribute('type') === 'submit' || element.getAttribute('type') === 'image')) {
|
||||
if (element.classList.contains('validate')) {
|
||||
element.addEventListener('click', () => this.isValid(form));
|
||||
}
|
||||
} else if (tagName !== 'button' && !(tagName === 'input' && element.getAttribute('type') === 'button')) {
|
||||
// Attach validate method only to fields
|
||||
if (tagName !== 'fieldset') {
|
||||
element.addEventListener('blur', ({
|
||||
target
|
||||
}) => this.validate(target));
|
||||
element.addEventListener('focus', ({
|
||||
target
|
||||
}) => this.removeMarking(target));
|
||||
if (element.classList.contains('validate-email') && this.inputEmail) {
|
||||
element.setAttribute('type', 'email');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const initialize = () => {
|
||||
document.formvalidator = new JFormValidator();
|
||||
|
||||
// Cleanup
|
||||
document.removeEventListener('DOMContentLoaded', initialize);
|
||||
};
|
||||
document.addEventListener('DOMContentLoaded', initialize);
|
||||
1
media/system/js/fields/validate.min.js
vendored
Normal file
1
media/system/js/fields/validate.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/validate.min.js.gz
Normal file
BIN
media/system/js/fields/validate.min.js.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user