summaryrefslogtreecommitdiffstats
path: root/core/src
diff options
context:
space:
mode:
Diffstat (limited to 'core/src')
-rw-r--r--core/src/OC/appconfig.js4
-rw-r--r--core/src/components/ContactsMenu.js34
-rw-r--r--core/src/components/MainMenu.js93
-rw-r--r--core/src/components/UserMenu.js53
-rw-r--r--core/src/globals.js3
-rw-r--r--core/src/init.js307
-rw-r--r--core/src/jquery/index.js27
-rw-r--r--core/src/main.js8
-rw-r--r--core/src/session-heartbeat.js76
9 files changed, 600 insertions, 5 deletions
diff --git a/core/src/OC/appconfig.js b/core/src/OC/appconfig.js
index ee7efb8a3ac..1248475ce77 100644
--- a/core/src/OC/appconfig.js
+++ b/core/src/OC/appconfig.js
@@ -26,7 +26,7 @@ export const appConfig = window.oc_appconfig || {}
* @namespace
* @deprecated 16.0.0 Use OCP.AppConfig instead
*/
-const AppConfig = {
+export const AppConfig = {
/**
* @deprecated Use OCP.AppConfig.getValue() instead
*/
@@ -69,5 +69,3 @@ const AppConfig = {
}
};
-
-export default AppConfig;
diff --git a/core/src/components/ContactsMenu.js b/core/src/components/ContactsMenu.js
new file mode 100644
index 00000000000..8243b9ca1f2
--- /dev/null
+++ b/core/src/components/ContactsMenu.js
@@ -0,0 +1,34 @@
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+import $ from 'jquery'
+
+import OC from '../OC'
+
+/**
+ * @todo move to contacts menu code https://github.com/orgs/nextcloud/projects/31#card-21213129
+ */
+export const setUp = () => {
+ new OC.ContactsMenu({
+ el: $('#contactsmenu .menu'),
+ trigger: $('#contactsmenu .menutoggle')
+ })
+}
diff --git a/core/src/components/MainMenu.js b/core/src/components/MainMenu.js
new file mode 100644
index 00000000000..40eca1ecd9d
--- /dev/null
+++ b/core/src/components/MainMenu.js
@@ -0,0 +1,93 @@
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+import $ from 'jquery'
+
+import OC from '../OC'
+
+/**
+ * Set up the main menu toggle to react to media query changes.
+ * If the screen is small enough, the main menu becomes a toggle.
+ * If the screen is bigger, the main menu is not a toggle any more.
+ */
+export const setUp = () => {
+ // init the more-apps menu
+ OC.registerMenu($('#more-apps > a'), $('#navigation'))
+
+ // toggle the navigation
+ const $toggle = $('#header .header-appname-container')
+ const $navigation = $('#navigation')
+ const $appmenu = $('#appmenu')
+
+ // init the menu
+ OC.registerMenu($toggle, $navigation)
+ $toggle.data('oldhref', $toggle.attr('href'))
+ $toggle.attr('href', '#')
+ $navigation.hide()
+
+ // show loading feedback on more apps list
+ $navigation.delegate('a', 'click', event => {
+ let $app = $(event.target)
+ if (!$app.is('a')) {
+ $app = $app.closest('a')
+ }
+ if (event.which === 1 && !event.ctrlKey && !event.metaKey) {
+ $app.find('svg').remove()
+ $app.find('div').remove() // prevent odd double-clicks
+ // no need for theming, loader is already inverted on dark mode
+ // but we need it over the primary colour
+ $app.prepend($('<div/>').addClass('icon-loading-small'))
+ } else {
+ // Close navigation when opening app in
+ // a new tab
+ OC.hideMenus(() => false)
+ }
+ })
+
+ $navigation.delegate('a', 'mouseup', event => {
+ if (event.which === 2) {
+ // Close navigation when opening app in
+ // a new tab via middle click
+ OC.hideMenus(() => false)
+ }
+ })
+
+ // show loading feedback on visible apps list
+ $appmenu.delegate('li:not(#more-apps) > a', 'click', event => {
+ let $app = $(event.target)
+ if (!$app.is('a')) {
+ $app = $app.closest('a')
+ }
+ if (event.which === 1 && !event.ctrlKey && !event.metaKey && $app.parent('#more-apps').length === 0) {
+ $app.find('svg').remove()
+ $app.find('div').remove() // prevent odd double-clicks
+ $app.prepend($('<div/>').addClass(
+ OCA.Theming && OCA.Theming.inverted
+ ? 'icon-loading-small'
+ : 'icon-loading-small-dark'
+ ))
+ } else {
+ // Close navigation when opening app in
+ // a new tab
+ OC.hideMenus(() => false)
+ }
+ })
+}
diff --git a/core/src/components/UserMenu.js b/core/src/components/UserMenu.js
new file mode 100644
index 00000000000..a9e7d8725bb
--- /dev/null
+++ b/core/src/components/UserMenu.js
@@ -0,0 +1,53 @@
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+import OC from '../OC'
+
+import $ from 'jquery'
+
+export const setUp = () => {
+ const $menu = $('#header #settings')
+
+ // show loading feedback
+ $menu.delegate('a', 'click', event => {
+ let $page = $(event.target)
+ if (!$page.is('a')) {
+ $page = $page.closest('a')
+ }
+ if (event.which === 1 && !event.ctrlKey && !event.metaKey) {
+ $page.find('img').remove()
+ $page.find('div').remove() // prevent odd double-clicks
+ $page.prepend($('<div/>').addClass('icon-loading-small'))
+ } else {
+ // Close navigation when opening menu entry in
+ // a new tab
+ OC.hideMenus(() => false)
+ }
+ })
+
+ $menu.delegate('a', 'mouseup', event => {
+ if (event.which === 2) {
+ // Close navigation when opening app in
+ // a new tab via middle click
+ OC.hideMenus(() => false)
+ }
+ })
+}
diff --git a/core/src/globals.js b/core/src/globals.js
index 312952fe90c..65ad3148ec6 100644
--- a/core/src/globals.js
+++ b/core/src/globals.js
@@ -19,7 +19,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import appswebroots from "./OC/appswebroots";
+import {initCore} from './init'
const warnIfNotTesting = function() {
if (window.TESTING === undefined) {
@@ -115,6 +115,7 @@ window['md5'] = md5
window['moment'] = moment
window['OC'] = OC
+setDeprecatedProp('initCore', initCore, 'this is an internal function')
setDeprecatedProp('oc_appswebroots', OC.appswebroots, 'use OC.appswebroots instead')
setDeprecatedProp('oc_config', OC.config, 'use OC.config instead')
setDeprecatedProp('oc_current_user', OC.getCurrentUser().uid, 'use OC.getCurrentUser().uid instead')
diff --git a/core/src/init.js b/core/src/init.js
new file mode 100644
index 00000000000..ebf99ffd640
--- /dev/null
+++ b/core/src/init.js
@@ -0,0 +1,307 @@
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+import _ from 'underscore'
+import $ from 'jquery'
+import moment from 'moment'
+
+import {initSessionHeartBeat} from './session-heartbeat'
+import OC from './OC/index'
+import {setUp as setUpContactsMenu} from './components/ContactsMenu'
+import {setUp as setUpMainMenu} from './components/MainMenu'
+import {setUp as setUpUserMenu} from './components/UserMenu'
+import PasswordConfirmation from './OC/password-confirmation'
+
+const resizeMenu = () => {
+ const appList = $('#appmenu li')
+ const rightHeaderWidth = $('.header-right').outerWidth()
+ const headerWidth = $('header').outerWidth()
+ const usePercentualAppMenuLimit = 0.33
+ const minAppsDesktop = 8
+ let availableWidth = headerWidth - $('#nextcloud').outerWidth() - (rightHeaderWidth > 210 ? rightHeaderWidth : 210)
+ const isMobile = $(window).width() < 768
+ if (!isMobile) {
+ availableWidth = availableWidth * usePercentualAppMenuLimit
+ }
+ let appCount = Math.floor((availableWidth / $(appList).width()))
+ if (isMobile && appCount > minAppsDesktop) {
+ appCount = minAppsDesktop
+ }
+ if (!isMobile && appCount < minAppsDesktop) {
+ appCount = minAppsDesktop
+ }
+
+ // show at least 2 apps in the popover
+ if (appList.length - 1 - appCount >= 1) {
+ appCount--
+ }
+
+ $('#more-apps a').removeClass('active')
+ let lastShownApp
+ for (let k = 0; k < appList.length - 1; k++) {
+ const name = $(appList[k]).data('id')
+ if (k < appCount) {
+ $(appList[k]).removeClass('hidden')
+ $('#apps li[data-id=' + name + ']').addClass('in-header')
+ lastShownApp = appList[k]
+ } else {
+ $(appList[k]).addClass('hidden')
+ $('#apps li[data-id=' + name + ']').removeClass('in-header')
+ // move active app to last position if it is active
+ if (appCount > 0 && $(appList[k]).children('a').hasClass('active')) {
+ $(lastShownApp).addClass('hidden')
+ $('#apps li[data-id=' + $(lastShownApp).data('id') + ']').removeClass('in-header')
+ $(appList[k]).removeClass('hidden')
+ $('#apps li[data-id=' + name + ']').addClass('in-header')
+ }
+ }
+ }
+
+ // show/hide more apps icon
+ if ($('#apps li:not(.in-header)').length === 0) {
+ $('#more-apps').hide()
+ $('#navigation').hide()
+ } else {
+ $('#more-apps').show()
+ }
+}
+
+const initLiveTimestamps = () => {
+ // Update live timestamps every 30 seconds
+ setInterval(() => {
+ $('.live-relative-timestamp').each(function () {
+ $(this).text(OC.Util.relativeModifiedDate(parseInt($(this).attr('data-timestamp'), 10)))
+ })
+ }, 30 * 1000)
+}
+
+/**
+ * Initializes core
+ */
+export const initCore = () => {
+ /**
+ * Set users locale to moment.js as soon as possible
+ */
+ moment.locale(OC.getLocale())
+
+ const userAgent = window.navigator.userAgent
+ const msie = userAgent.indexOf('MSIE ')
+ const trident = userAgent.indexOf('Trident/')
+ const edge = userAgent.indexOf('Edge/')
+
+ if (msie > 0 || trident > 0) {
+ // (IE 10 or older) || IE 11
+ $('html').addClass('ie')
+ } else if (edge > 0) {
+ // for edge
+ $('html').addClass('edge')
+ }
+
+ // css variables fallback for IE
+ if (msie > 0 || trident > 0 || edge > 0) {
+ console.info('Legacy browser detected, applying css vars polyfill')
+ cssVars({
+ watch: true,
+ // set edge < 16 as incompatible
+ onlyLegacy: !(/Edge\/([0-9]{2})\./i.test(navigator.userAgent)
+ && parseInt(/Edge\/([0-9]{2})\./i.exec(navigator.userAgent)[1]) < 16)
+ })
+ }
+
+ $(window).on('unload.main', () => OC._unloadCalled = true)
+ $(window).on('beforeunload.main', () => {
+ // super-trick thanks to http://stackoverflow.com/a/4651049
+ // in case another handler displays a confirmation dialog (ex: navigating away
+ // during an upload), there are two possible outcomes: user clicked "ok" or
+ // "cancel"
+
+ // first timeout handler is called after unload dialog is closed
+ setTimeout(() => {
+ OC._userIsNavigatingAway = true
+
+ // second timeout event is only called if user cancelled (Chrome),
+ // but in other browsers it might still be triggered, so need to
+ // set a higher delay...
+ setTimeout(() => {
+ if (!OC._unloadCalled) {
+ OC._userIsNavigatingAway = false
+ }
+ }, 10000)
+ }, 1)
+ })
+ $(document).on('ajaxError.main', function (event, request, settings) {
+ if (settings && settings.allowAuthErrors) {
+ return
+ }
+ OC._processAjaxError(request)
+ })
+
+ initSessionHeartBeat();
+
+ OC.registerMenu($('#expand'), $('#expanddiv'), false, true)
+
+ // toggle for menus
+ $(document).on('mouseup.closemenus', event => {
+ const $el = $(event.target)
+ if ($el.closest('.menu').length || $el.closest('.menutoggle').length) {
+ // don't close when clicking on the menu directly or a menu toggle
+ return false
+ }
+
+ OC.hideMenus()
+ })
+
+ setUpMainMenu()
+ setUpUserMenu()
+ setUpContactsMenu()
+
+ // move triangle of apps dropdown to align with app name triangle
+ // 2 is the additional offset between the triangles
+ if ($('#navigation').length) {
+ $('#header #nextcloud + .menutoggle').on('click', () => {
+ $('#menu-css-helper').remove()
+ const caretPosition = $('.header-appname + .icon-caret').offset().left - 2
+ if (caretPosition > 255) {
+ // if the app name is longer than the menu, just put the triangle in the middle
+ return
+ } else {
+ $('head').append('<style id="menu-css-helper">#navigation:after { left: ' + caretPosition + 'px }</style>')
+ }
+ })
+ $('#header #appmenu .menutoggle').on('click', () => {
+ $('#appmenu').toggleClass('menu-open')
+ if ($('#appmenu').is(':visible')) {
+ $('#menu-css-helper').remove()
+ }
+ })
+ }
+
+ $(window).resize(resizeMenu)
+ setTimeout(resizeMenu, 0)
+
+ // just add snapper for logged in users
+ // and if the app doesn't handle the nav slider itself
+ if ($('#app-navigation').length && !$('html').hasClass('lte9')
+ && !$('#app-content').hasClass('no-snapper')) {
+
+ // App sidebar on mobile
+ const snapper = new Snap({
+ element: document.getElementById('app-content'),
+ disable: 'right',
+ maxPosition: 300, // $navigation-width
+ minDragDistance: 100
+ })
+
+ $('#app-content').prepend('<div id="app-navigation-toggle" class="icon-menu" style="display:none" tabindex="0"></div>')
+
+ const toggleSnapperOnButton = () => {
+ if (snapper.state().state === 'left') {
+ snapper.close()
+ } else {
+ snapper.open('left')
+ }
+ }
+
+ $('#app-navigation-toggle').click(toggleSnapperOnButton)
+ $('#app-navigation-toggle').keypress(e => {
+ if (e.which === 13) {
+ toggleSnapperOnButton()
+ }
+ })
+
+ // close sidebar when switching navigation entry
+ const $appNavigation = $('#app-navigation')
+ $appNavigation.delegate('a, :button', 'click', event => {
+ const $target = $(event.target)
+ // don't hide navigation when changing settings or adding things
+ if ($target.is('.app-navigation-noclose') ||
+ $target.closest('.app-navigation-noclose').length) {
+ return
+ }
+ if ($target.is('.app-navigation-entry-utils-menu-button') ||
+ $target.closest('.app-navigation-entry-utils-menu-button').length) {
+ return
+ }
+ if ($target.is('.add-new') ||
+ $target.closest('.add-new').length) {
+ return
+ }
+ if ($target.is('#app-settings') ||
+ $target.closest('#app-settings').length) {
+ return
+ }
+ snapper.close()
+ })
+
+ let navigationBarSlideGestureEnabled = false
+ let navigationBarSlideGestureAllowed = true
+ let navigationBarSlideGestureEnablePending = false
+
+ OC.allowNavigationBarSlideGesture = () => {
+ navigationBarSlideGestureAllowed = true
+
+ if (navigationBarSlideGestureEnablePending) {
+ snapper.enable()
+
+ navigationBarSlideGestureEnabled = true
+ navigationBarSlideGestureEnablePending = false
+ }
+ }
+
+ OC.disallowNavigationBarSlideGesture = () => {
+ navigationBarSlideGestureAllowed = false
+
+ if (navigationBarSlideGestureEnabled) {
+ const endCurrentDrag = true
+ snapper.disable(endCurrentDrag)
+
+ navigationBarSlideGestureEnabled = false
+ navigationBarSlideGestureEnablePending = true
+ }
+ }
+
+ const toggleSnapperOnSize = () => {
+ if ($(window).width() > 768) {
+ snapper.close()
+ snapper.disable()
+
+ navigationBarSlideGestureEnabled = false
+ navigationBarSlideGestureEnablePending = false
+ } else if (navigationBarSlideGestureAllowed) {
+ snapper.enable()
+
+ navigationBarSlideGestureEnabled = true
+ navigationBarSlideGestureEnablePending = false
+ } else {
+ navigationBarSlideGestureEnablePending = true
+ }
+ }
+
+ $(window).resize(_.debounce(toggleSnapperOnSize, 250))
+
+ // initial call
+ toggleSnapperOnSize()
+
+ }
+
+ initLiveTimestamps()
+ PasswordConfirmation.init()
+}
diff --git a/core/src/jquery/index.js b/core/src/jquery/index.js
index 71659137b3e..43b379fd2e5 100644
--- a/core/src/jquery/index.js
+++ b/core/src/jquery/index.js
@@ -19,6 +19,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+import $ from 'jquery'
+
import './avatar'
import './contactsmenu'
import './exists'
@@ -33,3 +35,28 @@ import './ui-fixes'
import './css/jquery-ui-fixes.scss'
import './css/jquery.ocdialog.scss'
+
+/**
+ * Disable automatic evaluation of responses for $.ajax() functions (and its
+ * higher-level alternatives like $.get() and $.post()).
+ *
+ * If a response to a $.ajax() request returns a content type of "application/javascript"
+ * JQuery would previously execute the response body. This is a pretty unexpected
+ * behaviour and can result in a bypass of our Content-Security-Policy as well as
+ * multiple unexpected XSS vectors.
+ */
+$.ajaxSetup({
+ contents: {
+ script: false
+ }
+})
+
+/**
+ * Disable execution of eval in jQuery. We do require an allowed eval CSP
+ * configuration at the moment for handlebars et al. But for jQuery there is
+ * not much of a reason to execute JavaScript directly via eval.
+ *
+ * This thus mitigates some unexpected XSS vectors.
+ */
+$.globalEval = function () {
+}
diff --git a/core/src/main.js b/core/src/main.js
index 4d963a1795c..1f8f12c1a0f 100644
--- a/core/src/main.js
+++ b/core/src/main.js
@@ -19,14 +19,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+import $ from 'jquery'
import '@babel/polyfill'
import './Polyfill/index'
+// If you remove the line below, tests won't pass
+import OC from './OC/index'
+
import './globals'
-import $ from 'jquery'
import './jquery/index'
+import {initCore} from './init'
import {registerAppsSlideToggle} from './OC/apps'
$(document).ready(function () {
+ initCore();
+
registerAppsSlideToggle();
});
diff --git a/core/src/session-heartbeat.js b/core/src/session-heartbeat.js
new file mode 100644
index 00000000000..5d1f7177cf7
--- /dev/null
+++ b/core/src/session-heartbeat.js
@@ -0,0 +1,76 @@
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ */
+
+import $ from 'jquery'
+
+import {generateUrl} from './OC/routing'
+import OC from './OC'
+
+/**
+ * session heartbeat (defaults to enabled)
+ * @return {boolean}
+ */
+const keepSessionAlive = () => {
+ return OC.config.session_keepalive === undefined
+ || !!OC.config.session_keepalive
+}
+
+/**
+ * get interval in seconds
+ * @return {Number}
+ */
+const getInterval = () => {
+ let interval = NaN
+ if (OC.config.session_lifetime) {
+ interval = Math.floor(OC.config.session_lifetime / 2)
+ }
+
+ // minimum one minute, max 24 hours, default 15 minutes
+ return Math.min(
+ 24 * 3600,
+ Math.max(
+ 60,
+ isNaN(interval) ? 900 : interval
+ )
+ )
+}
+
+/**
+ * Calls the server periodically to ensure that session and CSRF
+ * token doesn't expire
+ */
+export const initSessionHeartBeat = () => {
+ if (!keepSessionAlive()) {
+ console.info('session heartbeat disabled')
+ return;
+ }
+
+ setInterval(() => {
+ $.ajax(generateUrl('/csrftoken'))
+ .then(resp => {
+ oc_requesttoken = resp.token
+ OC.requestToken = resp.token
+ })
+ .fail(e => {
+ console.error('session heartbeat failed', e)
+ })
+ }, getInterval() * 1000)
+}