From 3ced9cd83db38731a1e82dafc0dcbdfe7db6cc2b Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Tue, 29 Jan 2019 10:15:46 +0100 Subject: Move jQuery plugins into modules and add them to the bundle Signed-off-by: Christoph Wurst --- core/src/Util/escapeHTML.js | 36 ++++++ core/src/globals.js | 2 + core/src/jquery/avatar.js | 162 ++++++++++++++++++++++++ core/src/jquery/contactsmenu.js | 125 +++++++++++++++++++ core/src/jquery/exists.js | 31 +++++ core/src/jquery/filterattr.js | 31 +++++ core/src/jquery/index.js | 32 +++++ core/src/jquery/ocdialog.js | 270 ++++++++++++++++++++++++++++++++++++++++ core/src/jquery/octemplate.js | 104 ++++++++++++++++ core/src/jquery/placeholder.js | 174 ++++++++++++++++++++++++++ core/src/jquery/selectrange.js | 43 +++++++ core/src/jquery/showpassword.js | 149 ++++++++++++++++++++++ core/src/jquery/tipsy.js | 83 ++++++++++++ core/src/jquery/ui-fixes.js | 8 ++ core/src/main.js | 1 + 15 files changed, 1251 insertions(+) create mode 100644 core/src/Util/escapeHTML.js create mode 100644 core/src/jquery/avatar.js create mode 100644 core/src/jquery/contactsmenu.js create mode 100644 core/src/jquery/exists.js create mode 100644 core/src/jquery/filterattr.js create mode 100644 core/src/jquery/index.js create mode 100644 core/src/jquery/ocdialog.js create mode 100644 core/src/jquery/octemplate.js create mode 100644 core/src/jquery/placeholder.js create mode 100644 core/src/jquery/selectrange.js create mode 100644 core/src/jquery/showpassword.js create mode 100644 core/src/jquery/tipsy.js create mode 100644 core/src/jquery/ui-fixes.js (limited to 'core/src') diff --git a/core/src/Util/escapeHTML.js b/core/src/Util/escapeHTML.js new file mode 100644 index 00000000000..f6cf868a6d0 --- /dev/null +++ b/core/src/Util/escapeHTML.js @@ -0,0 +1,36 @@ +/* + * @copyright 2019 Christoph Wurst + * + * @author 2019 Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Sanitizes a HTML string by replacing all potential dangerous characters with HTML entities + * @param {string} s String to sanitize + * @return {string} Sanitized string + */ +export default function escapeHTML (s) { + return s.toString() + .split('&') + .join('&') + .split('<') + .join('<').split('>') + .join('>').split('"') + .join('"').split('\'') + .join('''); +} diff --git a/core/src/globals.js b/core/src/globals.js index 5d077f22486..ad1997c470d 100644 --- a/core/src/globals.js +++ b/core/src/globals.js @@ -51,6 +51,7 @@ import 'strengthify/strengthify.css' import OC from './OC/index' import OCP from './OCP/index' import OCA from './OCA/index' +import escapeHTML from './Util/escapeHTML' window['_'] = _ window['$'] = $ @@ -72,6 +73,7 @@ window['moment'] = moment window['OC'] = OC window['OCP'] = OCP window['OCA'] = OCA +window['escapeHTML'] = escapeHTML /** * translate a string diff --git a/core/src/jquery/avatar.js b/core/src/jquery/avatar.js new file mode 100644 index 00000000000..75aef6d958f --- /dev/null +++ b/core/src/jquery/avatar.js @@ -0,0 +1,162 @@ +/* + * @copyright 2018 Christoph Wurst + * + * @author 2018 Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import $ from 'jquery' + +import OC from '../OC' + +/** + * This plugin inserts the right avatar for the user, depending on, whether a + * custom avatar is uploaded - which it uses then - or not, and display a + * placeholder with the first letter of the users name instead. + * For this it queries the core_avatar_get route, thus this plugin is fit very + * tightly for owncloud, and it may not work anywhere else. + * + * You may use this on any
+ * Here I'm using
as an example. + * + * There are 5 ways to call this: + * + * 1. $('.avatardiv').avatar('jdoe', 128); + * This will make the div to jdoe's fitting avatar, with a size of 128px. + * + * 2. $('.avatardiv').avatar('jdoe'); + * This will make the div to jdoe's fitting avatar. If the div already has a + * height, it will be used for the avatars size. Otherwise this plugin will + * search for 'size' DOM data, to use for avatar size. If neither are available + * it will default to 64px. + * + * 3. $('.avatardiv').avatar(); + * This will search the DOM for 'user' data, to use as the username. If there + * is no username available it will default to a placeholder with the value of + * "?". The size will be determined the same way, as the second example. + * + * 4. $('.avatardiv').avatar('jdoe', 128, true); + * This will behave like the first example, except it will also append random + * hashes to the custom avatar images, to force image reloading in IE8. + * + * 5. $('.avatardiv').avatar('jdoe', 128, undefined, true); + * This will behave like the first example, but it will hide the avatardiv, if + * it will display the default placeholder. undefined is the ie8fix from + * example 4 and can be either true, or false/undefined, to be ignored. + * + * 6. $('.avatardiv').avatar('jdoe', 128, undefined, true, callback); + * This will behave like the above example, but it will call the function + * defined in callback after the avatar is placed into the DOM. + * + */ + +$.fn.avatar = function (user, size, ie8fix, hidedefault, callback, displayname) { + var setAvatarForUnknownUser = function (target) { + target.imageplaceholder('?'); + target.css('background-color', '#b9b9b9'); + }; + + if (typeof (user) !== 'undefined') { + user = String(user); + } + if (typeof (displayname) !== 'undefined') { + displayname = String(displayname); + } + + if (typeof (size) === 'undefined') { + if (this.height() > 0) { + size = this.height(); + } else if (this.data('size') > 0) { + size = this.data('size'); + } else { + size = 64; + } + } + + this.height(size); + this.width(size); + + if (typeof (user) === 'undefined') { + if (typeof (this.data('user')) !== 'undefined') { + user = this.data('user'); + } else { + setAvatarForUnknownUser(this); + return; + } + } + + // sanitize + user = String(user).replace(/\//g, ''); + + var $div = this; + var url; + + // If this is our own avatar we have to use the version attribute + if (user === OC.getCurrentUser().uid) { + url = OC.generateUrl( + '/avatar/{user}/{size}?v={version}', + { + user: user, + size: Math.ceil(size * window.devicePixelRatio), + version: oc_userconfig.avatar.version + }); + } else { + url = OC.generateUrl( + '/avatar/{user}/{size}', + { + user: user, + size: Math.ceil(size * window.devicePixelRatio) + }); + } + + var img = new Image(); + + // If the new image loads successfully set it. + img.onload = function () { + $div.clearimageplaceholder(); + $div.append(img); + + if (typeof callback === 'function') { + callback(); + } + }; + // Fallback when avatar loading fails: + // Use old placeholder when a displayname attribute is defined, + // otherwise show the unknown user placeholder. + img.onerror = function () { + $div.clearimageplaceholder(); + if (typeof (displayname) !== 'undefined') { + $div.imageplaceholder(user, displayname); + } else { + setAvatarForUnknownUser($div); + } + + if (typeof callback === 'function') { + callback(); + } + }; + + if (size < 32) { + $div.addClass('icon-loading-small'); + } else { + $div.addClass('icon-loading'); + } + img.width = size; + img.height = size; + img.src = url; + img.alt = ''; +}; diff --git a/core/src/jquery/contactsmenu.js b/core/src/jquery/contactsmenu.js new file mode 100644 index 00000000000..8ee97dd51e9 --- /dev/null +++ b/core/src/jquery/contactsmenu.js @@ -0,0 +1,125 @@ +/* + * @copyright 2018 Christoph Wurst + * + * @author 2018 Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import $ from 'jquery' + +import OC from '../OC' + +const LIST = '' + + ''; + +$.fn.contactsMenu = function (shareWith, shareType, appendTo) { + // 0 - user, 4 - email, 6 - remote + var allowedTypes = [0, 4, 6]; + if (allowedTypes.indexOf(shareType) === -1) { + return; + } + + var $div = this; + appendTo.append(LIST); + var $list = appendTo.find('div.contactsmenu-popover'); + + $div.click(function () { + if (!$list.hasClass('hidden')) { + $list.addClass('hidden'); + $list.hide(); + return; + } + + $list.removeClass('hidden'); + $list.show(); + + if ($list.hasClass('loaded')) { + return; + } + + $list.addClass('loaded'); + $.ajax(OC.generateUrl('/contactsmenu/findOne'), { + method: 'POST', + data: { + shareType: shareType, + shareWith: shareWith + } + }).then(function (data) { + $list.find('ul').find('li').addClass('hidden'); + + var actions; + if (!data.topAction) { + actions = [{ + hyperlink: '#', + title: t('core', 'No action available') + }]; + } else { + actions = [data.topAction].concat(data.actions); + } + + actions.forEach(function (action) { + var template = OC.ContactsMenu.Templates['jquery_entry']; + $list.find('ul').append(template(action)); + }); + + if (actions.length === 0) { + + } + }, function (jqXHR) { + $list.find('ul').find('li').addClass('hidden'); + + var title; + if (jqXHR.status === 404) { + title = t('core', 'No action available'); + } else { + title = t('core', 'Error fetching contact actions'); + } + + var template = OC.ContactsMenu.Templates['jquery_entry']; + $list.find('ul').append(template({ + hyperlink: '#', + title: title + })); + }); + }); + + $(document).click(function (event) { + var clickedList = ($list.has(event.target).length > 0); + var clickedTarget = ($div.has(event.target).length > 0); + + $div.each(function () { + if ($(this).is(event.target)) { + clickedTarget = true; + } + }); + + if (clickedList || clickedTarget) { + return; + } + + $list.addClass('hidden'); + $list.hide(); + }); +}; diff --git a/core/src/jquery/exists.js b/core/src/jquery/exists.js new file mode 100644 index 00000000000..bdf62ab7d4b --- /dev/null +++ b/core/src/jquery/exists.js @@ -0,0 +1,31 @@ +/* + * @copyright 2019 Christoph Wurst + * + * @author 2019 Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import $ from 'jquery' + +/** + * check if an element exists. + * allows you to write if ($('#myid').exists()) to increase readability + * @link http://stackoverflow.com/questions/31044/is-there-an-exists-function-for-jquery + */ +$.fn.exists = function () { + return this.length > 0; +}; diff --git a/core/src/jquery/filterattr.js b/core/src/jquery/filterattr.js new file mode 100644 index 00000000000..7675eaa1e2d --- /dev/null +++ b/core/src/jquery/filterattr.js @@ -0,0 +1,31 @@ +/* + * @copyright 2018 Christoph Wurst + * + * @author 2018 Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import $ from 'jquery' + +/** + * Filter jQuery selector by attribute value + */ +$.fn.filterAttr = function (attrName, attrValue) { + return this.filter(function () { + return $(this).attr(attrName) === attrValue; + }); +}; diff --git a/core/src/jquery/index.js b/core/src/jquery/index.js new file mode 100644 index 00000000000..8e2feb2b65e --- /dev/null +++ b/core/src/jquery/index.js @@ -0,0 +1,32 @@ +/* + * @copyright 2019 Christoph Wurst + * + * @author 2019 Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import './avatar' +import './contactsmenu' +import './exists' +import './filterattr' +import './ocdialog' +import './octemplate' +import './placeholder' +import './selectrange' +import './showpassword' +import './tipsy' +import './ui-fixes' diff --git a/core/src/jquery/ocdialog.js b/core/src/jquery/ocdialog.js new file mode 100644 index 00000000000..8899d9433d1 --- /dev/null +++ b/core/src/jquery/ocdialog.js @@ -0,0 +1,270 @@ +/* + * @copyright 2018 Christoph Wurst + * + * @author 2018 Christoph Wurst + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import $ from 'jquery' + +$.widget('oc.ocdialog', { + options: { + width: 'auto', + height: 'auto', + closeButton: true, + closeOnEscape: true, + modal: false + }, + _create: function () { + var self = this; + + this.originalCss = { + display: this.element[0].style.display, + width: this.element[0].style.width, + height: this.element[0].style.height + }; + + this.originalTitle = this.element.attr('title'); + this.options.title = this.options.title || this.originalTitle; + + this.$dialog = $('
') + .attr({ + // Setting tabIndex makes the div focusable + tabIndex: -1, + role: 'dialog' + }) + .insertBefore(this.element); + this.$dialog.append(this.element.detach()); + this.element.removeAttr('title').addClass('oc-dialog-content').appendTo(this.$dialog); + + this.$dialog.css({ + display: 'inline-block', + position: 'fixed' + }); + + this.enterCallback = null; + + $(document).on('keydown keyup', function (event) { + if ( + event.target !== self.$dialog.get(0) && + self.$dialog.find($(event.target)).length === 0 + ) { + return; + } + // Escape + if ( + event.keyCode === 27 && + event.type === 'keydown' && + self.options.closeOnEscape + ) { + event.stopImmediatePropagation(); + self.close(); + return false; + } + // Enter + if (event.keyCode === 13) { + event.stopImmediatePropagation(); + if (self.enterCallback !== null) { + self.enterCallback(); + event.preventDefault(); + return false; + } + if (event.type === 'keyup') { + event.preventDefault(); + return false; + } + // If no button is selected we trigger the primary + if ( + self.$buttonrow && + self.$buttonrow.find($(event.target)).length === 0 + ) { + var $button = self.$buttonrow.find('button.primary'); + if ($button && !$button.prop('disabled')) { + $button.trigger('click'); + } + } else if (self.$buttonrow) { + $(event.target).trigger('click'); + } + return false; + } + }); + + this._setOptions(this.options); + this._createOverlay(); + }, + _init: function () { + this.$dialog.focus(); + this._trigger('open'); + }, + _setOption: function (key, value) { + var self = this; + switch (key) { + case 'title': + if (this.$title) { + this.$title.text(value); + } else { + var $title = $('

' + + value + + '

'); + this.$title = $title.prependTo(this.$dialog); + } + this._setSizes(); + break; + case 'buttons': + if (this.$buttonrow) { + this.$buttonrow.empty(); + } else { + var $buttonrow = $('
'); + this.$buttonrow = $buttonrow.appendTo(this.$dialog); + } + if (value.length === 1) { + this.$buttonrow.addClass('onebutton'); + } else if (value.length === 2) { + this.$buttonrow.addClass('twobuttons'); + } else if (value.length === 3) { + this.$buttonrow.addClass('threebuttons'); + } + $.each(value, function (idx, val) { + var $button = $('