Move initCore to the bundle

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
pull/15440/head
Christoph Wurst 2019-05-10 13:38:11 +07:00
parent 855486d7c1
commit 51d49c3134
No known key found for this signature in database
GPG Key ID: CC42AC2A7F0E56D8
15 changed files with 687 additions and 535 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

112
core/js/dist/main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -469,461 +469,3 @@ Object.assign(window.OC, {
}
});
/**
* Initializes core
*/
function initCore() {
/**
* 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.
*/
jQuery.globalEval = function(){};
/**
* Set users locale to moment.js as soon as possible
*/
moment.locale(OC.getLocale());
var userAgent = window.navigator.userAgent;
var msie = userAgent.indexOf('MSIE ');
var trident = userAgent.indexOf('Trident/');
var 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', function() {
OC._unloadCalled = true;
});
$(window).on('beforeunload.main', function() {
// 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(function() {
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(function() {
if (!OC._unloadCalled) {
OC._userIsNavigatingAway = false;
}
}, 10000);
},1);
});
$(document).on('ajaxError.main', function( event, request, settings ) {
if (settings && settings.allowAuthErrors) {
return;
}
OC._processAjaxError(request);
});
/**
* Calls the server periodically to ensure that session and CSRF
* token doesn't expire
*/
function initSessionHeartBeat() {
// interval in seconds
var interval = NaN;
if (OC.config.session_lifetime) {
interval = Math.floor(OC.config.session_lifetime / 2);
}
interval = isNaN(interval)? 900: interval;
// minimum one minute
interval = Math.max(60, interval);
// max interval in seconds set to 24 hours
interval = Math.min(24 * 3600, interval);
var url = OC.generateUrl('/csrftoken');
setInterval(function() {
$.ajax(url).then(function(resp) {
oc_requesttoken = resp.token;
OC.requestToken = resp.token;
}).fail(function(e) {
console.error('session heartbeat failed', e);
});
}, interval * 1000);
}
// session heartbeat (defaults to enabled)
if (typeof(OC.config.session_keepalive) === 'undefined' ||
!!OC.config.session_keepalive) {
initSessionHeartBeat();
}
OC.registerMenu($('#expand'), $('#expanddiv'), false, true);
// toggle for menus
//$(document).on('mouseup.closemenus keyup', function(event) {
$(document).on('mouseup.closemenus', function(event) {
// allow enter as a trigger
// if (event.key && event.key !== "Enter") {
// return;
// }
var $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();
});
/**
* 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.
*/
function setupMainMenu() {
// init the more-apps menu
OC.registerMenu($('#more-apps > a'), $('#navigation'));
// toggle the navigation
var $toggle = $('#header .header-appname-container');
var $navigation = $('#navigation');
var $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', function(event) {
var $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(function(){return false;});
}
});
$navigation.delegate('a', 'mouseup', function(event) {
if(event.which === 2) {
// Close navigation when opening app in
// a new tab via middle click
OC.hideMenus(function(){return false;});
}
});
// show loading feedback on visible apps list
$appmenu.delegate('li:not(#more-apps) > a', 'click', function(event) {
var $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(function(){return false;});
}
});
}
function setupUserMenu() {
var $menu = $('#header #settings');
// show loading feedback
$menu.delegate('a', 'click', function(event) {
var $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(function(){return false;});
}
});
$menu.delegate('a', 'mouseup', function(event) {
if(event.which === 2) {
// Close navigation when opening app in
// a new tab via middle click
OC.hideMenus(function(){return false;});
}
});
}
function setupContactsMenu() {
new OC.ContactsMenu({
el: $('#contactsmenu .menu'),
trigger: $('#contactsmenu .menutoggle')
});
}
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', function(){
$('#menu-css-helper').remove();
var 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', function() {
$('#appmenu').toggleClass('menu-open');
if($('#appmenu').is(':visible')) {
$('#menu-css-helper').remove();
}
});
}
var resizeMenu = function() {
var appList = $('#appmenu li');
var rightHeaderWidth = $('.header-right').outerWidth();
var headerWidth = $('header').outerWidth();
var usePercentualAppMenuLimit = 0.33;
var minAppsDesktop = 8;
var availableWidth = headerWidth - $('#nextcloud').outerWidth() - (rightHeaderWidth > 210 ? rightHeaderWidth : 210)
var isMobile = $(window).width() < 768;
if (!isMobile) {
availableWidth = availableWidth * usePercentualAppMenuLimit;
}
var 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');
var lastShownApp;
for (var k = 0; k < appList.length-1; k++) {
var 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();
}
};
$(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
var 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>');
var toggleSnapperOnButton = function(){
if(snapper.state().state == 'left'){
snapper.close();
} else {
snapper.open('left');
}
};
$('#app-navigation-toggle').click(function(){
toggleSnapperOnButton();
});
$('#app-navigation-toggle').keypress(function(e) {
if(e.which == 13) {
toggleSnapperOnButton();
}
});
// close sidebar when switching navigation entry
var $appNavigation = $('#app-navigation');
$appNavigation.delegate('a, :button', 'click', function(event) {
var $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();
});
var navigationBarSlideGestureEnabled = false;
var navigationBarSlideGestureAllowed = true;
var navigationBarSlideGestureEnablePending = false;
OC.allowNavigationBarSlideGesture = function() {
navigationBarSlideGestureAllowed = true;
if (navigationBarSlideGestureEnablePending) {
snapper.enable();
navigationBarSlideGestureEnabled = true;
navigationBarSlideGestureEnablePending = false;
}
};
OC.disallowNavigationBarSlideGesture = function() {
navigationBarSlideGestureAllowed = false;
if (navigationBarSlideGestureEnabled) {
var endCurrentDrag = true;
snapper.disable(endCurrentDrag);
navigationBarSlideGestureEnabled = false;
navigationBarSlideGestureEnablePending = true;
}
};
var toggleSnapperOnSize = function() {
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();
}
// Update live timestamps every 30 seconds
setInterval(function() {
$('.live-relative-timestamp').each(function() {
$(this).text(OC.Util.relativeModifiedDate(parseInt($(this).attr('data-timestamp'), 10)));
});
}, 30 * 1000);
OC.PasswordConfirmation.init();
}
$(document).ready(initCore);
/**
// fallback to hashchange when no history support
if (window.history.pushState) {
window.onpopstate = _.bind(OC.Util.History._onPopState, OC.Util.History);
}
else {
$(window).on('hashchange', _.bind(OC.Util.History._onPopState, OC.Util.History));
}
*/

@ -368,13 +368,11 @@ describe('Core base tests', function() {
describe('Session heartbeat', function() {
var clock,
oldConfig,
routeStub,
counter;
beforeEach(function() {
clock = sinon.useFakeTimers();
oldConfig = OC.config;
routeStub = sinon.stub(OC, 'generateUrl').returns('/csrftoken');
counter = 0;
fakeServer.autoRespond = true;
@ -389,7 +387,6 @@ describe('Core base tests', function() {
clock.restore();
/* jshint camelcase: false */
OC.config = oldConfig;
routeStub.restore();
$(document).off('ajaxError');
$(document).off('ajaxComplete');
});
@ -400,7 +397,6 @@ describe('Core base tests', function() {
session_lifetime: 300
};
window.initCore();
expect(routeStub.calledWith('/csrftoken')).toEqual(true);
expect(counter).toEqual(0);
@ -427,7 +423,6 @@ describe('Core base tests', function() {
session_lifetime: 300
};
window.initCore();
expect(routeStub.notCalled).toEqual(true);
expect(counter).toEqual(0);

@ -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;

@ -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')
})
}

@ -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)
}
})
}

@ -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)
}
})
}

@ -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')

@ -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()
}

@ -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 () {
}

@ -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();
});

@ -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)
}