mirror of
https://github.com/nextcloud/server.git
synced 2026-06-09 00:32:29 -04:00
Move initCore to the bundle
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
This commit is contained in:
parent
855486d7c1
commit
51d49c3134
15 changed files with 825 additions and 673 deletions
176
core/js/dist/login.js
vendored
176
core/js/dist/login.js
vendored
File diff suppressed because one or more lines are too long
2
core/js/dist/login.js.map
vendored
2
core/js/dist/login.js.map
vendored
File diff suppressed because one or more lines are too long
250
core/js/dist/main.js
vendored
250
core/js/dist/main.js
vendored
File diff suppressed because one or more lines are too long
2
core/js/dist/main.js.map
vendored
2
core/js/dist/main.js.map
vendored
File diff suppressed because one or more lines are too long
458
core/js/js.js
458
core/js/js.js
|
|
@ -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;
|
||||
|
|
|
|||
34
core/src/components/ContactsMenu.js
Normal file
34
core/src/components/ContactsMenu.js
Normal file
|
|
@ -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')
|
||||
})
|
||||
}
|
||||
93
core/src/components/MainMenu.js
Normal file
93
core/src/components/MainMenu.js
Normal file
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
53
core/src/components/UserMenu.js
Normal file
53
core/src/components/UserMenu.js
Normal file
|
|
@ -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')
|
||||
|
|
|
|||
307
core/src/init.js
Normal file
307
core/src/init.js
Normal file
|
|
@ -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()
|
||||
}
|
||||
27
core/src/jquery/index.js
vendored
27
core/src/jquery/index.js
vendored
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
76
core/src/session-heartbeat.js
Normal file
76
core/src/session-heartbeat.js
Normal file
|
|
@ -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)
|
||||
}
|
||||
Loading…
Reference in a new issue