primo commit
This commit is contained in:
793
media/system/js/core.js
Normal file
793
media/system/js/core.js
Normal 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
4
media/system/js/core.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/core.min.js.gz
Normal file
BIN
media/system/js/core.min.js.gz
Normal file
Binary file not shown.
175
media/system/js/draggable.js
Normal file
175
media/system/js/draggable.js
Normal 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
4
media/system/js/draggable.min.js
vendored
Normal 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)})}
|
||||
BIN
media/system/js/draggable.min.js.gz
Normal file
BIN
media/system/js/draggable.min.js.gz
Normal file
Binary file not shown.
175
media/system/js/editors/editor-api.js
Normal file
175
media/system/js/editors/editor-api.js
Normal 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 };
|
||||
4
media/system/js/editors/editor-api.min.js
vendored
Normal file
4
media/system/js/editors/editor-api.min.js
vendored
Normal 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};
|
||||
BIN
media/system/js/editors/editor-api.min.js.gz
Normal file
BIN
media/system/js/editors/editor-api.min.js.gz
Normal file
Binary file not shown.
137
media/system/js/editors/editor-decorator.js
Normal file
137
media/system/js/editors/editor-decorator.js
Normal 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 };
|
||||
4
media/system/js/editors/editor-decorator.min.js
vendored
Normal file
4
media/system/js/editors/editor-decorator.min.js
vendored
Normal 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};
|
||||
BIN
media/system/js/editors/editor-decorator.min.js.gz
Normal file
BIN
media/system/js/editors/editor-decorator.min.js.gz
Normal file
Binary file not shown.
102
media/system/js/editors/editors.js
Normal file
102
media/system/js/editors/editors.js
Normal 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);
|
||||
}
|
||||
});
|
||||
4
media/system/js/editors/editors.min.js
vendored
Normal file
4
media/system/js/editors/editors.min.js
vendored
Normal 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)});
|
||||
BIN
media/system/js/editors/editors.min.js.gz
Normal file
BIN
media/system/js/editors/editors.min.js.gz
Normal file
Binary file not shown.
@ -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);
|
||||
2
media/system/js/fields/calendar-locales/date/gregorian/date-helper.min.js
vendored
Normal file
2
media/system/js/fields/calendar-locales/date/gregorian/date-helper.min.js
vendored
Normal 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);
|
||||
Binary file not shown.
@ -0,0 +1,658 @@
|
||||
!(function(Date){
|
||||
'use strict';
|
||||
|
||||
var localNumbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
||||
|
||||
/** BEGIN: DATE OBJECT PATCHES **/
|
||||
/** Adds the number of days array to the Date object. */
|
||||
Date.gregorian_MD = [31,28,31,30,31,30,31,31,30,31,30,31];
|
||||
Date.local_MD = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
|
||||
|
||||
/** Constants used for time computations */
|
||||
Date.SECOND = 1000 /* milliseconds */;
|
||||
Date.MINUTE = 60 * Date.SECOND;
|
||||
Date.HOUR = 60 * Date.MINUTE;
|
||||
Date.DAY = 24 * Date.HOUR;
|
||||
Date.WEEK = 7 * Date.DAY;
|
||||
|
||||
/** MODIFY ONLY THE MARKED PARTS OF THE METHODS **/
|
||||
/************ START *************/
|
||||
/** INTERFACE METHODS FOR THE CALENDAR PICKER **/
|
||||
|
||||
/********************** *************************/
|
||||
/**************** SETTERS ***********************/
|
||||
/********************** *************************/
|
||||
|
||||
/** Sets the date for the current date without h/m/s. */
|
||||
Date.prototype.setLocalDateOnly = function (dateType, date) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return '';
|
||||
} else {
|
||||
var tmp = new Date(date);
|
||||
this.setDate(1);
|
||||
this.setFullYear(tmp.getFullYear());
|
||||
this.setMonth(tmp.getMonth());
|
||||
this.setDate(tmp.getDate());
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the full date for the current date. */
|
||||
Date.prototype.setLocalDate = function (dateType, d) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.setJalaliDate(d);
|
||||
} else {
|
||||
return this.setDate(d);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the month for the current date. */
|
||||
Date.prototype.setLocalMonth = function (dateType, m, d) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.setJalaliMonth(m, d);
|
||||
} else {
|
||||
if (d == undefined) this.getDate();
|
||||
return this.setMonth(m);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the year for the current date. */
|
||||
Date.prototype.setOtherFullYear = function(dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
var date = new Date(this);
|
||||
date.setLocalFullYear(y);
|
||||
if (date.getLocalMonth('jalali') != this.getLocalMonth('jalali')) this.setLocalDate('jalali', 29);
|
||||
return this.setLocalFullYear('jalali', y);
|
||||
} else {
|
||||
var date = new Date(this);
|
||||
date.setFullYear(y);
|
||||
if (date.getMonth() != this.getMonth()) this.setDate(28);
|
||||
return this.setUTCFullYear(y);
|
||||
}
|
||||
};
|
||||
|
||||
/** Sets the year for the current date. */
|
||||
Date.prototype.setLocalFullYear = function (dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.setJalaliFullYear(y);
|
||||
} else {
|
||||
var date = new Date(this);
|
||||
date.setFullYear(y);
|
||||
if (date.getMonth() != this.getMonth()) this.setDate(28);
|
||||
return this.setFullYear(y);
|
||||
}
|
||||
};
|
||||
|
||||
/********************** *************************/
|
||||
/**************** GETTERS ***********************/
|
||||
/********************** *************************/
|
||||
|
||||
/** The number of days per week **/
|
||||
Date.prototype.getLocalWeekDays = function (dateType, y) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return 6;
|
||||
} else {
|
||||
return 6; // 7 days per week
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the year for the current date. */
|
||||
Date.prototype.getOtherFullYear = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.getJalaliFullYear();
|
||||
} else {
|
||||
return this.getFullYear();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the year for the current date. */
|
||||
Date.prototype.getLocalFullYear = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.getJalaliFullYear();
|
||||
} else {
|
||||
return this.getFullYear();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the month the date. */
|
||||
Date.prototype.getLocalMonth = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.getJalaliMonth();
|
||||
} else {
|
||||
return this.getMonth();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the date. */
|
||||
Date.prototype.getLocalDate = function (dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return this.getJalaliDate();
|
||||
} else {
|
||||
return this.getDate();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of day in the year. */
|
||||
Date.prototype.getLocalDay = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
return this.getJalaliDay();
|
||||
} else {
|
||||
return this.getDay();
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of days in the current month */
|
||||
Date.prototype.getLocalMonthDays = function(dateType, month) {
|
||||
if (dateType != 'gregorian') {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
var year = this.getLocalFullYear('jalali');
|
||||
if (typeof month == "undefined") {
|
||||
month = this.getLocalMonth('jalali');
|
||||
}
|
||||
if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
|
||||
return 29;
|
||||
} else {
|
||||
return Date.local_MD[month];
|
||||
}
|
||||
} else {
|
||||
var year = this.getFullYear();
|
||||
if (typeof month == "undefined") {
|
||||
month = this.getMonth();
|
||||
}
|
||||
if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
|
||||
return 29;
|
||||
} else {
|
||||
return Date.gregorian_MD[month];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the week number for the current date. */
|
||||
Date.prototype.getLocalWeekNumber = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
|
||||
var DoW = d.getDay();
|
||||
d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
|
||||
var ms = d.valueOf(); // GMT
|
||||
d.setMonth(0);
|
||||
d.setDate(4); // Thu in Week 1
|
||||
return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
|
||||
} else {
|
||||
var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
|
||||
var DoW = d.getDay();
|
||||
d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
|
||||
var ms = d.valueOf(); // GMT
|
||||
d.setMonth(0);
|
||||
d.setDate(4); // Thu in Week 1
|
||||
return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of day in the year. */
|
||||
Date.prototype.getLocalDayOfYear = function(dateType) {
|
||||
if (dateType != 'gregorian') {
|
||||
var now = new Date(this.getOtherFullYear(dateType), this.getLocalMonth(dateType), this.getLocalDate(dateType), 0, 0, 0);
|
||||
var then = new Date(this.getOtherFullYear(dateType), 0, 0, 0, 0, 0);
|
||||
var time = now - then;
|
||||
return Math.floor(time / Date.DAY);
|
||||
} else {
|
||||
var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
|
||||
var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
|
||||
var time = now - then;
|
||||
return Math.floor(time / Date.DAY);
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the number of days in the current month */
|
||||
Date.prototype.getMonthDays = function(month) {
|
||||
var year = this.getFullYear();
|
||||
if (typeof month == "undefined") {
|
||||
month = this.getMonth();
|
||||
}
|
||||
if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
|
||||
return 29;
|
||||
} else {
|
||||
if (Date.dateType != 'gregorian') {
|
||||
return Date.local_MD[month];
|
||||
} else {
|
||||
return Date.gregorian_MD[month];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Checks date and time equality */
|
||||
Date.prototype.equalsTo = function(date) {
|
||||
return ((this.getFullYear() == date.getFullYear()) &&
|
||||
(this.getMonth() == date.getMonth()) &&
|
||||
(this.getDate() == date.getDate()) &&
|
||||
(this.getHours() == date.getHours()) &&
|
||||
(this.getMinutes() == date.getMinutes()));
|
||||
};
|
||||
|
||||
/** Converts foreign date to gregorian date. */
|
||||
Date.localCalToGregorian = function(y, m, d) {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return JalaliDate.jalaliToGregorian(y, m, d);
|
||||
};
|
||||
|
||||
/** Converts gregorian date to foreign date. */
|
||||
Date.gregorianToLocalCal = function(y, m, d) {
|
||||
/** Modify to match the current calendar when overriding **/
|
||||
return JalaliDate.gregorianToJalali(y, m, d);
|
||||
};
|
||||
|
||||
/** Method to convert numbers from local symbols to English numbers. */
|
||||
Date.numbersToIso = function(str) {
|
||||
var i, nums = [0,1,2,3,4,5,6,7,8,9];
|
||||
str = str.toString();
|
||||
|
||||
|
||||
for (i = 0; i < nums.length; i++) {
|
||||
str = str.replace(new RegExp(localNumbers[i], 'g'), nums[i]);
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
/** Method to convert numbers to local symbols. */
|
||||
Date.convertNumbers = function(str) {
|
||||
str = str.toString();
|
||||
|
||||
for (var i = 0, l = localNumbers.length; i < l; i++) {
|
||||
str = str.replace(new RegExp(i, 'g'), localNumbers[i]);
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
/** Translates to english numbers a string. */
|
||||
Date.toEnglish = function(str) {
|
||||
str = this.toString();
|
||||
var nums = [0,1,2,3,4,5,6,7,8,9];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
str = str.replace(new RegExp(nums[i], 'g'), i);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
/** Order the days from Gergorian to calendar order */
|
||||
Date.monthsToLocalOrder = function(months, dateType) {
|
||||
if (dateType === 'jalali'){
|
||||
months.push(months.shift()); // January to the end
|
||||
months.push(months.shift()); // February to the end
|
||||
|
||||
return months;
|
||||
} else {
|
||||
return months;
|
||||
}
|
||||
};
|
||||
|
||||
/** INTERFACE METHODS FOR THE CALENDAR PICKER **/
|
||||
/************* END **************/
|
||||
|
||||
/** Prints the date in a string according to the given format. */
|
||||
Date.prototype.print = function (str, dateType, translate, localStrings) {
|
||||
/** Handle calendar type **/
|
||||
if (typeof dateType !== 'string') str = '';
|
||||
if (!dateType) dateType = 'gregorian';
|
||||
|
||||
/** Handle wrong format **/
|
||||
if (typeof str !== 'string') str = '';
|
||||
if (!str) return '';
|
||||
|
||||
|
||||
if (this.getLocalDate(dateType) == 'NaN' || !this.getLocalDate(dateType)) return '';
|
||||
var m = this.getLocalMonth(dateType);
|
||||
var d = this.getLocalDate(dateType);
|
||||
var y = this.getLocalFullYear(dateType);
|
||||
var wn = this.getLocalWeekNumber(dateType);
|
||||
var w = this.getLocalDay(dateType);
|
||||
var s = {};
|
||||
var hr = this.getHours();
|
||||
var pm = (hr >= 12);
|
||||
var ir = (pm) ? (hr - 12) : hr;
|
||||
var dy = this.getLocalDayOfYear(dateType);
|
||||
if (ir == 0)
|
||||
ir = 12;
|
||||
var min = this.getMinutes();
|
||||
var sec = this.getSeconds();
|
||||
s["%a"] = localStrings.shortDays[w]; // abbreviated weekday name
|
||||
s["%A"] = localStrings.days[w]; // full weekday name
|
||||
s["%b"] = localStrings.shortMonths[m]; // abbreviated month name
|
||||
s["%B"] = localStrings.months[m]; // full month name
|
||||
// FIXME: %c : preferred date and time representation for the current locale
|
||||
s["%C"] = 1 + Math.floor(y / 100); // the century number
|
||||
s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
|
||||
s["%e"] = d; // the day of the month (range 1 to 31)
|
||||
// FIXME: %D : american date style: %m/%d/%y
|
||||
// FIXME: %E, %F, %G, %g, %h (man strftime)
|
||||
s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
|
||||
s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
|
||||
s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
|
||||
s["%k"] = hr; // hour, range 0 to 23 (24h format)
|
||||
s["%l"] = ir; // hour, range 1 to 12 (12h format)
|
||||
s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
|
||||
s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
|
||||
s["%n"] = "\n"; // a newline character
|
||||
s["%p"] = pm ? localStrings.pm.toUpperCase() : localStrings.am.toUpperCase();
|
||||
s["%P"] = pm ? localStrings.pm : localStrings.am;
|
||||
// FIXME: %r : the time in am/pm notation %I:%M:%S %p
|
||||
// FIXME: %R : the time in 24-hour notation %H:%M
|
||||
s["%s"] = Math.floor(this.getTime() / 1000);
|
||||
s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
|
||||
s["%t"] = "\t"; // a tab character
|
||||
// FIXME: %T : the time in 24-hour notation (%H:%M:%S)
|
||||
s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
|
||||
s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
|
||||
s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
|
||||
// FIXME: %x : preferred date representation for the current locale without the time
|
||||
// FIXME: %X : preferred time representation for the current locale without the date
|
||||
s["%y"] = ('' + y).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);
|
||||
2
media/system/js/fields/calendar-locales/date/jalali/date-helper.min.js
vendored
Normal file
2
media/system/js/fields/calendar-locales/date/jalali/date-helper.min.js
vendored
Normal 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);
|
||||
Binary file not shown.
1224
media/system/js/fields/calendar.js
Normal file
1224
media/system/js/fields/calendar.js
Normal file
File diff suppressed because it is too large
Load Diff
7
media/system/js/fields/calendar.min.js
vendored
Normal file
7
media/system/js/fields/calendar.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/calendar.min.js.gz
Normal file
BIN
media/system/js/fields/calendar.min.js.gz
Normal file
Binary file not shown.
38
media/system/js/fields/color-field-adv-init.js
Normal file
38
media/system/js/fields/color-field-adv-init.js
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
!(function(document, $) {
|
||||
"use strict";
|
||||
|
||||
function initMinicolorsField (event) {
|
||||
$(event.target).find('.minicolors').each(function() {
|
||||
$(this).minicolors({
|
||||
control: $(this).attr('data-control') || 'hue',
|
||||
format: $(this).attr('data-validate') === 'color'
|
||||
? 'hex'
|
||||
: ($(this).attr('data-format') === 'rgba'
|
||||
? 'rgb'
|
||||
: $(this).attr('data-format'))
|
||||
|| 'hex',
|
||||
keywords: $(this).attr('data-keywords') || '',
|
||||
opacity: $(this).attr('data-format') === 'rgba',
|
||||
position: $(this).attr('data-position') || 'default',
|
||||
swatches: $(this).attr('data-colors') ? $(this).attr('data-colors').split(",") : [],
|
||||
theme: 'bootstrap'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize at an initial page load
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", initMinicolorsField);
|
||||
|
||||
/**
|
||||
* Initialize when a part of the page was updated
|
||||
*/
|
||||
document.addEventListener("joomla:updated", initMinicolorsField);
|
||||
|
||||
})(document, jQuery);
|
||||
4
media/system/js/fields/color-field-adv-init.min.js
vendored
Normal file
4
media/system/js/fields/color-field-adv-init.min.js
vendored
Normal 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);
|
||||
BIN
media/system/js/fields/color-field-adv-init.min.js.gz
Normal file
BIN
media/system/js/fields/color-field-adv-init.min.js.gz
Normal file
Binary file not shown.
599
media/system/js/fields/joomla-field-color-slider.js
Normal file
599
media/system/js/fields/joomla-field-color-slider.js
Normal file
@ -0,0 +1,599 @@
|
||||
/**
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["rgbToHex", "hslToRgb"] }] */
|
||||
|
||||
(document => {
|
||||
|
||||
/**
|
||||
* Regex for hex values e.g. #FF3929
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const hexRegex = /^#([a-z0-9]{1,2})([a-z0-9]{1,2})([a-z0-9]{1,2})$/i;
|
||||
|
||||
/**
|
||||
* Regex for rgb values e.g. rgba(255, 0, 24, 0.5);
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const rgbRegex = /^rgba?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)(?:[\D]+([0-9](?:.\d+)?))?\)$/i;
|
||||
|
||||
/**
|
||||
* Regex for hsl values e.g. hsl(255,0,24);
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const hslRegex = /^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$/i;
|
||||
|
||||
/**
|
||||
* Regex for saturation and lightness of hsl - only accepts 1 or 0 or 0.4 or 40
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const hslNumberRegex = /^(([0-1])|(0\\.[0-9]+)|([0-9]{1,2})|(100))$/;
|
||||
|
||||
/**
|
||||
* Regex for hue values - one to three numbers
|
||||
* @type {RegExp}
|
||||
*/
|
||||
const hueRegex = /^[0-9]{1,3}$/;
|
||||
|
||||
/**
|
||||
* Creates a slider for the color values hue, saturation and light.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
class JoomlaFieldColorSlider {
|
||||
/**
|
||||
* @param {HTMLElement} element
|
||||
*/
|
||||
constructor(element) {
|
||||
// Elements
|
||||
this.messageSpan = element.querySelector('.form-control-feedback');
|
||||
this.mainInput = element.querySelector('.color-input');
|
||||
this.input = element.querySelector('#slider-input');
|
||||
this.sliders = element.querySelectorAll('.color-slider');
|
||||
this.hueSlider = element.querySelector('#hue-slider');
|
||||
this.saturationSlider = element.querySelector('#saturation-slider');
|
||||
this.lightSlider = element.querySelector('#light-slider');
|
||||
this.alphaSlider = element.querySelector('#alpha-slider');
|
||||
|
||||
// Attributes
|
||||
this.color = element.dataset.color || '';
|
||||
this.default = element.dataset.default || '';
|
||||
this.format = this.input.dataset.format || 'hex';
|
||||
this.saveFormat = this.mainInput.dataset.format || 'hex';
|
||||
this.preview = element.dataset.preview === 'true';
|
||||
this.setAlpha = this.format === 'hsla' || this.format === 'rgba';
|
||||
this.hue = 360;
|
||||
this.saturation = 1;
|
||||
this.light = 1;
|
||||
this.alpha = 1;
|
||||
this.defaultHsl = [this.hue, this.saturation, this.light, this.alpha];
|
||||
this.setInitValue();
|
||||
this.setBackground();
|
||||
|
||||
// Hide preview field, when selected value should not be visible
|
||||
if (!this.preview) {
|
||||
this.input.classList.add('hidden');
|
||||
} else {
|
||||
this.setInputPattern();
|
||||
}
|
||||
|
||||
// Always hide main input field (value saved in database)
|
||||
this.mainInput.classList.add('hidden');
|
||||
Array.prototype.forEach.call(this.sliders, slider => {
|
||||
slider.addEventListener('change', () => this.updateValue(slider));
|
||||
});
|
||||
this.input.addEventListener('change', () => this.changeInput(this.input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selected value into input field and set it as its background-color.
|
||||
*/
|
||||
updateValue(slider) {
|
||||
this.showError('');
|
||||
const hsl = this.getSliderValueAsHsl(slider.value, slider.dataset.type);
|
||||
const rgb = this.hslToRgb(hsl);
|
||||
[this.hue, this.saturation, this.light, this.alpha] = hsl;
|
||||
this.input.style.border = `2px solid ${this.getRgbString(rgb)}`;
|
||||
this.setSliderValues(hsl, slider.dataset.type);
|
||||
this.setInputValue(hsl);
|
||||
this.setBackground(slider);
|
||||
}
|
||||
|
||||
/**
|
||||
* React on user changing input value
|
||||
*
|
||||
* @param {HTMLElement} inputField
|
||||
*/
|
||||
changeInput(inputField) {
|
||||
let hsl = [this.hue, this.saturation, this.light, this.alpha];
|
||||
if (!inputField.value) {
|
||||
this.mainInput.value = '';
|
||||
this.showError('');
|
||||
return;
|
||||
}
|
||||
if (!this.checkValue(inputField.value)) {
|
||||
this.showError('JFIELD_COLOR_ERROR_WRONG_FORMAT');
|
||||
this.setInputValue(this.defaultHsl);
|
||||
} else {
|
||||
this.showError('');
|
||||
switch (this.format) {
|
||||
case 'hue':
|
||||
hsl[0] = inputField.value;
|
||||
this.hue = inputField.value;
|
||||
break;
|
||||
case 'saturation':
|
||||
hsl[1] = inputField.value;
|
||||
this.saturation = inputField.value;
|
||||
break;
|
||||
case 'light':
|
||||
hsl[2] = inputField.value;
|
||||
this.light = inputField.value;
|
||||
break;
|
||||
case 'alpha':
|
||||
hsl[3] = inputField.value;
|
||||
this.alpha = inputField.value;
|
||||
break;
|
||||
default:
|
||||
hsl = this.getHsl(inputField.value);
|
||||
}
|
||||
this.setSliderValues(hsl);
|
||||
this.setInputValue(hsl, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check validity of value
|
||||
*
|
||||
* @param {number|string} value to check
|
||||
* @param {string=false} format for which the value gets tested
|
||||
* @returns {boolean}
|
||||
*/
|
||||
checkValue(value, format) {
|
||||
const test = format || this.format;
|
||||
switch (test) {
|
||||
case 'hue':
|
||||
return value <= 360 && hueRegex.test(value);
|
||||
case 'saturation':
|
||||
case 'light':
|
||||
case 'alpha':
|
||||
return hslNumberRegex.test(value);
|
||||
case 'hsl':
|
||||
case 'hsla':
|
||||
return hslRegex.test(value);
|
||||
case 'hex':
|
||||
return hexRegex.test(value);
|
||||
case 'rgb':
|
||||
case 'rgba':
|
||||
return rgbRegex.test(value);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set validation pattern on input field
|
||||
*/
|
||||
setInputPattern() {
|
||||
let pattern;
|
||||
|
||||
// RegExp has '/' at start and end
|
||||
switch (this.format) {
|
||||
case 'hue':
|
||||
pattern = hueRegex.source.slice(1, -1);
|
||||
break;
|
||||
case 'saturation':
|
||||
case 'light':
|
||||
case 'alpha':
|
||||
pattern = hslNumberRegex.source.slice(1, -1);
|
||||
break;
|
||||
case 'hsl':
|
||||
case 'hsla':
|
||||
pattern = hslRegex.source.slice(1, -1);
|
||||
break;
|
||||
case 'rgb':
|
||||
pattern = rgbRegex.source.slice(1, -1);
|
||||
break;
|
||||
case 'hex':
|
||||
default:
|
||||
pattern = hexRegex.source.slice(1, -1);
|
||||
}
|
||||
this.input.setAttribute('pattern', pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set linear gradient for slider background
|
||||
* @param {HTMLInputElement} [exceptSlider]
|
||||
*/
|
||||
setBackground(exceptSlider) {
|
||||
Array.prototype.forEach.call(this.sliders, slider => {
|
||||
// Jump over changed slider
|
||||
if (exceptSlider === slider) {
|
||||
return;
|
||||
}
|
||||
let colors = [];
|
||||
let endValue = 100;
|
||||
|
||||
// Longer start color so slider selection matches displayed colors
|
||||
colors.push(this.getSliderValueAsRgb(0, slider.dataset.type));
|
||||
if (slider.dataset.type === 'hue') {
|
||||
const steps = Math.floor(360 / 20);
|
||||
endValue = 360;
|
||||
for (let i = 0; i <= 360; i += steps) {
|
||||
colors.push(this.getSliderValueAsRgb(i, slider.dataset.type));
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i <= 100; i += 10) {
|
||||
colors.push(this.getSliderValueAsRgb(i, slider.dataset.type));
|
||||
}
|
||||
}
|
||||
|
||||
// Longer end color so slider selection matches displayed colors
|
||||
colors.push(this.getSliderValueAsRgb(endValue, slider.dataset.type));
|
||||
colors = colors.map(value => this.getRgbString(value));
|
||||
slider.style.background = `linear-gradient(90deg, ${colors.join(',')})`;
|
||||
slider.style.webkitAppearance = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert given color into hue, saturation and light
|
||||
*/
|
||||
setInitValue() {
|
||||
// The initial value can be also a color defined in css
|
||||
const cssValue = window.getComputedStyle(this.input).getPropertyValue(this.default);
|
||||
this.default = cssValue || this.default;
|
||||
if (this.color === '' || typeof this.color === 'undefined') {
|
||||
// Unable to get hsl with empty value
|
||||
this.input.value = '';
|
||||
this.mainInput.value = '';
|
||||
return;
|
||||
}
|
||||
const value = this.checkValue(this.color, this.saveFormat) ? this.color : this.default;
|
||||
if (!value) {
|
||||
this.showError('JFIELD_COLOR_ERROR_NO_COLOUR');
|
||||
return;
|
||||
}
|
||||
let hsl = [];
|
||||
// When given value is a number, use it as defined format and get rest from default value
|
||||
if (/^[0-9]+$/.test(value)) {
|
||||
hsl = this.default && this.getHsl(this.default);
|
||||
if (this.format === 'hue') {
|
||||
hsl[0] = value;
|
||||
}
|
||||
if (this.format === 'saturation') {
|
||||
hsl[1] = value > 1 ? value / 100 : value;
|
||||
}
|
||||
if (this.format === 'light') {
|
||||
hsl[2] = value > 1 ? value / 100 : value;
|
||||
}
|
||||
if (this.format === 'alpha') {
|
||||
hsl[3] = value > 1 ? value / 100 : value;
|
||||
}
|
||||
} else {
|
||||
hsl = this.getHsl(value);
|
||||
}
|
||||
[this.hue, this.saturation, this.light] = hsl;
|
||||
this.alpha = hsl[4] || this.alpha;
|
||||
this.defaultHsl = this.default ? this.getHsl(this.default) : hsl;
|
||||
this.setSliderValues(hsl);
|
||||
this.setInputValue(hsl);
|
||||
this.input.style.border = `2px solid ${this.getRgbString(this.hslToRgb(hsl))}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert message into error message span
|
||||
* Message gets handled with Joomla.Text or as empty string
|
||||
*
|
||||
* @param {string} msg
|
||||
*/
|
||||
showError(msg) {
|
||||
this.messageSpan.innerText = msg ? Joomla.Text._(msg) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert value into HSLa e.g. #003E7C => [210, 100, 24]
|
||||
* @param {array|number|string} value
|
||||
* @returns {array}
|
||||
*/
|
||||
getHsl(value) {
|
||||
let hsl = [];
|
||||
if (Array.isArray(value)) {
|
||||
hsl = value;
|
||||
} else if (hexRegex.test(value)) {
|
||||
hsl = this.hexToHsl(value);
|
||||
} else if (rgbRegex.test(value)) {
|
||||
hsl = this.rgbToHsl(value);
|
||||
} else if (hslRegex.test(value)) {
|
||||
const matches = value.match(hslRegex);
|
||||
hsl = [matches[1], matches[2], matches[3], matches[4]];
|
||||
} else {
|
||||
this.showError('JFIELD_COLOR_ERROR_CONVERT_HSL');
|
||||
return this.defaultHsl;
|
||||
}
|
||||
|
||||
// Convert saturation etc. values from e.g. 40 to 0.4
|
||||
let i;
|
||||
for (i = 1; i < hsl.length; i += 1) {
|
||||
hsl[i] = hsl[i] > 1 ? hsl[i] / 100 : hsl[i];
|
||||
}
|
||||
return hsl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HSL value from color slider value
|
||||
* @params {int} value convert this value
|
||||
* @params {string} type type of value: hue, saturation, light or alpha
|
||||
* @returns array
|
||||
*/
|
||||
getSliderValueAsHsl(value, type) {
|
||||
let h = this.hue;
|
||||
let s = this.saturation;
|
||||
let l = this.light;
|
||||
let a = this.alpha;
|
||||
switch (type) {
|
||||
case 'alpha':
|
||||
a = value;
|
||||
break;
|
||||
case 'saturation':
|
||||
s = value;
|
||||
break;
|
||||
case 'light':
|
||||
l = value;
|
||||
break;
|
||||
case 'hue':
|
||||
default:
|
||||
h = value;
|
||||
}
|
||||
|
||||
// Percentage light and saturation
|
||||
if (l > 1) {
|
||||
l /= 100;
|
||||
}
|
||||
if (s > 1) {
|
||||
s /= 100;
|
||||
}
|
||||
if (a > 1) {
|
||||
a /= 100;
|
||||
}
|
||||
return [h, s, l, a];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates RGB value from color slider value
|
||||
* @params {int} value convert this value
|
||||
* @params {string} type type of value: hue, saturation, light or alpha
|
||||
* @returns array
|
||||
*/
|
||||
getSliderValueAsRgb(value, type) {
|
||||
return this.hslToRgb(this.getSliderValueAsHsl(value, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value in all sliders
|
||||
* @param {array} [hsla]
|
||||
* @param {string} [except]
|
||||
*/
|
||||
setSliderValues([h, s, l, a], except) {
|
||||
if (this.hueSlider && except !== 'hue') {
|
||||
this.hueSlider.value = Math.round(h);
|
||||
}
|
||||
if (this.saturationSlider && except !== 'saturation') {
|
||||
this.saturationSlider.value = Math.round(s * 100);
|
||||
}
|
||||
if (this.lightSlider && except !== 'light') {
|
||||
this.lightSlider.value = Math.round(l * 100);
|
||||
}
|
||||
if (a && this.alphaSlider && except !== 'alpha') {
|
||||
this.alphaSlider.value = Math.round(a * 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value in text input fields depending on their format
|
||||
* @param {array} hsl
|
||||
* @param {boolean=false} onlyMain indicates to change mainInput only
|
||||
*/
|
||||
setInputValue(hsl, onlyMain) {
|
||||
const inputs = [this.mainInput];
|
||||
if (!onlyMain) {
|
||||
inputs.push(this.input);
|
||||
}
|
||||
inputs.forEach(input => {
|
||||
let value;
|
||||
switch (input.dataset.format) {
|
||||
case 'hsl':
|
||||
value = this.getHslString(hsl);
|
||||
break;
|
||||
case 'hsla':
|
||||
value = this.getHslString(hsl, true);
|
||||
break;
|
||||
case 'rgb':
|
||||
value = this.getRgbString(this.hslToRgb(hsl));
|
||||
break;
|
||||
case 'rgba':
|
||||
value = this.getRgbString(this.hslToRgb(hsl), true);
|
||||
break;
|
||||
case 'hex':
|
||||
value = this.rgbToHex(this.hslToRgb(hsl));
|
||||
break;
|
||||
case 'alpha':
|
||||
value = Math.round(hsl[3] * 100);
|
||||
break;
|
||||
case 'saturation':
|
||||
value = Math.round(hsl[1] * 100);
|
||||
break;
|
||||
case 'light':
|
||||
value = Math.round(hsl[2] * 100);
|
||||
break;
|
||||
case 'hue':
|
||||
default:
|
||||
value = Math.round(hsl[0]);
|
||||
break;
|
||||
}
|
||||
input.value = value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Put RGB values into a string like 'rgb(<R>, <G>, <B>)'
|
||||
* @params {array} rgba
|
||||
* @params {boolean=false} withAlpha
|
||||
* @return {string}
|
||||
*/
|
||||
getRgbString([r, g, b, a], withAlpha) {
|
||||
if (withAlpha || this.setAlpha) {
|
||||
const alpha = typeof a === 'undefined' ? this.alpha : a;
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put HSL values into a string like 'hsl(<H>, <S>%, <L>%, <a>)'
|
||||
* @params {array} values
|
||||
* @params {boolean=false} withAlpha
|
||||
* @return {string}
|
||||
*/
|
||||
getHslString(values, withAlpha) {
|
||||
let [h, s, l, a] = values;
|
||||
s *= 100;
|
||||
l *= 100;
|
||||
[h, s, l] = [h, s, l].map(value => Math.round(value));
|
||||
if (withAlpha || this.setAlpha) {
|
||||
a = a || this.alpha;
|
||||
return `hsla(${h}, ${s}%, ${l}%, ${a})`;
|
||||
}
|
||||
return `hsl(${h}, ${s}%, ${l}%)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hsl values out of hex
|
||||
* @param {array} rgb
|
||||
* @return {string}
|
||||
*/
|
||||
rgbToHex(rgb) {
|
||||
let r = rgb[0].toString(16).toUpperCase();
|
||||
let g = rgb[1].toString(16).toUpperCase();
|
||||
let b = rgb[2].toString(16).toUpperCase();
|
||||
|
||||
// Double value for hex with '#' and 6 chars
|
||||
r = r.length === 1 ? `${r}${r}` : r;
|
||||
g = g.length === 1 ? `${g}${g}` : g;
|
||||
b = b.length === 1 ? `${b}${b}` : b;
|
||||
return `#${r}${g}${b}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hsl values out of rgb
|
||||
* @param {string|array} values
|
||||
* @return {array}
|
||||
*/
|
||||
rgbToHsl(values) {
|
||||
let rgb = values;
|
||||
if (typeof values === 'string') {
|
||||
const parts = values.match(rgbRegex);
|
||||
rgb = [parts[1], parts[2], parts[3], parts[4]];
|
||||
}
|
||||
const [r, g, b] = rgb.map(value => value > 1 ? value / 255 : value);
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const l = (max + min) / 2;
|
||||
const d = max - min;
|
||||
let h = 0;
|
||||
let s = 0;
|
||||
let a = rgb[3] || values[3] || this.alpha;
|
||||
if (max !== min) {
|
||||
if (max === 0) {
|
||||
s = max;
|
||||
} else if (min === 1) {
|
||||
s = min;
|
||||
} else {
|
||||
s = (max - l) / Math.min(l, 1 - l);
|
||||
}
|
||||
switch (max) {
|
||||
case r:
|
||||
h = 60 * (g - b) / d;
|
||||
break;
|
||||
case g:
|
||||
h = 60 * (2 + (b - r) / d);
|
||||
break;
|
||||
case b:
|
||||
default:
|
||||
h = 60 * (4 + (r - g) / d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
h = h < 0 ? h + 360 : h;
|
||||
a = a > 1 ? a / 100 : a;
|
||||
return [h, s, l, a];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hsl values out of hex
|
||||
* @param {string} hex
|
||||
* @return {array}
|
||||
*/
|
||||
hexToHsl(hex) {
|
||||
const parts = hex.match(hexRegex);
|
||||
const r = parts[1];
|
||||
const g = parts[2];
|
||||
const b = parts[3];
|
||||
const rgb = [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
|
||||
return this.rgbToHsl(rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSLa values into RGBa
|
||||
* @param {array} hsla
|
||||
* @returns {number[]}
|
||||
*/
|
||||
hslToRgb([h, sat, light, alpha]) {
|
||||
let r = 1;
|
||||
let g = 1;
|
||||
let b = 1;
|
||||
|
||||
// Saturation and light were calculated as 0.24 instead of 24%
|
||||
const s = sat > 1 ? sat / 100 : sat;
|
||||
const l = light > 1 ? light / 100 : light;
|
||||
const a = alpha > 1 ? alpha / 100 : alpha;
|
||||
if (h < 0 || h > 360 || s < 0 || s > 1 || l < 0 || l > 1) {
|
||||
this.showError('JFIELD_COLOR_ERROR_CONVERT_HSL');
|
||||
return this.hslToRgb(this.defaultHsl);
|
||||
}
|
||||
const c = (1 - Math.abs(2 * l - 1)) * s;
|
||||
const hi = h / 60;
|
||||
const x = c * (1 - Math.abs(hi % 2 - 1));
|
||||
const m = l - c / 2;
|
||||
if (h >= 0 && h < 60) {
|
||||
[r, g, b] = [c, x, 0];
|
||||
} else if (h >= 60 && h < 120) {
|
||||
[r, g, b] = [x, c, 0];
|
||||
} else if (h >= 120 && h < 180) {
|
||||
[r, g, b] = [0, c, x];
|
||||
} else if (h >= 180 && h < 240) {
|
||||
[r, g, b] = [0, x, c];
|
||||
} else if (h >= 240 && h < 300) {
|
||||
[r, g, b] = [x, 0, c];
|
||||
} else if (h >= 300 && h <= 360) {
|
||||
[r, g, b] = [c, 0, x];
|
||||
} else {
|
||||
this.showError('JFIELD_COLOR_ERROR_CONVERT_HUE');
|
||||
return this.hslToRgb(this.defaultHsl);
|
||||
}
|
||||
const rgb = [r, g, b].map(value => Math.round((value + m) * 255));
|
||||
rgb.push(a);
|
||||
return rgb;
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const fields = document.querySelectorAll('.color-slider-wrapper');
|
||||
if (fields) {
|
||||
Array.prototype.forEach.call(fields, slider => {
|
||||
// eslint-disable-next-line no-new
|
||||
new JoomlaFieldColorSlider(slider);
|
||||
});
|
||||
}
|
||||
});
|
||||
})(document);
|
||||
4
media/system/js/fields/joomla-field-color-slider.min.js
vendored
Normal file
4
media/system/js/fields/joomla-field-color-slider.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-color-slider.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-color-slider.min.js.gz
Normal file
Binary file not shown.
395
media/system/js/fields/joomla-field-fancy-select.js
Normal file
395
media/system/js/fields/joomla-field-fancy-select.js
Normal 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;
|
||||
}
|
||||
});
|
||||
4
media/system/js/fields/joomla-field-fancy-select.min.js
vendored
Normal file
4
media/system/js/fields/joomla-field-fancy-select.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-fancy-select.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-fancy-select.min.js.gz
Normal file
Binary file not shown.
381
media/system/js/fields/joomla-field-media.js
Normal file
381
media/system/js/fields/joomla-field-media.js
Normal 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);
|
||||
4
media/system/js/fields/joomla-field-media.min.js
vendored
Normal file
4
media/system/js/fields/joomla-field-media.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-media.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-media.min.js.gz
Normal file
Binary file not shown.
122
media/system/js/fields/joomla-field-module-order.js
Normal file
122
media/system/js/fields/joomla-field-module-order.js
Normal file
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @package Joomla.JavaScript
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
customElements.define('joomla-field-module-order', class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.linkedFieldSelector = '';
|
||||
this.linkedFieldElement = '';
|
||||
this.originalPosition = '';
|
||||
this.writeDynaList.bind(this);
|
||||
this.getNewOrder.bind(this);
|
||||
}
|
||||
connectedCallback() {
|
||||
this.linkedFieldSelector = this.getAttribute('data-linked-field') || 'jform_position';
|
||||
if (!this.linkedFieldSelector) {
|
||||
throw new Error('No linked field defined!');
|
||||
}
|
||||
this.linkedFieldElement = document.getElementById(this.linkedFieldSelector);
|
||||
if (!this.linkedFieldElement) {
|
||||
throw new Error('No linked field defined!');
|
||||
}
|
||||
const that = this;
|
||||
this.originalPosition = this.linkedFieldElement.value;
|
||||
|
||||
/** Initialize the field * */
|
||||
this.getNewOrder(this.originalPosition);
|
||||
|
||||
/** Watch for changes on the linked field * */
|
||||
this.linkedFieldElement.addEventListener('change', () => {
|
||||
that.originalPosition = that.linkedFieldElement.value;
|
||||
that.getNewOrder(that.linkedFieldElement.value);
|
||||
});
|
||||
}
|
||||
writeDynaList(selectProperties, source, originalPositionName, originalPositionValue) {
|
||||
let i = 0;
|
||||
const selectNode = document.createElement('select');
|
||||
if (this.hasAttribute('disabled')) {
|
||||
selectNode.setAttribute('disabled', '');
|
||||
}
|
||||
if (this.getAttribute('onchange')) {
|
||||
selectNode.setAttribute('onchange', this.getAttribute('onchange'));
|
||||
}
|
||||
if (this.getAttribute('size')) {
|
||||
selectNode.setAttribute('size', this.getAttribute('size'));
|
||||
}
|
||||
selectNode.classList.add(selectProperties.itemClass);
|
||||
selectNode.setAttribute('name', selectProperties.name);
|
||||
selectNode.id = selectProperties.id;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const x in source) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!source.hasOwnProperty(x)) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
const node = document.createElement('option');
|
||||
const item = source[x];
|
||||
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
node.value = item[1];
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
node.innerHTML = Joomla.sanitizeHtml(item[2]);
|
||||
if (originalPositionName && originalPositionValue === item[1] || !originalPositionName && i === 0) {
|
||||
node.setAttribute('selected', 'selected');
|
||||
}
|
||||
selectNode.appendChild(node);
|
||||
i += 1;
|
||||
}
|
||||
this.innerHTML = '';
|
||||
this.appendChild(selectNode);
|
||||
}
|
||||
getNewOrder(originalPosition) {
|
||||
const url = this.getAttribute('data-url');
|
||||
const clientId = this.getAttribute('data-client-id');
|
||||
const originalOrder = this.getAttribute('data-ordering');
|
||||
const name = this.getAttribute('data-name');
|
||||
const attr = this.getAttribute('data-client-attr') ? this.getAttribute('data-client-attr') : 'form-select';
|
||||
const id = `${this.getAttribute('data-id')}`;
|
||||
const moduleId = `${this.getAttribute('data-module-id')}`;
|
||||
const orders = [];
|
||||
const that = this;
|
||||
Joomla.request({
|
||||
url: `${url}&client_id=${clientId}&position=${originalPosition}&module_id=${moduleId}`,
|
||||
method: 'GET',
|
||||
perform: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
onSuccess(resp) {
|
||||
if (resp) {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(resp);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
/** Check if everything is OK * */
|
||||
if (response.data.length > 0) {
|
||||
for (let i = 0; i < response.data.length; i += 1) {
|
||||
orders[i] = response.data[i].split(',');
|
||||
}
|
||||
that.writeDynaList({
|
||||
name,
|
||||
id,
|
||||
itemClass: attr
|
||||
}, orders, that.originalPosition, originalOrder);
|
||||
}
|
||||
}
|
||||
|
||||
/** Render messages, if any. There are only message in case of errors. * */
|
||||
if (typeof resp.messages === 'object' && resp.messages !== null) {
|
||||
Joomla.renderMessages(resp.messages);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
5
media/system/js/fields/joomla-field-module-order.min.js
vendored
Normal file
5
media/system/js/fields/joomla-field-module-order.min.js
vendored
Normal 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)}})}});
|
||||
BIN
media/system/js/fields/joomla-field-module-order.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-module-order.min.js.gz
Normal file
Binary file not shown.
166
media/system/js/fields/joomla-field-permissions.js
Normal file
166
media/system/js/fields/joomla-field-permissions.js
Normal file
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
window.customElements.define('joomla-field-permissions', class extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
if (!Joomla) {
|
||||
throw new Error('Joomla API is not properly initiated');
|
||||
}
|
||||
if (!this.getAttribute('data-uri')) {
|
||||
throw new Error('No valid url for validation');
|
||||
}
|
||||
this.query = window.location.search.substring(1);
|
||||
this.buttons = '';
|
||||
this.buttonDataSelector = 'data-onchange-task';
|
||||
this.onDropdownChange = this.onDropdownChange.bind(this);
|
||||
this.getUrlParam = this.getUrlParam.bind(this);
|
||||
this.component = this.getUrlParam('component');
|
||||
this.extension = this.getUrlParam('extension');
|
||||
this.option = this.getUrlParam('option');
|
||||
this.view = this.getUrlParam('view');
|
||||
this.asset = 'not';
|
||||
this.context = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle
|
||||
*/
|
||||
connectedCallback() {
|
||||
this.buttons = 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;
|
||||
}
|
||||
});
|
||||
4
media/system/js/fields/joomla-field-permissions.min.js
vendored
Normal file
4
media/system/js/fields/joomla-field-permissions.min.js
vendored
Normal 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}});
|
||||
BIN
media/system/js/fields/joomla-field-permissions.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-permissions.min.js.gz
Normal file
Binary file not shown.
74
media/system/js/fields/joomla-field-send-test-mail.js
Normal file
74
media/system/js/fields/joomla-field-send-test-mail.js
Normal file
@ -0,0 +1,74 @@
|
||||
((customElements, Joomla) => {
|
||||
class JoomlaFieldSendTestMail extends HTMLElement {
|
||||
// attributeChangedCallback(attr, oldValue, newValue) {}
|
||||
constructor() {
|
||||
super();
|
||||
if (!Joomla) {
|
||||
throw new Error('Joomla API is not properly initiated');
|
||||
}
|
||||
if (!this.getAttribute('uri')) {
|
||||
throw new Error('No valid url for validation');
|
||||
}
|
||||
}
|
||||
connectedCallback() {
|
||||
const self = this;
|
||||
const button = document.getElementById('sendtestmail');
|
||||
if (button) {
|
||||
button.addEventListener('click', () => {
|
||||
self.sendTestMail(self);
|
||||
});
|
||||
}
|
||||
}
|
||||
sendTestMail() {
|
||||
const emailData = {
|
||||
smtpauth: document.getElementById('jform_smtpauth1').checked ? 1 : 0,
|
||||
smtpuser: this.querySelector('[name="jform[smtpuser]"]').value,
|
||||
smtphost: this.querySelector('[name="jform[smtphost]"]').value,
|
||||
smtpsecure: this.querySelector('[name="jform[smtpsecure]"]').value,
|
||||
smtpport: this.querySelector('[name="jform[smtpport]"]').value,
|
||||
mailfrom: this.querySelector('[name="jform[mailfrom]"]').value,
|
||||
fromname: this.querySelector('[name="jform[fromname]"]').value,
|
||||
mailer: this.querySelector('[name="jform[mailer]"]').value,
|
||||
mailonline: document.getElementById('jform_mailonline1').checked ? 1 : 0
|
||||
};
|
||||
const smtppass = this.querySelector('[name="jform[smtppass]"]');
|
||||
if (smtppass.disabled === false) {
|
||||
emailData.smtppass = smtppass.value;
|
||||
}
|
||||
|
||||
// Remove js messages, if they exist.
|
||||
Joomla.removeMessages();
|
||||
Joomla.request({
|
||||
url: this.getAttribute('uri'),
|
||||
method: 'POST',
|
||||
data: JSON.stringify(emailData),
|
||||
perform: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
onSuccess: resp => {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(resp);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
if (typeof response.messages === 'object' && response.messages !== null) {
|
||||
Joomla.renderMessages(response.messages);
|
||||
}
|
||||
document.body.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
},
|
||||
onError: xhr => {
|
||||
Joomla.renderMessages(Joomla.ajaxErrorsMessages(xhr));
|
||||
document.body.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-send-test-mail', JoomlaFieldSendTestMail);
|
||||
})(customElements, Joomla);
|
||||
1
media/system/js/fields/joomla-field-send-test-mail.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-send-test-mail.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
((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);
|
||||
BIN
media/system/js/fields/joomla-field-send-test-mail.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-send-test-mail.min.js.gz
Normal file
Binary file not shown.
410
media/system/js/fields/joomla-field-simple-color.js
Normal file
410
media/system/js/fields/joomla-field-simple-color.js
Normal file
@ -0,0 +1,410 @@
|
||||
/**
|
||||
* Based on:
|
||||
* Very simple jQuery Color Picker
|
||||
* Copyright (C) 2012 Tanguy Krotoff
|
||||
* Licensed under the MIT license
|
||||
*
|
||||
* ADAPTED BY: Dimitris Grammatikogiannis
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
(customElements => {
|
||||
const KEYCODE = {
|
||||
TAB: 9,
|
||||
ESC: 27
|
||||
};
|
||||
const colorNames = {
|
||||
aliceblue: '#f0f8ff',
|
||||
antiquewhite: '#faebd7',
|
||||
aqua: '#00ffff',
|
||||
aquamarine: '#7fffd4',
|
||||
azure: '#f0ffff',
|
||||
beige: '#f5f5dc',
|
||||
bisque: '#ffe4c4',
|
||||
black: '#000000',
|
||||
blanchedalmond: '#ffebcd',
|
||||
blue: '#0000ff',
|
||||
blueviolet: '#8a2be2',
|
||||
brown: '#a52a2a',
|
||||
burlywood: '#deb887',
|
||||
cadetblue: '#5f9ea0',
|
||||
chartreuse: '#7fff00',
|
||||
chocolate: '#d2691e',
|
||||
coral: '#ff7f50',
|
||||
cornflowerblue: '#6495ed',
|
||||
cornsilk: '#fff8dc',
|
||||
crimson: '#dc143c',
|
||||
cyan: '#00ffff',
|
||||
darkblue: '#00008b',
|
||||
darkcyan: '#008b8b',
|
||||
darkgoldenrod: '#b8860b',
|
||||
darkgray: '#a9a9a9',
|
||||
darkgreen: '#006400',
|
||||
darkgrey: '#a9a9a9',
|
||||
darkkhaki: '#bdb76b',
|
||||
darkmagenta: '#8b008b',
|
||||
darkolivegreen: '#556b2f',
|
||||
darkorange: '#ff8c00',
|
||||
darkorchid: '#9932cc',
|
||||
darkred: '#8b0000',
|
||||
darksalmon: '#e9967a',
|
||||
darkseagreen: '#8fbc8f',
|
||||
darkslateblue: '#483d8b',
|
||||
darkslategray: '#2f4f4f',
|
||||
darkslategrey: '#2f4f4f',
|
||||
darkturquoise: '#00ced1',
|
||||
darkviolet: '#9400d3',
|
||||
deeppink: '#ff1493',
|
||||
deepskyblue: '#00bfff',
|
||||
dimgray: '#696969',
|
||||
dimgrey: '#696969',
|
||||
dodgerblue: '#1e90ff',
|
||||
firebrick: '#b22222',
|
||||
floralwhite: '#fffaf0',
|
||||
forestgreen: '#228b22',
|
||||
fuchsia: '#ff00ff',
|
||||
gainsboro: '#dcdcdc',
|
||||
ghostwhite: '#f8f8ff',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
grey: '#808080',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
lawngreen: '#7cfc00',
|
||||
lemonchiffon: '#fffacd',
|
||||
lightblue: '#add8e6',
|
||||
lightcoral: '#f08080',
|
||||
lightcyan: '#e0ffff',
|
||||
lightgoldenrodyellow: '#fafad2',
|
||||
lightgray: '#d3d3d3',
|
||||
lightgreen: '#90ee90',
|
||||
lightgrey: '#d3d3d3',
|
||||
lightpink: '#ffb6c1',
|
||||
lightsalmon: '#ffa07a',
|
||||
lightseagreen: '#20b2aa',
|
||||
lightskyblue: '#87cefa',
|
||||
lightslategray: '#778899',
|
||||
lightslategrey: '#778899',
|
||||
lightsteelblue: '#b0c4de',
|
||||
lightyellow: '#ffffe0',
|
||||
lime: '#00ff00',
|
||||
limegreen: '#32cd32',
|
||||
linen: '#faf0e6',
|
||||
magenta: '#ff00ff',
|
||||
maroon: '#800000',
|
||||
mediumaquamarine: '#66cdaa',
|
||||
mediumblue: '#0000cd',
|
||||
mediumorchid: '#ba55d3',
|
||||
mediumpurple: '#9370db',
|
||||
mediumseagreen: '#3cb371',
|
||||
mediumslateblue: '#7b68ee',
|
||||
mediumspringgreen: '#00fa9a',
|
||||
mediumturquoise: '#48d1cc',
|
||||
mediumvioletred: '#c71585',
|
||||
midnightblue: '#191970',
|
||||
mintcream: '#f5fffa',
|
||||
mistyrose: '#ffe4e1',
|
||||
moccasin: '#ffe4b5',
|
||||
navajowhite: '#ffdead',
|
||||
navy: '#000080',
|
||||
oldlace: '#fdf5e6',
|
||||
olive: '#808000',
|
||||
olivedrab: '#6b8e23',
|
||||
orange: '#ffa500',
|
||||
orangered: '#ff4500',
|
||||
orchid: '#da70d6',
|
||||
palegoldenrod: '#eee8aa',
|
||||
palegreen: '#98fb98',
|
||||
paleturquoise: '#afeeee',
|
||||
palevioletred: '#db7093',
|
||||
papayawhip: '#ffefd5',
|
||||
peachpuff: '#ffdab9',
|
||||
peru: '#cd853f',
|
||||
pink: '#ffc0cb',
|
||||
plum: '#dda0dd',
|
||||
powderblue: '#b0e0e6',
|
||||
purple: '#800080',
|
||||
red: '#ff0000',
|
||||
rosybrown: '#bc8f8f',
|
||||
royalblue: '#4169e1',
|
||||
saddlebrown: '#8b4513',
|
||||
salmon: '#fa8072',
|
||||
sandybrown: '#f4a460',
|
||||
seagreen: '#2e8b57',
|
||||
seashell: '#fff5ee',
|
||||
sienna: '#a0522d',
|
||||
silver: '#c0c0c0',
|
||||
skyblue: '#87ceeb',
|
||||
slateblue: '#6a5acd',
|
||||
slategray: '#708090',
|
||||
slategrey: '#708090',
|
||||
snow: '#fffafa',
|
||||
springgreen: '#00ff7f',
|
||||
steelblue: '#4682b4',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
tomato: '#ff6347',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
wheat: '#f5deb3',
|
||||
white: '#ffffff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ffff00',
|
||||
yellowgreen: '#9acd32'
|
||||
};
|
||||
class JoomlaFieldSimpleColor extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Define some variables
|
||||
this.select = '';
|
||||
this.options = [];
|
||||
this.icon = '';
|
||||
this.panel = '';
|
||||
this.buttons = [];
|
||||
this.focusableElements = null;
|
||||
this.focusableSelectors = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])'];
|
||||
}
|
||||
connectedCallback() {
|
||||
this.select = this.querySelector('select');
|
||||
if (!this.select) {
|
||||
throw new Error('Simple color field requires a select element');
|
||||
}
|
||||
this.options = 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);
|
||||
1
media/system/js/fields/joomla-field-simple-color.min.js
vendored
Normal file
1
media/system/js/fields/joomla-field-simple-color.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-simple-color.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-simple-color.min.js.gz
Normal file
Binary file not shown.
610
media/system/js/fields/joomla-field-subform.js
Normal file
610
media/system/js/fields/joomla-field-subform.js
Normal 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);
|
||||
4
media/system/js/fields/joomla-field-subform.min.js
vendored
Normal file
4
media/system/js/fields/joomla-field-subform.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/joomla-field-subform.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-subform.min.js.gz
Normal file
Binary file not shown.
152
media/system/js/fields/joomla-field-user.js
Normal file
152
media/system/js/fields/joomla-field-user.js
Normal 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);
|
||||
4
media/system/js/fields/joomla-field-user.min.js
vendored
Normal file
4
media/system/js/fields/joomla-field-user.min.js
vendored
Normal 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);
|
||||
BIN
media/system/js/fields/joomla-field-user.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-field-user.min.js.gz
Normal file
Binary file not shown.
557
media/system/js/fields/joomla-media-select.js
Normal file
557
media/system/js/fields/joomla-media-select.js
Normal 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, '"'));
|
||||
}
|
||||
return el;
|
||||
});
|
||||
|
||||
// Set initial values
|
||||
this.setAttribute('is-lazy', !!this.lazyInput.checked);
|
||||
this.setAttribute('alt-check', false);
|
||||
} else if (['audios', 'videos', 'documents'].includes(this.type)) {
|
||||
this.innerHTML = `<details open>
|
||||
<summary>${this.summarytext}</summary>
|
||||
<div class="">
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input radio" type="radio" name="flexRadioDefault" id="${this.parentId}-embed-check-2" value="0" checked>
|
||||
<label class="form-check-label" for="${this.parentId}-embed-check-2">
|
||||
${this.downloadchecktext}
|
||||
<div><small class="form-text">${this.downloadcheckdesctext}</small></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input radio" type="radio" name="flexRadioDefault" id="${this.parentId}-embed-check-1" value="1">
|
||||
<label class="form-check-label" for="${this.parentId}-embed-check-1">
|
||||
${this.embedchecktext}
|
||||
<div><small class="form-text">${this.embedcheckdesctext}</small></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toggable-parts" style="display: none">
|
||||
<div style="display: ${this.type === 'audios' ? 'none' : 'block'}">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-width">${this.widthtext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-width" value="800" data-is="width"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-height">${this.heighttext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-height" value="600" data-is="height"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: ${this.type === 'document' ? 'block' : 'none'}">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="${this.parentId}-title">${this.titletext}</label>
|
||||
<input class="form-control" type="text" id="${this.parentId}-title" value="" data-is="title"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>`;
|
||||
this.embedInputFn = this.embedInputFn.bind(this);
|
||||
this.inputFn = this.inputFn.bind(this);
|
||||
[].slice.call(this.querySelectorAll('.form-check-input.radio')).map(el => el.addEventListener('input', this.embedInputFn));
|
||||
this.setAttribute('embed-it', false);
|
||||
[].slice.call(this.querySelectorAll('input[type="text"]')).map(el => {
|
||||
el.addEventListener('input', this.inputFn);
|
||||
const {
|
||||
is
|
||||
} = el.dataset;
|
||||
if (is) {
|
||||
this.setAttribute(is, el.value.replace(/"/g, '"'));
|
||||
}
|
||||
return el;
|
||||
});
|
||||
}
|
||||
}
|
||||
disconnectedCallback() {
|
||||
if (this.type === 'image') {
|
||||
this.lazyInput.removeEventListener('input', this.lazyInputFn);
|
||||
this.altInput.removeEventListener('input', this.inputFn);
|
||||
this.altCheck.removeEventListener('input', this.altCheckFn);
|
||||
}
|
||||
if (['audio', 'video', 'document'].includes(this.type)) {
|
||||
[].slice.call(this.querySelectorAll('.form-check-input.radio')).map(el => el.removeEventListener('input', this.embedInputFn));
|
||||
[].slice.call(this.querySelectorAll('input[type="text"]')).map(el => el.removeEventListener('input', this.embedInputFn));
|
||||
}
|
||||
this.innerHTML = '';
|
||||
}
|
||||
lazyInputFn(e) {
|
||||
this.setAttribute('is-lazy', !!e.target.checked);
|
||||
}
|
||||
altCheckFn(e) {
|
||||
this.setAttribute('alt-check', !!e.target.checked);
|
||||
}
|
||||
inputFn(e) {
|
||||
const {
|
||||
is
|
||||
} = e.target.dataset;
|
||||
if (is) {
|
||||
this.setAttribute(is, e.target.value.replace(/"/g, '"'));
|
||||
}
|
||||
}
|
||||
embedInputFn(e) {
|
||||
const {
|
||||
value
|
||||
} = e.target;
|
||||
this.setAttribute('embed-it', value !== '0');
|
||||
const toggable = this.querySelector('.toggable-parts');
|
||||
if (toggable) {
|
||||
if (toggable.style.display !== 'block') {
|
||||
toggable.style.display = 'block';
|
||||
} else {
|
||||
toggable.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-mediamore', JoomlaFieldMediaOptions);
|
||||
111
media/system/js/fields/joomla-media-select.min.js
vendored
Normal file
111
media/system/js/fields/joomla-media-select.min.js
vendored
Normal 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,""")),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,""")),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,"""))}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);
|
||||
BIN
media/system/js/fields/joomla-media-select.min.js.gz
Normal file
BIN
media/system/js/fields/joomla-media-select.min.js.gz
Normal file
Binary file not shown.
174
media/system/js/fields/modal-content-select-field.js
Normal file
174
media/system/js/fields/modal-content-select-field.js
Normal 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));
|
||||
4
media/system/js/fields/modal-content-select-field.min.js
vendored
Normal file
4
media/system/js/fields/modal-content-select-field.min.js
vendored
Normal 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));
|
||||
BIN
media/system/js/fields/modal-content-select-field.min.js.gz
Normal file
BIN
media/system/js/fields/modal-content-select-field.min.js.gz
Normal file
Binary file not shown.
216
media/system/js/fields/modal-fields.js
Normal file
216
media/system/js/fields/modal-fields.js
Normal file
@ -0,0 +1,216 @@
|
||||
/**
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Process modal fields in parent.
|
||||
*
|
||||
* @param string fieldPrefix The fields to be updated prefix.
|
||||
* @param string id The new id for the item.
|
||||
* @param string title The new title for the item.
|
||||
* @param string catid Future usage.
|
||||
* @param object object Future usage.
|
||||
* @param string url Future usage.
|
||||
* @param string language Future usage.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
window.processModalParent = function (fieldPrefix, id, title, catid, url, language, object)
|
||||
{
|
||||
var fieldId = document.getElementById(fieldPrefix + '_id') || document.getElementById(fieldPrefix + '_value'),
|
||||
fieldTitle = document.getElementById(fieldPrefix + '_name') || document.getElementById(fieldPrefix);
|
||||
|
||||
// Default values.
|
||||
id = id || '';
|
||||
title = title || '';
|
||||
catid = catid || '';
|
||||
object = object || '';
|
||||
url = url || '';
|
||||
language = language || '';
|
||||
|
||||
var isChanged = fieldId.value !== id;
|
||||
|
||||
if (id)
|
||||
{
|
||||
fieldId.value = id;
|
||||
fieldTitle.value = title;
|
||||
|
||||
if (document.getElementById(fieldPrefix + '_select'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_select').classList.add('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_new'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_new').classList.add('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_edit'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_edit').classList.remove('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_clear'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_clear').classList.remove('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_propagate'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_propagate').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldId.value = '';
|
||||
fieldTitle.value = fieldId.getAttribute('data-text');
|
||||
|
||||
if (document.getElementById(fieldPrefix + '_select'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_select').classList.remove('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_new'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_new').classList.remove('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_edit'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_edit').classList.add('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_clear'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_clear').classList.add('hidden');
|
||||
}
|
||||
if (document.getElementById(fieldPrefix + '_propagate'))
|
||||
{
|
||||
document.getElementById(fieldPrefix + '_propagate').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (isChanged) {
|
||||
fieldId.dispatchEvent(new CustomEvent('change', { bubbles: true, cancelable: true }));
|
||||
}
|
||||
|
||||
if (fieldId.getAttribute('data-required') == '1')
|
||||
{
|
||||
document.formvalidator.validate(fieldId);
|
||||
document.formvalidator.validate(fieldTitle);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Process new/edit modal fields in child.
|
||||
*
|
||||
* @param object element The modal footer button element.
|
||||
* @param string fieldPrefix The fields to be updated prefix.
|
||||
* @param string action Modal action (add, edit).
|
||||
* @param string itemType The item type (Article, Contact, etc).
|
||||
* @param string task Task to be done (apply, save, cancel).
|
||||
* @param string formId Id of the form field (defaults to itemtype-form).
|
||||
* @param string idFieldId Id of the id field (defaults to jform_id).
|
||||
* @param string titleFieldId Id of the title field (defaults to jform_title).
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
window.processModalEdit = function (element, fieldPrefix, action, itemType, task, formId, idFieldId, titleFieldId)
|
||||
{
|
||||
formId = formId || itemType.toLowerCase() + '-form';
|
||||
idFieldId = idFieldId || 'jform_id';
|
||||
titleFieldId = titleFieldId || 'jform_title';
|
||||
|
||||
var modalId = element.parentNode.parentNode.parentNode.parentNode.id, submittedTask = task;
|
||||
var iframe = document.getElementById(modalId).getElementsByTagName('iframe')[0];
|
||||
|
||||
// Set frame id.
|
||||
iframe.id = 'Frame_' + modalId;
|
||||
|
||||
var iframeDocument = iframe.contentDocument;
|
||||
|
||||
// If Close (cancel task), close the modal.
|
||||
if (task === 'cancel')
|
||||
{
|
||||
// Submit button on child iframe so we can check out.
|
||||
iframe.contentWindow.Joomla.submitbutton(itemType.toLowerCase() + '.' + task);
|
||||
|
||||
Joomla.Modal.getCurrent().close();
|
||||
}
|
||||
// For Save (apply task) and Save & Close (save task).
|
||||
else
|
||||
{
|
||||
// Attach onload event to the iframe.
|
||||
iframe.addEventListener('load', function()
|
||||
{
|
||||
// Reload iframe document var value.
|
||||
iframeDocument = this.contentDocument;
|
||||
|
||||
// Validate the child form and update parent form.
|
||||
if (
|
||||
iframeDocument.getElementById(idFieldId)
|
||||
&& iframeDocument.getElementById(idFieldId).value != '0'
|
||||
&& [].slice.call(iframeDocument.querySelectorAll('joomla-alert[type="danger"]')).length == 0
|
||||
) {
|
||||
window.processModalParent(fieldPrefix, iframeDocument.getElementById(idFieldId).value, iframeDocument.getElementById(titleFieldId).value);
|
||||
|
||||
// If Save & Close (save task), submit the edit close action (so we don't have checked out items).
|
||||
if (task === 'save')
|
||||
{
|
||||
window.processModalEdit(element, fieldPrefix, 'edit', itemType, 'cancel', formId, idFieldId, titleFieldId);
|
||||
}
|
||||
}
|
||||
|
||||
// Show the iframe again for future modals or in case of error.
|
||||
iframe.classList.remove('visually-hidden');
|
||||
});
|
||||
|
||||
// Submit button on child iframe.
|
||||
if (iframeDocument.formvalidator.isValid(iframeDocument.getElementById(formId)))
|
||||
{
|
||||
// For Save & Close (save task) when creating we need to replace the task as apply because of redirects after submit and hide the iframe.
|
||||
if (task === 'save')
|
||||
{
|
||||
submittedTask = 'apply';
|
||||
iframe.classList.add('visually-hidden');
|
||||
}
|
||||
|
||||
iframe.contentWindow.Joomla.submitbutton(itemType.toLowerCase() + '.' + submittedTask);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Process select modal fields in child.
|
||||
*
|
||||
* @param string itemType The item type (Article, Contact, etc).
|
||||
* @param string fieldPrefix The fields to be updated prefix.
|
||||
* @param string id The new id for the item.
|
||||
* @param string title The new title for the item.
|
||||
* @param string catid Future usage.
|
||||
* @param object object Future usage.
|
||||
* @param string url Future usage.
|
||||
* @param string language Future usage.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @since 3.7.0
|
||||
*/
|
||||
window.processModalSelect = function(itemType, fieldPrefix, id, title, catid, object, url, language) {
|
||||
window.processModalParent(fieldPrefix, id, title, catid, url, language, object);
|
||||
|
||||
// Close Modal only when necessary.
|
||||
if (Joomla.Modal.getCurrent())
|
||||
{
|
||||
Joomla.Modal.getCurrent().close();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
}());
|
||||
4
media/system/js/fields/modal-fields.min.js
vendored
Normal file
4
media/system/js/fields/modal-fields.min.js
vendored
Normal 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}})();
|
||||
BIN
media/system/js/fields/modal-fields.min.js.gz
Normal file
BIN
media/system/js/fields/modal-fields.min.js.gz
Normal file
Binary file not shown.
168
media/system/js/fields/passwordstrength.js
Normal file
168
media/system/js/fields/passwordstrength.js
Normal 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);
|
||||
4
media/system/js/fields/passwordstrength.min.js
vendored
Normal file
4
media/system/js/fields/passwordstrength.min.js
vendored
Normal 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);
|
||||
BIN
media/system/js/fields/passwordstrength.min.js.gz
Normal file
BIN
media/system/js/fields/passwordstrength.min.js.gz
Normal file
Binary file not shown.
80
media/system/js/fields/passwordview.js
Normal file
80
media/system/js/fields/passwordview.js
Normal 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);
|
||||
4
media/system/js/fields/passwordview.min.js
vendored
Normal file
4
media/system/js/fields/passwordview.min.js
vendored
Normal 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);
|
||||
BIN
media/system/js/fields/passwordview.min.js.gz
Normal file
BIN
media/system/js/fields/passwordview.min.js.gz
Normal file
Binary file not shown.
43
media/system/js/fields/select-colour.js
Normal file
43
media/system/js/fields/select-colour.js
Normal 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);
|
||||
})();
|
||||
4
media/system/js/fields/select-colour.min.js
vendored
Normal file
4
media/system/js/fields/select-colour.min.js
vendored
Normal 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)})();
|
||||
BIN
media/system/js/fields/select-colour.min.js.gz
Normal file
BIN
media/system/js/fields/select-colour.min.js.gz
Normal file
Binary file not shown.
66
media/system/js/fields/tag.js
Normal file
66
media/system/js/fields/tag.js
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Field user
|
||||
*/
|
||||
jQuery(document).ready(function ($) {
|
||||
|
||||
if (Joomla.getOptions('field-tag-custom')) {
|
||||
|
||||
var options = Joomla.getOptions('field-tag-custom'),
|
||||
customTagPrefix = '#new#';
|
||||
|
||||
// Method to add tags pressing enter
|
||||
$(options.selector + '_chosen input').keyup(function(event) {
|
||||
|
||||
var tagOption;
|
||||
|
||||
// Tag is greater than the minimum required chars and enter pressed
|
||||
if (this.value && this.value.length >= options.minTermLength && (event.which === 13 || event.which === 188)) {
|
||||
|
||||
// Search a highlighted result
|
||||
var highlighted = $(options.selector + '_chosen').find('li.active-result.highlighted').first();
|
||||
|
||||
// Add the highlighted option
|
||||
if (event.which === 13 && highlighted.text() !== '')
|
||||
{
|
||||
// Extra check. If we have added a custom tag with this text remove it
|
||||
var customOptionValue = customTagPrefix + highlighted.text();
|
||||
$(options.selector + ' option').filter(function () { return $(this).val() == customOptionValue; }).remove();
|
||||
|
||||
// Select the highlighted result
|
||||
tagOption = $(options.selector + ' option').filter(function () { return $(this).html() == highlighted.text(); });
|
||||
tagOption.attr('selected', 'selected');
|
||||
}
|
||||
// Add the custom tag option
|
||||
else
|
||||
{
|
||||
var customTag = this.value;
|
||||
|
||||
// Extra check. Search if the custom tag already exists (typed faster than AJAX ready)
|
||||
tagOption = $(options.selector + ' option').filter(function () { return $(this).html() == customTag; });
|
||||
if (tagOption.text() !== '')
|
||||
{
|
||||
tagOption.attr('selected', 'selected');
|
||||
}
|
||||
else
|
||||
{
|
||||
var option = $('<option>');
|
||||
option.text(this.value).val(customTagPrefix + this.value);
|
||||
option.attr('selected','selected');
|
||||
|
||||
// Append the option and repopulate the chosen field
|
||||
$(options.selector).append(option);
|
||||
}
|
||||
}
|
||||
|
||||
this.value = '';
|
||||
$(options.selector).trigger('chosen:updated');
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
4
media/system/js/fields/tag.min.js
vendored
Normal file
4
media/system/js/fields/tag.min.js
vendored
Normal 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()}})}});
|
||||
BIN
media/system/js/fields/tag.min.js.gz
Normal file
BIN
media/system/js/fields/tag.min.js.gz
Normal file
Binary file not shown.
731
media/system/js/fields/validate.js
Normal file
731
media/system/js/fields/validate.js
Normal 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;
|
||||
|
||||
})();
|
||||
4
media/system/js/fields/validate.min.js
vendored
Normal file
4
media/system/js/fields/validate.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/fields/validate.min.js.gz
Normal file
BIN
media/system/js/fields/validate.min.js.gz
Normal file
Binary file not shown.
1847
media/system/js/highlight.js
Normal file
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
1
media/system/js/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/highlight.min.js.gz
Normal file
BIN
media/system/js/highlight.min.js.gz
Normal file
Binary file not shown.
56
media/system/js/inlinehelp.js
Normal file
56
media/system/js/inlinehelp.js
Normal 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 vice–versa.
|
||||
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
4
media/system/js/inlinehelp.min.js
vendored
Normal 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)})});
|
||||
BIN
media/system/js/inlinehelp.min.js.gz
Normal file
BIN
media/system/js/inlinehelp.min.js.gz
Normal file
Binary file not shown.
103
media/system/js/joomla-core-loader.js
Normal file
103
media/system/js/joomla-core-loader.js
Normal 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);
|
||||
10
media/system/js/joomla-core-loader.min.js
vendored
Normal file
10
media/system/js/joomla-core-loader.min.js
vendored
Normal 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);
|
||||
BIN
media/system/js/joomla-core-loader.min.js.gz
Normal file
BIN
media/system/js/joomla-core-loader.min.js.gz
Normal file
Binary file not shown.
94
media/system/js/joomla-dialog-autocreate.js
Normal file
94
media/system/js/joomla-dialog-autocreate.js
Normal 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();
|
||||
});
|
||||
4
media/system/js/joomla-dialog-autocreate.min.js
vendored
Normal file
4
media/system/js/joomla-dialog-autocreate.min.js
vendored
Normal 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()});
|
||||
BIN
media/system/js/joomla-dialog-autocreate.min.js.gz
Normal file
BIN
media/system/js/joomla-dialog-autocreate.min.js.gz
Normal file
Binary file not shown.
623
media/system/js/joomla-dialog.js
Normal file
623
media/system/js/joomla-dialog.js
Normal 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
8
media/system/js/joomla-dialog.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/system/js/joomla-dialog.min.js.gz
Normal file
BIN
media/system/js/joomla-dialog.min.js.gz
Normal file
Binary file not shown.
67
media/system/js/joomla-hidden-mail.js
Normal file
67
media/system/js/joomla-hidden-mail.js
Normal 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(''));
|
||||
}
|
||||
});
|
||||
4
media/system/js/joomla-hidden-mail.min.js
vendored
Normal file
4
media/system/js/joomla-hidden-mail.min.js
vendored
Normal 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(""))}});
|
||||
BIN
media/system/js/joomla-hidden-mail.min.js.gz
Normal file
BIN
media/system/js/joomla-hidden-mail.min.js.gz
Normal file
Binary file not shown.
124
media/system/js/joomla-toolbar-button.js
Normal file
124
media/system/js/joomla-toolbar-button.js
Normal 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;
|
||||
}
|
||||
});
|
||||
4
media/system/js/joomla-toolbar-button.min.js
vendored
Normal file
4
media/system/js/joomla-toolbar-button.min.js
vendored
Normal 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)}});
|
||||
BIN
media/system/js/joomla-toolbar-button.min.js.gz
Normal file
BIN
media/system/js/joomla-toolbar-button.min.js.gz
Normal file
Binary file not shown.
28
media/system/js/keepalive.js
Normal file
28
media/system/js/keepalive.js
Normal 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(/&/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
Reference in New Issue
Block a user