init
This commit is contained in:
@@ -0,0 +1,788 @@
|
||||
(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
|
||||
*/
|
||||
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'],
|
||||
details: ['open'],
|
||||
summary: []
|
||||
};
|
||||
|
||||
// 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 7.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;
|
||||
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;
|
||||
}
|
||||
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
|
||||
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) {
|
||||
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();
|
||||
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
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @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);
|
||||
};
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/let url,direction,isNested,dragElementIndex,dropElementIndex,container=document.querySelector(".js-draggable"),form,formData;if(container)url=container.dataset.url,direction=container.dataset.direction,isNested=container.dataset.nested;else if(Joomla.getOptions("draggable-list")){const o=Joomla.getOptions("draggable-list");container=document.querySelector(o.id),container.classList.contains("js-draggable")||container.classList.add("js-draggable"),{url}=o,{direction}=o,isNested=o.nested}if(container){form=container.closest("form"),formData=new FormData(form),formData.delete("task"),formData.delete("order[]"),document.addEventListener("touchstart",()=>{},!1);const o=(e,l,r,a)=>{let t;const s=[];if(r<a)for(e[a].value=e[a-1].value,t=r;t<a;t+=1)direction==="asc"?e[t].value=parseInt(e[t].value,10)-1:e[t].value=parseInt(e[t].value,10)+1;else for(e[a].value=e[a+1].value,t=a+1;t<=r;t+=1)direction==="asc"?e[t].value=parseInt(e[t].value,10)+1:e[t].value=parseInt(e[t].value,10)-1;for(t=0;t<e.length-1;t+=1)s.push(`order[]=${encodeURIComponent(e[t].value)}`),s.push(`cid[]=${encodeURIComponent(l[t].value)}`);return s},c=e=>{if(!e.dataset.itemId)return;const l=e.dataset.itemId,r=container.querySelectorAll(`tr[data-parents~="${l}"]`);r.length&&e.after(...r)},d=e=>{let l,r,a;const t=e.dataset.draggableGroup;t?(a=`tr[data-draggable-group="${t}"]`,l=`[data-draggable-group="${t}"] [name="order[]"]`,r=`[data-draggable-group="${t}"] [name="cid[]"]`):(a="tr",l='[name="order[]"]',r='[name="cid[]"]');const s=[].slice.call(container.querySelectorAll(a)),u=[].slice.call(container.querySelectorAll(l)),i=[].slice.call(container.querySelectorAll(r));if(dropElementIndex=s.indexOf(e),url){const n=document.querySelector('[name="task"]');n&&n.setAttribute("name","some__Temporary__Name__");const g={url,method:"POST",data:`${new URLSearchParams(formData).toString()}&${o(u,i,dragElementIndex,dropElementIndex).join("&")}`,perform:!0};Joomla.request(g),n&&n.setAttribute("name","task")}c(e)};dragula([container],{direction:"vertical",copy:!1,revertOnSpill:!0,accepts(e,l,r,a){return isNested&&a!==null?a.dataset.draggableGroup&&a.dataset.draggableGroup===e.dataset.draggableGroup:a===null||a&&a.tagName.toLowerCase()==="tr"},mirrorContainer:container}).on("drag",e=>{let l;const r=e.dataset.draggableGroup;r?l=`tr[data-draggable-group="${r}"]`:l="tr",dragElementIndex=[].slice.call(container.querySelectorAll(l)).indexOf(e)}).on("drop",e=>{d(e)})}
|
||||
Binary file not shown.
@@ -0,0 +1,174 @@
|
||||
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) {
|
||||
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 };
|
||||
@@ -0,0 +1,4 @@
|
||||
import r from"editor-decorator";export{default as JoomlaEditorDecorator}from"editor-decorator";/**
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/const c={instances:{},active:null,register(t){if(!(t instanceof r))throw new Error("Unexpected editor instance");return this.instances[t.getId()]=t,Joomla.editors.instances[t.getId()]=t,this},unregister(t){let e;if(t instanceof r)e=t.getId();else if(typeof t=="string")e=t;else throw new Error("Unexpected editor instance or identifier");return this.active&&this.active===this.instances[e]&&(this.active=null),delete this.instances[e],delete Joomla.editors.instances[e],this},get(t){return this.instances[t]||!1},setActive(t){if(t instanceof r)this.active=t;else if(this.instances[t])this.active=this.instances[t];else throw new Error("The editor instance not found or it is incorrect");return this},getActive(){return this.active}},l={actions:{},registerAction(t,e){if(!t||!e)throw new Error("Missed values for Action registration");if(!(e instanceof Function))throw new Error(`Unexpected handler for action "${t}", expecting Function`);return this.actions[t]=e,this},getActionHandler(t){return this.actions[t]||!1},runAction(t,e,o){const s=this.getActionHandler(t);let i=c.getActive();if(!s)throw new Error(`Handler for "${t}" action not found`);if(!i&&o){const a=o.closest("fieldset, div:not(.editor-xtd-buttons)"),n=a?a.querySelector("textarea[id]"):!1;i=n&&Joomla.editors.instances[n.id]?Joomla.editors.instances[n.id]:!1,i&&console.warn("Legacy editors is deprecated. Set active editor instance with JoomlaEditor.setActive().")}if(!i)throw new Error("An active editor are not available");return s(i,e)}};export{c as JoomlaEditor,l as JoomlaEditorButton};
|
||||
Binary file not shown.
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @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}
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
setValue(value) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the selected text from the editor.
|
||||
* Should be implemented by editor provider.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
disable(enable) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
export { JoomlaEditorDecorator as default };
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/class o{constructor(e,t,r){if(!e||!t||!r)throw new Error("Missed values for class constructor");this.instance=e,this.type=t,this.id=r}getRawInstance(){return this.instance}getType(){return this.type}getId(){return this.id}getValue(){throw new Error("Not implemented")}setValue(e){throw new Error("Not implemented")}getSelection(){throw new Error("Not implemented")}replaceSelection(e){throw new Error("Not implemented")}disable(e){throw new Error("Not implemented")}}export{o as default};
|
||||
Binary file not shown.
@@ -0,0 +1,99 @@
|
||||
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) {
|
||||
if (!(editor instanceof JoomlaEditorDecorator)) {
|
||||
// Add missed method in Legacy editor
|
||||
editor.getId = () => p;
|
||||
console.warn('Legacy editors is deprecated. Register the editor instance with JoomlaEditor.register().', p, editor);
|
||||
}
|
||||
target[p] = editor;
|
||||
return true;
|
||||
},
|
||||
get(target, p) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import{JoomlaEditorDecorator as i,JoomlaEditorButton as n}from"editor-api";import c from"joomla.dialog";/**
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/if(!window.Joomla)throw new Error("JoomlaEditors API require Joomla to be loaded.");Joomla.editors=Joomla.editors||{},Joomla.editors.instances=new Proxy({},{set(o,e,t){return t instanceof i||(t.getId=()=>e,console.warn("Legacy editors is deprecated. Register the editor instance with JoomlaEditor.register().",e,t)),o[e]=t,!0},get(o,e){return console.warn("Direct access to Joomla.editors.instances is deprecated. Use JoomlaEditor.getActive() or JoomlaEditor.get(id) to retrieve the editor instance."),o[e]}}),n.registerAction("insert",(o,e)=>{const t=e.content||"";o.replaceSelection(t)}),n.registerAction("modal",(o,e)=>{if(e.src&&e.src[0]!=="#"&&e.src[0]!=="."){const a=e.src.indexOf("http")===0?new URL(e.src):new URL(e.src,window.location.origin);a.searchParams.set("editor",o.getId()),a.searchParams.has("e_name")&&a.searchParams.set("e_name",o.getId()),e.src=a.toString()}const t=new c(e),r=a=>{a.origin===window.location.origin&&(a.data.messageType==="joomla:content-select"?(o.replaceSelection(a.data.html||a.data.text),t.close()):a.data.messageType==="joomla:cancel"&&t.close())};window.JoomlaExpectingPostMessage=!0,window.addEventListener("message",r),t.addEventListener("joomla-dialog:close",()=>{delete window.JoomlaExpectingPostMessage,window.removeEventListener("message",r),Joomla.Modal.setCurrent(null),t.destroy()}),Joomla.Modal.setCurrent(t),t.show()});const l="[data-joomla-editor-button-action]",d="joomlaEditorButtonAction",s="joomlaEditorButtonOptions";document.addEventListener("click",o=>{const e=o.target.closest(l);if(!e)return;const t=e.dataset[d],r=e.dataset[s]?JSON.parse(e.dataset[s]):{};t&&n.runAction(t,r,e)});
|
||||
Binary file not shown.
@@ -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
@@ -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);
|
||||
BIN
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
@@ -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.
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -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);
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/(function(a,t){"use strict";function o(i){t(i.target).find(".minicolors").each(function(){t(this).minicolors({control:t(this).attr("data-control")||"hue",format:t(this).attr("data-validate")==="color"?"hex":(t(this).attr("data-format")==="rgba"?"rgb":t(this).attr("data-format"))||"hex",keywords:t(this).attr("data-keywords")||"",opacity:t(this).attr("data-format")==="rgba",position:t(this).attr("data-position")||"default",swatches:t(this).attr("data-colors")?t(this).attr("data-colors").split(","):[],theme:"bootstrap"})})}a.addEventListener("DOMContentLoaded",o),a.addEventListener("joomla:updated",o)})(document,jQuery);
|
||||
Binary file not shown.
@@ -0,0 +1,598 @@
|
||||
/**
|
||||
* @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 => {
|
||||
new JoomlaFieldColorSlider(slider);
|
||||
});
|
||||
}
|
||||
});
|
||||
})(document);
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,390 @@
|
||||
/**
|
||||
* @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;
|
||||
|
||||
// START Work around for issue https://github.com/joomla/joomla-cms/issues/47507
|
||||
// For single-select fields, if selectedIndex is 0 and no option has a
|
||||
// selected attribute, set it before choices.js init.
|
||||
//
|
||||
// This workaround can be removed when choices.js
|
||||
// handles this state on init.
|
||||
if (!this.select.multiple && this.select.options.length && this.select.selectedIndex === 0 && !this.select.querySelector('option[selected]')) {
|
||||
this.select.options[0].selected = true;
|
||||
this.select.options[0].setAttribute('selected', '');
|
||||
}
|
||||
// END workaround for issue #47507
|
||||
|
||||
// Init Choices
|
||||
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,
|
||||
renderSelectedChoices: 'always',
|
||||
shouldSort: false,
|
||||
fuseOptions: {
|
||||
threshold: 0.3,
|
||||
// Strict search
|
||||
distance: 850 // Set distance so Fuse can match across full label length (~255 chars)
|
||||
},
|
||||
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.
|
||||
|
||||
const _highlightChoice = this.choicesInstance._highlightChoice;
|
||||
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');
|
||||
});
|
||||
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();
|
||||
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.passedElement.optionsAsChoices().forEach(choiceItem => {
|
||||
if (choiceItem.value !== undefined) {
|
||||
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;
|
||||
for (let i = items.length - 1; i >= 0; i--) {
|
||||
// The loop must be form the end !!!
|
||||
item = items[i];
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
const {
|
||||
choices
|
||||
} = this.choicesInstance._store;
|
||||
const values = this.choicesInstance.getValue(true);
|
||||
choices.forEach((elem, index) => {
|
||||
if (elem.value === $val) {
|
||||
choices[index].disabled = false;
|
||||
}
|
||||
});
|
||||
this.choicesInstance.clearStore();
|
||||
this.choicesInstance.setChoices(choices, 'value', 'label', true);
|
||||
this.value = values;
|
||||
}
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,386 @@
|
||||
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.inputGroup = this.querySelector('.input-group');
|
||||
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) {
|
||||
if (this.buttonClearEl.parentElement) {
|
||||
this.buttonClearEl.remove();
|
||||
}
|
||||
this.previewElement.innerHTML = Joomla.sanitizeHtml('<span class="field-media-preview-icon"></span>');
|
||||
} else {
|
||||
let type;
|
||||
if (!this.buttonClearEl.parentElement && this.inputGroup) {
|
||||
this.inputGroup.appendChild(this.buttonClearEl);
|
||||
}
|
||||
this.previewElement.innerHTML = '';
|
||||
const ext = getExtension(value).toLowerCase();
|
||||
if (supportedExtensions.images.includes(ext)) type = 'images';
|
||||
if (supportedExtensions.audios.includes(ext)) type = 'audios';
|
||||
if (supportedExtensions.videos.includes(ext)) type = 'videos';
|
||||
if (supportedExtensions.documents.includes(ext)) type = 'documents';
|
||||
let previewElement;
|
||||
const mediaType = {
|
||||
images: () => {
|
||||
if (supportedExtensions.images.includes(ext)) {
|
||||
previewElement = new Image();
|
||||
previewElement.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
|
||||
previewElement.setAttribute('alt', '');
|
||||
}
|
||||
},
|
||||
audios: () => {
|
||||
if (supportedExtensions.audios.includes(ext)) {
|
||||
previewElement = document.createElement('audio');
|
||||
previewElement.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
|
||||
previewElement.setAttribute('controls', '');
|
||||
}
|
||||
},
|
||||
videos: () => {
|
||||
if (supportedExtensions.videos.includes(ext)) {
|
||||
previewElement = document.createElement('video');
|
||||
const previewElementSource = document.createElement('source');
|
||||
previewElementSource.src = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
|
||||
previewElementSource.type = this.mimeType;
|
||||
previewElement.setAttribute('controls', '');
|
||||
previewElement.setAttribute('width', this.previewWidth);
|
||||
previewElement.setAttribute('height', this.previewHeight);
|
||||
previewElement.appendChild(previewElementSource);
|
||||
}
|
||||
},
|
||||
documents: () => {
|
||||
if (supportedExtensions.documents.includes(ext)) {
|
||||
previewElement = document.createElement('object');
|
||||
previewElement.data = /http/.test(value) ? value : Joomla.getOptions('system.paths').rootFull + value;
|
||||
previewElement.type = this.mimeType;
|
||||
previewElement.setAttribute('width', this.previewWidth);
|
||||
previewElement.setAttribute('height', this.previewHeight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// @todo more checks
|
||||
if (this.givenType && ['images', 'audios', 'videos', 'documents'].includes(this.givenType)) {
|
||||
mediaType[this.givenType]();
|
||||
} else if (type && ['images', 'audios', 'videos', 'documents'].includes(type)) {
|
||||
mediaType[type]();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.previewElement.style.width = this.previewWidth;
|
||||
this.previewElement.appendChild(previewElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-media', JoomlaFieldMedia);
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @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;
|
||||
for (const x in source) {
|
||||
if (!source.hasOwnProperty(x)) {
|
||||
continue;
|
||||
}
|
||||
const node = document.createElement('option');
|
||||
const item = source[x];
|
||||
node.value = item[1];
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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)}})}});
|
||||
Binary file not shown.
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* @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) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
@@ -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}});
|
||||
Binary file not shown.
@@ -0,0 +1,73 @@
|
||||
((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) {
|
||||
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);
|
||||
@@ -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);
|
||||
Binary file not shown.
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* @copyright (C) 2024 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
function getText(translatableText, fallbackText) {
|
||||
var _Joomla;
|
||||
const translatedText = typeof ((_Joomla = Joomla) == null || (_Joomla = _Joomla.Text) == null ? void 0 : _Joomla._) === 'function' ? Joomla.Text._(translatableText) : '';
|
||||
return translatedText !== translatableText ? translatedText : fallbackText;
|
||||
}
|
||||
const texts = {
|
||||
none: ['JNONE', 'None'],
|
||||
close: ['JCLOSE', 'Close']
|
||||
};
|
||||
const checker = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3ggRDQENU0dyawAAACZJREFUGNNjPHXqDAMSMDY2ROYyMeAFNJVm/Pv3LzL/7Nnzg8VpAKebCGpIIxHBAAAAAElFTkSuQmCC")';
|
||||
const template = Object.assign(document.createElement('template'), {
|
||||
innerHTML: `
|
||||
<button type="button" part="opener" aria-expanded="false"></button>
|
||||
<div part="panel">
|
||||
<slot name="colors"></slot>
|
||||
<button type="button" aria-label="Close" part="close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="16" height="16" fill="currentColor"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>
|
||||
</button>
|
||||
</div>`
|
||||
});
|
||||
const sheet = new CSSStyleSheet();
|
||||
sheet.replaceSync('[part=close] svg { padding-block-start: .2rem; }');
|
||||
|
||||
// Expand any short code
|
||||
function getColorName(value) {
|
||||
let newValue = value;
|
||||
if (newValue === 'none') return getText(texts.none[0], texts.none[1]);
|
||||
if (value.startsWith('#') && value.length === 4) {
|
||||
const tmpValue = value.split('');
|
||||
newValue = tmpValue[0] + tmpValue[1] + tmpValue[1] + tmpValue[2] + tmpValue[2] + tmpValue[3] + tmpValue[3];
|
||||
}
|
||||
return newValue;
|
||||
}
|
||||
class JoomlaFieldSimpleColor extends HTMLElement {
|
||||
get value() {
|
||||
return this.getAttribute('value');
|
||||
}
|
||||
set value(value) {
|
||||
this.setAttribute('value', value);
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({
|
||||
mode: 'open'
|
||||
});
|
||||
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
||||
this.shadowRoot.adoptedStyleSheets = [sheet];
|
||||
this.internals = null;
|
||||
this.show = this.show.bind(this);
|
||||
this.hide = this.hide.bind(this);
|
||||
this.keys = this.keys.bind(this);
|
||||
this.colorSelect = this.colorSelect.bind(this);
|
||||
this.getActiveElement = this.getActiveElement.bind(this);
|
||||
this.onDocumentClick = this.onDocumentClick.bind(this);
|
||||
|
||||
// Create a dummy div for the validation of the colors
|
||||
this.div = document.createElement('div');
|
||||
}
|
||||
connectedCallback() {
|
||||
try {
|
||||
this.internals = this.attachInternals();
|
||||
this.form = this.internals.form;
|
||||
} catch (error) {
|
||||
throw new Error('Unsupported browser');
|
||||
}
|
||||
if (this.internals) {
|
||||
var _this$querySelector;
|
||||
(_this$querySelector = this.querySelector('input[type=hidden]')) == null || _this$querySelector.remove();
|
||||
}
|
||||
if (this.internals && this.internals.labels.length) {
|
||||
this.internals.labels.forEach(label => label.addEventListener('click', this.show));
|
||||
}
|
||||
this.button = this.shadowRoot.querySelector('[part=opener]');
|
||||
this.panel = this.shadowRoot.querySelector('[part=panel]');
|
||||
this.closeButton = this.panel.querySelector('[part=close]');
|
||||
this.panel.style.display = 'none';
|
||||
this.button.style.background = this.value === 'none' ? checker : this.value;
|
||||
this.button.addEventListener('click', this.show);
|
||||
this.internals.setFormValue(this.value);
|
||||
}
|
||||
|
||||
// Show the panel
|
||||
show() {
|
||||
let focused;
|
||||
this.slotted = this.shadowRoot.querySelector('slot[name=colors]');
|
||||
this.addEventListener('keydown', this.keys);
|
||||
this.closeButton.addEventListener('click', this.hide);
|
||||
this.closeButton.setAttribute('aria-label', getText(texts.close[0], texts.close[1]));
|
||||
this.slotted.assignedElements().forEach(element => {
|
||||
if (!this.validateColor(element.value)) {
|
||||
element.remove();
|
||||
}
|
||||
element.style.background = element.value === 'none' ? checker : element.value;
|
||||
element.setAttribute('aria-label', getColorName(element.value));
|
||||
element.addEventListener('click', this.colorSelect);
|
||||
if (element.getAttribute('aria-pressed') === 'true') {
|
||||
focused = element;
|
||||
}
|
||||
});
|
||||
this.button.style.display = 'none';
|
||||
this.panel.style.display = 'flex';
|
||||
this.button.setAttribute('aria-expanded', 'true');
|
||||
if (focused) {
|
||||
focused.focus();
|
||||
} else {
|
||||
this.closeButton.focus();
|
||||
}
|
||||
document.addEventListener('click', this.onDocumentClick);
|
||||
}
|
||||
|
||||
// Hide the panel
|
||||
hide() {
|
||||
this.removeEventListener('keydown', this.keys);
|
||||
document.removeEventListener('click', this.onDocumentClick);
|
||||
this.button.setAttribute('aria-expanded', 'false');
|
||||
this.panel.style.display = 'none';
|
||||
this.button.style.display = 'block';
|
||||
this.slotted.assignedElements().forEach(element => element.removeEventListener('click', this.colorSelect));
|
||||
this.button.focus();
|
||||
}
|
||||
onDocumentClick(e) {
|
||||
if ([...this.internals.labels].includes(e.target)) return;
|
||||
if (e.target.closest('joomla-field-simple-color') !== this && this.panel.style.display === 'flex') {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
colorSelect(event) {
|
||||
const {
|
||||
currentTarget
|
||||
} = event;
|
||||
this.slotted.assignedElements().forEach(element => element.setAttribute('aria-pressed', element !== currentTarget ? 'false' : 'true'));
|
||||
this.button.style.background = currentTarget.value === 'none' ? checker : currentTarget.value;
|
||||
this.hide();
|
||||
this.internals.setFormValue(currentTarget.value);
|
||||
this.value = currentTarget.value;
|
||||
this.dispatchEvent(new Event('change'));
|
||||
}
|
||||
keys(e) {
|
||||
if (e.code === 'Escape') {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
// Trap the focus
|
||||
if (e.code === 'Tab') {
|
||||
const focusableElements = [...this.slotted.assignedElements(), this.closeButton];
|
||||
const focusedIndex = focusableElements.indexOf(this.getActiveElement());
|
||||
if (e.shiftKey && focusedIndex === 0) {
|
||||
focusableElements[focusableElements.length - 1].focus();
|
||||
e.preventDefault();
|
||||
} else if (!e.shiftKey && focusedIndex === focusableElements.length - 1) {
|
||||
focusableElements[0].focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
getActiveElement(root = document) {
|
||||
const activeEl = root.activeElement;
|
||||
if (!activeEl) {
|
||||
return null;
|
||||
}
|
||||
return activeEl.shadowRoot ? this.getActiveElement(activeEl.shadowRoot) : activeEl;
|
||||
}
|
||||
validateColor(color) {
|
||||
this.div.style.color = color;
|
||||
return this.div.style.color !== '';
|
||||
}
|
||||
}
|
||||
JoomlaFieldSimpleColor.formAssociated = true;
|
||||
customElements.define('joomla-field-simple-color', JoomlaFieldSimpleColor);
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @copyright (C) 2024 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/function getText(s,e){var t;const i=typeof((t=Joomla)==null||(t=t.Text)==null?void 0:t._)=="function"?Joomla.Text._(s):"";return i!==s?i:e}const texts={none:["JNONE","None"],close:["JCLOSE","Close"]},checker='url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3ggRDQENU0dyawAAACZJREFUGNNjPHXqDAMSMDY2ROYyMeAFNJVm/Pv3LzL/7Nnzg8VpAKebCGpIIxHBAAAAAElFTkSuQmCC")',template=Object.assign(document.createElement("template"),{innerHTML:`
|
||||
<button type="button" part="opener" aria-expanded="false"></button>
|
||||
<div part="panel">
|
||||
<slot name="colors"></slot>
|
||||
<button type="button" aria-label="Close" part="close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="16" height="16" fill="currentColor"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>
|
||||
</button>
|
||||
</div>`}),sheet=new CSSStyleSheet;sheet.replaceSync("[part=close] svg { padding-block-start: .2rem; }");function getColorName(s){let e=s;if(e==="none")return getText(texts.none[0],texts.none[1]);if(s.startsWith("#")&&s.length===4){const t=s.split("");e=t[0]+t[1]+t[1]+t[2]+t[2]+t[3]+t[3]}return e}class JoomlaFieldSimpleColor extends HTMLElement{get value(){return this.getAttribute("value")}set value(e){this.setAttribute("value",e)}constructor(){super(),this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(template.content.cloneNode(!0)),this.shadowRoot.adoptedStyleSheets=[sheet],this.internals=null,this.show=this.show.bind(this),this.hide=this.hide.bind(this),this.keys=this.keys.bind(this),this.colorSelect=this.colorSelect.bind(this),this.getActiveElement=this.getActiveElement.bind(this),this.onDocumentClick=this.onDocumentClick.bind(this),this.div=document.createElement("div")}connectedCallback(){try{this.internals=this.attachInternals(),this.form=this.internals.form}catch{throw new Error("Unsupported browser")}if(this.internals){var e;(e=this.querySelector("input[type=hidden]"))==null||e.remove()}this.internals&&this.internals.labels.length&&this.internals.labels.forEach(t=>t.addEventListener("click",this.show)),this.button=this.shadowRoot.querySelector("[part=opener]"),this.panel=this.shadowRoot.querySelector("[part=panel]"),this.closeButton=this.panel.querySelector("[part=close]"),this.panel.style.display="none",this.button.style.background=this.value==="none"?checker:this.value,this.button.addEventListener("click",this.show),this.internals.setFormValue(this.value)}show(){let e;this.slotted=this.shadowRoot.querySelector("slot[name=colors]"),this.addEventListener("keydown",this.keys),this.closeButton.addEventListener("click",this.hide),this.closeButton.setAttribute("aria-label",getText(texts.close[0],texts.close[1])),this.slotted.assignedElements().forEach(t=>{this.validateColor(t.value)||t.remove(),t.style.background=t.value==="none"?checker:t.value,t.setAttribute("aria-label",getColorName(t.value)),t.addEventListener("click",this.colorSelect),t.getAttribute("aria-pressed")==="true"&&(e=t)}),this.button.style.display="none",this.panel.style.display="flex",this.button.setAttribute("aria-expanded","true"),e?e.focus():this.closeButton.focus(),document.addEventListener("click",this.onDocumentClick)}hide(){this.removeEventListener("keydown",this.keys),document.removeEventListener("click",this.onDocumentClick),this.button.setAttribute("aria-expanded","false"),this.panel.style.display="none",this.button.style.display="block",this.slotted.assignedElements().forEach(e=>e.removeEventListener("click",this.colorSelect)),this.button.focus()}onDocumentClick(e){[...this.internals.labels].includes(e.target)||e.target.closest("joomla-field-simple-color")!==this&&this.panel.style.display==="flex"&&this.hide()}colorSelect(e){const{currentTarget:t}=e;this.slotted.assignedElements().forEach(i=>i.setAttribute("aria-pressed",i!==t?"false":"true")),this.button.style.background=t.value==="none"?checker:t.value,this.hide(),this.internals.setFormValue(t.value),this.value=t.value,this.dispatchEvent(new Event("change"))}keys(e){if(e.code==="Escape"&&this.hide(),e.code==="Tab"){const t=[...this.slotted.assignedElements(),this.closeButton],i=t.indexOf(this.getActiveElement());e.shiftKey&&i===0?(t[t.length-1].focus(),e.preventDefault()):!e.shiftKey&&i===t.length-1&&(t[0].focus(),e.preventDefault())}}getActiveElement(e=document){const t=e.activeElement;return t?t.shadowRoot?this.getActiveElement(t.shadowRoot):t:null}validateColor(e){return this.div.style.color=e,this.div.style.color!==""}}JoomlaFieldSimpleColor.formAssociated=!0,customElements.define("joomla-field-simple-color",JoomlaFieldSimpleColor);
|
||||
Binary file not shown.
@@ -0,0 +1,625 @@
|
||||
/**
|
||||
* @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');
|
||||
if (fieldset) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find row position before sorting
|
||||
const fromPosition = Array.from(src.parentElement.children).filter(row => row.matches(that.repeatableElement)).indexOf(src);
|
||||
if (isRowBefore) {
|
||||
dest.parentNode.insertBefore(src, dest);
|
||||
} else {
|
||||
dest.parentNode.insertBefore(src, dest.nextSibling);
|
||||
}
|
||||
|
||||
// Find final row position, after sorting
|
||||
const toPosition = Array.from(src.parentElement.children).filter(row => row.matches(that.repeatableElement)).indexOf(src);
|
||||
|
||||
// Dispatch order-changed event
|
||||
if (fromPosition !== toPosition) {
|
||||
that.dispatchEvent(new CustomEvent('subform-order-changed', {
|
||||
detail: {
|
||||
row: src,
|
||||
fromPosition,
|
||||
toPosition
|
||||
},
|
||||
bubbles: true
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch interaction:
|
||||
*
|
||||
* - a touch of "move button" marks a row draggable / "selected",
|
||||
* or deselect previous selected
|
||||
*
|
||||
* - a touch of "move button" in the destination row will move
|
||||
* a selected row to a new position
|
||||
*/
|
||||
this.addEventListener('touchstart', event => {
|
||||
touched = true;
|
||||
|
||||
// Check for .move button
|
||||
const handler = getMoveHandler(event.target);
|
||||
const row = handler ? handler.closest(that.repeatableElement) : null;
|
||||
if (!row || row.closest('joomla-field-subform') !== that) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First selection
|
||||
if (!item) {
|
||||
row.setAttribute('draggable', 'true');
|
||||
row.setAttribute('aria-grabbed', 'true');
|
||||
item = row;
|
||||
} else {
|
||||
// Second selection
|
||||
// Move to selected position
|
||||
if (row !== item) {
|
||||
switchRowPositions(item, row);
|
||||
}
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
// Mouse interaction
|
||||
// - mouse down, enable "draggable" and allow to drag the row,
|
||||
// - mouse up, disable "draggable"
|
||||
this.addEventListener('mousedown', ({
|
||||
target
|
||||
}) => {
|
||||
if (touched) return;
|
||||
|
||||
// Check for .move button
|
||||
const handler = getMoveHandler(target);
|
||||
const row = handler ? handler.closest(that.repeatableElement) : null;
|
||||
if (!row || row.closest('joomla-field-subform') !== that) {
|
||||
return;
|
||||
}
|
||||
row.setAttribute('draggable', 'true');
|
||||
row.setAttribute('aria-grabbed', 'true');
|
||||
item = row;
|
||||
});
|
||||
this.addEventListener('mouseup', () => {
|
||||
if (item && !touched) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard interaction
|
||||
// - "tab" to navigate to needed row,
|
||||
// - modifier (ctr,alt,shift) + "space" select the row,
|
||||
// - "tab" to select destination,
|
||||
// - "enter" to place selected row in to destination
|
||||
// - "esc" to cancel selection
|
||||
this.addEventListener('keydown', event => {
|
||||
if (event.code !== KEYCODE.ESC && event.code !== KEYCODE.SPACE && event.code !== KEYCODE.ENTER || event.target.form || !event.target.matches(that.repeatableElement)) {
|
||||
return;
|
||||
}
|
||||
const row = event.target;
|
||||
|
||||
// Make sure we handle correct children
|
||||
if (!row || row.closest('joomla-field-subform') !== that) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Space is the selection or unselection keystroke
|
||||
if (event.code === KEYCODE.SPACE && hasModifier(event)) {
|
||||
// Unselect previously selected
|
||||
if (row.getAttribute('aria-grabbed') === 'true') {
|
||||
row.setAttribute('draggable', 'false');
|
||||
row.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
} else {
|
||||
// Select new
|
||||
// If there was previously selected
|
||||
if (item) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
|
||||
// Mark new selection
|
||||
row.setAttribute('draggable', 'true');
|
||||
row.setAttribute('aria-grabbed', 'true');
|
||||
item = row;
|
||||
}
|
||||
|
||||
// Prevent default to suppress any native actions
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// Escape is the cancel keystroke (for any target element)
|
||||
if (event.code === KEYCODE.ESC && item) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
|
||||
// Enter, to place selected item in selected position
|
||||
if (event.code === KEYCODE.ENTER && item) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
|
||||
// Do nothing here
|
||||
if (row === item) {
|
||||
item = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the item to selected position
|
||||
switchRowPositions(item, row);
|
||||
event.preventDefault();
|
||||
item = null;
|
||||
}
|
||||
});
|
||||
|
||||
// dragstart event to initiate mouse dragging
|
||||
this.addEventListener('dragstart', ({
|
||||
dataTransfer
|
||||
}) => {
|
||||
if (item) {
|
||||
// We going to move the row
|
||||
dataTransfer.effectAllowed = 'move';
|
||||
|
||||
// This need to work in Firefox and IE10+
|
||||
dataTransfer.setData('text', '');
|
||||
}
|
||||
});
|
||||
this.addEventListener('dragover', event => {
|
||||
if (item) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle drag action, move element to hovered position
|
||||
this.addEventListener('dragenter', ({
|
||||
target
|
||||
}) => {
|
||||
// Make sure the target in the correct container
|
||||
if (!item || target.parentElement.closest('joomla-field-subform') !== that) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a hovered row
|
||||
const row = target.closest(that.repeatableElement);
|
||||
|
||||
// One more check for correct parent
|
||||
if (!row || row.closest('joomla-field-subform') !== that) return;
|
||||
switchRowPositions(item, row);
|
||||
});
|
||||
|
||||
// dragend event to clean-up after drop or cancelation
|
||||
// which fires whether or not the drop target was valid
|
||||
this.addEventListener('dragend', () => {
|
||||
if (item) {
|
||||
item.setAttribute('draggable', 'false');
|
||||
item.setAttribute('aria-grabbed', 'false');
|
||||
item = null;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Move UP, Move Down sorting
|
||||
*/
|
||||
const btnUp = `${that.buttonMove}-up`;
|
||||
const btnDown = `${that.buttonMove}-down`;
|
||||
this.addEventListener('click', ({
|
||||
target
|
||||
}) => {
|
||||
if (target.closest('joomla-field-subform') !== this) {
|
||||
return;
|
||||
}
|
||||
const btnUpEl = target.closest(btnUp);
|
||||
const btnDownEl = !btnUpEl ? target.closest(btnDown) : null;
|
||||
if (!btnUpEl && !btnDownEl) {
|
||||
return;
|
||||
}
|
||||
let row = (btnUpEl || btnDownEl).closest(that.repeatableElement);
|
||||
row = row && row.closest('joomla-field-subform') === this ? row : null;
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const rows = this.getRows();
|
||||
const curIdx = rows.indexOf(row);
|
||||
let dstIdx = 0;
|
||||
if (btnUpEl) {
|
||||
dstIdx = curIdx - 1;
|
||||
dstIdx = dstIdx < 0 ? rows.length - 1 : dstIdx;
|
||||
} else {
|
||||
dstIdx = curIdx + 1;
|
||||
dstIdx = dstIdx > rows.length - 1 ? 0 : dstIdx;
|
||||
}
|
||||
switchRowPositions(row, rows[dstIdx]);
|
||||
});
|
||||
}
|
||||
}
|
||||
customElements.define('joomla-field-subform', JoomlaFieldSubform);
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -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) {
|
||||
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);
|
||||
@@ -0,0 +1,4 @@
|
||||
import s from"joomla.dialog";/**
|
||||
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/class n extends HTMLElement{constructor(){super(),this.onUserSelect="",this.onchangeStr="",this.modalClose=this.modalClose.bind(this),this.setValue=this.setValue.bind(this),this.modalOpen=this.modalOpen.bind(this)}static get observedAttributes(){return["url","modal","modal-width","modal-height","modal-title","input","input-name","button-select"]}get url(){return this.getAttribute("url")}set url(t){this.setAttribute("url",t)}get modalWidth(){return this.getAttribute("modal-width")}set modalWidth(t){this.setAttribute("modal-width",t)}get modalTitle(){return this.getAttribute("modal-title")}set modalTitle(t){this.setAttribute("modal-title",t)}get modalHeight(){return this.getAttribute("modal-height")}set modalHeight(t){this.setAttribute("modal-height",t)}get inputId(){return this.getAttribute("input")}set inputId(t){this.setAttribute("input",t)}get inputNameClass(){return this.getAttribute("input-name")}set inputNameClass(t){this.setAttribute("input-name",t)}get buttonSelectClass(){return this.getAttribute("button-select")}set buttonSelectClass(t){this.setAttribute("button-select",t)}connectedCallback(){this.input=this.querySelector(this.inputId),this.inputName=this.querySelector(this.inputNameClass),this.buttonSelect=this.querySelector(this.buttonSelectClass),this.buttonSelect&&(this.buttonSelect.addEventListener("click",this.modalOpen.bind(this)),this.onchangeStr=this.input.getAttribute("data-onchange"),this.onchangeStr&&(this.onUserSelect=new Function(this.onchangeStr),this.input.addEventListener("change",this.onUserSelect)))}disconnectedCallback(){this.onUserSelect&&this.input&&this.input.removeEventListener("change",this.onUserSelect),this.buttonSelect&&this.buttonSelect.removeEventListener("click",this.modalOpen),this.modal&&this.modal.removeEventListener("hide",this)}modalOpen(){const t=new s({popupType:"iframe",src:this.url,textHeader:this.modalTitle,width:this.modalWidth,height:this.modalHeight});t.classList.add("joomla-dialog-user-field"),t.show();const e=i=>{i.origin===window.location.origin&&(i.data.messageType==="joomla:content-select"?(this.setValue(i.data.id,i.data.name),t.close()):i.data.messageType==="joomla:cancel"&&t.close())};window.addEventListener("message",e),t.addEventListener("joomla-dialog:close",()=>{window.removeEventListener("message",e),t.destroy(),this.dialog=null,this.inputName.focus(),this.buttonSelect.focus()}),this.dialog=t}modalClose(){this.dialog&&this.dialog.close()}setValue(t,e){this.input.setAttribute("value",t),this.inputName.setAttribute("value",e||t),this.input.dispatchEvent(new CustomEvent("change")),this.dispatchEvent(new CustomEvent("change",{detail:{value:t,name:e},bubbles:!0}))}}customElements.define("joomla-field-user",n);
|
||||
Binary file not shown.
@@ -0,0 +1,553 @@
|
||||
/**
|
||||
* @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 = () => {
|
||||
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) {
|
||||
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);
|
||||
} 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) {
|
||||
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 = Joomla.selectedMediaFile.url.substr(0, Joomla.selectedMediaFile.url.lastIndexOf('.')).replace(/%20/g, ' ').split('/').pop();
|
||||
outputText = `<a download href="${Joomla.selectedMediaFile.url}">${Joomla.Text._('JFIELD_MEDIA_DOWNLOAD_FILE').replace('{file}', name)}</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);
|
||||
@@ -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 u=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),u.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=Joomla.selectedMediaFile.url.substr(0,Joomla.selectedMediaFile.url.lastIndexOf(".")).replace(/%20/g," ").split("/").pop();o=`<a download href="${Joomla.selectedMediaFile.url}">${Joomla.Text._("JFIELD_MEDIA_DOWNLOAD_FILE").replace("{file}",r)}</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);
|
||||
Binary file not shown.
@@ -0,0 +1,173 @@
|
||||
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) {
|
||||
hasValue ? el.removeAttribute('hidden') : el.setAttribute('hidden', '');
|
||||
} else {
|
||||
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));
|
||||
@@ -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));
|
||||
Binary file not shown.
@@ -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;
|
||||
};
|
||||
|
||||
}());
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/(function(){"use strict";window.processModalParent=function(e,o,l,d,a,m,n){var t=document.getElementById(e+"_id")||document.getElementById(e+"_value"),u=document.getElementById(e+"_name")||document.getElementById(e);o=o||"",l=l||"",d=d||"",n=n||"",a=a||"",m=m||"";var r=t.value!==o;return o?(t.value=o,u.value=l,document.getElementById(e+"_select")&&document.getElementById(e+"_select").classList.add("hidden"),document.getElementById(e+"_new")&&document.getElementById(e+"_new").classList.add("hidden"),document.getElementById(e+"_edit")&&document.getElementById(e+"_edit").classList.remove("hidden"),document.getElementById(e+"_clear")&&document.getElementById(e+"_clear").classList.remove("hidden"),document.getElementById(e+"_propagate")&&document.getElementById(e+"_propagate").classList.remove("hidden")):(t.value="",u.value=t.getAttribute("data-text"),document.getElementById(e+"_select")&&document.getElementById(e+"_select").classList.remove("hidden"),document.getElementById(e+"_new")&&document.getElementById(e+"_new").classList.remove("hidden"),document.getElementById(e+"_edit")&&document.getElementById(e+"_edit").classList.add("hidden"),document.getElementById(e+"_clear")&&document.getElementById(e+"_clear").classList.add("hidden"),document.getElementById(e+"_propagate")&&document.getElementById(e+"_propagate").classList.add("hidden")),r&&t.dispatchEvent(new CustomEvent("change",{bubbles:!0,cancelable:!0})),t.getAttribute("data-required")=="1"&&(document.formvalidator.validate(t),document.formvalidator.validate(u)),!1},window.processModalEdit=function(e,o,l,d,a,m,n,t){m=m||d.toLowerCase()+"-form",n=n||"jform_id",t=t||"jform_title";var u=e.parentNode.parentNode.parentNode.parentNode.id,r=a,s=document.getElementById(u).getElementsByTagName("iframe")[0];s.id="Frame_"+u;var c=s.contentDocument;return a==="cancel"?(s.contentWindow.Joomla.submitbutton(d.toLowerCase()+"."+a),Joomla.Modal.getCurrent().close()):(s.addEventListener("load",function(){c=this.contentDocument,c.getElementById(n)&&c.getElementById(n).value!="0"&&[].slice.call(c.querySelectorAll('joomla-alert[type="danger"]')).length==0&&(window.processModalParent(o,c.getElementById(n).value,c.getElementById(t).value),a==="save"&&window.processModalEdit(e,o,"edit",d,"cancel",m,n,t)),s.classList.remove("visually-hidden")}),c.formvalidator.isValid(c.getElementById(m))&&(a==="save"&&(r="apply",s.classList.add("visually-hidden")),s.contentWindow.Joomla.submitbutton(d.toLowerCase()+"."+r))),!1},window.processModalSelect=function(e,o,l,d,a,m,n,t){return window.processModalParent(o,l,d,a,n,t,m),Joomla.Modal.getCurrent()&&Joomla.Modal.getCurrent().close(),!1}})();
|
||||
Binary file not shown.
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* PasswordStrength script by Thomas Kjaergaard
|
||||
* License: MIT
|
||||
* Repo: https://github.com/tkjaergaard/Password-Strength
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014 Thomas Kjærgaard
|
||||
*
|
||||
* 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);
|
||||
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('output');
|
||||
label.setAttribute('id', `password-${index}`);
|
||||
label.setAttribute('for', field.id);
|
||||
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);
|
||||
@@ -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 a=0,t=0;return["lowercase","uppercase","numbers","special","length"].forEach(r=>{this[r]>0&&(t+=1)}),a+=this.constructor.calc(e,/[a-z]/g,this.lowercase,t),a+=this.constructor.calc(e,/[A-Z]/g,this.uppercase,t),a+=this.constructor.calc(e,/[0-9]/g,this.numbers,t),a+=this.constructor.calc(e,/[@$!#?=;:*\-_€%&()`´+[\]{}'"\\|,.<>/~^]/g,this.special,t),t===1?a+=e.length>this.length?100:100/this.length*e.length:a+=e.length>this.length?100/t:100/t/this.length*e.length,a}static calc(e,a,t,s){const r=e.match(a);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 a=t=>{const s=e.querySelector("meter"),r=t.getAttribute("data-min-length"),c=t.getAttribute("data-min-integers"),n=t.getAttribute("data-min-symbols"),i=t.getAttribute("data-min-uppercase"),o=t.getAttribute("data-min-lowercase"),h=new PasswordStrength({lowercase:o||0,uppercase:i||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 i=e.createElement("output");i.setAttribute("id",`password-${r}`),i.setAttribute("for",s.id),s.parentNode.insertAdjacentElement("afterEnd",i),s.parentNode.insertAdjacentElement("afterEnd",n),s.value.length>0&&s.setAttribute("required",!0),s.addEventListener("keyup",({target:o})=>a(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"),i=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:i||0,length:c||12}).getScore(s)===100})})})(Joomla,document);
|
||||
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @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');
|
||||
if (toggleButton) {
|
||||
const hasClickListener = toggleButton.getAttribute('clickListener') === 'true';
|
||||
if (!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);
|
||||
@@ -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");s&&(s.getAttribute("clickListener")==="true"||(s.setAttribute("clickListener","true"),s.addEventListener("click",()=>{const o=s.firstElementChild,i=s.lastElementChild;e.type==="password"?(o.classList.remove("icon-eye"),o.classList.add("icon-eye-slash"),e.type="text",e.focus(),i.innerText=Joomla.Text._("JHIDEPASSWORD")):e.type==="text"&&(o.classList.add("icon-eye"),o.classList.remove("icon-eye-slash"),e.type="password",e.focus(),i.innerText=Joomla.Text._("JSHOWPASSWORD"))})));const t=e.parentNode.querySelector(".input-password-modify");t&&t.addEventListener("click",()=>{!t.classList.contains("locked")===!0?(t.classList.add("locked"),e.value="",e.setAttribute("disabled",""),t.innerText=Joomla.Text._("JMODIFY")):(t.classList.remove("locked"),e.removeAttribute("disabled"),e.focus(),t.innerText=Joomla.Text._("JCANCEL"))})})}l.addEventListener("joomla:updated",c),l.addEventListener("DOMContentLoaded",c)})(document);
|
||||
Binary file not shown.
@@ -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);
|
||||
})();
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/(()=>{const n=({target:s})=>{const e=s,a=parseInt(e.value,10);e.classList.remove("form-select-success","form-select-danger"),a===1?e.classList.add("form-select-success"):(a===0||a===-2)&&e.classList.add("form-select-danger")},t=()=>{document.querySelectorAll(".form-select-color-state").forEach(s=>{const e=parseInt(s.value,10);e===1?s.classList.add("form-select-success"):(e===0||e===-2)&&s.classList.add("form-select-danger"),s.addEventListener("change",n)}),document.removeEventListener("DOMContentLoaded",t,!0)};document.addEventListener("DOMContentLoaded",t,!0),document.addEventListener("joomla:updated",t,!0)})();
|
||||
Binary file not shown.
@@ -0,0 +1,714 @@
|
||||
/** 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
|
||||
};
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
document.formvalidator = new JFormValidator();
|
||||
|
||||
/**
|
||||
* Expose the classes to the global scope
|
||||
* These will be removed in Joomla! 6.0
|
||||
*/
|
||||
window.JFormValidator = JFormValidator;
|
||||
window.punycode = punycode;
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/Joomla.toggleInlineHelp=l=>{document.querySelectorAll(`div.${l}`).forEach(t=>{t.classList.toggle("d-none");const e=t.id,o=e?e.substring(0,e.length-5):null,n=o?document.getElementById(o):null,s=t.classList.contains("d-none");!o||!n||(s&&n.hasAttribute("aria-describedby")?n.removeAttribute("aria-describedby"):s||n.setAttribute("aria-describedby",e))})},document.querySelectorAll(".button-inlinehelp").forEach(l=>{var t;const e=(t=l.dataset.class)!=null?t:"hide-aware-inline-help";if(document.getElementsByClassName(e).length===0){l.classList.add("d-none");return}l.addEventListener("click",n=>{n.preventDefault(),Joomla.toggleInlineHelp(e)})});
|
||||
Binary file not shown.
@@ -0,0 +1,127 @@
|
||||
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;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
:host(.fullscreen) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
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);
|
||||
@@ -0,0 +1,34 @@
|
||||
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;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
:host(.fullscreen) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
svg {
|
||||
width: 345px;
|
||||
height: 345px;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.joomla-spinner {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150" width="${this.size}" height="${this.size}">
|
||||
<style>@keyframes joomla-spinner{0%,28%,to{opacity:.30}20%{opacity:1}}.joomla-spinner{animation:joomla-spinner 1.6s infinite cubic-bezier(0,.15,1,.75)}
|
||||
</style>
|
||||
<path d="m27 75.5-2.9-2.9c-8.9-8.9-11.7-21.7-8.3-33C6.9 37.6.2 29.6.2 20.1c0-11.1 9-20 20-20 10 0 18.2 7.3 19.8 16.8 10.8-2.5 22.6.4 31.1 8.8l1.2 1.2-14.9 14.7-1.1-1.2c-4.8-4.8-12.6-4.8-17.4 0-4.8 4.8-4.8 12.6 0 17.4l2.9 2.9 14.8 14.8 15.6 15.6-14.8 14.8-15.6-15.7L27 75.5z" class="joomla-spinner" style="animation-delay:-1.2s" fill="#7ac143" />
|
||||
<path d="m43.5 58.9 15.6-15.6 14.8-14.8 2.9-2.9c8.9-8.9 21.6-11.7 32.8-8.4C111 7.5 119.4 0 129.5 0c11.1 0 20 9 20 20 0 10.2-7.6 18.6-17.4 19.9 3.2 11.2.4 23.8-8.4 32.7l-1.2 1.2L107.7 59l1.1-1.1c4.8-4.8 4.8-12.6 0-17.4-4.8-4.8-12.5-4.8-17.4 0l-2.9 2.9-14.6 14.7-15.6 15.6-14.8-14.8z" class="joomla-spinner" style="animation-delay:-.8s" fill="#f9a541" />
|
||||
<path d="M110.1 133.5c-11.4 3.5-24.2.7-33.2-8.3l-1.1-1.1 14.8-14.8 1.1 1.1c4.8 4.8 12.6 4.8 17.4 0 4.8-4.8 4.8-12.5 0-17.4l-2.9-2.9-14.9-14.6-15.6-15.7L90.5 45l15.6 15.6 14.8 14.8 2.9 2.9c8.5 8.5 11.4 20.5 8.8 31.3 9.7 1.4 17.2 9.7 17.2 19.8 0 11.1-9 20-20 20-9.8.2-17.9-6.7-19.7-15.9z" class="joomla-spinner" style="animation-delay:-.4s" fill="#f44321" />
|
||||
<path d="m104.3 92-15.6 15.6-14.8 14.8-2.9 2.9c-8.5 8.5-20.6 11.4-31.5 8.7-2 8.9-10 15.5-19.5 15.5-11.1 0-20-9-20-20 0-9.5 6.6-17.4 15.4-19.5-2.8-11 .1-23.1 8.7-31.7l1.1-1.1L40 92l-1.1 1.1c-4.8 4.8-4.8 12.6 0 17.4 4.8 4.8 12.6 4.8 17.4 0l2.9-2.9L74 92.8l15.6-15.6L104.3 92z" class="joomla-spinner" fill="#5091cd" />
|
||||
</svg>`,this.shadowRoot.appendChild(e.content.cloneNode(!0))}connectedCallback(){this.style.backgroundColor=this.color?this.color:getColorScheme(),darkModeMediaQuery.addEventListener("change",this.systemQuery),this.inline||this.classList.add("fullscreen")}disconnectedCallback(){darkModeMediaQuery.removeEventListener("change",this.systemQuery)}attributeChangedCallback(e,s,t){switch(e){case"color":t&&t!==s&&(this.style.backgroundColor=t);break;case"size":if(t&&t!==s){const i=this.shadowRoot.querySelector("svg");i.setAttribute("width",t),i.setAttribute("height",t)}break;case"inline":this.hasAttribute("inline")?this.classList.remove("fullscreen"):this.classList.add("fullscreen");break}}systemQuery(e){if(!notForced()||this.color)return;const s=e.matches===!0?darkColor:lightColor;this.style.backgroundColor!==s&&(this.style.backgroundColor=s)}}window.customElements.define("joomla-core-loader",JoomlaCoreLoader);
|
||||
Binary file not shown.
@@ -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();
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import r from"joomla.dialog";/**
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/const c="[data-joomla-dialog]",i="joomlaDialog",d="joomlaDialogCache";document.addEventListener("click",l=>{const o=l.target.closest(c);if(!o)return;l.preventDefault();const n=!!o.dataset[d];if(n&&o.JoomlaDialogInstance){Joomla.Modal.setCurrent(o.JoomlaDialogInstance),o.JoomlaDialogInstance.show();return}const e=o.dataset[i]?JSON.parse(o.dataset[i]):{};o.nodeName==="A"&&(e.popupType||(e.popupType=o.hash?"inline":"iframe"),!e.src&&e.popupType==="iframe"?e.src=o.href:!e.src&&e.popupType==="inline"&&(e.src=o.hash)),delete e.popupTemplate;const a=new r(e);n&&(o.JoomlaDialogInstance=a),"closeOnMessage"in o.dataset&&window.addEventListener("message",t=>{t.source===(a.getBodyContent().contentWindow||window)&&a.close()}),a.addEventListener("joomla-dialog:close",()=>{Joomla.Modal.setCurrent(null),n||a.destroy();const{checkinUrl:t}=o.dataset,s="reloadOnClose"in o.dataset;t?Joomla.request({url:t,method:"POST",promise:!0}).then(()=>{s&&window.location.reload()}):s&&window.location.reload()}),a.JoomlaDialogTrigger=o,Joomla.Modal.setCurrent(a),a.show()});
|
||||
Binary file not shown.
@@ -0,0 +1,642 @@
|
||||
/**
|
||||
* @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;
|
||||
// @internal. Hold properties addressed directly to <dialog> element, like "aria-".
|
||||
this.dialogProps = {};
|
||||
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 for properties which should be applied to the <dialog> not to the <JoomlaDialog>, like "aria-".
|
||||
['ariaLabelledby', 'ariaLabel'].forEach(key => {
|
||||
if (config[key] !== undefined) {
|
||||
this.dialogProps[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);
|
||||
|
||||
// Apply dialog properties if any
|
||||
Object.entries(this.dialogProps).forEach(([k, v]) => {
|
||||
this.dialog[k] = v;
|
||||
});
|
||||
|
||||
// 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 aria-label if it is still missing
|
||||
if (!this.dialog.ariaLabel) {
|
||||
this.dialog.ariaLabel = this.textHeader;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 };
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -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(''));
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/window.customElements.define("joomla-hidden-mail",class extends HTMLElement{constructor(){super(),this.newElement="",this.base=""}disconnectedCallback(){this.innerHTML=""}connectedCallback(){if(this.base=`${this.getAttribute("base")}/`,this.getAttribute("is-link")==="1"?(this.newElement=document.createElement("a"),this.newElement.setAttribute("href",`mailto:${this.constructor.b64DecodeUnicode(this.getAttribute("first"))}@${this.constructor.b64DecodeUnicode(this.getAttribute("last"))}`),[].slice.call(this.attributes).forEach((t,e)=>{const{nodeName:i}=this.attributes.item(e);if(i&&["is-link","is-email","first","last","text"].indexOf(i)===-1){const{nodeValue:s}=this.attributes.item(e);this.newElement.setAttribute(i,s)}})):this.newElement=document.createElement("span"),this.getAttribute("text")){let t=this.constructor.b64DecodeUnicode(this.getAttribute("text"));t=t.replace('src="images/',`src="${this.base}images/`).replace('src="media/',`src="${this.base}media/`),this.newElement.innerHTML=Joomla.sanitizeHtml(t)}else this.newElement.innerText=`${window.atob(this.getAttribute("first"))}@${window.atob(this.getAttribute("last"))}`;this.removeAttribute("class"),this.removeAttribute("style"),this.innerText="",this.appendChild(this.newElement)}static b64DecodeUnicode(t){return decodeURIComponent(Array.prototype.map.call(atob(t),e=>`%${`00${e.charCodeAt(0).toString(16)}`.slice(-2)}`).join(""))}});
|
||||
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @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) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/window.customElements.define("joomla-toolbar-button",class extends HTMLElement{get task(){return this.getAttribute("task")}get listSelection(){return this.hasAttribute("list-selection")}get form(){return this.getAttribute("form")}get formValidation(){return this.hasAttribute("form-validation")}get confirmMessage(){return this.getAttribute("confirm-message")}constructor(){if(super(),!Joomla)throw new Error("Joomla API is not properly initiated");this.confirmationReceived=!1,this.onChange=this.onChange.bind(this),this.executeTask=this.executeTask.bind(this)}connectedCallback(){this.buttonElement=this.querySelector("button, a"),this.buttonElement.addEventListener("click",this.executeTask);const e=this.form||"adminForm";if(this.formElement=document.getElementById(e),this.disabled=!1,this.listSelection&&this.setDisabled(!0),this.listSelection){if(!this.formElement)throw new Error(`The form "${e}" is required to perform the task, but the form was not found on the page.`);this.formElement.boxchecked.addEventListener("change",this.onChange)}}disconnectedCallback(){this.formElement.boxchecked&&this.formElement.boxchecked.removeEventListener("change",this.onChange),this.buttonElement.removeEventListener("click",this.executeTask)}onChange({target:e}){this.setDisabled(e.value<1)}setDisabled(e){this.disabled=!!e,this.buttonElement&&(this.disabled?this.buttonElement.nodeName==="BUTTON"?this.buttonElement.disabled=!0:this.buttonElement.classList.add("disabled"):this.buttonElement.nodeName==="BUTTON"?this.buttonElement.disabled=!1:this.buttonElement.classList.remove("disabled"))}executeTask(){return this.disabled?!1:this.confirmMessage&&!this.confirmationReceived?(import("joomla.dialog").then(e=>e.default.confirm(this.confirmMessage,Joomla.Text._("WARNING","Warning"))).then(e=>{e&&(this.confirmationReceived=!0,this.buttonElement.click())}),!1):(this.confirmationReceived=!1,this.task&&Joomla.submitbutton(this.task,this.form,this.formValidation),!0)}});
|
||||
Binary file not shown.
@@ -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);
|
||||
@@ -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
|
||||
*/if(!window.Joomla)throw new Error("Joomla API was not properly initialised");const keepAliveOptions=Joomla.getOptions("system.keepalive"),keepAliveInterval=keepAliveOptions&&keepAliveOptions.interval?parseInt(keepAliveOptions.interval,10):45*1e3;let keepAliveUri=keepAliveOptions&&keepAliveOptions.uri?keepAliveOptions.uri.replace(/&/g,"&"):"";if(keepAliveUri===""){const e=Joomla.getOptions("system.paths");keepAliveUri=`${e?`${e.root}/index.php`:window.location.pathname}?option=com_ajax&format=json`}setInterval(()=>fetch(keepAliveUri,{method:"POST"}),keepAliveInterval);
|
||||
Binary file not shown.
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Apply the predefined action for the current element
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
function gridItemAction(event) {
|
||||
let item = event.target;
|
||||
if (item.nodeName === 'SPAN' && ['A', 'BUTTON'].includes(item.parentNode.nodeName)) {
|
||||
item = item.parentNode;
|
||||
}
|
||||
if (item.nodeName === 'A') {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (item.hasAttribute('disabled') || !item.hasAttribute('data-item-task')) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
itemId
|
||||
} = item.dataset;
|
||||
const {
|
||||
itemTask
|
||||
} = item.dataset;
|
||||
const {
|
||||
itemFormId
|
||||
} = item.dataset;
|
||||
if (itemFormId) {
|
||||
Joomla.listItemTask(itemId, itemTask, itemFormId);
|
||||
} else {
|
||||
Joomla.listItemTask(itemId, itemTask);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply the transition state for the current element
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
function gridTransitionItemAction(event) {
|
||||
const item = event.target;
|
||||
if (item.nodeName !== 'SELECT' || item.hasAttribute('disabled')) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
itemId
|
||||
} = item.dataset;
|
||||
const {
|
||||
itemTask
|
||||
} = item.dataset;
|
||||
const {
|
||||
itemFormId
|
||||
} = item.dataset;
|
||||
item.form.transition_id.value = item.value;
|
||||
if (itemFormId) {
|
||||
Joomla.listItemTask(itemId, itemTask, itemFormId);
|
||||
} else {
|
||||
Joomla.listItemTask(itemId, itemTask);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply the transition state for the current element
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
function gridTransitionButtonAction(event) {
|
||||
let item = event.target;
|
||||
if (item.nodeName === 'SPAN' && item.parentNode.nodeName === 'BUTTON') {
|
||||
item = item.parentNode;
|
||||
}
|
||||
if (item.hasAttribute('disabled')) {
|
||||
return;
|
||||
}
|
||||
Joomla.toggleAllNextElements(item, 'd-none');
|
||||
}
|
||||
|
||||
/*
|
||||
* Switch the check state for the current element
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
function applyIsChecked(event) {
|
||||
const item = event.target;
|
||||
const itemFormId = item.dataset.itemFormId || '';
|
||||
if (itemFormId) {
|
||||
Joomla.isChecked(item.checked, itemFormId);
|
||||
} else {
|
||||
Joomla.isChecked(item.checked);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set up an interactive list elements
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
const setup = ({
|
||||
target
|
||||
}) => {
|
||||
target.querySelectorAll('.js-grid-item-check-all').forEach(element => element.addEventListener('click', event => Joomla.checkAll(event.target)));
|
||||
target.querySelectorAll('.js-grid-item-is-checked').forEach(element => element.addEventListener('click', applyIsChecked));
|
||||
target.querySelectorAll('.js-grid-item-action').forEach(element => element.addEventListener('click', gridItemAction));
|
||||
target.querySelectorAll('.js-grid-item-transition-action').forEach(element => element.addEventListener('change', gridTransitionItemAction));
|
||||
target.querySelectorAll('.js-grid-button-transition-action').forEach(element => element.addEventListener('click', gridTransitionButtonAction));
|
||||
};
|
||||
setup({
|
||||
target: document
|
||||
});
|
||||
document.addEventListener('joomla:updated', setup);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user