primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

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

@ -0,0 +1,793 @@
(function () {
'use strict';
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, 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;
}
/**
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
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 || {};
// 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 = Joomla.editors.instances || {
/**
* *****************************************************************
* All Editors MUST register, per instance, the following callbacks:
* *****************************************************************
*
* getValue Type Function Should return the complete data from the editor
* Example: () => { return this.element.value; }
* setValue Type Function Should replace the complete data of the editor
* Example: (text) => { return this.element.value = text; }
* getSelection Type Function Should return the selected text from the editor
* Example: function () { return this.selectedText; }
* disable Type Function Toggles the editor into disabled mode. When the editor is
* active then everything should be usable. When inactive the
* editor should be unusable AND disabled for form validation
* Example: (bool) => { return this.disable = value; }
* replaceSelection Type Function Should replace the selected text of the editor
* If nothing selected, will insert the data at the cursor
* Example:
* (text) => {
* return insertAtCursor(this.element, text);
* }
*
* USAGE (assuming that jform_articletext is the textarea id)
* {
* To get the current editor value:
* Joomla.editors.instances['jform_articletext'].getValue();
* To set the current editor value:
* Joomla.editors.instances['jform_articletext'].setValue('Joomla! rocks');
* To replace(selection) or insert a value at the current editor cursor (replaces the J3
* jInsertEditorText API):
* replaceSelection:
* Joomla.editors.instances['jform_articletext'].replaceSelection('Joomla! rocks')
* }
*
* *********************************************************
* ANY INTERACTION WITH THE EDITORS SHOULD USE THE ABOVE API
* *********************************************************
*/
};
Joomla.Modal = 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 => {
Joomla.Modal.current = element;
},
getCurrent: () => Joomla.Modal.current
};
/**
* 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 = {};
}
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) {
let counter = 0;
document.querySelectorAll('.joomla-script-options.new').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
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 => {
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;
}
document.querySelectorAll('input[type="hidden"]').forEach(element => {
if (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) {
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;
};
})();

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

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

@ -0,0 +1,4 @@
/**
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/let url,direction,isNested,dragElementIndex,dropElementIndex,container=document.querySelector(".js-draggable"),form,formData;if(container)url=container.dataset.url,direction=container.dataset.direction,isNested=container.dataset.nested;else if(Joomla.getOptions("draggable-list")){const o=Joomla.getOptions("draggable-list");container=document.querySelector(o.id),container.classList.contains("js-draggable")||container.classList.add("js-draggable"),{url}=o,{direction}=o,isNested=o.nested}if(container){form=container.closest("form"),formData=new FormData(form),formData.delete("task"),formData.delete("order[]"),document.addEventListener("touchstart",()=>{},!1);const o=(e,l,r,a)=>{let t;const s=[];if(r<a)for(e[a].value=e[a-1].value,t=r;t<a;t+=1)direction==="asc"?e[t].value=parseInt(e[t].value,10)-1:e[t].value=parseInt(e[t].value,10)+1;else for(e[a].value=e[a+1].value,t=a+1;t<=r;t+=1)direction==="asc"?e[t].value=parseInt(e[t].value,10)+1:e[t].value=parseInt(e[t].value,10)-1;for(t=0;t<e.length-1;t+=1)s.push(`order[]=${encodeURIComponent(e[t].value)}`),s.push(`cid[]=${encodeURIComponent(l[t].value)}`);return s},c=e=>{if(!e.dataset.itemId)return;const l=e.dataset.itemId,r=container.querySelectorAll(`tr[data-parents~="${l}"]`);r.length&&e.after(...r)},d=e=>{let l,r,a;const t=e.dataset.draggableGroup;t?(a=`tr[data-draggable-group="${t}"]`,l=`[data-draggable-group="${t}"] [name="order[]"]`,r=`[data-draggable-group="${t}"] [name="cid[]"]`):(a="tr",l='[name="order[]"]',r='[name="cid[]"]');const s=[].slice.call(container.querySelectorAll(a)),u=[].slice.call(container.querySelectorAll(l)),i=[].slice.call(container.querySelectorAll(r));if(dropElementIndex=s.indexOf(e),url){const n=document.querySelector('[name="task"]');n&&n.setAttribute("name","some__Temporary__Name__");const g={url,method:"POST",data:`${new URLSearchParams(formData).toString()}&${o(u,i,dragElementIndex,dropElementIndex).join("&")}`,perform:!0};Joomla.request(g),n&&n.setAttribute("name","task")}c(e)};dragula([container],{direction:"vertical",copy:!1,revertOnSpill:!0,accepts(e,l,r,a){return isNested&&a!==null?a.dataset.draggableGroup&&a.dataset.draggableGroup===e.dataset.draggableGroup:a===null||a&&a.tagName.toLowerCase()==="tr"},mirrorContainer:container}).on("drag",e=>{let l;const r=e.dataset.draggableGroup;r?l=`tr[data-draggable-group="${r}"]`:l="tr",dragElementIndex=[].slice.call(container.querySelectorAll(l)).indexOf(e)}).on("drop",e=>{d(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,4 @@
import r 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
*/const c={instances:{},active:null,register(t){if(!(t instanceof r))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 r)e=t.getId();else if(typeof t=="string")e=t;else throw new Error("Unexpected editor instance or identifier");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 r)this.active=t;else if(this.instances[t])this.active=this.instances[t];else throw new Error("The editor instance not found or it is incorrect");return this},getActive(){return this.active}},l={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 s=this.getActionHandler(t);let i=c.getActive();if(!s)throw new Error(`Handler for "${t}" action not found`);if(!i&&o){const a=o.closest("fieldset, div:not(.editor-xtd-buttons)"),n=a?a.querySelector("textarea[id]"):!1;i=n&&Joomla.editors.instances[n.id]?Joomla.editors.instances[n.id]:!1,i&&console.warn("Legacy editors is deprecated. Set active editor instance with JoomlaEditor.setActive().")}if(!i)throw new Error("An active editor are not available");return s(i,e)}};export{c as JoomlaEditor,l as 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,4 @@
/**
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/class o{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{o 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,4 @@
import{JoomlaEditorDecorator as i,JoomlaEditorButton as n}from"editor-api";import c 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.");Joomla.editors=Joomla.editors||{},Joomla.editors.instances=new Proxy({},{set(o,e,t){return t instanceof i||(t.getId=()=>e,console.warn("Legacy editors is deprecated. Register the editor instance with JoomlaEditor.register().",e,t)),o[e]=t,!0},get(o,e){return console.warn("Direct access to Joomla.editors.instances is deprecated. Use JoomlaEditor.getActive() or JoomlaEditor.get(id) to retrieve the editor instance."),o[e]}}),n.registerAction("insert",(o,e)=>{const t=e.content||"";o.replaceSelection(t)}),n.registerAction("modal",(o,e)=>{if(e.src&&e.src[0]!=="#"&&e.src[0]!=="."){const a=e.src.indexOf("http")===0?new URL(e.src):new URL(e.src,window.location.origin);a.searchParams.set("editor",o.getId()),a.searchParams.has("e_name")&&a.searchParams.set("e_name",o.getId()),e.src=a.toString()}const t=new c(e),r=a=>{a.origin===window.location.origin&&(a.data.messageType==="joomla:content-select"?(o.replaceSelection(a.data.html||a.data.text),t.close()):a.data.messageType==="joomla:cancel"&&t.close())};window.JoomlaExpectingPostMessage=!0,window.addEventListener("message",r),t.addEventListener("joomla-dialog:close",()=>{delete window.JoomlaExpectingPostMessage,window.removeEventListener("message",r),Joomla.Modal.setCurrent(null),t.destroy()}),Joomla.Modal.setCurrent(t),t.show()});const l="[data-joomla-editor-button-action]",d="joomlaEditorButtonAction",s="joomlaEditorButtonOptions";document.addEventListener("click",o=>{const e=o.target.closest(l);if(!e)return;const t=e.dataset[d],r=e.dataset[s]?JSON.parse(e.dataset[s]):{};t&&n.runAction(t,r,e)});

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].substring(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].substring(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).substring(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);

View File

@ -0,0 +1,2 @@
(function(r){"use strict";var y=[0,1,2,3,4,5,6,7,8,9];r.SECOND=1e3,r.MINUTE=60*r.SECOND,r.HOUR=60*r.MINUTE,r.DAY=24*r.HOUR,r.WEEK=7*r.DAY,r.prototype.setLocalDateOnly=function(e,t){if(e!="gregorian")return"";var i=new r(t);this.setDate(1),this.setFullYear(i.getFullYear()),this.setMonth(i.getMonth()),this.setDate(i.getDate())},r.prototype.setLocalDate=function(e,t){return e!="gregorian"?"":this.setDate(t)},r.prototype.setLocalMonth=function(e,t,i){return e!="gregorian"?"":(i==null&&this.getDate(),this.setMonth(t))},r.prototype.setOtherFullYear=function(e,t){if(e!="gregorian")return"";var i=new r(this);return i.setFullYear(t),i.getMonth()!=this.getMonth()&&this.setDate(28),this.setUTCFullYear(t)},r.prototype.setLocalFullYear=function(e,t){if(e!="gregorian")return"";var i=new r(this);return i.setFullYear(t),i.getMonth()!=this.getMonth()&&this.setDate(28),this.setFullYear(t)},r.prototype.getLocalWeekDays=function(e,t){return e!="gregorian",6},r.prototype.getOtherFullYear=function(e){return e!="gregorian"?"":this.getFullYear()},r.prototype.getLocalFullYear=function(e){return e!="gregorian"?"":this.getFullYear()},r.prototype.getLocalMonth=function(e){return e!="gregorian"?"":this.getMonth()},r.prototype.getLocalDate=function(e){return e!="gregorian"?"":this.getDate()},r.prototype.getLocalDay=function(e){return e!="gregorian"?"":this.getDay()},r.prototype.getLocalMonthDays=function(e,t){if(e!="gregorian")return"";var i=this.getFullYear();return typeof t>"u"&&(t=this.getMonth()),i%4==0&&(i%100!=0||i%400==0)&&t==1?29:[31,28,31,30,31,30,31,31,30,31,30,31][t]},r.prototype.getLocalWeekNumber=function(e){if(e!="gregorian")return"";var t=new r(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0),i=t.getDay();t.setDate(t.getDate()-(i+6)%7+3);var l=t.valueOf();return t.setMonth(0),t.setDate(4),Math.round((l-t.valueOf())/(7*864e5))+1},r.prototype.getLocalDayOfYear=function(e){if(e!="gregorian")return"";var t=new r(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0),i=new r(this.getFullYear(),0,0,0,0,0),l=t-i;return Math.floor(l/r.DAY)},r.prototype.equalsTo=function(e){return this.getFullYear()==e.getFullYear()&&this.getMonth()==e.getMonth()&&this.getDate()==e.getDate()&&this.getHours()==e.getHours()&&this.getMinutes()==e.getMinutes()},r.localCalToGregorian=function(e,t,i){return""},r.gregorianToLocalCal=function(e,t,i){return""},r.convertNumbers=function(e){e=e.toString();for(var t=0,i=y.length;t<i;t++)e=e.replace(new RegExp(t,"g"),y[t]);return e},r.toEnglish=function(e){e=this.toString();for(var t=[0,1,2,3,4,5,6,7,8,9],i=0;i<10;i++)e=e.replace(new RegExp(t[i],"g"),i);return e},r.monthsToLocalOrder=function(e){return e},r.parseFieldDate=function(e,t,i,l){i!="gregorian"&&(e=r.toEnglish(e));var h=new r,a=0,u=-1,f=0,s=e.split(/\W+/),o=t.match(/%./g),n=0,c=0,g=0,p=0,v=0;for(n=0;n<s.length;++n)if(s[n])switch(o[n]){case"%d":case"%e":f=parseInt(s[n],10);break;case"%m":u=parseInt(s[n],10)-1;break;case"%Y":case"%y":a=parseInt(s[n],10),a<100&&(a+=a>29?1900:2e3);break;case"%b":case"%B":for(c=0;c<12;++c)if(l.months[c].substring(0,s[n].length).toLowerCase()===s[n].toLowerCase()){u=c;break}break;case"%H":case"%I":case"%k":case"%l":g=parseInt(s[n],10);break;case"%P":case"%p":/pm/i.test(s[n])&&g<12?g+=12:/am/i.test(s[n])&&g>=12&&(g-=12);break;case"%M":p=parseInt(s[n],10);break;case"%S":v=parseInt(s[n],10);break}if(isNaN(a)&&(a=h.getFullYear()),isNaN(u)&&(u=h.getMonth()),isNaN(f)&&(f=h.getDate()),isNaN(g)&&(g=h.getHours()),isNaN(p)&&(p=h.getMinutes()),isNaN(v)&&(v=h.getSeconds()),a!=0&&u!=-1&&f!=0)return new r(a,u,f,g,p,v);for(a=0,u=-1,f=0,n=0;n<s.length;++n)if(s[n].search(/[a-zA-Z]+/)!=-1){var M=-1;for(c=0;c<12;++c)if(l.months[c].substring(0,s[n].length).toLowerCase()===s[n].toLowerCase()){M=c;break}M!=-1&&(u!=-1&&(f=u+1),u=M)}else parseInt(s[n],10)<=12&&u==-1?u=s[n]-1:parseInt(s[n],10)>31&&a==0?(a=parseInt(s[n],10),a<100&&(a+=a>29?1900:2e3)):f==0&&(f=s[n]);return a==0&&(a=h.getFullYear()),u!=-1&&f!=0?new r(a,u,f,g,p,v):h},r.prototype.print=function(e,t,i,l){if(typeof t!="string"&&(e=""),t||(t="gregorian"),typeof e!="string"&&(e=""),!e||this.getLocalDate(t)=="NaN"||!this.getLocalDate(t))return"";var h=this.getLocalMonth(t),a=this.getLocalDate(t),u=this.getLocalFullYear(t),f=this.getLocalWeekNumber(t),s=this.getDay(),o={},n=this.getHours(),c=n>=12,g=c?n-12:n,p=this.getLocalDayOfYear(t);g==0&&(g=12);var v=this.getMinutes(),M=this.getSeconds();o["%a"]=l.shortDays[s],o["%A"]=l.days[s],o["%b"]=l.shortMonths[h],o["%B"]=l.months[h],o["%C"]=1+Math.floor(u/100),o["%d"]=a<10?"0"+a:a,o["%e"]=a,o["%H"]=n<10?"0"+n:n,o["%I"]=g<10?"0"+g:g,o["%j"]=p<100?p<10?"00"+p:"0"+p:p,o["%k"]=n,o["%l"]=g,o["%m"]=h<9?"0"+(1+h):1+h,o["%M"]=v<10?"0"+v:v,o["%n"]=`
`,o["%p"]=c?l.pm.toUpperCase():l.am.toUpperCase(),o["%P"]=c?l.pm:l.am,o["%s"]=Math.floor(this.getTime()/1e3),o["%S"]=M<10?"0"+M:M,o["%t"]=" ",o["%U"]=o["%W"]=o["%V"]=f<10?"0"+f:f,o["%u"]=s+1,o["%w"]=s,o["%y"]=(""+u).substring(2),o["%Y"]=u,o["%%"]="%";var m=/%./g,Y=e.replace(m,function(L){return o[L]||L});return t!="gregorian"&&i&&(Y=r.convertNumbers(Y)),Y}})(Date);

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).substring(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].substring(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].substring(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);

View File

@ -0,0 +1,2 @@
(function(a){"use strict";var Y=["\u06F0","\u06F1","\u06F2","\u06F3","\u06F4","\u06F5","\u06F6","\u06F7","\u06F8","\u06F9"];a.gregorian_MD=[31,28,31,30,31,30,31,31,30,31,30,31],a.local_MD=[31,31,31,31,31,31,30,30,30,30,30,29],a.SECOND=1e3,a.MINUTE=60*a.SECOND,a.HOUR=60*a.MINUTE,a.DAY=24*a.HOUR,a.WEEK=7*a.DAY,a.prototype.setLocalDateOnly=function(e,t){if(e!="gregorian")return"";var r=new a(t);this.setDate(1),this.setFullYear(r.getFullYear()),this.setMonth(r.getMonth()),this.setDate(r.getDate())},a.prototype.setLocalDate=function(e,t){return e!="gregorian"?this.setJalaliDate(t):this.setDate(t)},a.prototype.setLocalMonth=function(e,t,r){return e!="gregorian"?this.setJalaliMonth(t,r):(r==null&&this.getDate(),this.setMonth(t))},a.prototype.setOtherFullYear=function(e,t){if(e!="gregorian"){var r=new a(this);return r.setLocalFullYear(t),r.getLocalMonth("jalali")!=this.getLocalMonth("jalali")&&this.setLocalDate("jalali",29),this.setLocalFullYear("jalali",t)}else{var r=new a(this);return r.setFullYear(t),r.getMonth()!=this.getMonth()&&this.setDate(28),this.setUTCFullYear(t)}},a.prototype.setLocalFullYear=function(e,t){if(e!="gregorian")return this.setJalaliFullYear(t);var r=new a(this);return r.setFullYear(t),r.getMonth()!=this.getMonth()&&this.setDate(28),this.setFullYear(t)},a.prototype.getLocalWeekDays=function(e,t){return e!="gregorian",6},a.prototype.getOtherFullYear=function(e){return e!="gregorian"?this.getJalaliFullYear():this.getFullYear()},a.prototype.getLocalFullYear=function(e){return e!="gregorian"?this.getJalaliFullYear():this.getFullYear()},a.prototype.getLocalMonth=function(e){return e!="gregorian"?this.getJalaliMonth():this.getMonth()},a.prototype.getLocalDate=function(e){return e!="gregorian"?this.getJalaliDate():this.getDate()},a.prototype.getLocalDay=function(e){return e!="gregorian"?this.getJalaliDay():this.getDay()},a.prototype.getLocalMonthDays=function(e,t){if(e!="gregorian"){var r=this.getLocalFullYear("jalali");return typeof t>"u"&&(t=this.getLocalMonth("jalali")),r%4==0&&(r%100!=0||r%400==0)&&t==1?29:a.local_MD[t]}else{var r=this.getFullYear();return typeof t>"u"&&(t=this.getMonth()),r%4==0&&(r%100!=0||r%400==0)&&t==1?29:a.gregorian_MD[t]}},a.prototype.getLocalWeekNumber=function(e){if(e!="gregorian"){var t=new a(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0),r=t.getDay();t.setDate(t.getDate()-(r+6)%7+3);var i=t.valueOf();return t.setMonth(0),t.setDate(4),Math.round((i-t.valueOf())/(7*864e5))+1}else{var t=new a(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0),r=t.getDay();t.setDate(t.getDate()-(r+6)%7+3);var i=t.valueOf();return t.setMonth(0),t.setDate(4),Math.round((i-t.valueOf())/(7*864e5))+1}},a.prototype.getLocalDayOfYear=function(e){if(e!="gregorian"){var t=new a(this.getOtherFullYear(e),this.getLocalMonth(e),this.getLocalDate(e),0,0,0),r=new a(this.getOtherFullYear(e),0,0,0,0,0),i=t-r;return Math.floor(i/a.DAY)}else{var t=new a(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0),r=new a(this.getFullYear(),0,0,0,0,0),i=t-r;return Math.floor(i/a.DAY)}},a.prototype.getMonthDays=function(e){var t=this.getFullYear();return typeof e>"u"&&(e=this.getMonth()),t%4==0&&(t%100!=0||t%400==0)&&e==1?29:a.dateType!="gregorian"?a.local_MD[e]:a.gregorian_MD[e]},a.prototype.equalsTo=function(e){return this.getFullYear()==e.getFullYear()&&this.getMonth()==e.getMonth()&&this.getDate()==e.getDate()&&this.getHours()==e.getHours()&&this.getMinutes()==e.getMinutes()},a.localCalToGregorian=function(e,t,r){return f.jalaliToGregorian(e,t,r)},a.gregorianToLocalCal=function(e,t,r){return f.gregorianToJalali(e,t,r)},a.numbersToIso=function(e){var t,r=[0,1,2,3,4,5,6,7,8,9];for(e=e.toString(),t=0;t<r.length;t++)e=e.replace(new RegExp(Y[t],"g"),r[t]);return e},a.convertNumbers=function(e){e=e.toString();for(var t=0,r=Y.length;t<r;t++)e=e.replace(new RegExp(t,"g"),Y[t]);return e},a.toEnglish=function(e){e=this.toString();for(var t=[0,1,2,3,4,5,6,7,8,9],r=0;r<10;r++)e=e.replace(new RegExp(t[r],"g"),r);return e},a.monthsToLocalOrder=function(e,t){return t==="jalali"&&(e.push(e.shift()),e.push(e.shift())),e},a.prototype.print=function(e,t,r,i){if(typeof t!="string"&&(e=""),t||(t="gregorian"),typeof e!="string"&&(e=""),!e||this.getLocalDate(t)=="NaN"||!this.getLocalDate(t))return"";var h=this.getLocalMonth(t),l=this.getLocalDate(t),g=this.getLocalFullYear(t),s=this.getLocalWeekNumber(t),n=this.getLocalDay(t),u={},o=this.getHours(),c=o>=12,v=c?o-12:o,p=this.getLocalDayOfYear(t);v==0&&(v=12);var y=this.getMinutes(),M=this.getSeconds();u["%a"]=i.shortDays[n],u["%A"]=i.days[n],u["%b"]=i.shortMonths[h],u["%B"]=i.months[h],u["%C"]=1+Math.floor(g/100),u["%d"]=l<10?"0"+l:l,u["%e"]=l,u["%H"]=o<10?"0"+o:o,u["%I"]=v<10?"0"+v:v,u["%j"]=p<100?p<10?"00"+p:"0"+p:p,u["%k"]=o,u["%l"]=v,u["%m"]=h<9?"0"+(1+h):1+h,u["%M"]=y<10?"0"+y:y,u["%n"]=`
`,u["%p"]=c?i.pm.toUpperCase():i.am.toUpperCase(),u["%P"]=c?i.pm:i.am,u["%s"]=Math.floor(this.getTime()/1e3),u["%S"]=M<10?"0"+M:M,u["%t"]=" ",u["%U"]=u["%W"]=u["%V"]=s<10?"0"+s:s,u["%u"]=n+1,u["%w"]=n,u["%y"]=(""+g).substring(2),u["%Y"]=g,u["%%"]="%";var I=/%./g,F=e.replace(I,function(L){return u[L]||L});return r&&(F=a.convertNumbers(F)),F},a.parseFieldDate=function(e,t,r,i){e=a.numbersToIso(e);var h=new a,l=0,g=-1,s=0,n=e.split(/\W+/),u=t.match(/%./g),o=0,c=0,v=0,p=0,y=0;for(o=0;o<n.length;++o)if(n[o])switch(u[o]){case"%d":case"%e":s=parseInt(n[o],10);break;case"%m":g=parseInt(n[o],10)-1;break;case"%Y":case"%y":l=parseInt(n[o],10),l<100&&(l+=l>29?1900:2e3);break;case"%b":case"%B":for(c=0;c<12;++c)if(i.months[c].substring(0,n[o].length).toLowerCase()===n[o].toLowerCase()){g=c;break}break;case"%H":case"%I":case"%k":case"%l":v=parseInt(n[o],10);break;case"%P":case"%p":/pm/i.test(n[o])&&v<12?v+=12:/am/i.test(n[o])&&v>=12&&(v-=12);break;case"%M":p=parseInt(n[o],10);break;case"%S":y=parseInt(n[o],10);break}if(isNaN(l)&&(l=h.getFullYear()),isNaN(g)&&(g=h.getMonth()),isNaN(s)&&(s=h.getDate()),isNaN(v)&&(v=h.getHours()),isNaN(p)&&(p=h.getMinutes()),l!=0&&g!=-1&&s!=0)return new a(l,g,s,v,p,0);for(l=0,g=-1,s=0,o=0;o<n.length;++o)if(n[o].search(/[a-zA-Z]+/)!=-1){var M=-1;for(c=0;c<12;++c)if(i.months[c].substring(0,n[o].length).toLowerCase()===n[o].toLowerCase()){M=c;break}M!=-1&&(g!=-1&&(s=g+1),g=M)}else parseInt(n[o],10)<=12&&g==-1?g=n[o]-1:parseInt(n[o],10)>31&&l==0?(l=parseInt(n[o],10),l<100&&(l+=l>29?1900:2e3)):s==0&&(s=n[o]);return l==0&&(l=h.getFullYear()),g!=-1&&s!=0?new a(l,g,s,v,p,0):h};var f={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]};f.jalaliToGregorian=function(e,t,r){e=parseInt(e),t=parseInt(t),r=parseInt(r);for(var i=e-979,h=t-1,l=r-1,g=365*i+parseInt(i/33)*8+parseInt((i%33+3)/4),s=0;s<h;++s)g+=f.j_days_in_month[s];g+=l;var n=g+79,u=1600+400*parseInt(n/146097);n=n%146097;var o=!0;n>=36525&&(n--,u+=100*parseInt(n/36524),n=n%36524,n>=365?n++:o=!1),u+=4*parseInt(n/1461),n%=1461,n>=366&&(o=!1,n--,u+=parseInt(n/365),n=n%365);for(var s=0;n>=f.g_days_in_month[s]+(s==1&&o);s++)n-=f.g_days_in_month[s]+(s==1&&o);var c=s+1,v=n+1;return[u,c,v]},f.checkDate=function(e,t,r){return!(e<0||e>32767||t<1||t>12||r<1||r>f.j_days_in_month[t-1]+(t==12&&!((e-979)%33%4)))},f.gregorianToJalali=function(e,t,r){e=parseInt(e),t=parseInt(t),r=parseInt(r);for(var i=e-1600,h=t-1,l=r-1,g=365*i+parseInt((i+3)/4)-parseInt((i+99)/100)+parseInt((i+399)/400),s=0;s<h;++s)g+=f.g_days_in_month[s];h>1&&(i%4==0&&i%100!=0||i%400==0)&&++g,g+=l;var n=g-79,u=parseInt(n/12053);n%=12053;var o=979+33*u+4*parseInt(n/1461);n%=1461,n>=366&&(o+=parseInt((n-1)/365),n=(n-1)%365);for(var s=0;s<11&&n>=f.j_days_in_month[s];++s)n-=f.j_days_in_month[s];var c=s+1,v=n+1;return[o,c,v]},a.prototype.setJalaliFullYear=function(e,t,r){var i=this.getDate(),h=this.getMonth(),l=this.getFullYear(),g=f.gregorianToJalali(l,h+1,i);e<100&&(e+=1300),g[0]=e,t!=null&&(t>11&&(g[0]+=Math.floor(t/12),t=t%12),g[1]=t+1),r!=null&&(g[2]=r);var s=f.jalaliToGregorian(g[0],g[1],g[2]);return this.setFullYear(s[0],s[1]-1,s[2])},a.prototype.setJalaliMonth=function(e,t){var r=this.getDate(),i=this.getMonth(),h=this.getFullYear(),l=f.gregorianToJalali(h,i+1,r);e>11&&(l[0]+=Math.floor(e/12),e=e%12),l[1]=e+1,t!=null&&(l[2]=t);var g=f.jalaliToGregorian(l[0],l[1],l[2]);return this.setFullYear(g[0],g[1]-1,g[2])},a.prototype.setJalaliDate=function(e){var t=this.getDate(),r=this.getMonth(),i=this.getFullYear(),h=f.gregorianToJalali(i,r+1,t);h[2]=e;var l=f.jalaliToGregorian(h[0],h[1],h[2]);return this.setFullYear(l[0],l[1]-1,l[2])},a.prototype.getJalaliFullYear=function(){var e=this.getDate(),t=this.getMonth(),r=this.getFullYear(),i=f.gregorianToJalali(r,t+1,e);return i[0]},a.prototype.getJalaliMonth=function(){var e=this.getDate(),t=this.getMonth(),r=this.getFullYear(),i=f.gregorianToJalali(r,t+1,e);return i[1]-1},a.prototype.getJalaliDate=function(){var e=this.getDate(),t=this.getMonth(),r=this.getFullYear(),i=f.gregorianToJalali(r,t+1,e);return i[2]},a.prototype.getJalaliDay=function(){var e=this.getDay();return e=e%7,e}})(Date);

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,4 @@
/**
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/(function(a,t){"use strict";function o(i){t(i.target).find(".minicolors").each(function(){t(this).minicolors({control:t(this).attr("data-control")||"hue",format:t(this).attr("data-validate")==="color"?"hex":(t(this).attr("data-format")==="rgba"?"rgb":t(this).attr("data-format"))||"hex",keywords:t(this).attr("data-keywords")||"",opacity:t(this).attr("data-format")==="rgba",position:t(this).attr("data-position")||"default",swatches:t(this).attr("data-colors")?t(this).attr("data-colors").split(","):[],theme:"bootstrap"})})}a.addEventListener("DOMContentLoaded",o),a.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,395 @@
/**
* @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.choicesInstance.setChoiceByValue(this.disconnectValues);
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: new DOMParser().parseFromString(this.newItemPrefix + event.target.value, 'text/html').body.textContent,
label: new DOMParser().parseFromString(event.target.value, 'text/html').body.textContent,
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) {
// Keep selected values, because choices will reset them on re-init
this.disconnectValues = this.choicesInstance.getValue(true);
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,381 @@
import JoomlaDialog from 'joomla.dialog';
/**
* @copyright (C) 2018 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('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.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 ['base-path', 'root-folder', 'url', 'modal-title', 'modal-width', 'modal-height', 'input', 'button-select', 'button-clear', 'preview', 'preview-width', 'preview-height'];
}
get types() {
return this.getAttribute('types') || '';
}
set types(value) {
this.setAttribute('types', value);
}
get basePath() {
return this.getAttribute('base-path');
}
set basePath(value) {
this.setAttribute('base-path', value);
}
get url() {
return this.getAttribute('url');
}
set url(value) {
this.setAttribute('url', 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 modalWidth() {
return this.getAttribute('modal-width');
}
set modalWidth(value) {
this.setAttribute('modal-width', value);
}
get modalHeight() {
return this.getAttribute('modal-height');
}
set modalHeight(value) {
this.setAttribute('modal-height', value);
}
get modalTitle() {
return this.getAttribute('modal-title');
}
set modalTitle(value) {
this.setAttribute('modal-title', 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');
}
connectedCallback() {
this.button = this.querySelector(this.buttonSelect);
this.inputElement = this.querySelector(this.input);
this.buttonClearEl = this.querySelector(this.buttonClear);
this.previewElement = this.querySelector('.field-media-preview');
if (!this.button || !this.inputElement || !this.buttonClearEl) {
throw new Error('Misconfiguaration...');
}
this.button.addEventListener('click', this.show);
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);
}
if (this.dialog) {
this.dialog.close();
}
}
show() {
// Create and show the dialog
const dialog = new JoomlaDialog({
popupType: 'iframe',
src: this.url,
textHeader: this.modalTitle,
width: this.modalWidth,
height: this.modalHeight,
popupButtons: [{
label: Joomla.Text._('JSELECT'),
className: 'button button-success btn btn-success',
location: 'header',
onClick: () => {
this.modalClose();
}
}, {
label: '',
ariaLabel: Joomla.Text._('JCLOSE'),
className: 'button-close btn-close',
data: {
buttonClose: '',
dialogClose: ''
},
location: 'header'
}]
});
dialog.classList.add('joomla-dialog-media-field');
dialog.show();
Joomla.Modal.setCurrent(dialog);
dialog.addEventListener('joomla-dialog:close', () => {
Joomla.Modal.setCurrent(null);
dialog.destroy();
this.dialog = null;
Joomla.selectedMediaFile = {};
});
this.dialog = dialog;
}
async modalClose() {
try {
const item = Joomla.selectedMediaFile;
if (item && item.type === 'dir') {
// Set directory path as value only when the field is configured to support of directories
this.setValue(this.types.includes('directories') ? item.path : '');
} else {
await Joomla.getMedia(item, this.inputElement, this);
}
} catch (err) {
Joomla.renderMessages({
error: [Joomla.Text._('JLIB_APPLICATION_ERROR_SERVER')]
});
}
Joomla.selectedMediaFile = {};
this.dialog.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,5 @@
/**
* @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(){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 t=this;this.originalPosition=this.linkedFieldElement.value,this.getNewOrder(this.originalPosition),this.linkedFieldElement.addEventListener("change",()=>{t.originalPosition=t.linkedFieldElement.value,t.getNewOrder(t.linkedFieldElement.value)})}writeDynaList(t,d,o,c){let r=0;const e=document.createElement("select");this.hasAttribute("disabled")&&e.setAttribute("disabled",""),this.getAttribute("onchange")&&e.setAttribute("onchange",this.getAttribute("onchange")),this.getAttribute("size")&&e.setAttribute("size",this.getAttribute("size")),e.classList.add(t.itemClass),e.setAttribute("name",t.name),e.id=t.id;for(const a in d){if(!d.hasOwnProperty(a))continue;const i=document.createElement("option"),n=d[a];i.value=n[1],i.innerHTML=Joomla.sanitizeHtml(n[2]),(o&&c===n[1]||!o&&r===0)&&i.setAttribute("selected","selected"),e.appendChild(i),r+=1}this.innerHTML="",this.appendChild(e)}getNewOrder(t){const d=this.getAttribute("data-url"),o=this.getAttribute("data-client-id"),c=this.getAttribute("data-ordering"),r=this.getAttribute("data-name"),e=this.getAttribute("data-client-attr")?this.getAttribute("data-client-attr"):"form-select",a=`${this.getAttribute("data-id")}`,i=`${this.getAttribute("data-module-id")}`,n=[],u=this;Joomla.request({url:`${d}&client_id=${o}&position=${t}&module_id=${i}`,method:"GET",perform:!0,headers:{"Content-Type":"application/x-www-form-urlencoded"},onSuccess(l){if(l){let h;try{h=JSON.parse(l)}catch(s){console.error(s)}if(h.data.length>0){for(let s=0;s<h.data.length;s+=1)n[s]=h.data[s].split(",");u.writeDynaList({name:r,id:a,itemClass:e},n,u.originalPosition,c)}}typeof l.messages=="object"&&l.messages!==null&&Joomla.renderMessages(l.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 = 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,4 @@
/**
* @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(){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=document.querySelectorAll(`[${this.buttonDataSelector}]`),this.buttons&&this.buttons.forEach(e=>{e.addEventListener("change",this.onDropdownChange)})}disconnectedCallback(){this.buttons&&this.buttons.forEach(e=>{e.removeEventListener("change",this.onDropdownChange)})}onDropdownChange(e){e.preventDefault(),e.target.getAttribute(this.buttonDataSelector)==="permissions.apply"&&this.sendPermissions(e)}sendPermissions(e){const{target:i}=e,t=document.getElementById(`icon_${i.id}`);t.removeAttribute("class"),t.setAttribute("class","joomla-icon joomla-field-permissions__spinner");const{value:n}=i;document.getElementById("jform_context")&&(this.context=document.getElementById("jform_context").value,[this.context]=this.context.split(".")),this.option==="com_config"&&!this.component&&!this.extension?this.asset="root.1":!this.extension&&this.view==="component"?this.asset=this.component:this.context?(this.view==="group"?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);const r=i.id.replace("jform_rules_",""),l=r.lastIndexOf("_"),h={comp:this.asset,action:r.substring(0,l),rule:r.substring(l+1),value:n,title:this.title};Joomla.removeMessages(),Joomla.request({url:this.getAttribute("data-uri"),method:"POST",data:JSON.stringify(h),perform:!0,headers:{"Content-Type":"application/json"},onSuccess:a=>{let s;try{s=JSON.parse(a)}catch(o){console.error(o)}if(t.removeAttribute("class"),s.data&&s.data.result){t.setAttribute("class","joomla-icon joomla-field-permissions__allowed");const o=i.parentNode.parentNode.nextElementSibling.querySelector("span");o.removeAttribute("class"),o.setAttribute("class",s.data.class),o.innerHTML=Joomla.sanitizeHtml(s.data.text)}typeof s.messages=="object"&&s.messages!==null&&(Joomla.renderMessages(s.messages),s.data&&s.data.result?t.setAttribute("class","joomla-icon joomla-field-permissions__allowed"):t.setAttribute("class","joomla-icon joomla-field-permissions__denied"))},onError:a=>{t.removeAttribute("style"),Joomla.renderMessages(Joomla.ajaxErrorsMessages(a,a.statusText)),t.setAttribute("class","joomla-icon joomla-field-permissions__denied")}})}getUrlParam(e){const i=this.query.split("&");let t=0;for(t;t<i.length;t+=1){const n=i[t].split("=");if(n[0]===e)return n[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 @@
((a,e)=>{class m extends HTMLElement{constructor(){if(super(),!e)throw new Error("Joomla API is not properly initiated");if(!this.getAttribute("uri"))throw new Error("No valid url for validation")}connectedCallback(){const t=this,s=document.getElementById("sendtestmail");s&&s.addEventListener("click",()=>{t.sendTestMail(t)})}sendTestMail(){const t={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]"]');s.disabled===!1&&(t.smtppass=s.value),e.removeMessages(),e.request({url:this.getAttribute("uri"),method:"POST",data:JSON.stringify(t),perform:!0,headers:{"Content-Type":"application/json"},onSuccess:o=>{let r;try{r=JSON.parse(o)}catch(n){console.error(n)}typeof r.messages=="object"&&r.messages!==null&&e.renderMessages(r.messages),document.body.scrollIntoView({behavior:"smooth"})},onError:o=>{e.renderMessages(e.ajaxErrorsMessages(o)),document.body.scrollIntoView({behavior:"smooth"})}})}}a.define("joomla-field-send-test-mail",m)})(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 = 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.setAttribute('aria-label', 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).substring(2, 12)}`;
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,610 @@
/**
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
const KEYCODE = {
SPACE: 'Space',
ESC: 'Escape',
ENTER: 'Enter'
};
/**
* 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.closest(that.buttonAdd);
}
if (that.buttonRemove) {
btnRem = 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.code !== 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
const forOldAttr = $el.id; // Fix "for" in the labels
if ($el.type === 'checkbox' && name.match(/\[\]$/)) {
// <input type="checkbox" name="name[]"> fix
countMulti = ids[id] ? ids[id].length : 0;
// Set the id for fieldset and group label
if (!countMulti) {
// Look for <fieldset class="checkboxes"></fieldset> or <fieldset><div class="checkboxes"></div></fieldset>
let fieldset = $el.closest('.checkboxes, fieldset');
// eslint-disable-next-line no-nested-ternary
if (fieldset) {
// eslint-disable-next-line no-nested-ternary
fieldset = fieldset.nodeName === 'FIELDSET' ? fieldset : fieldset.parentElement.nodeName === 'FIELDSET' ? fieldset.parentElement : false;
}
if (fieldset) {
const oldSetId = fieldset.id;
fieldset.id = idNew;
const groupLbl = row.querySelector(`label[for="${oldSetId}"]`);
if (groupLbl) {
groupLbl.setAttribute('for', idNew);
if (groupLbl.id) {
groupLbl.setAttribute('id', `${idNew}-lbl`);
}
}
}
}
idNew += countMulti;
} else if ($el.type === 'radio') {
// <input type="radio"> fix
countMulti = ids[id] ? ids[id].length : 0;
// Set the id for fieldset and group label
if (!countMulti) {
/**
* Look for one of:
* - <fieldset class="radio"></fieldset>
* - <fieldset><div class="radio"></div></fieldset>
* - <fieldset><div class="switcher"></div></fieldset>
*/
let fieldset = $el.closest('.radio, .switcher, fieldset');
if (fieldset) {
// eslint-disable-next-line no-nested-ternary
fieldset = fieldset.nodeName === 'FIELDSET' ? fieldset : fieldset.parentElement.nodeName === 'FIELDSET' ? fieldset.parentElement : false;
}
if (fieldset) {
const oldSetId = fieldset.id;
fieldset.id = idNew;
const groupLbl = row.querySelector(`label[for="${oldSetId}"]`);
if (groupLbl) {
groupLbl.setAttribute('for', idNew);
if (groupLbl.id) {
groupLbl.setAttribute('id', `${idNew}-lbl`);
}
}
}
}
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);
if (lbl.id) {
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
this.getRows().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.code !== KEYCODE.ESC && event.code !== KEYCODE.SPACE && event.code !== 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.code === 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.code === KEYCODE.ESC && item) {
item.setAttribute('draggable', 'false');
item.setAttribute('aria-grabbed', 'false');
item = null;
}
// Enter, to place selected item in selected position
if (event.code === 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;
}
});
/**
* Move UP, Move Down sorting
*/
const btnUp = `${that.buttonMove}-up`;
const btnDown = `${that.buttonMove}-down`;
this.addEventListener('click', ({
target
}) => {
if (target.closest('joomla-field-subform') !== this) {
return;
}
const btnUpEl = target.closest(btnUp);
const btnDownEl = !btnUpEl ? target.closest(btnDown) : null;
if (!btnUpEl && !btnDownEl) {
return;
}
let row = (btnUpEl || btnDownEl).closest(that.repeatableElement);
row = row && row.closest('joomla-field-subform') === this ? row : null;
if (!row) {
return;
}
const rows = this.getRows();
const curIdx = rows.indexOf(row);
let dstIdx = 0;
if (btnUpEl) {
dstIdx = curIdx - 1;
dstIdx = dstIdx < 0 ? rows.length - 1 : dstIdx;
} else {
dstIdx = curIdx + 1;
dstIdx = dstIdx > rows.length - 1 ? 0 : dstIdx;
}
switchRowPositions(row, rows[dstIdx]);
});
}
}
customElements.define('joomla-field-subform', JoomlaFieldSubform);

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,152 @@
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;
// Focus on the input field to re-trigger the validation
this.inputName.focus();
this.buttonSelect.focus();
});
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,4 @@
import s 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 n 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 s({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=i=>{i.origin===window.location.origin&&(i.data.messageType==="joomla:content-select"?(this.setValue(i.data.id,i.data.name),t.close()):i.data.messageType==="joomla:cancel"&&t.close())};window.addEventListener("message",e),t.addEventListener("joomla-dialog:close",()=>{window.removeEventListener("message",e),t.destroy(),this.dialog=null,this.inputName.focus(),this.buttonSelect.focus()}),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",n);

Binary file not shown.

View File

@ -0,0 +1,557 @@
/**
* @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('.joomla-dialog-body');
// No extra attributes (lazy, alt) for fields
if (!container || container.closest('.joomla-dialog-media-field')) {
return;
}
const optionsEl = container.querySelector('joomla-field-mediamore');
if (optionsEl) {
optionsEl.parentNode.removeChild(optionsEl);
}
const {
images,
audios,
videos,
documents
} = supportedExtensions;
if (Joomla.selectedMediaFile.path && Joomla.selectedMediaFile.type === 'file') {
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) || editor.replaceSelection) {
const editorInst = editor.replaceSelection ? editor : Joomla.editors.instances[editor];
const currentModal = Joomla.Modal.getCurrent();
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);
}
editorInst.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) || editor.replaceSelection) {
let outputText;
const editorInst = editor.replaceSelection ? editor : Joomla.editors.instances[editor];
const currentModal = Joomla.Modal.getCurrent();
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 (editorInst.getSelection() !== '') {
outputText = `<a download href="${Joomla.selectedMediaFile.url}">${editorInst.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);
}
editorInst.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 '';
};
/**
* 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 apiUrl = Joomla.getOptions('media-picker-api', {}).apiBaseUrl || 'index.php?option=com_media&format=json';
const url = new URL(apiUrl, window.location.origin);
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);

View File

@ -0,0 +1,111 @@
/**
* @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");Joomla.selectedMediaFile={};const supportedExtensions=Joomla.getOptions("media-picker",{});if(!Object.keys(supportedExtensions).length)throw new Error("No supported extensions provided");document.addEventListener("onMediaFileSelected",async i=>{Joomla.selectedMediaFile=i.detail;const t=Joomla.Modal.getCurrent(),l=t.querySelector(".joomla-dialog-body");if(!l||l.closest(".joomla-dialog-media-field"))return;const e=l.querySelector("joomla-field-mediamore");e&&e.parentNode.removeChild(e);const{images:a,audios:o,videos:s,documents:c}=supportedExtensions;if(Joomla.selectedMediaFile.path&&Joomla.selectedMediaFile.type==="file"){let d;a.includes(Joomla.selectedMediaFile.extension.toLowerCase())?d="images":o.includes(Joomla.selectedMediaFile.extension.toLowerCase())?d="audios":s.includes(Joomla.selectedMediaFile.extension.toLowerCase())?d="videos":c.includes(Joomla.selectedMediaFile.extension.toLowerCase())&&(d="documents"),d&&l.insertAdjacentHTML("afterbegin",`<joomla-field-mediamore
parent-id="${t.id}"
type="${d}"
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>
`)}});const isElement=i=>typeof HTMLElement=="object"?i instanceof HTMLElement:i&&typeof i=="object"&&i.nodeType===1&&typeof i.nodeName=="string",getImageSize=i=>new Promise((t,l)=>{const e=new Image;e.src=i,e.onload=()=>{Joomla.selectedMediaFile.width=e.width,Joomla.selectedMediaFile.height=e.height,t(!0)},e.onerror=()=>{l(!1)}}),insertAsImage=async(i,t,l)=>{if(i.url){const{rootFull:e}=Joomla.getOptions("system.paths"),a=i.url.split(e);a.length>1?(Joomla.selectedMediaFile.url=a[1],i.thumb_path?Joomla.selectedMediaFile.thumb=i.thumb_path:Joomla.selectedMediaFile.thumb=!1):i.thumb_path&&(Joomla.selectedMediaFile.url=i.url,Joomla.selectedMediaFile.thumb=i.thumb_path)}else Joomla.selectedMediaFile.url=!1;if(Joomla.selectedMediaFile.url){let e,a="",o="",s="",c="",d="",r="",n="";if(!isElement(t)||t.replaceSelection){const h=t.replaceSelection?t:Joomla.editors.instances[t];if(e=Joomla.Modal.getCurrent().querySelector("joomla-field-mediamore"),e&&(e.getAttribute("alt-check")==="true"&&(s=' alt=""'),o=e.getAttribute("alt-value")?` alt="${e.getAttribute("alt-value")}"`:s,c=e.getAttribute("img-classes")?` class="${e.getAttribute("img-classes")}"`:"",d=e.getAttribute("fig-classes")?` class="image ${e.getAttribute("fig-classes")}"`:' class="image"',r=e.getAttribute("fig-caption")?`${e.getAttribute("fig-caption")}`:"",e.getAttribute("is-lazy")==="true"&&(a=` loading="lazy" width="${Joomla.selectedMediaFile.width}" height="${Joomla.selectedMediaFile.height}"`,Joomla.selectedMediaFile.width===0||Joomla.selectedMediaFile.height===0)))try{await getImageSize(Joomla.selectedMediaFile.url),a=` loading="lazy" width="${Joomla.selectedMediaFile.width}" height="${Joomla.selectedMediaFile.height}"`}catch{a=""}r?n=`<figure${d}><img src="${Joomla.selectedMediaFile.url}"${c}${a}${o} data-path="${Joomla.selectedMediaFile.path}"/><figcaption>${r}</figcaption></figure>`:n=`<img src="${Joomla.selectedMediaFile.url}"${c}${a}${o} data-path="${Joomla.selectedMediaFile.path}"/>`,e&&e.parentNode.removeChild(e),h.replaceSelection(n)}else{if(Joomla.selectedMediaFile.width===0||Joomla.selectedMediaFile.height===0)try{await getImageSize(Joomla.selectedMediaFile.url)}catch{Joomla.selectedMediaFile.height=0,Joomla.selectedMediaFile.width=0}l.markValid(),l.setValue(`${Joomla.selectedMediaFile.url}#joomlaImage://${i.path.replace(":","")}?width=${Joomla.selectedMediaFile.width}&height=${Joomla.selectedMediaFile.height}`)}}},insertAsOther=(i,t,l,e)=>{if(i.url){const{rootFull:o}=Joomla.getOptions("system.paths"),s=i.url.split(o);s.length>1?Joomla.selectedMediaFile.url=s[1]:Joomla.selectedMediaFile.url=i.url}else Joomla.selectedMediaFile.url=!1;let a;if(Joomla.selectedMediaFile.url)if(!isElement(t)||t.replaceSelection){let o;const s=t.replaceSelection?t:Joomla.editors.instances[t];if(a=Joomla.Modal.getCurrent().querySelector("joomla-field-mediamore"),a){const d=a.getAttribute("embed-it");if(d&&d==="true"){if(e==="audios"&&(o=`<audio controls src="${Joomla.selectedMediaFile.url}"></audio>`),e==="documents"){const r=a.getAttribute("title");o=`<object type="application/${Joomla.selectedMediaFile.extension}" data="${Joomla.selectedMediaFile.url}" ${r?`title="${r}"`:""} width="${a.getAttribute("width")}" height="${a.getAttribute("height")}">
${Joomla.Text._("JFIELD_MEDIA_UNSUPPORTED").replace("{tag}",`<a download href="${Joomla.selectedMediaFile.url}">`).replace(/{extension}/g,Joomla.selectedMediaFile.extension)}
</object>`}e==="videos"&&(o=`<video controls width="${a.getAttribute("width")}" height="${a.getAttribute("height")}">
<source src="${Joomla.selectedMediaFile.url}" type="${Joomla.selectedMediaFile.fileType}">
</video>`)}else if(s.getSelection()!=="")o=`<a download href="${Joomla.selectedMediaFile.url}">${s.getSelection()}</a>`;else{const r=/([\w-]+)\./.exec(Joomla.selectedMediaFile.url);o=`<a download href="${Joomla.selectedMediaFile.url}">${Joomla.Text._("JFIELD_MEDIA_DOWNLOAD_FILE").replace("{file}",r[1])}</a>`}}a&&a.parentNode.removeChild(a),s.replaceSelection(o)}else l.markValid(),l.givenType=e,l.setValue(Joomla.selectedMediaFile.url)},execTransform=async(i,t,l)=>{if(i.success===!0){const e=i.data[0],{images:a,audios:o,videos:s,documents:c}=supportedExtensions;if(Joomla.selectedMediaFile.extension&&a.includes(e.extension.toLowerCase()))return insertAsImage(e,t,l);if(Joomla.selectedMediaFile.extension&&o.includes(e.extension.toLowerCase()))return insertAsOther(e,t,l,"audios");if(Joomla.selectedMediaFile.extension&&c.includes(e.extension.toLowerCase()))return insertAsOther(e,t,l,"documents");if(Joomla.selectedMediaFile.extension&&s.includes(e.extension.toLowerCase()))return insertAsOther(e,t,l,"videos")}return""};Joomla.getMedia=(i,t,l)=>new Promise((e,a)=>{if(!i||typeof i=="object"&&(!i.path||i.path==="")){Joomla.selectedMediaFile={},e({resp:{success:!1}});return}const o=Joomla.getOptions("media-picker-api",{}).apiBaseUrl||"index.php?option=com_media&format=json",s=new URL(o,window.location.origin);s.searchParams.append("task","api.files"),s.searchParams.append("url",!0),s.searchParams.append("path",i.path),s.searchParams.append("mediatypes","0,1,2,3"),s.searchParams.append(Joomla.getOptions("csrf.token"),1),fetch(s,{method:"GET",headers:{"Content-Type":"application/json"}}).then(c=>c.json()).then(async c=>e(await execTransform(c,t,l))).catch(c=>a(c))}),Joomla.getImage=Joomla.getMedia;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(){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),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(t=>{t.addEventListener("input",this.inputFn);const{is:l}=t.dataset;return l&&this.setAttribute(l,t.value.replace(/"/g,"&quot;")),t}),this.setAttribute("is-lazy",!!this.lazyInput.checked),this.setAttribute("alt-check",!1)):["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(t=>t.addEventListener("input",this.embedInputFn)),this.setAttribute("embed-it",!1),[].slice.call(this.querySelectorAll('input[type="text"]')).map(t=>{t.addEventListener("input",this.inputFn);const{is:l}=t.dataset;return l&&this.setAttribute(l,t.value.replace(/"/g,"&quot;")),t}))}disconnectedCallback(){this.type==="image"&&(this.lazyInput.removeEventListener("input",this.lazyInputFn),this.altInput.removeEventListener("input",this.inputFn),this.altCheck.removeEventListener("input",this.altCheckFn)),["audio","video","document"].includes(this.type)&&([].slice.call(this.querySelectorAll(".form-check-input.radio")).map(t=>t.removeEventListener("input",this.embedInputFn)),[].slice.call(this.querySelectorAll('input[type="text"]')).map(t=>t.removeEventListener("input",this.embedInputFn))),this.innerHTML=""}lazyInputFn(t){this.setAttribute("is-lazy",!!t.target.checked)}altCheckFn(t){this.setAttribute("alt-check",!!t.target.checked)}inputFn(t){const{is:l}=t.target.dataset;l&&this.setAttribute(l,t.target.value.replace(/"/g,"&quot;"))}embedInputFn(t){const{value:l}=t.target;this.setAttribute("embed-it",l!=="0");const e=this.querySelector(".toggable-parts");e&&(e.style.display!=="block"?e.style.display="block":e.style.display="none")}}customElements.define("joomla-field-mediamore",JoomlaFieldMediaOptions);

Binary file not shown.

View File

@ -0,0 +1,174 @@
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) : {};
const keyName = container.dataset.keyName || 'id';
const token = Joomla.getOptions('csrf.token', '');
// Handle requested action
let handle;
switch (action) {
case 'select':
case 'create':
{
const url = dialogConfig.src.indexOf('http') === 0 ? new URL(dialogConfig.src) : new URL(dialogConfig.src, window.location.origin);
url.searchParams.set(token, '1');
dialogConfig.src = url.toString();
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(keyName, inputValue.value);
url.searchParams.set(token, '1');
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(keyName, 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,4 @@
import f 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
*/const w=(e,t,s)=>{const o=`${e.id||e.value||""}`,n=t.value!==o;t.value=o,s&&(s.value=e.title||t.value),n&&t.dispatchEvent(new CustomEvent("change",{bubbles:!0,cancelable:!0}))},h=(e,t,s)=>{window.JoomlaExpectingPostMessage=!0;const o=new f(s);return o.classList.add("joomla-dialog-content-select-field"),o.show(),new Promise(n=>{const i=a=>{a.origin===window.location.origin&&(a.data.messageType==="joomla:content-select"?(w(a.data,e,t),o.close()):a.data.messageType==="joomla:cancel"&&o.close())};o.addEventListener("joomla-dialog:close",()=>{delete window.JoomlaExpectingPostMessage,window.removeEventListener("message",i),o.destroy(),n()}),window.addEventListener("message",i)})},p=(e,t)=>{const s=!!e.value;t.querySelectorAll("[data-show-when-value]").forEach(o=>{o.dataset.showWhenValue?s?o.removeAttribute("hidden"):o.setAttribute("hidden",""):s?o.setAttribute("hidden",""):o.removeAttribute("hidden")})},v=e=>{const t=e?e.querySelector(".js-input-value"):null,s=e?e.querySelector(".js-input-title"):null;if(!e||!t)throw new Error("Incomplete markup of Content dialog field");e.addEventListener("change",()=>{p(t,e)}),e.addEventListener("click",o=>{const n=o.target.closest("[data-button-action]");if(!n)return;o.preventDefault();const i=n.dataset.buttonAction,a=n.dataset.modalConfig?JSON.parse(n.dataset.modalConfig):{},u=e.dataset.keyName||"id",m=Joomla.getOptions("csrf.token","");let l;switch(i){case"select":case"create":{const c=a.src.indexOf("http")===0?new URL(a.src):new URL(a.src,window.location.origin);c.searchParams.set(m,"1"),a.src=c.toString(),l=h(t,s,a);break}case"edit":{const c=a.src.indexOf("http")===0?new URL(a.src):new URL(a.src,window.location.origin);c.searchParams.set(u,t.value),c.searchParams.set(m,"1"),a.src=c.toString(),l=h(t,s,a);break}case"clear":l=(async()=>w({id:"",title:""},t,s))();break;default:throw new Error(`Unknown action ${i} for Modal select field`)}l.then(()=>{if(n.dataset.checkinUrl){const c=n.dataset.checkinUrl,r=c.indexOf("http")===0?new URL(c):new URL(c,window.location.origin);r.searchParams.set(u,t.value),r.searchParams.set("cid[]",t.value);const d=new FormData;d.append("id",t.value),d.append("cid[]",t.value),Joomla.request({url:r.toString(),method:"POST",promise:!0,data:d})}})})},g=e=>{e.querySelectorAll(".js-modal-content-select-field").forEach(t=>v(t))};document.addEventListener("DOMContentLoaded",()=>g(document)),document.addEventListener("joomla:updated",e=>g(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,4 @@
/**
* @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";window.processModalParent=function(e,o,l,d,a,m,n){var t=document.getElementById(e+"_id")||document.getElementById(e+"_value"),u=document.getElementById(e+"_name")||document.getElementById(e);o=o||"",l=l||"",d=d||"",n=n||"",a=a||"",m=m||"";var r=t.value!==o;return o?(t.value=o,u.value=l,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")):(t.value="",u.value=t.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")),r&&t.dispatchEvent(new CustomEvent("change",{bubbles:!0,cancelable:!0})),t.getAttribute("data-required")=="1"&&(document.formvalidator.validate(t),document.formvalidator.validate(u)),!1},window.processModalEdit=function(e,o,l,d,a,m,n,t){m=m||d.toLowerCase()+"-form",n=n||"jform_id",t=t||"jform_title";var u=e.parentNode.parentNode.parentNode.parentNode.id,r=a,s=document.getElementById(u).getElementsByTagName("iframe")[0];s.id="Frame_"+u;var c=s.contentDocument;return a==="cancel"?(s.contentWindow.Joomla.submitbutton(d.toLowerCase()+"."+a),Joomla.Modal.getCurrent().close()):(s.addEventListener("load",function(){c=this.contentDocument,c.getElementById(n)&&c.getElementById(n).value!="0"&&[].slice.call(c.querySelectorAll('joomla-alert[type="danger"]')).length==0&&(window.processModalParent(o,c.getElementById(n).value,c.getElementById(t).value),a==="save"&&window.processModalEdit(e,o,"edit",d,"cancel",m,n,t)),s.classList.remove("visually-hidden")}),c.formvalidator.isValid(c.getElementById(m))&&(a==="save"&&(r="apply",s.classList.add("visually-hidden")),s.contentWindow.Joomla.submitbutton(d.toLowerCase()+"."+r))),!1},window.processModalSelect=function(e,o,l,d,a,m,n,t){return window.processModalParent(o,l,d,a,n,t,m),Joomla.Modal.getCurrent()&&Joomla.Modal.getCurrent().close(),!1}})();

Binary file not shown.

View File

@ -0,0 +1,168 @@
/**
* PasswordStrength script by Thomas Kjaergaard
* License: MIT
* Repo: https://github.com/tkjaergaard/Password-Strength
*
* The MIT License (MIT)
*
* Copyright (c) 2014 Thomas Kjærgaard
*
* ADAPTED BY: Joomla for use in the Joomla! CMS
*
* 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 (label) {
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 = 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,4 @@
class PasswordStrength{constructor(e){this.lowercase=parseInt(e.lowercase,10)||0,this.uppercase=parseInt(e.uppercase,10)||0,this.numbers=parseInt(e.numbers,10)||0,this.special=parseInt(e.special,10)||0,this.length=parseInt(e.length,10)||12}getScore(e){let i=0,t=0;return["lowercase","uppercase","numbers","special","length"].forEach(r=>{this[r]>0&&(t+=1)}),i+=this.constructor.calc(e,/[a-z]/g,this.lowercase,t),i+=this.constructor.calc(e,/[A-Z]/g,this.uppercase,t),i+=this.constructor.calc(e,/[0-9]/g,this.numbers,t),i+=this.constructor.calc(e,/[@$!#?=;:*\-_€%&()`´+[\]{}'"\\|,.<>/~^]/g,this.special,t),t===1?i+=e.length>this.length?100:100/this.length*e.length:i+=e.length>this.length?100/t:100/t/this.length*e.length,i}static calc(e,i,t,s){const r=e.match(i);return r&&r.length>t&&t!==0?100/s:r&&t>0?100/s/t*r.length:0}}/**
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/((u,e)=>{const i=t=>{const s=e.querySelector("meter"),r=t.getAttribute("data-min-length"),c=t.getAttribute("data-min-integers"),n=t.getAttribute("data-min-symbols"),a=t.getAttribute("data-min-uppercase"),o=t.getAttribute("data-min-lowercase"),h=new PasswordStrength({lowercase:o||0,uppercase:a||0,numbers:c||0,special:n||0,length:r||12}).getScore(t.value),p=s.getAttribute("id").replace(/^\D+/g,""),l=t.parentNode.parentNode.querySelector(`#password-${p}`);l&&(h===100?l.innerText=u.Text._("JFIELD_PASSWORD_INDICATE_COMPLETE"):l.innerText=u.Text._("JFIELD_PASSWORD_INDICATE_INCOMPLETE"),s.value=h,t.value.length||(l.innerText="",t.setAttribute("required","")))};e.addEventListener("DOMContentLoaded",()=>{const t=e.querySelectorAll(".js-password-strength");t.forEach((s,r)=>{let c="";s.value.length||(c=0);const n=e.createElement("meter");n.setAttribute("id",`progress-${r}`),n.setAttribute("min",0),n.setAttribute("max",100),n.setAttribute("low",40),n.setAttribute("high",99),n.setAttribute("optimum",100),n.value=c;const a=e.createElement("div");a.setAttribute("class","text-center"),a.setAttribute("id",`password-${r}`),a.setAttribute("aria-live","polite"),s.parentNode.insertAdjacentElement("afterEnd",a),s.parentNode.insertAdjacentElement("afterEnd",n),s.value.length>0&&s.setAttribute("required",!0),s.addEventListener("keyup",({target:o})=>i(o))}),t[0]&&e.formvalidator.setHandler("password-strength",s=>{const r=e.querySelectorAll(".js-password-strength"),c=r[0].getAttribute("data-min-length"),n=r[0].getAttribute("data-min-integers"),a=r[0].getAttribute("data-min-symbols"),o=r[0].getAttribute("data-min-uppercase"),g=r[0].getAttribute("data-min-lowercase");return new PasswordStrength({lowercase:g||0,uppercase:o||0,numbers:n||0,special:a||0,length:c||12}).getScore(s)===100})})})(Joomla,document);

Binary file not shown.

View File

@ -0,0 +1,80 @@
/**
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
(document => {
function togglePassword() {
[].slice.call(document.querySelectorAll('input[type="password"]')).forEach(input => {
const toggleButton = input.parentNode.querySelector('.input-password-toggle');
const hasClickListener = toggleButton.getAttribute('clickListener') === 'true';
if (toggleButton && !hasClickListener) {
toggleButton.setAttribute('clickListener', 'true');
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.addEventListener('joomla:updated', togglePassword);
document.addEventListener('DOMContentLoaded', togglePassword);
})(document);

View File

@ -0,0 +1,4 @@
/**
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/(l=>{function c(){[].slice.call(l.querySelectorAll('input[type="password"]')).forEach(e=>{const s=e.parentNode.querySelector(".input-password-toggle"),i=s.getAttribute("clickListener")==="true";s&&!i&&(s.setAttribute("clickListener","true"),s.addEventListener("click",()=>{const o=s.firstElementChild,a=s.lastElementChild;e.type==="password"?(o.classList.remove("icon-eye"),o.classList.add("icon-eye-slash"),e.type="text",e.focus(),a.innerText=Joomla.Text._("JHIDEPASSWORD")):e.type==="text"&&(o.classList.add("icon-eye"),o.classList.remove("icon-eye-slash"),e.type="password",e.focus(),a.innerText=Joomla.Text._("JSHOWPASSWORD"))}));const t=e.parentNode.querySelector(".input-password-modify");t&&t.addEventListener("click",()=>{!t.classList.contains("locked")===!0?(t.classList.add("locked"),e.value="",e.setAttribute("disabled",""),t.innerText=Joomla.Text._("JMODIFY")):(t.classList.remove("locked"),e.removeAttribute("disabled"),e.focus(),t.innerText=Joomla.Text._("JCANCEL"))})})}l.addEventListener("joomla:updated",c),l.addEventListener("DOMContentLoaded",c)})(document);

Binary file not shown.

View File

@ -0,0 +1,43 @@
/**
* @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 = () => {
document.querySelectorAll('.form-select-color-state').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,4 @@
/**
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/(()=>{const n=({target:s})=>{const e=s,a=parseInt(e.value,10);e.classList.remove("form-select-success","form-select-danger"),a===1?e.classList.add("form-select-success"):(a===0||a===-2)&&e.classList.add("form-select-danger")},t=()=>{document.querySelectorAll(".form-select-color-state").forEach(s=>{const e=parseInt(s.value,10);e===1?s.classList.add("form-select-success"):(e===0||e===-2)&&s.classList.add("form-select-danger"),s.addEventListener("change",n)}),document.removeEventListener("DOMContentLoaded",t,!0)};document.addEventListener("DOMContentLoaded",t,!0),document.addEventListener("joomla:updated",t,!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();
}
});
}
});

4
media/system/js/fields/tag.min.js vendored Normal file
View File

@ -0,0 +1,4 @@
/**
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/jQuery(document).ready(function(e){if(Joomla.getOptions("field-tag-custom")){var t=Joomla.getOptions("field-tag-custom"),a="#new#";e(t.selector+"_chosen input").keyup(function(r){var l;if(this.value&&this.value.length>=t.minTermLength&&(r.which===13||r.which===188)){var i=e(t.selector+"_chosen").find("li.active-result.highlighted").first();if(r.which===13&&i.text()!==""){var s=a+i.text();e(t.selector+" option").filter(function(){return e(this).val()==s}).remove(),l=e(t.selector+" option").filter(function(){return e(this).html()==i.text()}),l.attr("selected","selected")}else{var u=this.value;if(l=e(t.selector+" option").filter(function(){return e(this).html()==u}),l.text()!=="")l.attr("selected","selected");else{var c=e("<option>");c.text(this.value).val(a+this.value),c.attr("selected","selected"),e(t.selector).append(c)}}this.value="",e(t.selector).trigger("chosen:updated"),r.preventDefault()}})}});

Binary file not shown.

View File

@ -0,0 +1,731 @@
(function () {
'use strict';
/** 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.3.1',
/**
* 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'
document.querySelectorAll('form').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', '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', 'valid');
element.classList.add('form-control-danger', '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', 'form-control-success', 'remove');
element.classList.add('valid');
element.parentNode.classList.remove('has-danger', 'has-success');
// Remove message
if (message && 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 = 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 = 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);
/**
* Expose the classes to the global scope
* These will be removed in Joomla! 6.0
*/
window.JFormValidator = JFormValidator;
window.punycode = punycode;
})();

File diff suppressed because one or more lines are too long

Binary file not shown.

1847
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 => {
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.substring(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.
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);
});
});

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

@ -0,0 +1,4 @@
/**
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/Joomla.toggleInlineHelp=l=>{document.querySelectorAll(`div.${l}`).forEach(t=>{t.classList.toggle("d-none");const e=t.id,o=e?e.substring(0,e.length-5):null,n=o?document.getElementById(o):null,s=t.classList.contains("d-none");!o||!n||(s&&n.hasAttribute("aria-describedby")?n.removeAttribute("aria-describedby"):s||n.setAttribute("aria-describedby",e))})},document.querySelectorAll(".button-inlinehelp").forEach(l=>{var t;const e=(t=l.dataset.class)!=null?t:"hide-aware-inline-help";if(document.getElementsByClassName(e).length===0){l.classList.add("d-none");return}l.addEventListener("click",n=>{n.preventDefault(),Joomla.toggleInlineHelp(e)})});

Binary file not shown.

View File

@ -0,0 +1,103 @@
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const notForced = () => !('colorSchemeOs' in document.documentElement.dataset);
const lightColor = 'rgba(255, 255, 255, 0.8)';
const darkColor = 'rgba(0, 0, 0, 0.8)';
const getColorScheme = () => {
if (notForced()) {
return darkModeMediaQuery.matches ? darkColor : lightColor;
}
if ('colorScheme' in document.documentElement.dataset) {
return document.documentElement.dataset.colorScheme === 'dark' ? darkColor : lightColor;
}
return darkModeMediaQuery.matches ? darkColor : lightColor;
};
/**
* Creates a custom element with the default spinner of the Joomla logo
*/
class JoomlaCoreLoader extends HTMLElement {
get inline() {
return this.hasAttribute('inline');
}
set inline(value) {
if (value !== null) {
this.setAttribute('inline', '');
} else {
this.removeAttribute('inline');
}
}
get size() {
return this.getAttribute('size') || '345';
}
set size(value) {
this.setAttribute('size', value);
}
get color() {
return this.getAttribute('color');
}
set color(value) {
this.setAttribute('color', value);
}
static get observedAttributes() {
return ['color', 'size', 'inline'];
}
constructor() {
super();
this.attachShadow({
mode: 'open'
});
const template = document.createElement('template');
template.innerHTML = `
<style>:host{z-index:10000;justify-content:center;align-items:center;display:flex;overflow:hidden}:host(.fullscreen){width:100%;height:100%;position:fixed;top:0;left:0}:host(.fullscreen) svg{width:345px;height:345px}@media (prefers-reduced-motion:reduce){.joomla-spinner{animation:none!important}}</style>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150" width="${this.size}" height="${this.size}">
<style>@keyframes joomla-spinner{0%,28%,to{opacity:.30}20%{opacity:1}}.joomla-spinner{animation:joomla-spinner 1.6s infinite cubic-bezier(0,.15,1,.75)}
</style>
<path d="m27 75.5-2.9-2.9c-8.9-8.9-11.7-21.7-8.3-33C6.9 37.6.2 29.6.2 20.1c0-11.1 9-20 20-20 10 0 18.2 7.3 19.8 16.8 10.8-2.5 22.6.4 31.1 8.8l1.2 1.2-14.9 14.7-1.1-1.2c-4.8-4.8-12.6-4.8-17.4 0-4.8 4.8-4.8 12.6 0 17.4l2.9 2.9 14.8 14.8 15.6 15.6-14.8 14.8-15.6-15.7L27 75.5z" class="joomla-spinner" style="animation-delay:-1.2s" fill="#7ac143" />
<path d="m43.5 58.9 15.6-15.6 14.8-14.8 2.9-2.9c8.9-8.9 21.6-11.7 32.8-8.4C111 7.5 119.4 0 129.5 0c11.1 0 20 9 20 20 0 10.2-7.6 18.6-17.4 19.9 3.2 11.2.4 23.8-8.4 32.7l-1.2 1.2L107.7 59l1.1-1.1c4.8-4.8 4.8-12.6 0-17.4-4.8-4.8-12.5-4.8-17.4 0l-2.9 2.9-14.6 14.7-15.6 15.6-14.8-14.8z" class="joomla-spinner" style="animation-delay:-.8s" fill="#f9a541" />
<path d="M110.1 133.5c-11.4 3.5-24.2.7-33.2-8.3l-1.1-1.1 14.8-14.8 1.1 1.1c4.8 4.8 12.6 4.8 17.4 0 4.8-4.8 4.8-12.5 0-17.4l-2.9-2.9-14.9-14.6-15.6-15.7L90.5 45l15.6 15.6 14.8 14.8 2.9 2.9c8.5 8.5 11.4 20.5 8.8 31.3 9.7 1.4 17.2 9.7 17.2 19.8 0 11.1-9 20-20 20-9.8.2-17.9-6.7-19.7-15.9z" class="joomla-spinner" style="animation-delay:-.4s" fill="#f44321" />
<path d="m104.3 92-15.6 15.6-14.8 14.8-2.9 2.9c-8.5 8.5-20.6 11.4-31.5 8.7-2 8.9-10 15.5-19.5 15.5-11.1 0-20-9-20-20 0-9.5 6.6-17.4 15.4-19.5-2.8-11 .1-23.1 8.7-31.7l1.1-1.1L40 92l-1.1 1.1c-4.8 4.8-4.8 12.6 0 17.4 4.8 4.8 12.6 4.8 17.4 0l2.9-2.9L74 92.8l15.6-15.6L104.3 92z" class="joomla-spinner" fill="#5091cd" />
</svg>`;
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
this.style.backgroundColor = this.color ? this.color : getColorScheme();
darkModeMediaQuery.addEventListener('change', this.systemQuery);
if (!this.inline) {
this.classList.add('fullscreen');
}
}
disconnectedCallback() {
darkModeMediaQuery.removeEventListener('change', this.systemQuery);
}
attributeChangedCallback(attr, oldValue, newValue) {
switch (attr) {
case 'color':
if (newValue && newValue !== oldValue) {
this.style.backgroundColor = newValue;
}
break;
case 'size':
if (newValue && newValue !== oldValue) {
const svg = this.shadowRoot.querySelector('svg');
svg.setAttribute('width', newValue);
svg.setAttribute('height', newValue);
}
break;
case 'inline':
if (this.hasAttribute('inline')) {
this.classList.remove('fullscreen');
} else {
this.classList.add('fullscreen');
}
break;
}
}
systemQuery(event) {
if (!notForced() || this.color) return;
const color = event.matches === true ? darkColor : lightColor;
if (this.style.backgroundColor !== color) {
this.style.backgroundColor = color;
}
}
}
window.customElements.define('joomla-core-loader', JoomlaCoreLoader);

View File

@ -0,0 +1,10 @@
const darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),notForced=()=>!("colorSchemeOs"in document.documentElement.dataset),lightColor="rgba(255, 255, 255, 0.8)",darkColor="rgba(0, 0, 0, 0.8)",getColorScheme=()=>notForced()?darkModeMediaQuery.matches?darkColor:lightColor:"colorScheme"in document.documentElement.dataset?document.documentElement.dataset.colorScheme==="dark"?darkColor:lightColor:darkModeMediaQuery.matches?darkColor:lightColor;class JoomlaCoreLoader extends HTMLElement{get inline(){return this.hasAttribute("inline")}set inline(e){e!==null?this.setAttribute("inline",""):this.removeAttribute("inline")}get size(){return this.getAttribute("size")||"345"}set size(e){this.setAttribute("size",e)}get color(){return this.getAttribute("color")}set color(e){this.setAttribute("color",e)}static get observedAttributes(){return["color","size","inline"]}constructor(){super(),this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`
<style>:host{z-index:10000;justify-content:center;align-items:center;display:flex;overflow:hidden}:host(.fullscreen){width:100%;height:100%;position:fixed;top:0;left:0}:host(.fullscreen) svg{width:345px;height:345px}@media (prefers-reduced-motion:reduce){.joomla-spinner{animation:none!important}}</style>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150" width="${this.size}" height="${this.size}">
<style>@keyframes joomla-spinner{0%,28%,to{opacity:.30}20%{opacity:1}}.joomla-spinner{animation:joomla-spinner 1.6s infinite cubic-bezier(0,.15,1,.75)}
</style>
<path d="m27 75.5-2.9-2.9c-8.9-8.9-11.7-21.7-8.3-33C6.9 37.6.2 29.6.2 20.1c0-11.1 9-20 20-20 10 0 18.2 7.3 19.8 16.8 10.8-2.5 22.6.4 31.1 8.8l1.2 1.2-14.9 14.7-1.1-1.2c-4.8-4.8-12.6-4.8-17.4 0-4.8 4.8-4.8 12.6 0 17.4l2.9 2.9 14.8 14.8 15.6 15.6-14.8 14.8-15.6-15.7L27 75.5z" class="joomla-spinner" style="animation-delay:-1.2s" fill="#7ac143" />
<path d="m43.5 58.9 15.6-15.6 14.8-14.8 2.9-2.9c8.9-8.9 21.6-11.7 32.8-8.4C111 7.5 119.4 0 129.5 0c11.1 0 20 9 20 20 0 10.2-7.6 18.6-17.4 19.9 3.2 11.2.4 23.8-8.4 32.7l-1.2 1.2L107.7 59l1.1-1.1c4.8-4.8 4.8-12.6 0-17.4-4.8-4.8-12.5-4.8-17.4 0l-2.9 2.9-14.6 14.7-15.6 15.6-14.8-14.8z" class="joomla-spinner" style="animation-delay:-.8s" fill="#f9a541" />
<path d="M110.1 133.5c-11.4 3.5-24.2.7-33.2-8.3l-1.1-1.1 14.8-14.8 1.1 1.1c4.8 4.8 12.6 4.8 17.4 0 4.8-4.8 4.8-12.5 0-17.4l-2.9-2.9-14.9-14.6-15.6-15.7L90.5 45l15.6 15.6 14.8 14.8 2.9 2.9c8.5 8.5 11.4 20.5 8.8 31.3 9.7 1.4 17.2 9.7 17.2 19.8 0 11.1-9 20-20 20-9.8.2-17.9-6.7-19.7-15.9z" class="joomla-spinner" style="animation-delay:-.4s" fill="#f44321" />
<path d="m104.3 92-15.6 15.6-14.8 14.8-2.9 2.9c-8.5 8.5-20.6 11.4-31.5 8.7-2 8.9-10 15.5-19.5 15.5-11.1 0-20-9-20-20 0-9.5 6.6-17.4 15.4-19.5-2.8-11 .1-23.1 8.7-31.7l1.1-1.1L40 92l-1.1 1.1c-4.8 4.8-4.8 12.6 0 17.4 4.8 4.8 12.6 4.8 17.4 0l2.9-2.9L74 92.8l15.6-15.6L104.3 92z" class="joomla-spinner" fill="#5091cd" />
</svg>`,this.shadowRoot.appendChild(e.content.cloneNode(!0))}connectedCallback(){this.style.backgroundColor=this.color?this.color:getColorScheme(),darkModeMediaQuery.addEventListener("change",this.systemQuery),this.inline||this.classList.add("fullscreen")}disconnectedCallback(){darkModeMediaQuery.removeEventListener("change",this.systemQuery)}attributeChangedCallback(e,s,t){switch(e){case"color":t&&t!==s&&(this.style.backgroundColor=t);break;case"size":if(t&&t!==s){const i=this.shadowRoot.querySelector("svg");i.setAttribute("width",t),i.setAttribute("height",t)}break;case"inline":this.hasAttribute("inline")?this.classList.remove("fullscreen"):this.classList.add("fullscreen");break}}systemQuery(e){if(!notForced()||this.color)return;const s=e.matches===!0?darkColor:lightColor;this.style.backgroundColor!==s&&(this.style.backgroundColor=s)}}window.customElements.define("joomla-core-loader",JoomlaCoreLoader);

Binary file not shown.

View File

@ -0,0 +1,94 @@
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;
}
// Perform close when received any message
if ('closeOnMessage' in triggerEl.dataset) {
window.addEventListener('message', message => {
// Close when source Window match the iframe Window (for iframe) or current Window (for other popups)
if (message.source === (popup.getBodyContent().contentWindow || window)) {
popup.close();
}
});
}
// Perform post-close actions
popup.addEventListener('joomla-dialog:close', () => {
// Clean up after close
Joomla.Modal.setCurrent(null);
if (!cacheable) {
popup.destroy();
}
// Perform checkin request and page reload after close when needed
const {
checkinUrl
} = triggerEl.dataset;
const reloadOnClose = 'reloadOnClose' in triggerEl.dataset;
if (checkinUrl) {
Joomla.request({
url: checkinUrl,
method: 'POST',
promise: true
}).then(() => {
if (reloadOnClose) {
window.location.reload();
}
});
} else if (reloadOnClose) {
window.location.reload();
}
});
// Show the popup
popup.JoomlaDialogTrigger = triggerEl;
Joomla.Modal.setCurrent(popup);
popup.show();
});

View File

@ -0,0 +1,4 @@
import r 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
*/const c="[data-joomla-dialog]",i="joomlaDialog",d="joomlaDialogCache";document.addEventListener("click",l=>{const o=l.target.closest(c);if(!o)return;l.preventDefault();const n=!!o.dataset[d];if(n&&o.JoomlaDialogInstance){Joomla.Modal.setCurrent(o.JoomlaDialogInstance),o.JoomlaDialogInstance.show();return}const e=o.dataset[i]?JSON.parse(o.dataset[i]):{};o.nodeName==="A"&&(e.popupType||(e.popupType=o.hash?"inline":"iframe"),!e.src&&e.popupType==="iframe"?e.src=o.href:!e.src&&e.popupType==="inline"&&(e.src=o.hash)),delete e.popupTemplate;const a=new r(e);n&&(o.JoomlaDialogInstance=a),"closeOnMessage"in o.dataset&&window.addEventListener("message",t=>{t.source===(a.getBodyContent().contentWindow||window)&&a.close()}),a.addEventListener("joomla-dialog:close",()=>{Joomla.Modal.setCurrent(null),n||a.destroy();const{checkinUrl:t}=o.dataset,s="reloadOnClose"in o.dataset;t?Joomla.request({url:t,method:"POST",promise:!0}).then(()=>{s&&window.location.reload()}):s&&window.location.reload()}),a.JoomlaDialogTrigger=o,Joomla.Modal.setCurrent(a),a.show()});

Binary file not shown.

View File

@ -0,0 +1,623 @@
/**
* @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 = Joomla.Text._('JCLOSE', '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];
}
});
// Check class name
if (config.className) {
this.classList.add(...config.className.split(' '));
}
// Check dataset properties
if (config.data) {
Object.entries(config.data).forEach(([k, v]) => {
this.dataset[k] = v;
});
}
}
/**
* 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 };

8
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,4 @@
/**
* @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=""}disconnectedCallback(){this.innerHTML=""}connectedCallback(){if(this.base=`${this.getAttribute("base")}/`,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"))}`),[].slice.call(this.attributes).forEach((t,e)=>{const{nodeName:i}=this.attributes.item(e);if(i&&["is-link","is-email","first","last","text"].indexOf(i)===-1){const{nodeValue:s}=this.attributes.item(e);this.newElement.setAttribute(i,s)}})):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),e=>`%${`00${e.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,4 @@
/**
* @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{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 e=this.form||"adminForm";if(this.formElement=document.getElementById(e),this.disabled=!1,this.listSelection&&this.setDisabled(!0),this.listSelection){if(!this.formElement)throw new Error(`The form "${e}" 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:e}){this.setDisabled(e.value<1)}setDisabled(e){this.disabled=!!e,this.buttonElement&&(this.disabled?this.buttonElement.nodeName==="BUTTON"?this.buttonElement.disabled=!0:this.buttonElement.classList.add("disabled"):this.buttonElement.nodeName==="BUTTON"?this.buttonElement.disabled=!1:this.buttonElement.classList.remove("disabled"))}executeTask(){return this.disabled?!1:this.confirmMessage&&!this.confirmationReceived?(import("joomla.dialog").then(e=>e.default.confirm(this.confirmMessage,Joomla.Text._("WARNING","Warning"))).then(e=>{e&&(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