first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

748
media/system/js/core.js Normal file
View File

@ -0,0 +1,748 @@
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
/**
* --------------------------------------------------------------------------
* Bootstrap util/sanitizer.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
// js-docs-end allow-list
const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);
/**
* A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
* contexts.
*
* Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
*/
// eslint-disable-next-line unicorn/better-regex
const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;
const allowedAttribute = (attribute, allowedAttributeList) => {
const attributeName = attribute.nodeName.toLowerCase();
if (allowedAttributeList.includes(attributeName)) {
if (uriAttributes.has(attributeName)) {
return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));
}
return true;
}
// Check if a regular expression validates the attribute.
return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));
};
function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
if (!unsafeHtml.length) {
return unsafeHtml;
}
if (sanitizeFunction && typeof sanitizeFunction === 'function') {
return sanitizeFunction(unsafeHtml);
}
const domParser = new window.DOMParser();
const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');
const elements = [].concat(...createdDocument.body.querySelectorAll('*'));
for (const element of elements) {
const elementName = element.nodeName.toLowerCase();
if (!Object.keys(allowList).includes(elementName)) {
element.remove();
continue;
}
const attributeList = [].concat(...element.attributes);
const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);
for (const attribute of attributeList) {
if (!allowedAttribute(attribute, allowedAttributes)) {
element.removeAttribute(attribute.nodeName);
}
}
}
return createdDocument.body.innerHTML;
}
const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i;
const DATA_ATTRIBUTE_PATTERN = /^data-[\w-]*$/i;
const DefaultAllowlist = {
// Global attributes allowed on any supplied element below.
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN, DATA_ATTRIBUTE_PATTERN],
a: ['target', 'href', 'title', 'rel'],
area: [],
b: [],
br: [],
col: [],
code: [],
div: [],
em: [],
hr: [],
h1: [],
h2: [],
h3: [],
h4: [],
h5: [],
h6: [],
i: [],
img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
li: [],
ol: [],
p: [],
pre: [],
s: [],
small: [],
span: [],
sub: [],
sup: [],
strong: [],
u: [],
ul: [],
button: ['type'],
input: ['accept', 'alt', 'autocomplete', 'autofocus', 'capture', 'checked', 'dirname', 'disabled', 'height', 'list', 'max', 'maxlength', 'min', 'minlength', 'multiple', 'type', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'size', 'src', 'step', 'value', 'width', 'inputmode'],
select: ['name'],
textarea: ['name'],
option: ['value', 'selected']
};
// Only define the Joomla namespace if not defined.
window.Joomla = window.Joomla || {};
window.Joomla.Modal = window.Joomla.Modal || {
/**
* *****************************************************************
* Modals should implement
* *****************************************************************
*
* getCurrent Type Function Should return the modal element
* setCurrent Type Function Should set the modal element
* current Type {node} The modal element
*
* USAGE (assuming that exampleId is the modal id)
* To get the current modal element:
* Joomla.Modal.getCurrent(); // Returns node element, eg: document.getElementById('exampleId')
* To set the current modal element:
* Joomla.Modal.setCurrent(document.getElementById('exampleId'));
*
* *************************************************************
* Joomla's UI modal uses `element.close();` to close the modal
* and `element.open();` to open the modal
* If you are using another modal make sure the same
* functionality is bound to the modal element
* @see media/legacy/bootstrap.init.js
* *************************************************************
*/
current: '',
setCurrent: element => {
window.Joomla.Modal.current = element;
},
getCurrent: () => window.Joomla.Modal.current
};
(Joomla => {
/**
* Method to Extend Objects
*
* @param {Object} destination
* @param {Object} source
*
* @return Object
*/
Joomla.extend = (destination, source) => {
let newDestination = destination;
/**
* Technically null is an object, but trying to treat the destination as one in this
* context will error out.
* So emulate jQuery.extend(), and treat a destination null as an empty object.
*/
if (destination === null) {
newDestination = {};
}
[].slice.call(Object.keys(source)).forEach(key => {
newDestination[key] = source[key];
});
return destination;
};
/**
* Joomla options storage
*
* @type {{}}
*
* @since 3.7.0
*/
Joomla.optionsStorage = Joomla.optionsStorage || null;
/**
* Get script(s) options
*
* @param {String} key Name in Storage
* @param {mixed} def Default value if nothing found
*
* @return {mixed}
*
* @since 3.7.0
*/
Joomla.getOptions = (key, def) => {
// Load options if they not exists
if (!Joomla.optionsStorage) {
Joomla.loadOptions();
}
return Joomla.optionsStorage[key] !== undefined ? Joomla.optionsStorage[key] : def;
};
/**
* Load new options from given options object or from Element
*
* @param {Object|undefined} options The options object to load.
* Eg {"com_foobar" : {"option1": 1, "option2": 2}}
*
* @since 3.7.0
*/
Joomla.loadOptions = options => {
// Load form the script container
if (!options) {
const elements = [].slice.call(document.querySelectorAll('.joomla-script-options.new'));
let counter = 0;
elements.forEach(element => {
const str = element.text || element.textContent;
const option = JSON.parse(str);
if (option) {
Joomla.loadOptions(option);
counter += 1;
}
element.className = element.className.replace(' new', ' loaded');
});
if (counter) {
return;
}
}
// Initial loading
if (!Joomla.optionsStorage) {
Joomla.optionsStorage = options || {};
} else if (options) {
// Merge with existing
[].slice.call(Object.keys(options)).forEach(key => {
/**
* If both existing and new options are objects, merge them with Joomla.extend().
* But test for new option being null, as null is an object, but we want to allow
* clearing of options with ...
*
* Joomla.loadOptions({'joomla.jtext': null});
*/
if (options[key] !== null && typeof Joomla.optionsStorage[key] === 'object' && typeof options[key] === 'object') {
Joomla.optionsStorage[key] = Joomla.extend(Joomla.optionsStorage[key], options[key]);
} else {
Joomla.optionsStorage[key] = options[key];
}
});
}
};
/**
* Custom behavior for JavaScript I18N in Joomla! 1.6
*
* @type {{}}
*
* Allows you to call Joomla.Text._() to get a translated JavaScript string
* pushed in with Text::script() in Joomla.
*/
Joomla.Text = {
strings: {},
/**
* Translates a string into the current language.
*
* @param {String} key The string to translate
* @param {String} def Default string
*
* @returns {String}
*/
_: (key, def) => {
let newKey = key;
let newDef = def;
// Check for new strings in the optionsStorage, and load them
const newStrings = Joomla.getOptions('joomla.jtext');
if (newStrings) {
Joomla.Text.load(newStrings);
// Clean up the optionsStorage from useless data
Joomla.loadOptions({
'joomla.jtext': null
});
}
newDef = newDef === undefined ? newKey : newDef;
newKey = newKey.toUpperCase();
return Joomla.Text.strings[newKey] !== undefined ? Joomla.Text.strings[newKey] : newDef;
},
/**
* Load new strings in to Joomla.Text
*
* @param {Object} object Object with new strings
* @returns {Joomla.Text}
*/
load: object => {
[].slice.call(Object.keys(object)).forEach(key => {
Joomla.Text.strings[key.toUpperCase()] = object[key];
});
return Joomla.Text;
}
};
/**
* For B/C we still support Joomla.JText
*
* @type {{}}
*
* @deprecated 4.0 will be removed in 6.0
* Example: Joomla.Text._('...');
* Joomla.Text.load(...);
*/
Joomla.JText = Joomla.Text;
/**
* Generic submit form
*
* @param {String} task The given task
* @param {node} form The form element
* @param {bool} validate The form element
*
* @returns {void}
*/
Joomla.submitform = (task, form, validate) => {
let newForm = form;
const newTask = task;
if (!newForm) {
newForm = document.getElementById('adminForm');
}
if (newTask) {
newForm.task.value = newTask;
}
// Toggle HTML5 validation
newForm.noValidate = !validate;
if (!validate) {
newForm.setAttribute('novalidate', '');
} else if (newForm.hasAttribute('novalidate')) {
newForm.removeAttribute('novalidate');
}
// Submit the form.
// Create the input type="submit"
const button = document.createElement('input');
button.classList.add('hidden');
button.type = 'submit';
// Append it and click it
newForm.appendChild(button).click();
// If "submit" was prevented, make sure we don't get a build up of buttons
newForm.removeChild(button);
};
/**
* Default function. Can be overridden by the component to add custom logic
*
* @param {String} task The given task
* @param {String} formSelector The form selector eg '#adminForm'
* @param {bool} validate The form element
*
* @returns {void}
*/
Joomla.submitbutton = (task, formSelector, validate) => {
let form = document.querySelector(formSelector || 'form.form-validate');
let newValidate = validate;
if (typeof formSelector === 'string' && form === null) {
form = document.querySelector(`#${formSelector}`);
}
if (form) {
if (newValidate === undefined || newValidate === null) {
const pressbutton = task.split('.');
let cancelTask = form.getAttribute('data-cancel-task');
if (!cancelTask) {
cancelTask = `${pressbutton[0]}.cancel`;
}
newValidate = task !== cancelTask;
}
if (!newValidate || document.formvalidator.isValid(form)) {
Joomla.submitform(task, form);
}
} else {
Joomla.submitform(task);
}
};
/**
* USED IN: all list forms.
*
* Toggles the check state of a group of boxes
*
* Checkboxes must have an id attribute in the form cb0, cb1...
*
* @param {mixed} checkbox The number of box to 'check', for a checkbox element
* @param {string} stub An alternative field name
*
* @return {boolean}
*/
Joomla.checkAll = (checkbox, stub) => {
if (!checkbox.form) {
return false;
}
const currentStab = stub || 'cb';
const elements = [].slice.call(checkbox.form.elements);
let state = 0;
elements.forEach(element => {
if (element.type === checkbox.type && element.id.indexOf(currentStab) === 0) {
element.checked = checkbox.checked;
state += element.checked ? 1 : 0;
}
});
if (checkbox.form.boxchecked) {
checkbox.form.boxchecked.value = state;
checkbox.form.boxchecked.dispatchEvent(new CustomEvent('change', {
bubbles: true,
cancelable: true
}));
}
return true;
};
/**
* USED IN: administrator/components/com_cache/views/cache/tmpl/default.php
* administrator/components/com_installer/views/discover/tmpl/default_item.php
* administrator/components/com_installer/views/update/tmpl/default_item.php
* administrator/components/com_languages/helpers/html/languages.php
* libraries/joomla/html/html/grid.php
*
* @param {boolean} isitchecked Flag for checked
* @param {node} form The form
*
* @return {void}
*/
Joomla.isChecked = (isitchecked, form) => {
let newForm = form;
if (typeof newForm === 'undefined') {
newForm = document.getElementById('adminForm');
} else if (typeof form === 'string') {
newForm = document.getElementById(form);
}
newForm.boxchecked.value = isitchecked ? parseInt(newForm.boxchecked.value, 10) + 1 : parseInt(newForm.boxchecked.value, 10) - 1;
newForm.boxchecked.dispatchEvent(new CustomEvent('change', {
bubbles: true,
cancelable: true
}));
// If we don't have a checkall-toggle, done.
if (!newForm.elements['checkall-toggle']) {
return;
}
// Toggle main toggle checkbox depending on checkbox selection
let c = true;
let i;
let e;
let n;
// eslint-disable-next-line no-plusplus
for (i = 0, n = newForm.elements.length; i < n; i++) {
e = newForm.elements[i];
if (e.type === 'checkbox' && e.name !== 'checkall-toggle' && !e.checked) {
c = false;
break;
}
}
newForm.elements['checkall-toggle'].checked = c;
};
/**
* USED IN: libraries/joomla/html/html/grid.php
* In other words, on any reorderable table
*
* @param {string} order The order value
* @param {string} dir The direction
* @param {string} task The task
* @param {node} form The form
*
* return {void}
*/
Joomla.tableOrdering = (order, dir, task, form) => {
let newForm = form;
if (typeof newForm === 'undefined') {
newForm = document.getElementById('adminForm');
} else if (typeof form === 'string') {
newForm = document.getElementById(form);
}
newForm.filter_order.value = order;
newForm.filter_order_Dir.value = dir;
Joomla.submitform(task, newForm);
};
/**
* USED IN: all over :)
*
* @param {string} id The id
* @param {string} task The task
* @param {string} form The optional form
*
* @return {boolean}
*/
Joomla.listItemTask = (id, task, form = null) => {
let newForm = form;
if (form !== null) {
newForm = document.getElementById(form);
} else {
newForm = document.adminForm;
}
const cb = newForm[id];
let i = 0;
let cbx;
if (!cb) {
return false;
}
// eslint-disable-next-line no-constant-condition
while (true) {
cbx = newForm[`cb${i}`];
if (!cbx) {
break;
}
cbx.checked = false;
i += 1;
}
cb.checked = true;
newForm.boxchecked.value = 1;
Joomla.submitform(task, newForm);
return false;
};
/**
* Method to replace all request tokens on the page with a new one.
*
* @param {String} newToken The token
*
* Used in Joomla Installation
*/
Joomla.replaceTokens = newToken => {
if (!/^[0-9A-F]{32}$/i.test(newToken)) {
return;
}
const elements = [].slice.call(document.getElementsByTagName('input'));
elements.forEach(element => {
if (element.type === 'hidden' && element.value === '1' && element.name.length === 32) {
element.name = newToken;
}
});
};
/**
* Method to perform AJAX request
*
* @param {Object} options Request options:
* {
* url: 'index.php', Request URL
* method: 'GET', Request method GET (default), POST
* data: null, Data to be sent, see
* https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/send
* perform: true, Perform the request immediately
* or return XMLHttpRequest instance and perform it later
* headers: null, Object of custom headers, eg {'X-Foo': 'Bar', 'X-Bar': 'Foo'}
* promise: false Whether return a Promise instance.
* When true then next options is ignored: perform, onSuccess, onError, onComplete
*
* onBefore: (xhr) => {} // Callback on before the request
* onSuccess: (response, xhr) => {}, // Callback on the request success
* onError: (xhr) => {}, // Callback on the request error
* onComplete: (xhr) => {}, // Callback on the request completed, with/without error
* }
*
* @return XMLHttpRequest|Boolean
*
* @example
*
* Joomla.request({
* url: 'index.php?option=com_example&view=example',
* onSuccess: (response, xhr) => {
* JSON.parse(response);
* }
* })
*
* @see https://developer.mozilla.org/docs/Web/API/XMLHttpRequest
*/
Joomla.request = options => {
// Prepare the options
const newOptions = Joomla.extend({
url: '',
method: 'GET',
data: null,
perform: true,
promise: false
}, options);
// Setup XMLHttpRequest instance
const createRequest = (onSuccess, onError) => {
const xhr = new XMLHttpRequest();
xhr.open(newOptions.method, newOptions.url, true);
// Set the headers
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('X-Ajax-Engine', 'Joomla!');
if (newOptions.method !== 'GET') {
const token = Joomla.getOptions('csrf.token', '');
// Use the CSRF only on the site's domain
if (token && (!newOptions.url.startsWith('http:') && !newOptions.url.startsWith('https:') || newOptions.url.startsWith(window.location.origin))) {
xhr.setRequestHeader('X-CSRF-Token', token);
}
if (typeof newOptions.data === 'string' && (!newOptions.headers || !newOptions.headers['Content-Type'])) {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
}
// Custom headers
if (newOptions.headers) {
[].slice.call(Object.keys(newOptions.headers)).forEach(key => {
// Allow request without Content-Type
// eslint-disable-next-line no-empty
if (key === 'Content-Type' && newOptions.headers['Content-Type'] === 'false') ; else {
xhr.setRequestHeader(key, newOptions.headers[key]);
}
});
}
xhr.onreadystatechange = () => {
// Request not finished
if (xhr.readyState !== 4) {
return;
}
// Request finished and response is ready
if (xhr.status === 200) {
if (newOptions.promise) {
// A Promise accepts only one argument
onSuccess.call(window, xhr);
} else {
onSuccess.call(window, xhr.responseText, xhr);
}
} else {
onError.call(window, xhr);
}
if (newOptions.onComplete && !newOptions.promise) {
newOptions.onComplete.call(window, xhr);
}
};
// Do request
if (newOptions.perform) {
if (newOptions.onBefore && newOptions.onBefore.call(window, xhr) === false) {
// Request interrupted
if (newOptions.promise) {
onSuccess.call(window, xhr);
}
return xhr;
}
xhr.send(newOptions.data);
}
return xhr;
};
// Return a Promise
if (newOptions.promise) {
return new Promise((resolve, reject) => {
newOptions.perform = true;
createRequest(resolve, reject);
});
}
// Return a Request
try {
return createRequest(newOptions.onSuccess || (() => {}), newOptions.onError || (() => {}));
} catch (error) {
// eslint-disable-next-line no-unused-expressions,no-console
console.error(error);
return false;
}
};
let lastRequestPromise;
/**
* Joomla Request queue.
*
* A FIFO queue of requests to execute serially. Used to prevent simultaneous execution of
* multiple requests against the server which could trigger its Denial of Service protection.
*
* @param {object} options Options for Joomla.request()
* @returns {Promise}
*/
Joomla.enqueueRequest = options => {
if (!options.promise) {
throw new Error('Joomla.enqueueRequest supports only Joomla.request as Promise');
}
if (!lastRequestPromise) {
lastRequestPromise = Joomla.request(options);
} else {
lastRequestPromise = lastRequestPromise.then(() => Joomla.request(options));
}
return lastRequestPromise;
};
/**
*
* @param {string} unsafeHtml The html for sanitization
* @param {object} allowList The list of HTMLElements with an array of allowed attributes
* @param {function} sanitizeFn A custom sanitization function
*
* @return string
*/
Joomla.sanitizeHtml = (unsafeHtml, allowList, sanitizeFn) => {
const allowed = allowList === undefined || allowList === null ? DefaultAllowlist : _extends({}, DefaultAllowlist, allowList);
return sanitizeHtml(unsafeHtml, allowed, sanitizeFn);
};
/**
* Treat AJAX errors.
* Used by some javascripts such as sendtestmail.js and permissions.js
*
* @param {object} xhr XHR object.
* @param {string} textStatus Type of error that occurred.
* @param {string} error Textual portion of the HTTP status.
*
* @return {object} JavaScript object containing the system error message.
*
* @since 3.6.0
*/
Joomla.ajaxErrorsMessages = (xhr, textStatus) => {
const msg = {};
if (textStatus === 'parsererror') {
// For jQuery jqXHR
const buf = [];
// Html entity encode.
let encodedJson = xhr.responseText.trim();
// eslint-disable-next-line no-plusplus
for (let i = encodedJson.length - 1; i >= 0; i--) {
buf.unshift(['&#', encodedJson[i].charCodeAt(), ';'].join(''));
}
encodedJson = buf.join('');
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_PARSE').replace('%s', encodedJson)];
} else if (textStatus === 'nocontent') {
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_NO_CONTENT')];
} else if (textStatus === 'timeout') {
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_TIMEOUT')];
} else if (textStatus === 'abort') {
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_CONNECTION_ABORT')];
} else if (xhr.responseJSON && xhr.responseJSON.message) {
// For vanilla XHR
msg.error = [`${Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)} <em>${xhr.responseJSON.message}</em>`];
} else if (xhr.statusText) {
msg.error = [`${Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)} <em>${xhr.statusText}</em>`];
} else {
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)];
}
return msg;
};
})(Joomla);

1
media/system/js/core.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,175 @@
/**
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// The container where the draggable will be enabled
let url;
let direction;
let isNested;
let dragElementIndex;
let dropElementIndex;
let container = document.querySelector('.js-draggable');
let form;
let formData;
if (container) {
/** The script expects a form with a class js-form
* A table with the tbody with a class js-draggable
* with a data-url with the ajax request end point and
* with a data-direction for asc/desc
*/
url = container.dataset.url;
direction = container.dataset.direction;
isNested = container.dataset.nested;
} else if (Joomla.getOptions('draggable-list')) {
const options = Joomla.getOptions('draggable-list');
container = document.querySelector(options.id);
/**
* This is here to make the transition to new forms easier.
*/
if (!container.classList.contains('js-draggable')) {
container.classList.add('js-draggable');
}
({
url
} = options);
({
direction
} = options);
isNested = options.nested;
}
if (container) {
// Get the form
form = container.closest('form');
// Get the form data
formData = new FormData(form);
formData.delete('task');
formData.delete('order[]');
// IOS 10 BUG
document.addEventListener('touchstart', () => {}, false);
const getOrderData = (rows, inputRows, dragIndex, dropIndex) => {
let i;
const result = [];
// Element is moved down
if (dragIndex < dropIndex) {
rows[dropIndex].value = rows[dropIndex - 1].value;
for (i = dragIndex; i < dropIndex; i += 1) {
if (direction === 'asc') {
rows[i].value = parseInt(rows[i].value, 10) - 1;
} else {
rows[i].value = parseInt(rows[i].value, 10) + 1;
}
}
} else {
// Element is moved up
rows[dropIndex].value = rows[dropIndex + 1].value;
for (i = dropIndex + 1; i <= dragIndex; i += 1) {
if (direction === 'asc') {
rows[i].value = parseInt(rows[i].value, 10) + 1;
} else {
rows[i].value = parseInt(rows[i].value, 10) - 1;
}
}
}
for (i = 0; i < rows.length - 1; i += 1) {
result.push(`order[]=${encodeURIComponent(rows[i].value)}`);
result.push(`cid[]=${encodeURIComponent(inputRows[i].value)}`);
}
return result;
};
const rearrangeChildren = $parent => {
if (!$parent.dataset.itemId) {
return;
}
const parentId = $parent.dataset.itemId;
// Get children list. Each child row should have
// an attribute data-parents=" 1 2 3" where the number is id of parent
const $children = container.querySelectorAll(`tr[data-parents~="${parentId}"]`);
if ($children.length) {
$parent.after(...$children);
}
};
const saveTheOrder = el => {
let orderSelector;
let inputSelector;
let rowSelector;
const groupId = el.dataset.draggableGroup;
if (groupId) {
rowSelector = `tr[data-draggable-group="${groupId}"]`;
orderSelector = `[data-draggable-group="${groupId}"] [name="order[]"]`;
inputSelector = `[data-draggable-group="${groupId}"] [name="cid[]"]`;
} else {
rowSelector = 'tr';
orderSelector = '[name="order[]"]';
inputSelector = '[name="cid[]"]';
}
const rowElements = [].slice.call(container.querySelectorAll(rowSelector));
const rows = [].slice.call(container.querySelectorAll(orderSelector));
const inputRows = [].slice.call(container.querySelectorAll(inputSelector));
dropElementIndex = rowElements.indexOf(el);
if (url) {
// Detach task field if exists
const task = document.querySelector('[name="task"]');
// Detach task field if exists
if (task) {
task.setAttribute('name', 'some__Temporary__Name__');
}
// Prepare the options
const ajaxOptions = {
url,
method: 'POST',
data: `${new URLSearchParams(formData).toString()}&${getOrderData(rows, inputRows, dragElementIndex, dropElementIndex).join('&')}`,
perform: true
};
Joomla.request(ajaxOptions);
// Re-Append original task field
if (task) {
task.setAttribute('name', 'task');
}
}
// Update positions for a children of the moved item
rearrangeChildren(el);
};
// eslint-disable-next-line no-undef
dragula([container], {
// Y axis is considered when determining where an element would be dropped
direction: 'vertical',
// elements are moved by default, not copied
copy: false,
// elements in copy-source containers can be reordered
// copySortSource: true,
// spilling will put the element back where it was dragged from, if this is true
revertOnSpill: true,
// spilling will `.remove` the element, if this is true
// removeOnSpill: false,
accepts(el, target, source, sibling) {
if (isNested) {
if (sibling !== null) {
return sibling.dataset.draggableGroup && sibling.dataset.draggableGroup === el.dataset.draggableGroup;
}
return sibling === null || sibling && sibling.tagName.toLowerCase() === 'tr';
}
return sibling === null || sibling && sibling.tagName.toLowerCase() === 'tr';
},
mirrorContainer: container
}).on('drag', el => {
let rowSelector;
const groupId = el.dataset.draggableGroup;
if (groupId) {
rowSelector = `tr[data-draggable-group="${groupId}"]`;
} else {
rowSelector = 'tr';
}
const rowElements = [].slice.call(container.querySelectorAll(rowSelector));
dragElementIndex = rowElements.indexOf(el);
}).on('drop', el => {
saveTheOrder(el);
});
}

1
media/system/js/draggable.min.js vendored Normal file
View File

@ -0,0 +1 @@
let url,direction,isNested,dragElementIndex,dropElementIndex,form,formData,container=document.querySelector(".js-draggable");if(container)url=container.dataset.url,direction=container.dataset.direction,isNested=container.dataset.nested;else if(Joomla.getOptions("draggable-list")){const e=Joomla.getOptions("draggable-list");container=document.querySelector(e.id),container.classList.contains("js-draggable")||container.classList.add("js-draggable"),({url:url}=e),({direction:direction}=e),isNested=e.nested}if(container){form=container.closest("form"),formData=new FormData(form),formData.delete("task"),formData.delete("order[]"),document.addEventListener("touchstart",(()=>{}),!1);const e=(e,t,a,r)=>{let n;const o=[];if(a<r)for(e[r].value=e[r-1].value,n=a;n<r;n+=1)e[n].value="asc"===direction?parseInt(e[n].value,10)-1:parseInt(e[n].value,10)+1;else for(e[r].value=e[r+1].value,n=r+1;n<=a;n+=1)e[n].value="asc"===direction?parseInt(e[n].value,10)+1:parseInt(e[n].value,10)-1;for(n=0;n<e.length-1;n+=1)o.push(`order[]=${encodeURIComponent(e[n].value)}`),o.push(`cid[]=${encodeURIComponent(t[n].value)}`);return o},t=e=>{if(!e.dataset.itemId)return;const t=e.dataset.itemId,a=container.querySelectorAll(`tr[data-parents~="${t}"]`);a.length&&e.after(...a)},a=a=>{let r,n,o;const l=a.dataset.draggableGroup;l?(o=`tr[data-draggable-group="${l}"]`,r=`[data-draggable-group="${l}"] [name="order[]"]`,n=`[data-draggable-group="${l}"] [name="cid[]"]`):(o="tr",r='[name="order[]"]',n='[name="cid[]"]');const d=[].slice.call(container.querySelectorAll(o)),s=[].slice.call(container.querySelectorAll(r)),c=[].slice.call(container.querySelectorAll(n));if(dropElementIndex=d.indexOf(a),url){const t=document.querySelector('[name="task"]');t&&t.setAttribute("name","some__Temporary__Name__");const a={url:url,method:"POST",data:`${new URLSearchParams(formData).toString()}&${e(s,c,dragElementIndex,dropElementIndex).join("&")}`,perform:!0};Joomla.request(a),t&&t.setAttribute("name","task")}t(a)};dragula([container],{direction:"vertical",copy:!1,revertOnSpill:!0,accepts:(e,t,a,r)=>isNested&&null!==r?r.dataset.draggableGroup&&r.dataset.draggableGroup===e.dataset.draggableGroup:null===r||r&&"tr"===r.tagName.toLowerCase(),mirrorContainer:container}).on("drag",(e=>{let t;const a=e.dataset.draggableGroup;t=a?`tr[data-draggable-group="${a}"]`:"tr";const r=[].slice.call(container.querySelectorAll(t));dragElementIndex=r.indexOf(e)})).on("drop",(e=>{a(e)}))}

Binary file not shown.

View File

@ -0,0 +1,175 @@
import JoomlaEditorDecorator from 'editor-decorator';
export { default as JoomlaEditorDecorator } from 'editor-decorator';
/**
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
/**
* Editor API.
*/
const JoomlaEditor = {
/**
* Internal! The property should not be accessed directly.
*
* List of registered editors.
*/
instances: {},
/**
* Internal! The property should not be accessed directly.
*
* An active editor instance.
*/
active: null,
/**
* Register editor instance.
*
* @param {JoomlaEditorDecorator} editor The editor instance.
*
* @returns {JoomlaEditor}
*/
register(editor) {
if (!(editor instanceof JoomlaEditorDecorator)) {
throw new Error('Unexpected editor instance');
}
this.instances[editor.getId()] = editor;
// For backward compatibility
Joomla.editors.instances[editor.getId()] = editor;
return this;
},
/**
* Unregister editor instance.
*
* @param {JoomlaEditorDecorator|string} editor The editor instance or ID.
*
* @returns {JoomlaEditor}
*/
unregister(editor) {
let id;
if (editor instanceof JoomlaEditorDecorator) {
id = editor.getId();
} else if (typeof editor === 'string') {
id = editor;
} else {
throw new Error('Unexpected editor instance or identifier');
}
if (this.active && this.active === this.instances[id]) {
this.active = null;
}
delete this.instances[id];
// For backward compatibility
delete Joomla.editors.instances[id];
return this;
},
/**
* Return editor instance by ID.
*
* @param {String} id
*
* @returns {JoomlaEditorDecorator|boolean}
*/
get(id) {
return this.instances[id] || false;
},
/**
* Set currently active editor, the editor that in focus.
*
* @param {JoomlaEditorDecorator|string} editor The editor instance or ID.
*
* @returns {JoomlaEditor}
*/
setActive(editor) {
if (editor instanceof JoomlaEditorDecorator) {
this.active = editor;
} else if (this.instances[editor]) {
this.active = this.instances[editor];
} else {
throw new Error('The editor instance not found or it is incorrect');
}
return this;
},
/**
* Return active editor, if there exist eny.
*
* @returns {JoomlaEditorDecorator}
*/
getActive() {
return this.active;
}
};
/**
* Editor Buttons API.
*/
const JoomlaEditorButton = {
/**
* Internal! The property should not be accessed directly.
*
* A collection of button actions.
*/
actions: {},
/**
* Register new button action, or override existing.
*
* @param {String} name Action name
* @param {Function} handler Callback that will be executed.
*
* @returns {JoomlaEditorButton}
*/
registerAction(name, handler) {
if (!name || !handler) {
throw new Error('Missed values for Action registration');
}
if (!(handler instanceof Function)) {
throw new Error(`Unexpected handler for action "${name}", expecting Function`);
}
this.actions[name] = handler;
return this;
},
/**
* Get registered handler by action name.
*
* @param {String} name Action name
*
* @returns {Function|false}
*/
getActionHandler(name) {
return this.actions[name] || false;
},
/**
* Execute action.
*
* @param {String} name Action name
* @param {Object} options An options object
* @param {HTMLElement} button An optional element, that triggers the action
*
* @returns {*}
*/
runAction(name, options, button) {
const handler = this.getActionHandler(name);
let editor = JoomlaEditor.getActive();
if (!handler) {
throw new Error(`Handler for "${name}" action not found`);
}
// Try to find a legacy editor
// @TODO: Remove this section in Joomla 6
if (!editor && button) {
const parent = button.closest('fieldset, div:not(.editor-xtd-buttons)');
const textarea = parent ? parent.querySelector('textarea[id]') : false;
editor = textarea && Joomla.editors.instances[textarea.id] ? Joomla.editors.instances[textarea.id] : false;
if (editor) {
// eslint-disable-next-line no-console
console.warn('Legacy editors is deprecated. Set active editor instance with JoomlaEditor.setActive().');
}
}
if (!editor) {
throw new Error('An active editor are not available');
}
return handler(editor, options);
}
};
export { JoomlaEditor, JoomlaEditorButton };

View File

@ -0,0 +1 @@
import JoomlaEditorDecorator from"editor-decorator";export{default as JoomlaEditorDecorator}from"editor-decorator";const JoomlaEditor={instances:{},active:null,register(t){if(!(t instanceof JoomlaEditorDecorator))throw new Error("Unexpected editor instance");return this.instances[t.getId()]=t,Joomla.editors.instances[t.getId()]=t,this},unregister(t){let e;if(t instanceof JoomlaEditorDecorator)e=t.getId();else{if("string"!=typeof t)throw new Error("Unexpected editor instance or identifier");e=t}return this.active&&this.active===this.instances[e]&&(this.active=null),delete this.instances[e],delete Joomla.editors.instances[e],this},get(t){return this.instances[t]||!1},setActive(t){if(t instanceof JoomlaEditorDecorator)this.active=t;else{if(!this.instances[t])throw new Error("The editor instance not found or it is incorrect");this.active=this.instances[t]}return this},getActive(){return this.active}},JoomlaEditorButton={actions:{},registerAction(t,e){if(!t||!e)throw new Error("Missed values for Action registration");if(!(e instanceof Function))throw new Error(`Unexpected handler for action "${t}", expecting Function`);return this.actions[t]=e,this},getActionHandler(t){return this.actions[t]||!1},runAction(t,e,o){const i=this.getActionHandler(t);let r=JoomlaEditor.getActive();if(!i)throw new Error(`Handler for "${t}" action not found`);if(!r&&o){const t=o.closest("fieldset, div:not(.editor-xtd-buttons)"),e=!!t&&t.querySelector("textarea[id]");r=!(!e||!Joomla.editors.instances[e.id])&&Joomla.editors.instances[e.id],r&&console.warn("Legacy editors is deprecated. Set active editor instance with JoomlaEditor.setActive().")}if(!r)throw new Error("An active editor are not available");return i(r,e)}};export{JoomlaEditor,JoomlaEditorButton};

Binary file not shown.

View File

@ -0,0 +1,137 @@
/**
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
/**
* A decorator for Editor instance.
*/
class JoomlaEditorDecorator {
/**
* Internal! The property should not be accessed directly.
* The editor instance.
* @type {Object}
*/
// instance = null;
/**
* Internal! The property should not be accessed directly.
* The editor type/name, eg: tinymce, codemirror, none etc.
* @type {string}
*/
// type = '';
/**
* Internal! The property should not be accessed directly.
* HTML ID of the editor.
* @type {string}
*/
// id = '';
/**
* Class constructor.
*
* @param {Object} instance The editor instance
* @param {string} type The editor type/name
* @param {string} id The editor ID
*/
constructor(instance, type, id) {
if (!instance || !type || !id) {
throw new Error('Missed values for class constructor');
}
this.instance = instance;
this.type = type;
this.id = id;
}
/**
* Returns the editor instance object.
*
* @returns {Object}
*/
getRawInstance() {
return this.instance;
}
/**
* Returns the editor type/name.
*
* @returns {string}
*/
getType() {
return this.type;
}
/**
* Returns the editor id.
*
* @returns {string}
*/
getId() {
return this.id;
}
/**
* Return the complete data from the editor.
* Should be implemented by editor provider.
*
* @returns {string}
*/
// eslint-disable-next-line class-methods-use-this
getValue() {
throw new Error('Not implemented');
}
/**
* Replace the complete data of the editor
* Should be implemented by editor provider.
*
* @param {string} value Value to set.
*
* @returns {JoomlaEditorDecorator}
*/
// eslint-disable-next-line class-methods-use-this, no-unused-vars
setValue(value) {
throw new Error('Not implemented');
}
/**
* Return the selected text from the editor.
* Should be implemented by editor provider.
*
* @returns {string}
*/
// eslint-disable-next-line class-methods-use-this
getSelection() {
throw new Error('Not implemented');
}
/**
* Replace the selected text. If nothing selected, will insert the data at the cursor.
* Should be implemented by editor provider.
*
* @param {string} value
*
* @returns {JoomlaEditorDecorator}
*/
// eslint-disable-next-line class-methods-use-this, no-unused-vars
replaceSelection(value) {
throw new Error('Not implemented');
}
/**
* Toggles the editor disabled mode. When the editor is active then everything should be usable.
* When inactive the editor should be unusable AND disabled for form validation.
* Should be implemented by editor provider.
*
* @param {boolean} enable True to enable, false or undefined to disable.
*
* @returns {JoomlaEditorDecorator}
*/
// eslint-disable-next-line class-methods-use-this, no-unused-vars
disable(enable) {
throw new Error('Not implemented');
}
}
export { JoomlaEditorDecorator as default };

View File

@ -0,0 +1 @@
class JoomlaEditorDecorator{constructor(e,t,r){if(!e||!t||!r)throw new Error("Missed values for class constructor");this.instance=e,this.type=t,this.id=r}getRawInstance(){return this.instance}getType(){return this.type}getId(){return this.id}getValue(){throw new Error("Not implemented")}setValue(e){throw new Error("Not implemented")}getSelection(){throw new Error("Not implemented")}replaceSelection(e){throw new Error("Not implemented")}disable(e){throw new Error("Not implemented")}}export{JoomlaEditorDecorator as default};

Binary file not shown.

View File

@ -0,0 +1,102 @@
import { JoomlaEditorDecorator, JoomlaEditorButton } from 'editor-api';
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
*/
if (!window.Joomla) {
throw new Error('JoomlaEditors API require Joomla to be loaded.');
}
// === The code for keep backward compatibility ===
// Joomla.editors is deprecated use Joomla.Editor instead.
// @TODO: Remove this section in Joomla 6.
// Only define editors if not defined
Joomla.editors = Joomla.editors || {};
// An object to hold each editor instance on page, only define if not defined.
Joomla.editors.instances = new Proxy({}, {
set(target, p, editor) {
// eslint-disable-next-line no-use-before-define
if (!(editor instanceof JoomlaEditorDecorator)) {
// Add missed method in Legacy editor
editor.getId = () => p;
// eslint-disable-next-line no-console
console.warn('Legacy editors is deprecated. Register the editor instance with JoomlaEditor.register().', p, editor);
}
target[p] = editor;
return true;
},
get(target, p) {
// eslint-disable-next-line no-console
console.warn('Direct access to Joomla.editors.instances is deprecated. Use JoomlaEditor.getActive() or JoomlaEditor.get(id) to retrieve the editor instance.');
return target[p];
}
});
// === End of code for keep backward compatibility ===
// Register couple default actions for Editor Buttons
// Insert static content on cursor
JoomlaEditorButton.registerAction('insert', (editor, options) => {
const content = options.content || '';
editor.replaceSelection(content);
});
// Display modal dialog
JoomlaEditorButton.registerAction('modal', (editor, options) => {
if (options.src && options.src[0] !== '#' && options.src[0] !== '.') {
// Replace editor parameter to actual editor ID
const url = options.src.indexOf('http') === 0 ? new URL(options.src) : new URL(options.src, window.location.origin);
url.searchParams.set('editor', editor.getId());
if (url.searchParams.has('e_name')) {
url.searchParams.set('e_name', editor.getId());
}
options.src = url.toString();
}
// Create a dialog popup
const dialog = new JoomlaDialog(options);
// Listener for postMessage
const msgListener = event => {
// Avoid cross origins
if (event.origin !== window.location.origin) return;
// Check message type
if (event.data.messageType === 'joomla:content-select') {
editor.replaceSelection(event.data.html || event.data.text);
dialog.close();
} else if (event.data.messageType === 'joomla:cancel') {
dialog.close();
}
};
// Use a JoomlaExpectingPostMessage flag to be able to distinct legacy methods
// @TODO: This should be removed after full transition to postMessage()
window.JoomlaExpectingPostMessage = true;
window.addEventListener('message', msgListener);
// Clean up on close
dialog.addEventListener('joomla-dialog:close', () => {
delete window.JoomlaExpectingPostMessage;
window.removeEventListener('message', msgListener);
Joomla.Modal.setCurrent(null);
dialog.destroy();
});
Joomla.Modal.setCurrent(dialog);
// Show the popup
dialog.show();
});
// Listen to click on Editor button, and run action.
const btnDelegateSelector = '[data-joomla-editor-button-action]';
const btnActionDataAttr = 'joomlaEditorButtonAction';
const btnConfigDataAttr = 'joomlaEditorButtonOptions';
document.addEventListener('click', event => {
const btn = event.target.closest(btnDelegateSelector);
if (!btn) return;
const action = btn.dataset[btnActionDataAttr];
const options = btn.dataset[btnConfigDataAttr] ? JSON.parse(btn.dataset[btnConfigDataAttr]) : {};
if (action) {
JoomlaEditorButton.runAction(action, options, btn);
}
});

View File

@ -0,0 +1 @@
import{JoomlaEditorDecorator,JoomlaEditorButton}from"editor-api";import JoomlaDialog from"joomla.dialog";if(!window.Joomla)throw new Error("JoomlaEditors API require Joomla to be loaded.");Joomla.editors=Joomla.editors||{},Joomla.editors.instances=new Proxy({},{set:(o,t,e)=>(e instanceof JoomlaEditorDecorator||(e.getId=()=>t,console.warn("Legacy editors is deprecated. Register the editor instance with JoomlaEditor.register().",t,e)),o[t]=e,!0),get:(o,t)=>(console.warn("Direct access to Joomla.editors.instances is deprecated. Use JoomlaEditor.getActive() or JoomlaEditor.get(id) to retrieve the editor instance."),o[t])}),JoomlaEditorButton.registerAction("insert",((o,t)=>{const e=t.content||"";o.replaceSelection(e)})),JoomlaEditorButton.registerAction("modal",((o,t)=>{if(t.src&&"#"!==t.src[0]&&"."!==t.src[0]){const e=0===t.src.indexOf("http")?new URL(t.src):new URL(t.src,window.location.origin);e.searchParams.set("editor",o.getId()),e.searchParams.has("e_name")&&e.searchParams.set("e_name",o.getId()),t.src=e.toString()}const e=new JoomlaDialog(t),a=t=>{t.origin===window.location.origin&&("joomla:content-select"===t.data.messageType?(o.replaceSelection(t.data.html||t.data.text),e.close()):"joomla:cancel"===t.data.messageType&&e.close())};window.JoomlaExpectingPostMessage=!0,window.addEventListener("message",a),e.addEventListener("joomla-dialog:close",(()=>{delete window.JoomlaExpectingPostMessage,window.removeEventListener("message",a),Joomla.Modal.setCurrent(null),e.destroy()})),Joomla.Modal.setCurrent(e),e.show()}));const btnDelegateSelector="[data-joomla-editor-button-action]",btnActionDataAttr="joomlaEditorButtonAction",btnConfigDataAttr="joomlaEditorButtonOptions";document.addEventListener("click",(o=>{const t=o.target.closest(btnDelegateSelector);if(!t)return;const e=t.dataset[btnActionDataAttr],a=t.dataset[btnConfigDataAttr]?JSON.parse(t.dataset[btnConfigDataAttr]):{};e&&JoomlaEditorButton.runAction(e,a,t)}));

Binary file not shown.

View File

@ -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);

File diff suppressed because one or more lines are too long

View File

@ -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);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

View 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);

View 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);

Binary file not shown.

View 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);

File diff suppressed because one or more lines are too long

View 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;
}
});

File diff suppressed because one or more lines are too long

View 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);

File diff suppressed because one or more lines are too long

Binary file not shown.

View 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);
}
}
});
}
});

View 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)}})}});

View 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;
}
});

View 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}});

View 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);

View 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);

View 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);

File diff suppressed because one or more lines are too long

View 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);

File diff suppressed because one or more lines are too long

Binary file not shown.

View 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);

View 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);

Binary file not shown.

View 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, '&quot;'));
}
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, '&quot;'));
}
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, '&quot;'));
}
}
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);

File diff suppressed because one or more lines are too long

Binary file not shown.

View 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));

View 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)));

View 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;
};
}());

View 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}}();

Binary file not shown.

View 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);

View 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);

Binary file not shown.

View 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);

View 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);

Binary file not shown.

View 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);
})();

View 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)})();

Binary file not shown.

View 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
View 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()}}))}}));

Binary file not shown.

View 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);

File diff suppressed because one or more lines are too long

Binary file not shown.

1854
media/system/js/highlight.js Normal file

File diff suppressed because it is too large Load Diff

1
media/system/js/highlight.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,56 @@
/**
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
/**
* Toggles the display of inline help DIVs
*
* @param {String} toggleClass The class name of the DIVs to toggle display for
*/
Joomla.toggleInlineHelp = toggleClass => {
[].slice.call(document.querySelectorAll(`div.${toggleClass}`)).forEach(elDiv => {
// Toggle the visibility of the node by toggling the 'd-none' Bootstrap class.
elDiv.classList.toggle('d-none');
// The ID of the description whose visibility is toggled.
const myId = elDiv.id;
// The ID of the control described by this node (same ID, minus the '-desc' suffix).
const controlId = myId ? myId.substr(0, myId.length - 5) : null;
// Get the control described by this node.
const elControl = controlId ? document.getElementById(controlId) : null;
// Is this node hidden?
const isHidden = elDiv.classList.contains('d-none');
// If we do not have a control we will exit early
if (!controlId || !elControl) {
return;
}
// Unset the aria-describedby attribute in the control when the description is hidden and viceversa.
if (isHidden && elControl.hasAttribute('aria-describedby')) {
elControl.removeAttribute('aria-describedby');
} else if (!isHidden) {
elControl.setAttribute('aria-describedby', myId);
}
});
};
// Initialisation. Clicking on anything with the button-inlinehelp class will toggle the inline help.
[].slice.call(document.querySelectorAll('.button-inlinehelp')).forEach(elToggler => {
var _elToggler$dataset$cl;
// The class of the DIVs to toggle visibility on is defined by the data-class attribute of the click target.
const toggleClass = (_elToggler$dataset$cl = elToggler.dataset.class) != null ? _elToggler$dataset$cl : 'hide-aware-inline-help';
const collection = document.getElementsByClassName(toggleClass);
// no description => hide inlinehelp button
if (collection.length === 0) {
elToggler.classList.add('d-none');
return;
}
// Add the click handler.
elToggler.addEventListener('click', event => {
event.preventDefault();
Joomla.toggleInlineHelp(toggleClass);
});
});

1
media/system/js/inlinehelp.min.js vendored Normal file
View File

@ -0,0 +1 @@
Joomla.toggleInlineHelp=e=>{[].slice.call(document.querySelectorAll(`div.${e}`)).forEach((e=>{e.classList.toggle("d-none");const l=e.id,t=l?l.substr(0,l.length-5):null,n=t?document.getElementById(t):null,a=e.classList.contains("d-none");t&&n&&(a&&n.hasAttribute("aria-describedby")?n.removeAttribute("aria-describedby"):a||n.setAttribute("aria-describedby",l))}))},[].slice.call(document.querySelectorAll(".button-inlinehelp")).forEach((e=>{var l;const t=null!=(l=e.dataset.class)?l:"hide-aware-inline-help";0!==document.getElementsByClassName(t).length?e.addEventListener("click",(e=>{e.preventDefault(),Joomla.toggleInlineHelp(t)})):e.classList.add("d-none")}));

Binary file not shown.

View File

@ -0,0 +1,63 @@
(customElements => {
'strict';
/**
* Creates a custom element with the default spinner of the Joomla logo
*/
class JoomlaCoreLoader extends HTMLElement {
constructor() {
super();
const template = document.createElement('template');
template.innerHTML = `<style>:host{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;left:0;opacity:.8;overflow:hidden;position:fixed;top:0;width:100%;z-index:10000}.box{height:345px;margin:0 auto;position:relative;width:345px}.box p{color:#999;float:right;font:normal 1.25em/1em sans-serif;margin:95px 0 0}.box>span{-webkit-animation:jspinner 2s ease-in-out infinite;animation:jspinner 2s ease-in-out infinite}.box .red{-webkit-animation-delay:-1.5s;animation-delay:-1.5s}.box .blue{-webkit-animation-delay:-1s;animation-delay:-1s}.box .green{-webkit-animation-delay:-.5s;animation-delay:-.5s}.yellow{background:#f9a541;border-radius:90px;height:90px;width:90px}.yellow,.yellow:after,.yellow:before{content:"";left:0;position:absolute;top:0}.yellow:after,.yellow:before{background:transparent;border:50px solid #f9a541;-webkit-box-sizing:content-box;box-sizing:content-box;width:50px}.yellow:before{border-radius:75px 75px 0 0;border-width:50px 50px 0;height:35px;margin:60px 0 0 -30px}.yellow:after{border-width:0 0 0 50px;height:105px;margin:140px 0 0 -30px}.red{background:#f44321;border-radius:90px;height:90px;width:90px}.red,.red:after,.red:before{content:"";left:0;position:absolute;top:0}.red:after,.red:before{background:transparent;border:50px solid #f44321;-webkit-box-sizing:content-box;box-sizing:content-box;width:50px}.red:before{border-radius:75px 75px 0 0;border-width:50px 50px 0;height:35px;margin:60px 0 0 -30px}.red:after{border-width:0 0 0 50px;height:105px;margin:140px 0 0 -30px}.blue{background:#5091cd;border-radius:90px;height:90px;width:90px}.blue,.blue:after,.blue:before{content:"";left:0;position:absolute;top:0}.blue:after,.blue:before{background:transparent;border:50px solid #5091cd;-webkit-box-sizing:content-box;box-sizing:content-box;width:50px}.blue:before{border-radius:75px 75px 0 0;border-width:50px 50px 0;height:35px;margin:60px 0 0 -30px}.blue:after{border-width:0 0 0 50px;height:105px;margin:140px 0 0 -30px}.green{background:#7ac143;border-radius:90px;height:90px;width:90px}.green,.green:after,.green:before{content:"";left:0;position:absolute;top:0}.green:after,.green:before{background:transparent;border:50px solid #7ac143;-webkit-box-sizing:content-box;box-sizing:content-box;width:50px}.green:before{border-radius:75px 75px 0 0;border-width:50px 50px 0;height:35px;margin:60px 0 0 -30px}.green:after{border-width:0 0 0 50px;height:105px;margin:140px 0 0 -30px}.yellow{margin:0 0 0 255px;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.red{margin:255px 0 0 255px;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.blue{margin:255px 0 0;-webkit-transform:rotate(225deg);transform:rotate(225deg)}.green{-webkit-transform:rotate(315deg);transform:rotate(315deg)}@-webkit-keyframes jspinner{0%,40%,to{opacity:.3}20%{opacity:1}}@keyframes jspinner{0%,40%,to{opacity:.3}20%{opacity:1}}@media (prefers-reduced-motion:reduce){.box>span{-webkit-animation:none;animation:none}}</style>
<div>
<span class="yellow"></span>
<span class="red"></span>
<span class="blue"></span>
<span class="green"></span>
<p>&trade;</p>
</div>`;
// Patch the shadow DOM
if (window.ShadyCSS) {
window.ShadyCSS.prepareTemplate(template, 'joomla-core-loader');
}
this.attachShadow({
mode: 'open'
});
this.shadowRoot.appendChild(template.content.cloneNode(true));
// Patch the shadow DOM
if (window.ShadyCSS) {
window.ShadyCSS.styleElement(this);
}
}
connectedCallback() {
this.style.backgroundColor = this.color;
this.style.opacity = 0.8;
this.shadowRoot.querySelector('div').classList.add('box');
}
static get observedAttributes() {
return ['color'];
}
get color() {
return this.getAttribute('color') || '#fff';
}
set color(value) {
this.setAttribute('color', value);
}
attributeChangedCallback(attr, oldValue, newValue) {
switch (attr) {
case 'color':
if (newValue && newValue !== oldValue) {
this.style.backgroundColor = this.color;
}
break;
// Do nothing
}
}
}
if (!customElements.get('joomla-core-loader')) {
customElements.define('joomla-core-loader', JoomlaCoreLoader);
}
})(customElements);

View File

@ -0,0 +1 @@
(e=>{class t extends HTMLElement{constructor(){super();const e=document.createElement("template");e.innerHTML='<style>:host{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;height:100%;left:0;opacity:.8;overflow:hidden;position:fixed;top:0;width:100%;z-index:10000}.box{height:345px;margin:0 auto;position:relative;width:345px}.box p{color:#999;float:right;font:normal 1.25em/1em sans-serif;margin:95px 0 0}.box>span{-webkit-animation:jspinner 2s ease-in-out infinite;animation:jspinner 2s ease-in-out infinite}.box .red{-webkit-animation-delay:-1.5s;animation-delay:-1.5s}.box .blue{-webkit-animation-delay:-1s;animation-delay:-1s}.box .green{-webkit-animation-delay:-.5s;animation-delay:-.5s}.yellow{background:#f9a541;border-radius:90px;height:90px;width:90px}.yellow,.yellow:after,.yellow:before{content:"";left:0;position:absolute;top:0}.yellow:after,.yellow:before{background:transparent;border:50px solid #f9a541;-webkit-box-sizing:content-box;box-sizing:content-box;width:50px}.yellow:before{border-radius:75px 75px 0 0;border-width:50px 50px 0;height:35px;margin:60px 0 0 -30px}.yellow:after{border-width:0 0 0 50px;height:105px;margin:140px 0 0 -30px}.red{background:#f44321;border-radius:90px;height:90px;width:90px}.red,.red:after,.red:before{content:"";left:0;position:absolute;top:0}.red:after,.red:before{background:transparent;border:50px solid #f44321;-webkit-box-sizing:content-box;box-sizing:content-box;width:50px}.red:before{border-radius:75px 75px 0 0;border-width:50px 50px 0;height:35px;margin:60px 0 0 -30px}.red:after{border-width:0 0 0 50px;height:105px;margin:140px 0 0 -30px}.blue{background:#5091cd;border-radius:90px;height:90px;width:90px}.blue,.blue:after,.blue:before{content:"";left:0;position:absolute;top:0}.blue:after,.blue:before{background:transparent;border:50px solid #5091cd;-webkit-box-sizing:content-box;box-sizing:content-box;width:50px}.blue:before{border-radius:75px 75px 0 0;border-width:50px 50px 0;height:35px;margin:60px 0 0 -30px}.blue:after{border-width:0 0 0 50px;height:105px;margin:140px 0 0 -30px}.green{background:#7ac143;border-radius:90px;height:90px;width:90px}.green,.green:after,.green:before{content:"";left:0;position:absolute;top:0}.green:after,.green:before{background:transparent;border:50px solid #7ac143;-webkit-box-sizing:content-box;box-sizing:content-box;width:50px}.green:before{border-radius:75px 75px 0 0;border-width:50px 50px 0;height:35px;margin:60px 0 0 -30px}.green:after{border-width:0 0 0 50px;height:105px;margin:140px 0 0 -30px}.yellow{margin:0 0 0 255px;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.red{margin:255px 0 0 255px;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.blue{margin:255px 0 0;-webkit-transform:rotate(225deg);transform:rotate(225deg)}.green{-webkit-transform:rotate(315deg);transform:rotate(315deg)}@-webkit-keyframes jspinner{0%,40%,to{opacity:.3}20%{opacity:1}}@keyframes jspinner{0%,40%,to{opacity:.3}20%{opacity:1}}@media (prefers-reduced-motion:reduce){.box>span{-webkit-animation:none;animation:none}}</style>\n<div>\n <span class="yellow"></span>\n <span class="red"></span>\n <span class="blue"></span>\n <span class="green"></span>\n <p>&trade;</p>\n</div>',window.ShadyCSS&&window.ShadyCSS.prepareTemplate(e,"joomla-core-loader"),this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(e.content.cloneNode(!0)),window.ShadyCSS&&window.ShadyCSS.styleElement(this)}connectedCallback(){this.style.backgroundColor=this.color,this.style.opacity=.8,this.shadowRoot.querySelector("div").classList.add("box")}static get observedAttributes(){return["color"]}get color(){return this.getAttribute("color")||"#fff"}set color(e){this.setAttribute("color",e)}attributeChangedCallback(e,t,o){if("color"===e)o&&o!==t&&(this.style.backgroundColor=this.color)}}e.get("joomla-core-loader")||e.define("joomla-core-loader",t)})(customElements);

Binary file not shown.

View File

@ -0,0 +1,59 @@
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
*/
/**
* Auto create a popup dynamically on click, eg:
*
* <button type="button" data-joomla-dialog='{"popupType": "iframe", "src": "content/url.html"}'>Click</button>
* <button type="button" data-joomla-dialog='{"popupType": "inline", "popupContent": "#id-of-content-element"}'>Click</button>
* <a href="content/url.html" data-joomla-dialog>Click</a>
*/
const delegateSelector = '[data-joomla-dialog]';
const configDataAttr = 'joomlaDialog';
const configCacheFlag = 'joomlaDialogCache';
document.addEventListener('click', event => {
const triggerEl = event.target.closest(delegateSelector);
if (!triggerEl) return;
event.preventDefault();
// Check for cached instance
const cacheable = !!triggerEl.dataset[configCacheFlag];
if (cacheable && triggerEl.JoomlaDialogInstance) {
Joomla.Modal.setCurrent(triggerEl.JoomlaDialogInstance);
triggerEl.JoomlaDialogInstance.show();
return;
}
// Parse config
const config = triggerEl.dataset[configDataAttr] ? JSON.parse(triggerEl.dataset[configDataAttr]) : {};
// Check if the click is on anchor
if (triggerEl.nodeName === 'A') {
if (!config.popupType) {
config.popupType = triggerEl.hash ? 'inline' : 'iframe';
}
if (!config.src && config.popupType === 'iframe') {
config.src = triggerEl.href;
} else if (!config.src && config.popupType === 'inline') {
config.src = triggerEl.hash;
}
}
// Template not allowed here
delete config.popupTemplate;
const popup = new JoomlaDialog(config);
if (cacheable) {
triggerEl.JoomlaDialogInstance = popup;
}
popup.addEventListener('joomla-dialog:close', () => {
Joomla.Modal.setCurrent(null);
if (!cacheable) {
popup.destroy();
}
});
Joomla.Modal.setCurrent(popup);
popup.show();
});

View File

@ -0,0 +1 @@
import JoomlaDialog from"joomla.dialog";const delegateSelector="[data-joomla-dialog]",configDataAttr="joomlaDialog",configCacheFlag="joomlaDialogCache";document.addEventListener("click",(o=>{const a=o.target.closest(delegateSelector);if(!a)return;o.preventDefault();const e=!!a.dataset[configCacheFlag];if(e&&a.JoomlaDialogInstance)return Joomla.Modal.setCurrent(a.JoomlaDialogInstance),void a.JoomlaDialogInstance.show();const l=a.dataset.joomlaDialog?JSON.parse(a.dataset.joomlaDialog):{};"A"===a.nodeName&&(l.popupType||(l.popupType=a.hash?"inline":"iframe"),l.src||"iframe"!==l.popupType?l.src||"inline"!==l.popupType||(l.src=a.hash):l.src=a.href),delete l.popupTemplate;const t=new JoomlaDialog(l);e&&(a.JoomlaDialogInstance=t),t.addEventListener("joomla-dialog:close",(()=>{Joomla.Modal.setCurrent(null),e||t.destroy()})),Joomla.Modal.setCurrent(t),t.show()}));

Binary file not shown.

View File

@ -0,0 +1,614 @@
/**
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// Default template for the popup
const popupTemplate = `<div class="joomla-dialog-container">
<header class="joomla-dialog-header"></header>
<section class="joomla-dialog-body"></section>
<footer class="joomla-dialog-footer"></footer>
</div>`;
/**
* JoomlaDialog class for Joomla Dialog implementation.
* With use of <joomla-dialog> custom element as dialog holder.
*/
class JoomlaDialog extends HTMLElement {
/**
* The popup type, supported: inline, iframe, image, ajax.
* @type {string}
*/
// popupType = 'inline';
/**
* An optional text for header.
* @type {string}
*/
// textHeader = '';
/**
* An optional text for close button. Applied when no Buttons provided.
* @type {string}
*/
// textClose = 'Close';
/**
* Content string (html) for inline type popup.
* @type {string}
*/
// popupContent = '';
/**
* Source path for iframe, image, ajax.
* @type {string}
*/
// src = '';
/**
* An optional list of buttons, to be rendered in footer or header, or bottom or top of the popup body.
* Example:
* [{label: 'Yes', onClick: () => popup.close()},
* {label: 'No', onClick: () => popup.close(), className: 'btn btn-danger'},
* {label: 'Click me', onClick: () => popup.close(), location: 'header'}]
* @type {[]}
*/
// popupButtons = [];
/**
* Whether popup can be closed by Esc button.
*
* @type {boolean}
*/
// cancelable = true;
/**
* An optional limit for the popup width, any valid CSS value.
* @type {string}
*/
// width = '';
/**
* An optional height for the popup, any valid CSS value.
* @type {string}
*/
// height = '';
/**
* An optional Class names for header icon.
*
* @type {string}
*/
// iconHeader = '';
/**
* A template for the popup.
* @type {string|HTMLTemplateElement}
*/
// popupTemplate = popupTemplate;
/**
* The element where to attach the dialog, for cases when no parentElement exist, see show().
* This allows to keep the dialog in the same branch of DOM as the popupContent.
* @type {string|HTMLElement}
*/
// preferredParent = null;
/**
* Class constructor
* @param {Object} config
*/
constructor(config) {
super();
// Define default params (doing it here because browser support of public props)
this.popupType = this.getAttribute('type') || 'inline';
this.textHeader = this.getAttribute('text-header') || '';
this.iconHeader = '';
this.textClose = 'Close';
this.popupContent = '';
this.src = this.getAttribute('src') || '';
this.popupButtons = [];
this.cancelable = !this.hasAttribute('not-cancelable');
this.width = this.getAttribute('width') || '';
this.height = this.getAttribute('height') || '';
this.popupTemplate = popupTemplate;
this.preferredParent = null;
// @internal. Parent of the popupContent for cases when it is HTMLElement. Need for recovery on destroy().
this.popupContentSrcLocation = null;
if (!config) return;
// Check configurable properties
['popupType', 'textHeader', 'textClose', 'popupContent', 'src', 'popupButtons', 'cancelable', 'width', 'height', 'popupTemplate', 'iconHeader', 'id', 'preferredParent'].forEach(key => {
if (config[key] !== undefined) {
this[key] = config[key];
}
});
if (config.className) {
this.classList.add(...config.className.split(' '));
}
}
/**
* Internal. Connected Callback.
*/
connectedCallback() {
this.renderLayout();
}
/**
* Internal. Render a main layout, based on given template.
* @returns {JoomlaDialog}
*/
renderLayout() {
if (this.dialog) return this;
// On close callback
const onClose = () => {
this.dispatchEvent(new CustomEvent('joomla-dialog:close', {
bubbles: true
}));
};
const onCancel = event => {
if (!this.cancelable) {
// Prevent closing by Esc
event.preventDefault();
}
};
// Check for existing layout
if (this.firstElementChild && this.firstElementChild.nodeName === 'DIALOG') {
this.dialog = this.firstElementChild;
this.dialog.addEventListener('cancel', onCancel);
this.dialog.addEventListener('close', onClose);
this.popupTmplB = this.querySelector('.joomla-dialog-body') || this.dialog;
this.popupContentElement = this.popupTmplB;
return this;
}
// Render a template
let templateContent;
if (this.popupTemplate.tagName && this.popupTemplate.tagName === 'TEMPLATE') {
templateContent = this.popupTemplate.content.cloneNode(true);
} else {
const template = document.createElement('template');
template.innerHTML = this.popupTemplate;
templateContent = template.content;
}
this.dialog = document.createElement('dialog');
this.dialog.appendChild(templateContent);
this.dialog.addEventListener('cancel', onCancel);
this.dialog.addEventListener('close', onClose);
this.appendChild(this.dialog);
// Get template parts
this.popupTmplH = this.dialog.querySelector('.joomla-dialog-header');
this.popupTmplB = this.dialog.querySelector('.joomla-dialog-body');
this.popupTmplF = this.dialog.querySelector('.joomla-dialog-footer');
this.popupContentElement = null;
if (!this.popupTmplB) {
throw new Error('The popup body not found in the template.');
}
// Set the header
if (this.popupTmplH && this.textHeader) {
const h = document.createElement('h3');
h.insertAdjacentHTML('afterbegin', this.textHeader);
this.popupTmplH.insertAdjacentElement('afterbegin', h);
if (this.iconHeader) {
const i = document.createElement('span');
i.ariaHidden = true;
i.classList.add('header-icon');
i.classList.add(...this.iconHeader.split(' '));
this.popupTmplH.insertAdjacentElement('afterbegin', i);
}
}
// Set the body
this.renderBodyContent();
this.setAttribute('type', this.popupType);
// Create buttons if any
const buttons = this.popupButtons || [];
// Add at least one button to close the popup
if (!buttons.length) {
buttons.push({
label: '',
ariaLabel: this.textClose,
className: 'button-close btn-close',
data: {
buttonClose: ''
},
onClick: () => this.close(),
location: 'header'
});
}
// Buttons holders
const btnHHolder = document.createElement('div');
const btnFHolder = document.createElement('div');
btnHHolder.classList.add('buttons-holder');
btnFHolder.classList.add('buttons-holder');
this.popupButtons.forEach(btnData => {
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = btnData.label || '';
btn.ariaLabel = btnData.ariaLabel || null;
if (btnData.className) {
btn.classList.add(...btnData.className.split(' '));
} else {
btn.classList.add('button', 'button-primary', 'btn', 'btn-primary');
}
if (btnData.data) {
Object.entries(btnData.data).forEach(([k, v]) => {
btn.dataset[k] = v;
});
if (btnData.data.dialogClose !== undefined) {
btnData.onClick = () => this.close();
}
if (btnData.data.dialogDestroy !== undefined) {
btnData.onClick = () => this.destroy();
}
}
if (btnData.onClick) {
btn.addEventListener('click', btnData.onClick);
}
if (btnData.location === 'header') {
btnHHolder.appendChild(btn);
} else {
btnFHolder.appendChild(btn);
}
});
if (btnHHolder.children.length) {
if (this.popupType === 'image' && !this.textHeader) {
this.popupTmplB.insertAdjacentElement('afterbegin', btnHHolder);
} else if (this.popupTmplH) {
this.popupTmplH.insertAdjacentElement('beforeend', btnHHolder);
} else {
this.popupTmplB.insertAdjacentElement('afterbegin', btnHHolder);
}
}
if (btnFHolder.children.length) {
(this.popupTmplF || this.popupTmplB).insertAdjacentElement('beforeend', btnFHolder);
}
// Adjust the sizes if requested
if (this.width) {
this.dialog.style.width = '100%';
this.dialog.style.maxWidth = this.width;
}
if (this.height) {
this.dialog.style.height = this.height;
}
// Mark an empty template elements
if (this.popupTmplH && !this.popupTmplH.children.length) {
this.popupTmplH.classList.add('empty');
}
if (this.popupTmplF && !this.popupTmplF.children.length) {
this.popupTmplF.classList.add('empty');
}
return this;
}
/**
* Internal. Render the body content, based on popupType.
* @returns {JoomlaDialog}
*/
renderBodyContent() {
if (!this.popupTmplB || this.popupContentElement) return this;
// Callback for loaded content event listener
const onLoad = () => {
this.classList.add('loaded');
this.classList.remove('loading');
this.popupContentElement.removeEventListener('load', onLoad);
this.dispatchEvent(new CustomEvent('joomla-dialog:load'));
if (this.popupType === 'inline' || this.popupType === 'ajax') {
// Dispatch joomla:updated for inline content
this.popupContentElement.dispatchEvent(new CustomEvent('joomla:updated', {
bubbles: true,
cancelable: true
}));
}
};
this.classList.add('loading');
switch (this.popupType) {
// Create an Inline content
case 'inline':
{
let inlineContent = this.popupContent;
// Check for content selector: src: '#content-selector' or src: '.content-selector'
if (!inlineContent && this.src && (this.src[0] === '.' || this.src[0] === '#')) {
inlineContent = document.querySelector(this.src);
this.popupContent = inlineContent;
}
if (inlineContent instanceof HTMLElement) {
// Render content provided as HTMLElement
if (inlineContent.nodeName === 'TEMPLATE') {
this.popupTmplB.appendChild(inlineContent.content.cloneNode(true));
} else {
// Store parent reference to be able to recover after the popup is destroyed
this.popupContentSrcLocation = {
parent: inlineContent.parentElement,
nextSibling: inlineContent.nextSibling
};
this.popupTmplB.appendChild(inlineContent);
}
} else {
// Render content string
this.popupTmplB.insertAdjacentHTML('afterbegin', Joomla.sanitizeHtml(inlineContent));
}
this.popupContentElement = this.popupTmplB;
onLoad();
break;
}
// Create an IFrame content
case 'iframe':
{
const frame = document.createElement('iframe');
frame.addEventListener('load', onLoad);
frame.src = this.src;
// Enlarge default size of iframe, make sure it is usable without extra styling
frame.width = '100%';
frame.height = '720';
if (!this.width) {
frame.style.maxWidth = '100%';
frame.width = '1024';
}
frame.classList.add('iframe-content');
this.popupContentElement = frame;
this.popupTmplB.appendChild(frame);
break;
}
// Create an Image content
case 'image':
{
const img = document.createElement('img');
img.addEventListener('load', onLoad);
img.src = this.src;
this.popupContentElement = img;
this.popupTmplB.appendChild(img);
break;
}
// Create an AJAX content
case 'ajax':
{
fetch(this.src).then(response => {
if (response.status !== 200) {
throw new Error(response.statusText);
}
return response.text();
}).then(text => {
this.popupTmplB.insertAdjacentHTML('afterbegin', Joomla.sanitizeHtml(text));
this.popupContentElement = this.popupTmplB;
onLoad();
}).catch(error => {
throw error;
});
break;
}
default:
{
throw new Error('Unknown popup type requested');
}
}
return this;
}
/**
* Internal. Find an Element to be used as parent element,
* for cases when Dialog does not have one already. See show().
*
* @returns {HTMLElement|boolean}
*/
findPreferredParent() {
let parent;
if (this.preferredParent instanceof HTMLElement) {
// We have configured one already
parent = this.preferredParent;
} else if (this.preferredParent) {
// Query Document
parent = document.querySelector(this.preferredParent);
} else if (this.popupType === 'inline') {
// Pick the parent element of the Content
let inlineContent = this.popupContent;
// Check for content selector: src: '#content-selector' or src: '.content-selector'
if (!inlineContent && this.src && (this.src[0] === '.' || this.src[0] === '#')) {
inlineContent = document.querySelector(this.src);
parent = inlineContent ? inlineContent.parentElement : false;
}
}
return parent || false;
}
/**
* Return the popup header element.
* @returns {HTMLElement|boolean}
*/
getHeader() {
this.renderLayout();
return this.popupTmplH || false;
}
/**
* Return the popup body element.
* @returns {HTMLElement}
*/
getBody() {
this.renderLayout();
return this.popupTmplB;
}
/**
* Return the popup content element, or body for inline and ajax types.
* @returns {HTMLElement}
*/
getBodyContent() {
this.renderLayout();
return this.popupContentElement || this.popupTmplB;
}
/**
* Return the popup footer element.
* @returns {HTMLElement|boolean}
*/
getFooter() {
this.renderLayout();
return this.popupTmplF || false;
}
/**
* Open the popup as modal window.
* Will append the element to Document body if not appended before.
*
* @returns {JoomlaDialog}
*/
show() {
// Check whether the element already attached to DOM
if (!this.parentElement) {
// Check for preferred parent to attach to DOM
const parent = this.findPreferredParent();
(parent || document.body).appendChild(this);
}
this.dialog.showModal();
this.dispatchEvent(new CustomEvent('joomla-dialog:open', {
bubbles: true
}));
return this;
}
/**
* Alias for show() method.
* @returns {JoomlaDialog}
*/
open() {
return this.show();
}
/**
* Closes the popup
*
* @returns {JoomlaDialog}
*/
close() {
if (!this.dialog) {
throw new Error('Calling close for non opened dialog is discouraged.');
}
this.dialog.close();
return this;
}
/**
* Alias for close() method.
* @returns {JoomlaDialog}
*/
hide() {
return this.close();
}
/**
* Destroys the popup.
*/
destroy() {
if (!this.dialog) {
return;
}
this.dialog.close();
this.removeChild(this.dialog);
this.parentElement.removeChild(this);
// Restore original location of the popup content element
if (this.popupContentSrcLocation && this.popupContent) {
const {
parent,
nextSibling
} = this.popupContentSrcLocation;
parent.insertBefore(this.popupContent, nextSibling);
}
this.dialog = null;
this.popupTmplH = null;
this.popupTmplB = null;
this.popupTmplF = null;
this.popupContentElement = null;
this.popupContentSrcLocation = null;
}
/**
* Helper method to show an Alert.
*
* @param {String} message
* @param {String} title
* @returns {Promise}
*/
static alert(message, title) {
return new Promise(resolve => {
const popup = new this();
popup.popupType = 'inline';
popup.popupContent = message;
popup.textHeader = title || Joomla.Text._('INFO', 'Info');
popup.popupButtons = [{
label: Joomla.Text._('JOK', 'Okay'),
data: {
buttonOk: ''
},
onClick: () => popup.close()
}];
popup.classList.add('joomla-dialog-alert');
popup.addEventListener('joomla-dialog:close', () => {
popup.destroy();
resolve();
});
popup.show();
});
}
/**
* Helper method to show a Confirmation popup.
*
* @param {String} message
* @param {String} title
* @returns {Promise}
*/
static confirm(message, title) {
return new Promise(resolve => {
let result = false;
const popup = new this();
popup.popupType = 'inline';
popup.popupContent = message;
popup.textHeader = title || Joomla.Text._('INFO', 'Info');
popup.popupButtons = [{
label: Joomla.Text._('JYES', 'Yes'),
data: {
buttonOk: ''
},
onClick: () => {
result = true;
popup.destroy();
}
}, {
label: Joomla.Text._('JNO', 'No'),
data: {
buttonCancel: ''
},
onClick: () => {
result = false;
popup.destroy();
},
className: 'button button-secondary btn btn-outline-secondary'
}];
popup.cancelable = false;
popup.classList.add('joomla-dialog-confirm');
popup.addEventListener('joomla-dialog:close', () => resolve(result));
popup.show();
});
}
}
customElements.define('joomla-dialog', JoomlaDialog);
export { JoomlaDialog as default };

1
media/system/js/joomla-dialog.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,67 @@
/**
* @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-hidden-mail', class extends HTMLElement {
constructor() {
super();
this.newElement = '';
this.base = '';
}
/**
* Lifecycle
*/
disconnectedCallback() {
this.innerHTML = '';
}
/**
* Lifecycle
*/
connectedCallback() {
this.base = `${this.getAttribute('base')}/`;
if (this.getAttribute('is-link') === '1') {
this.newElement = document.createElement('a');
this.newElement.setAttribute('href', `mailto:${this.constructor.b64DecodeUnicode(this.getAttribute('first'))}@${this.constructor.b64DecodeUnicode(this.getAttribute('last'))}`);
// Get all of the original element attributes, and pass them to the link
[].slice.call(this.attributes).forEach((attribute, index) => {
const {
nodeName
} = this.attributes.item(index);
if (nodeName) {
// We do care for some attributes
if (['is-link', 'is-email', 'first', 'last', 'text'].indexOf(nodeName) === -1) {
const {
nodeValue
} = this.attributes.item(index);
this.newElement.setAttribute(nodeName, nodeValue);
}
}
});
} else {
this.newElement = document.createElement('span');
}
if (this.getAttribute('text')) {
let innerStr = this.constructor.b64DecodeUnicode(this.getAttribute('text'));
innerStr = innerStr.replace('src="images/', `src="${this.base}images/`).replace('src="media/', `src="${this.base}media/`);
this.newElement.innerHTML = Joomla.sanitizeHtml(innerStr);
} else {
this.newElement.innerText = `${window.atob(this.getAttribute('first'))}@${window.atob(this.getAttribute('last'))}`;
}
// Remove class and style Attributes
this.removeAttribute('class');
this.removeAttribute('style');
// Remove the noscript message
this.innerText = '';
// Display the new element
this.appendChild(this.newElement);
}
static b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(atob(str), c => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`).join(''));
}
});

View File

@ -0,0 +1 @@
window.customElements.define("joomla-hidden-mail",class extends HTMLElement{constructor(){super(),this.newElement="",this.base=""}disconnectedCallback(){this.innerHTML=""}connectedCallback(){if(this.base=`${this.getAttribute("base")}/`,"1"===this.getAttribute("is-link")?(this.newElement=document.createElement("a"),this.newElement.setAttribute("href",`mailto:${this.constructor.b64DecodeUnicode(this.getAttribute("first"))}@${this.constructor.b64DecodeUnicode(this.getAttribute("last"))}`),[].slice.call(this.attributes).forEach(((t,e)=>{const{nodeName:i}=this.attributes.item(e);if(i&&-1===["is-link","is-email","first","last","text"].indexOf(i)){const{nodeValue:t}=this.attributes.item(e);this.newElement.setAttribute(i,t)}}))):this.newElement=document.createElement("span"),this.getAttribute("text")){let t=this.constructor.b64DecodeUnicode(this.getAttribute("text"));t=t.replace('src="images/',`src="${this.base}images/`).replace('src="media/',`src="${this.base}media/`),this.newElement.innerHTML=Joomla.sanitizeHtml(t)}else this.newElement.innerText=`${window.atob(this.getAttribute("first"))}@${window.atob(this.getAttribute("last"))}`;this.removeAttribute("class"),this.removeAttribute("style"),this.innerText="",this.appendChild(this.newElement)}static b64DecodeUnicode(t){return decodeURIComponent(Array.prototype.map.call(atob(t),(t=>`%${`00${t.charCodeAt(0).toString(16)}`.slice(-2)}`)).join(""))}});

Binary file not shown.

View File

@ -0,0 +1,124 @@
/**
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
window.customElements.define('joomla-toolbar-button', class extends HTMLElement {
// Attribute getters
get task() {
return this.getAttribute('task');
}
get listSelection() {
return this.hasAttribute('list-selection');
}
get form() {
return this.getAttribute('form');
}
get formValidation() {
return this.hasAttribute('form-validation');
}
get confirmMessage() {
return this.getAttribute('confirm-message');
}
/**
* Lifecycle
*/
constructor() {
super();
if (!Joomla) {
throw new Error('Joomla API is not properly initiated');
}
this.confirmationReceived = false;
this.onChange = this.onChange.bind(this);
this.executeTask = this.executeTask.bind(this);
}
/**
* Lifecycle
*/
connectedCallback() {
// We need a button to support button behavior,
// because we cannot currently extend HTMLButtonElement
this.buttonElement = this.querySelector('button, a');
this.buttonElement.addEventListener('click', this.executeTask);
// Check whether we have a form
const formSelector = this.form || 'adminForm';
this.formElement = document.getElementById(formSelector);
this.disabled = false;
// If list selection is required, set button to disabled by default
if (this.listSelection) {
this.setDisabled(true);
}
if (this.listSelection) {
if (!this.formElement) {
throw new Error(`The form "${formSelector}" is required to perform the task, but the form was not found on the page.`);
}
// Watch on list selection
this.formElement.boxchecked.addEventListener('change', this.onChange);
}
}
/**
* Lifecycle
*/
disconnectedCallback() {
if (this.formElement.boxchecked) {
this.formElement.boxchecked.removeEventListener('change', this.onChange);
}
this.buttonElement.removeEventListener('click', this.executeTask);
}
onChange({
target
}) {
// Check whether we have selected something
this.setDisabled(target.value < 1);
}
setDisabled(disabled) {
// Make sure we have a boolean value
this.disabled = !!disabled;
// Switch attribute for native element
// An anchor does not support "disabled" attribute, so use class
if (this.buttonElement) {
if (this.disabled) {
if (this.buttonElement.nodeName === 'BUTTON') {
this.buttonElement.disabled = true;
} else {
this.buttonElement.classList.add('disabled');
}
} else if (this.buttonElement.nodeName === 'BUTTON') {
this.buttonElement.disabled = false;
} else {
this.buttonElement.classList.remove('disabled');
}
}
}
executeTask() {
if (this.disabled) {
return false;
}
// Ask for User confirmation when needed
if (this.confirmMessage && !this.confirmationReceived) {
// eslint-disable-next-line import/no-unresolved,no-undef
import('joomla.dialog').then(m => m.default.confirm(this.confirmMessage, Joomla.Text._('WARNING', 'Warning'))).then(confirmed => {
if (confirmed) {
// Set confirmation flag, and emulate the click again
this.confirmationReceived = true;
this.buttonElement.click();
}
});
return false;
}
// Reset any previous confirmation
this.confirmationReceived = false;
if (this.task) {
Joomla.submitbutton(this.task, this.form, this.formValidation);
}
return true;
}
});

View File

@ -0,0 +1 @@
window.customElements.define("joomla-toolbar-button",class extends HTMLElement{get task(){return this.getAttribute("task")}get listSelection(){return this.hasAttribute("list-selection")}get form(){return this.getAttribute("form")}get formValidation(){return this.hasAttribute("form-validation")}get confirmMessage(){return this.getAttribute("confirm-message")}constructor(){if(super(),!Joomla)throw new Error("Joomla API is not properly initiated");this.confirmationReceived=!1,this.onChange=this.onChange.bind(this),this.executeTask=this.executeTask.bind(this)}connectedCallback(){this.buttonElement=this.querySelector("button, a"),this.buttonElement.addEventListener("click",this.executeTask);const t=this.form||"adminForm";if(this.formElement=document.getElementById(t),this.disabled=!1,this.listSelection&&this.setDisabled(!0),this.listSelection){if(!this.formElement)throw new Error(`The form "${t}" is required to perform the task, but the form was not found on the page.`);this.formElement.boxchecked.addEventListener("change",this.onChange)}}disconnectedCallback(){this.formElement.boxchecked&&this.formElement.boxchecked.removeEventListener("change",this.onChange),this.buttonElement.removeEventListener("click",this.executeTask)}onChange({target:t}){this.setDisabled(t.value<1)}setDisabled(t){this.disabled=!!t,this.buttonElement&&(this.disabled?"BUTTON"===this.buttonElement.nodeName?this.buttonElement.disabled=!0:this.buttonElement.classList.add("disabled"):"BUTTON"===this.buttonElement.nodeName?this.buttonElement.disabled=!1:this.buttonElement.classList.remove("disabled"))}executeTask(){return!this.disabled&&(this.confirmMessage&&!this.confirmationReceived?(import("joomla.dialog").then((t=>t.default.confirm(this.confirmMessage,Joomla.Text._("WARNING","Warning")))).then((t=>{t&&(this.confirmationReceived=!0,this.buttonElement.click())})),!1):(this.confirmationReceived=!1,this.task&&Joomla.submitbutton(this.task,this.form,this.formValidation),!0))}});

Binary file not shown.

View File

@ -0,0 +1,28 @@
/**
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
/**
* Keepalive javascript behavior
*
* Used for keeping the session alive
*
* @package Joomla.JavaScript
* @since 3.7.0
*/
if (!window.Joomla) {
throw new Error('Joomla API was not properly initialised');
}
const keepAliveOptions = Joomla.getOptions('system.keepalive');
const keepAliveInterval = keepAliveOptions && keepAliveOptions.interval ? parseInt(keepAliveOptions.interval, 10) : 45 * 1000;
let keepAliveUri = keepAliveOptions && keepAliveOptions.uri ? keepAliveOptions.uri.replace(/&amp;/g, '&') : '';
// Fallback in case no keepalive uri was found.
if (keepAliveUri === '') {
const systemPaths = Joomla.getOptions('system.paths');
keepAliveUri = `${systemPaths ? `${systemPaths.root}/index.php` : window.location.pathname}?option=com_ajax&format=json`;
}
setInterval(() => fetch(keepAliveUri, {
method: 'POST'
}), keepAliveInterval);

Some files were not shown because too many files have changed in this diff Show More