From 84c96c2bf701711cc54d1bb44b49e364123a5aa9 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 14 Mar 2014 09:14:52 +0100 Subject: [PATCH 01/26] mobile: don't require a minimum width for controls bar --- core/css/mobile.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/css/mobile.css b/core/css/mobile.css index a63aa902d3..15ff8c6545 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -18,5 +18,10 @@ display: none; } +/* don’t require a minimum width for controls bar */ +#controls { + min-width: initial !important; +} + } From eb7b12525438df4345a588f1e87d208daa040654 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 14 Mar 2014 09:15:20 +0100 Subject: [PATCH 02/26] mobile: position share dropdown --- core/css/mobile.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/css/mobile.css b/core/css/mobile.css index 15ff8c6545..665ad0daae 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -23,5 +23,11 @@ min-width: initial !important; } +/* position share dropdown */ +#dropdown { + margin-right: 10% !important; + width: 80% !important; +} + } From ec67d7e63528069269e4469b4feeeb9b52b90680 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 14 Mar 2014 09:16:53 +0100 Subject: [PATCH 03/26] mobile: first mobile fixes for Files. We still need to hide Rename and Versions --- apps/files/css/mobile.css | 44 +++++++++++++++++++++++++++++++++++++++ apps/files/index.php | 1 + 2 files changed, 45 insertions(+) create mode 100644 apps/files/css/mobile.css diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css new file mode 100644 index 0000000000..5f1c08af58 --- /dev/null +++ b/apps/files/css/mobile.css @@ -0,0 +1,44 @@ +@media only screen and (max-width: 600px) { + +/* don’t require a minimum width for files table */ +#body-user #filestable { + min-width: initial !important; +} + +/* hide size and date columns */ +table th#headerSize, +table td.filesize, +table th#headerDate, +table td.date { + display: none; +} + +/* restrict length of displayed filename to prevent overflow */ +table td.filename .nametext { + max-width: 75% !important; +} + +/* do not show Rename or Versions on mobile */ +#fileList .action-rename, +#fileList .action-versions { + display: none !important; +} + +/* always show actions on mobile, not only on hover */ +#fileList a.action { + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)" !important; + filter: alpha(opacity=20) !important; + opacity: .2 !important; + display: inline !important; +} +/* some padding for better clickability */ +#fileList a.action img { + padding: 0 6px 0 12px; +} +/* hide text of the actions on mobile */ +#fileList a.action span { + display: none; +} + + +} diff --git a/apps/files/index.php b/apps/files/index.php index ad7a2e210e..a856a56136 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -27,6 +27,7 @@ OCP\User::checkLoggedIn(); // Load the files we need OCP\Util::addStyle('files', 'files'); OCP\Util::addStyle('files', 'upload'); +OCP\Util::addStyle('files', 'mobile'); OCP\Util::addscript('files', 'file-upload'); OCP\Util::addscript('files', 'jquery.iframe-transport'); OCP\Util::addscript('files', 'jquery.fileupload'); From b37aae9925a1d5516a24f1f9db31a156c2d3bc72 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 14 Mar 2014 10:33:19 +0100 Subject: [PATCH 04/26] mobile: menu togglable for mobile, use code by @PVince81 --- core/css/mobile.css | 65 +++++++++++++++++++++++++++++++++++++++++++++ core/js/js.js | 40 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/core/css/mobile.css b/core/css/mobile.css index 665ad0daae..5a465b35fb 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -18,6 +18,71 @@ display: none; } +/* toggle navigation */ +#content-wrapper { + padding-left: 0; +} + +#navigation { + top: 45px; + bottom: initial; + width: 90%; + max-width: 320px; + max-height: 90%; + margin-top: 0; + top: 45px; + background-color: rgba(36, 40, 47, .97); + overflow-x: initial; + border-bottom-right-radius: 7px; + border-bottom: 1px #333 solid; + border-right: 1px #333 solid; + box-shadow: 0 0 7px rgba(29,45,68,.97); + display: none; +} +#navigation, #navigation * { + box-sizing:border-box; -moz-box-sizing:border-box; +} +#navigation li { + display: inline-block; +} +#navigation a { + width: 70px; + height: 80px; + display: inline-block; + text-align: center; + padding: 20px 0; +} +#navigation a span { + display: inline-block; + font-size: 13px; + padding-bottom: 0; + padding-left: 0; + width: initial; +} +#navigation .icon { + margin: 0 auto; + padding: 0; +} +#navigation li:first-child .icon { + padding-top: 0; +} +/* Apps management as sticky footer */ +#navigation .wrapper { + min-height: initial; + margin: 0; +} +#apps-management, #navigation .push { + height: initial; +} + + + +/* shift to account for missing navigation */ +#body-user #controls, +#body-settings #controls { + padding-left: 0; +} + /* don’t require a minimum width for controls bar */ #controls { min-width: initial !important; diff --git a/core/js/js.js b/core/js/js.js index 841f3a769f..2b3a9f0477 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -482,6 +482,29 @@ var OC={ }).show(); }, 'html'); } + }, + + // for menu toggling + registerMenu: function($toggle, $menuEl) { + $menuEl.addClass('menu'); + $toggle.addClass('menutoggle'); + $toggle.on('click', function(event) { + if ($menuEl.is(OC._currentMenu)) { + $menuEl.hide(); + OC._currentMenu = null; + OC._currentMenuToggle = null; + return false; + } + // another menu was open? + else if (OC._currentMenu) { + // close it + OC._currentMenu.hide(); + } + $menuEl.show(); + OC._currentMenu = $menuEl; + OC._currentMenuToggle = $toggle; + return false + }); } }; OC.search.customResults={}; @@ -940,6 +963,23 @@ function initCore() { $('a.action').tipsy({gravity:'s', fade:true, live:true}); $('td .modified').tipsy({gravity:'s', fade:true, live:true}); $('input').tipsy({gravity:'w', fade:true}); + + // toggle for menus + $(document).on('mouseup.closemenus', function(event) { + 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; + } + if (OC._currentMenu) { + OC._currentMenu.hide(); + } + OC._currentMenu = null; + OC._currentMenuToggle = null; + }); + + // toggle the navigation on mobile + OC.registerMenu($('#header #owncloud'), $('#navigation')); } $(document).ready(initCore); From ac48563efd74675f75b2b300b7d8b0c4f74be70b Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 14 Mar 2014 10:48:28 +0100 Subject: [PATCH 05/26] add spans around replaced 'Shared' indicators to make text hide on mobile --- core/js/share.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/js/share.js b/core/js/share.js index 129e50b22d..90119105fa 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -48,7 +48,7 @@ OC.Share={ var action = $(file).find('.fileactions .action[data-action="Share"]'); var img = action.find('img').attr('src', image); action.addClass('permanent'); - action.html(' '+t('core', 'Shared')).prepend(img); + action.html(' '+t('core', 'Shared')+'').prepend(img); } else { var dir = $('#dir').val(); if (dir.length > 1) { @@ -63,7 +63,7 @@ OC.Share={ if (img.attr('src') != OC.imagePath('core', 'actions/public')) { img.attr('src', image); $(action).addClass('permanent'); - $(action).html(' '+t('core', 'Shared')).prepend(img); + $(action).html(' '+t('core', 'Shared')+'').prepend(img); } }); } @@ -103,10 +103,10 @@ OC.Share={ var img = action.find('img').attr('src', image); if (shares) { action.addClass('permanent'); - action.html(' '+ escapeHTML(t('core', 'Shared'))).prepend(img); + action.html(' '+ escapeHTML(t('core', 'Shared'))+'').prepend(img); } else { action.removeClass('permanent'); - action.html(' '+ escapeHTML(t('core', 'Share'))).prepend(img); + action.html(' '+ escapeHTML(t('core', 'Share'))+'').prepend(img); } } } From ea8705bac8ae10b0c3c11258e83d8fb80ee616e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 17 Mar 2014 20:15:10 +0100 Subject: [PATCH 06/26] additional class is added to the file actions called e.g. 'action-share', 'action-rename' in order to allow proper translations of the action's display name an additional parameter has been added to the register function --- apps/files/js/fileactions.js | 37 +++++++++++++++++++++--------- apps/files_versions/js/versions.js | 3 ++- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index 9a69d7b368..a7d1fa9d8a 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -15,21 +15,33 @@ var FileActions = { defaults: {}, icons: {}, currentFile: null, - register: function (mime, name, permissions, icon, action) { + register: function (mime, name, permissions, icon, action, displayName) { if (!FileActions.actions[mime]) { FileActions.actions[mime] = {}; } if (!FileActions.actions[mime][name]) { FileActions.actions[mime][name] = {}; } + if (!displayName) { + displayName = t('files', name); + } FileActions.actions[mime][name]['action'] = action; FileActions.actions[mime][name]['permissions'] = permissions; + FileActions.actions[mime][name]['displayName'] = displayName; FileActions.icons[name] = icon; }, setDefault: function (mime, name) { FileActions.defaults[mime] = name; }, get: function (mime, type, permissions) { + var actions = this.getActions(mime, type, permissions); + var filteredActions = {}; + $.each(actions, function (name, action) { + filteredActions[name] = action.action; + }); + return filteredActions; + }, + getActions: function (mime, type, permissions) { var actions = {}; if (FileActions.actions.all) { actions = $.extend(actions, FileActions.actions.all); @@ -51,7 +63,7 @@ var FileActions = { var filteredActions = {}; $.each(actions, function (name, action) { if (action.permissions & permissions) { - filteredActions[name] = action.action; + filteredActions[name] = action; } }); return filteredActions; @@ -82,7 +94,7 @@ var FileActions = { */ display: function (parent, triggerEvent) { FileActions.currentFile = parent; - var actions = FileActions.get(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions()); + var actions = FileActions.getActions(FileActions.getCurrentMimeType(), FileActions.getCurrentType(), FileActions.getCurrentPermissions()); var file = FileActions.getCurrentFile(); var nameLinks; if (FileList.findFileEl(file).data('renaming')) { @@ -105,15 +117,16 @@ var FileActions = { event.data.actionFunc(file); }; - var addAction = function (name, action) { + var addAction = function (name, action, displayName) { // NOTE: Temporary fix to prevent rename action in root of Shared directory if (name === 'Rename' && $('#dir').val() === '/Shared') { return true; } if ((name === 'Download' || action !== defaultAction) && name !== 'Delete') { + var img = FileActions.icons[name], - actionText = t('files', name), + actionText = displayName, actionContainer = 'a.name>span.fileactions'; if (name === 'Rename') { @@ -125,7 +138,7 @@ var FileActions = { if (img.call) { img = img(file); } - var html = ''; + var html = ''; if (img) { html += ''; } @@ -133,8 +146,7 @@ var FileActions = { var element = $(html); element.data('action', name); - //alert(element); - element.on('click', {a: null, elem: parent, actionFunc: actions[name]}, actionHandler); + element.on('click', {a: null, elem: parent, actionFunc: actions[name].action}, actionHandler); parent.find(actionContainer).append(element); } @@ -142,12 +154,15 @@ var FileActions = { $.each(actions, function (name, action) { if (name !== 'Share') { - addAction(name, action); + displayName = action.displayName; + ah = action.action; + + addAction(name, ah, displayName); } }); if(actions.Share && !($('#dir').val() === '/' && file === 'Shared')){ - // t('files', 'Share') - addAction('Share', actions.Share); + displayName = t('files', 'Share'); + addAction('Share', actions.Share, displayName); } // remove the existing delete action diff --git a/apps/files_versions/js/versions.js b/apps/files_versions/js/versions.js index 4adf14745d..b452bc25b1 100644 --- a/apps/files_versions/js/versions.js +++ b/apps/files_versions/js/versions.js @@ -11,7 +11,7 @@ $(document).ready(function(){ // Add versions button to 'files/index.php' FileActions.register( 'file' - , t('files_versions', 'Versions') + , 'Versions' , OC.PERMISSION_UPDATE , function() { // Specify icon for hitory button @@ -36,6 +36,7 @@ $(document).ready(function(){ createVersionsDropdown(filename, file); } } + , t('files_versions', 'Versions') ); } From 872006da035b92b17b0a0d5da781844080861531 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Mon, 17 Mar 2014 20:40:22 +0100 Subject: [PATCH 07/26] Only enable toggle for sidebar in mobile mode --- core/js/js.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/js/js.js b/core/js/js.js index 2b3a9f0477..23d16b6923 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -979,7 +979,12 @@ function initCore() { }); // toggle the navigation on mobile - OC.registerMenu($('#header #owncloud'), $('#navigation')); + if (window.matchMedia) { + var mq = window.matchMedia('(max-width: 600px)'); + if (mq && mq.matches) { + OC.registerMenu($('#header #owncloud'), $('#navigation')); + } + } } $(document).ready(initCore); From 285fc5ba96eba1c9046a3c05c39ed7708c13b897 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Tue, 18 Mar 2014 11:50:08 +0100 Subject: [PATCH 08/26] mobile: change CSS order so rename and versions are hidden --- apps/files/css/mobile.css | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css index 5f1c08af58..00c4630ea6 100644 --- a/apps/files/css/mobile.css +++ b/apps/files/css/mobile.css @@ -18,12 +18,6 @@ table td.filename .nametext { max-width: 75% !important; } -/* do not show Rename or Versions on mobile */ -#fileList .action-rename, -#fileList .action-versions { - display: none !important; -} - /* always show actions on mobile, not only on hover */ #fileList a.action { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)" !important; @@ -31,6 +25,11 @@ table td.filename .nametext { opacity: .2 !important; display: inline !important; } +/* do not show Rename or Versions on mobile */ +#fileList .action.action-rename, +#fileList .action.action-versions { + display: none !important; +} /* some padding for better clickability */ #fileList a.action img { padding: 0 6px 0 12px; From fe04106e0f6a88b5e3cf490773f2cb625465e98a Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 18 Mar 2014 13:09:25 +0100 Subject: [PATCH 09/26] Add/remove main menu action when switching between desktop/mobile mode --- core/js/js.js | 59 +++++++++++++++++-- core/js/tests/specs/coreSpec.js | 101 ++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 6 deletions(-) diff --git a/core/js/js.js b/core/js/js.js index 23d16b6923..aefca23509 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -488,7 +488,7 @@ var OC={ registerMenu: function($toggle, $menuEl) { $menuEl.addClass('menu'); $toggle.addClass('menutoggle'); - $toggle.on('click', function(event) { + $toggle.on('click.menu', function(event) { if ($menuEl.is(OC._currentMenu)) { $menuEl.hide(); OC._currentMenu = null; @@ -505,6 +505,17 @@ var OC={ OC._currentMenuToggle = $toggle; return false }); + }, + + unregisterMenu: function($toggle, $menuEl) { + // close menu if opened + if ($menuEl.is(OC._currentMenu)) { + $menuEl.hide(); + OC._currentMenu = null; + OC._currentMenuToggle = null; + } + $toggle.off('click.menu').removeClass('menutoggle'); + $menuEl.removeClass('menu'); } }; OC.search.customResults={}; @@ -978,13 +989,49 @@ function initCore() { OC._currentMenuToggle = null; }); - // toggle the navigation on mobile - if (window.matchMedia) { - var mq = window.matchMedia('(max-width: 600px)'); - if (mq && mq.matches) { - OC.registerMenu($('#header #owncloud'), $('#navigation')); + + /** + * 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() { + // toggle the navigation on mobile + if (window.matchMedia) { + var mq = window.matchMedia('(max-width: 600px)'); + var lastMatch = mq.matches; + var $toggle = $('#header #owncloud'); + var $navigation = $('#navigation'); + + function updateMainMenu() { + // mobile mode ? + if (lastMatch && !$toggle.hasClass('menutoggle')) { + // init the menu + OC.registerMenu($toggle, $navigation); + $toggle.data('oldhref', $toggle.attr('href')); + $toggle.attr('href', '#'); + $navigation.hide(); + } + else { + OC.unregisterMenu($toggle, $navigation); + $toggle.attr('href', $toggle.data('oldhref')); + $navigation.show(); + } + } + + updateMainMenu(); + + // TODO: debounce this + $(window).resize(function() { + if (lastMatch !== mq.matches) { + lastMatch = mq.matches; + updateMainMenu(); + } + }); } } + + setupMainMenu(); } $(document).ready(initCore); diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 069546387c..7fa0b8e9e6 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -279,5 +279,106 @@ describe('Core base tests', function() { expect(OC.generateUrl('apps/files/download{file}', {file: '/Welcome.txt'})).toEqual(OC.webroot + '/index.php/apps/files/download/Welcome.txt'); }); }); + describe('Main menu mobile toggle', function() { + var oldMatchMedia; + var $toggle; + var $navigation; + + beforeEach(function() { + oldMatchMedia = window.matchMedia; + window.matchMedia = sinon.stub(); + $('#testArea').append('' + + ''); + $toggle = $('#owncloud'); + $navigation = $('#navigation'); + }); + + afterEach(function() { + window.matchMedia = oldMatchMedia; + }); + it('Sets up menu toggle in mobile mode', function() { + window.matchMedia.returns({matches: true}); + window.initCore(); + expect($toggle.hasClass('menutoggle')).toEqual(true); + expect($navigation.hasClass('menu')).toEqual(true); + }); + it('Does not set up menu toggle in desktop mode', function() { + window.matchMedia.returns({matches: false}); + window.initCore(); + expect($toggle.hasClass('menutoggle')).toEqual(false); + expect($navigation.hasClass('menu')).toEqual(false); + }); + it('Switches on menu toggle when mobile mode changes', function() { + var mq = {matches: false}; + window.matchMedia.returns(mq); + window.initCore(); + expect($toggle.hasClass('menutoggle')).toEqual(false); + mq.matches = true; + $(window).trigger('resize'); + expect($toggle.hasClass('menutoggle')).toEqual(true); + }); + it('Switches off menu toggle when mobile mode changes', function() { + var mq = {matches: true}; + window.matchMedia.returns(mq); + window.initCore(); + expect($toggle.hasClass('menutoggle')).toEqual(true); + mq.matches = false; + $(window).trigger('resize'); + expect($toggle.hasClass('menutoggle')).toEqual(false); + }); + it('Clicking menu toggle toggles navigation in mobile mode', function() { + window.matchMedia.returns({matches: true}); + window.initCore(); + $navigation.hide(); // normally done through media query triggered CSS + expect($navigation.is(':visible')).toEqual(false); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(true); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(false); + }); + it('Clicking menu toggle does not toggle navigation in desktop mode', function() { + window.matchMedia.returns({matches: false}); + window.initCore(); + expect($navigation.is(':visible')).toEqual(true); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(true); + }); + it('Switching to mobile mode hides navigation', function() { + var mq = {matches: false}; + window.matchMedia.returns(mq); + window.initCore(); + expect($navigation.is(':visible')).toEqual(true); + mq.matches = true; + $(window).trigger('resize'); + expect($navigation.is(':visible')).toEqual(false); + }); + it('Switching to desktop mode shows navigation', function() { + var mq = {matches: true}; + window.matchMedia.returns(mq); + window.initCore(); + expect($navigation.is(':visible')).toEqual(false); + mq.matches = false; + $(window).trigger('resize'); + expect($navigation.is(':visible')).toEqual(true); + }); + it('Switch to desktop with opened menu then back to mobile resets toggle', function() { + var mq = {matches: true}; + window.matchMedia.returns(mq); + window.initCore(); + expect($navigation.is(':visible')).toEqual(false); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(true); + mq.matches = false; + $(window).trigger('resize'); + expect($navigation.is(':visible')).toEqual(true); + mq.matches = true; + $(window).trigger('resize'); + expect($navigation.is(':visible')).toEqual(false); + $toggle.click(); + expect($navigation.is(':visible')).toEqual(true); + }); + }); }); From cc6c1529848022765b9be6be808cf4dfb5b2d029 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 18 Mar 2014 15:52:06 +0100 Subject: [PATCH 10/26] Fixed matchMedia usage to make unit tests work in PhantomJS PhantomJS has a bug that makes it impossible to properly stub window.matchMedia. This fix adds a wrapper as OC._matchMedia that is used for unit tests --- core/js/js.js | 71 ++++++++++++++++++--------------- core/js/tests/specs/coreSpec.js | 27 +++++++------ 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/core/js/js.js b/core/js/js.js index aefca23509..87b2d59f16 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -997,41 +997,48 @@ function initCore() { */ function setupMainMenu() { // toggle the navigation on mobile - if (window.matchMedia) { - var mq = window.matchMedia('(max-width: 600px)'); - var lastMatch = mq.matches; - var $toggle = $('#header #owncloud'); - var $navigation = $('#navigation'); - - function updateMainMenu() { - // mobile mode ? - if (lastMatch && !$toggle.hasClass('menutoggle')) { - // init the menu - OC.registerMenu($toggle, $navigation); - $toggle.data('oldhref', $toggle.attr('href')); - $toggle.attr('href', '#'); - $navigation.hide(); - } - else { - OC.unregisterMenu($toggle, $navigation); - $toggle.attr('href', $toggle.data('oldhref')); - $navigation.show(); - } - } - - updateMainMenu(); - - // TODO: debounce this - $(window).resize(function() { - if (lastMatch !== mq.matches) { - lastMatch = mq.matches; - updateMainMenu(); - } - }); + if (!OC._matchMedia) { + return; } + var mq = OC._matchMedia('(max-width: 600px)'); + var lastMatch = mq.matches; + var $toggle = $('#header #owncloud'); + var $navigation = $('#navigation'); + + function updateMainMenu() { + // mobile mode ? + if (lastMatch && !$toggle.hasClass('menutoggle')) { + // init the menu + OC.registerMenu($toggle, $navigation); + $toggle.data('oldhref', $toggle.attr('href')); + $toggle.attr('href', '#'); + $navigation.hide(); + } + else { + OC.unregisterMenu($toggle, $navigation); + $toggle.attr('href', $toggle.data('oldhref')); + $navigation.show(); + } + } + + updateMainMenu(); + + // TODO: debounce this + $(window).resize(function() { + if (lastMatch !== mq.matches) { + lastMatch = mq.matches; + updateMainMenu(); + } + }); } - setupMainMenu(); + if (window.matchMedia) { + // wrapper needed for unit tests due to PhantomJS bugs + OC._matchMedia = function(media) { + return window.matchMedia(media); + } + setupMainMenu(); + } } $(document).ready(initCore); diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js index 7fa0b8e9e6..57ea5be8be 100644 --- a/core/js/tests/specs/coreSpec.js +++ b/core/js/tests/specs/coreSpec.js @@ -285,8 +285,11 @@ describe('Core base tests', function() { var $navigation; beforeEach(function() { - oldMatchMedia = window.matchMedia; - window.matchMedia = sinon.stub(); + oldMatchMedia = OC._matchMedia; + // a separate method was needed because window.matchMedia + // cannot be stubbed due to a bug in PhantomJS: + // https://github.com/ariya/phantomjs/issues/12069 + OC._matchMedia = sinon.stub(); $('#testArea').append('' + @@ -296,23 +299,23 @@ describe('Core base tests', function() { }); afterEach(function() { - window.matchMedia = oldMatchMedia; + OC._matchMedia = oldMatchMedia; }); it('Sets up menu toggle in mobile mode', function() { - window.matchMedia.returns({matches: true}); + OC._matchMedia.returns({matches: true}); window.initCore(); expect($toggle.hasClass('menutoggle')).toEqual(true); expect($navigation.hasClass('menu')).toEqual(true); }); it('Does not set up menu toggle in desktop mode', function() { - window.matchMedia.returns({matches: false}); + OC._matchMedia.returns({matches: false}); window.initCore(); expect($toggle.hasClass('menutoggle')).toEqual(false); expect($navigation.hasClass('menu')).toEqual(false); }); it('Switches on menu toggle when mobile mode changes', function() { var mq = {matches: false}; - window.matchMedia.returns(mq); + OC._matchMedia.returns(mq); window.initCore(); expect($toggle.hasClass('menutoggle')).toEqual(false); mq.matches = true; @@ -321,7 +324,7 @@ describe('Core base tests', function() { }); it('Switches off menu toggle when mobile mode changes', function() { var mq = {matches: true}; - window.matchMedia.returns(mq); + OC._matchMedia.returns(mq); window.initCore(); expect($toggle.hasClass('menutoggle')).toEqual(true); mq.matches = false; @@ -329,7 +332,7 @@ describe('Core base tests', function() { expect($toggle.hasClass('menutoggle')).toEqual(false); }); it('Clicking menu toggle toggles navigation in mobile mode', function() { - window.matchMedia.returns({matches: true}); + OC._matchMedia.returns({matches: true}); window.initCore(); $navigation.hide(); // normally done through media query triggered CSS expect($navigation.is(':visible')).toEqual(false); @@ -339,7 +342,7 @@ describe('Core base tests', function() { expect($navigation.is(':visible')).toEqual(false); }); it('Clicking menu toggle does not toggle navigation in desktop mode', function() { - window.matchMedia.returns({matches: false}); + OC._matchMedia.returns({matches: false}); window.initCore(); expect($navigation.is(':visible')).toEqual(true); $toggle.click(); @@ -347,7 +350,7 @@ describe('Core base tests', function() { }); it('Switching to mobile mode hides navigation', function() { var mq = {matches: false}; - window.matchMedia.returns(mq); + OC._matchMedia.returns(mq); window.initCore(); expect($navigation.is(':visible')).toEqual(true); mq.matches = true; @@ -356,7 +359,7 @@ describe('Core base tests', function() { }); it('Switching to desktop mode shows navigation', function() { var mq = {matches: true}; - window.matchMedia.returns(mq); + OC._matchMedia.returns(mq); window.initCore(); expect($navigation.is(':visible')).toEqual(false); mq.matches = false; @@ -365,7 +368,7 @@ describe('Core base tests', function() { }); it('Switch to desktop with opened menu then back to mobile resets toggle', function() { var mq = {matches: true}; - window.matchMedia.returns(mq); + OC._matchMedia.returns(mq); window.initCore(); expect($navigation.is(':visible')).toEqual(false); $toggle.click(); From 67b8cfedf9f500eb25871265a05c874acc4809ff Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Tue, 18 Mar 2014 16:02:13 +0100 Subject: [PATCH 11/26] Define _matchMedia wrapper earlier The unit test stub didn't work because the _matchMedia wrapper was defined too late. This fix defines it earlier. --- core/js/js.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/core/js/js.js b/core/js/js.js index 87b2d59f16..121a4062d3 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -516,6 +516,19 @@ var OC={ } $toggle.off('click.menu').removeClass('menutoggle'); $menuEl.removeClass('menu'); + }, + + /** + * Wrapper for matchMedia + * + * This is makes it possible for unit tests to + * stub matchMedia (which doesn't work in PhantomJS) + */ + _matchMedia: function(media) { + if (window.matchMedia) { + return window.matchMedia(media); + } + return false; } }; OC.search.customResults={}; @@ -1033,10 +1046,6 @@ function initCore() { } if (window.matchMedia) { - // wrapper needed for unit tests due to PhantomJS bugs - OC._matchMedia = function(media) { - return window.matchMedia(media); - } setupMainMenu(); } } From 59906fbb4deeed881dd1e0c590c360ad48697169 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Tue, 18 Mar 2014 17:48:28 +0100 Subject: [PATCH 12/26] mobile: show caret indicator next to logo to make clear it is tappable --- core/css/mobile.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/css/mobile.css b/core/css/mobile.css index 5a465b35fb..56bee8f8a3 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -1,5 +1,13 @@ @media only screen and (max-width: 600px) { +/* show caret indicator next to logo to make clear it is tappable */ +#owncloud.menutoggle { + background-image: url('../img/actions/caret.svg'); + background-repeat: no-repeat; + background-position: right 26px; + padding-right: 16px !important; +} + /* compress search box on mobile, expand when focused */ .searchbox input[type="search"] { width: 15%; From 76961ce0727d08ef3d12bd445798df221f5d2063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 18 Mar 2014 17:54:21 +0100 Subject: [PATCH 13/26] fixing javascript error where $(Files.breadcrumbs[1]).get(0) returns undefined - happens on resize to a very small width --- apps/files/js/files.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 1186a72a44..1137364db4 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -196,11 +196,14 @@ var Files = { if (width !== Files.lastWidth) { if ((width < Files.lastWidth || firstRun) && width < Files.breadcrumbsWidth) { if (Files.hiddenBreadcrumbs === 0) { - Files.breadcrumbsWidth -= $(Files.breadcrumbs[1]).get(0).offsetWidth; - $(Files.breadcrumbs[1]).find('a').hide(); - $(Files.breadcrumbs[1]).append('...'); - Files.breadcrumbsWidth += $(Files.breadcrumbs[1]).get(0).offsetWidth; - Files.hiddenBreadcrumbs = 2; + bc = $(Files.breadcrumbs[1]).get(0); + if (typeof bc != 'undefined') { + Files.breadcrumbsWidth -= bc.offsetWidth; + $(Files.breadcrumbs[1]).find('a').hide(); + $(Files.breadcrumbs[1]).append('...'); + Files.breadcrumbsWidth += bc.offsetWidth; + Files.hiddenBreadcrumbs = 2; + } } var i = Files.hiddenBreadcrumbs; while (width < Files.breadcrumbsWidth && i > 1 && i < Files.breadcrumbs.length - 1) { From c9d97d2ef22ca8b07f692a93f16619f7316d7d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 21 Mar 2014 11:56:47 +0100 Subject: [PATCH 14/26] add top:45px to position:fixed controls, fixes alignment on android 4.0.4 browser --- core/css/styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/core/css/styles.css b/core/css/styles.css index 69cf6df07d..af7f6dfd58 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -246,6 +246,7 @@ input[type="submit"].enabled { -webkit-box-sizing: border-box; box-sizing: border-box; position: fixed; + top:45px; right: 0; left: 0; height: 44px; From ecf52e05fb9e88910167351dfa75bde5db2267ae Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 21 Mar 2014 14:30:11 +0100 Subject: [PATCH 15/26] mobile: adjust width of app title, fix overlap --- core/css/mobile.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/css/mobile.css b/core/css/mobile.css index 56bee8f8a3..6665b95f27 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -54,7 +54,7 @@ display: inline-block; } #navigation a { - width: 70px; + width: 80px; height: 80px; display: inline-block; text-align: center; @@ -65,7 +65,7 @@ font-size: 13px; padding-bottom: 0; padding-left: 0; - width: initial; + width: 80px; } #navigation .icon { margin: 0 auto; From 40c20b2eebed45be56cf554b904fe2d7510ed448 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 21 Mar 2014 14:34:05 +0100 Subject: [PATCH 16/26] mobile: remove shift for multiselect bar to account for missing navigation --- apps/files/css/mobile.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css index 00c4630ea6..225448e5ab 100644 --- a/apps/files/css/mobile.css +++ b/apps/files/css/mobile.css @@ -13,6 +13,11 @@ table td.date { display: none; } +/* remove shift for multiselect bar to account for missing navigation */ +table.multiselect thead { + padding-left: 0; +} + /* restrict length of displayed filename to prevent overflow */ table td.filename .nametext { max-width: 75% !important; From c6aefada71097d2baf7db36b4829e7f6b258ea95 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 21 Mar 2014 15:29:37 +0100 Subject: [PATCH 17/26] do not show Deleted Files on mobile, not optimized yet and button too long --- apps/files/css/mobile.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css index 225448e5ab..221c23e5ad 100644 --- a/apps/files/css/mobile.css +++ b/apps/files/css/mobile.css @@ -5,6 +5,11 @@ min-width: initial !important; } +/* do not show Deleted Files on mobile, not optimized yet and button too long */ +#controls #trash { + display: none; +} + /* hide size and date columns */ table th#headerSize, table td.filesize, From f2566e649ffd944dd5dfea3ba38124fef9472ed8 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 21 Mar 2014 16:36:45 +0100 Subject: [PATCH 18/26] mobile: adjust break to 768, also fix min-widths --- apps/files/css/files.css | 2 +- apps/files/css/mobile.css | 2 +- core/css/mobile.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/files/css/files.css b/apps/files/css/files.css index af863aca33..a463eb51d8 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -77,7 +77,7 @@ } /* make sure there's enough room for the file actions */ #body-user #filestable { - min-width: 750px; + min-width: 688px; } #body-user #controls { min-width: 600px; diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css index 221c23e5ad..087bb1f836 100644 --- a/apps/files/css/mobile.css +++ b/apps/files/css/mobile.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 600px) { +@media only screen and (max-width: 768px) { /* don’t require a minimum width for files table */ #body-user #filestable { diff --git a/core/css/mobile.css b/core/css/mobile.css index 6665b95f27..dbe1c55a56 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 600px) { +@media only screen and (max-width: 768px) { /* show caret indicator next to logo to make clear it is tappable */ #owncloud.menutoggle { From 9b9b6ec31eac005f98bf38e691f81812335544e9 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 21 Mar 2014 16:40:39 +0100 Subject: [PATCH 19/26] mobile: set a width for navigation popover to it's always 3 columns --- core/css/mobile.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/css/mobile.css b/core/css/mobile.css index dbe1c55a56..c67ac3e5ec 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -34,8 +34,7 @@ #navigation { top: 45px; bottom: initial; - width: 90%; - max-width: 320px; + width: 255px; max-height: 90%; margin-top: 0; top: 45px; From 89ee5511580b7e9f5619961b12cd96b984b6b6e9 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 21 Mar 2014 16:43:04 +0100 Subject: [PATCH 20/26] mobile: adjust breakpoint in JS as well --- core/js/js.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/js/js.js b/core/js/js.js index 121a4062d3..3c169cfb21 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -1013,7 +1013,7 @@ function initCore() { if (!OC._matchMedia) { return; } - var mq = OC._matchMedia('(max-width: 600px)'); + var mq = OC._matchMedia('(max-width: 768px)'); var lastMatch = mq.matches; var $toggle = $('#header #owncloud'); var $navigation = $('#navigation'); From 905aabf5d41b0830cc2754229c39be709479ed7b Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 21 Mar 2014 16:43:40 +0100 Subject: [PATCH 21/26] mobile: document min-width value --- apps/files/css/files.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files/css/files.css b/apps/files/css/files.css index a463eb51d8..1bac5d2b7d 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -77,10 +77,10 @@ } /* make sure there's enough room for the file actions */ #body-user #filestable { - min-width: 688px; + min-width: 688px; /* 768 (mobile break) - 80 (nav width) */ } #body-user #controls { - min-width: 600px; + min-width: 688px; /* 768 (mobile break) - 80 (nav width) */ } #filestable tbody tr { background-color:#fff; height:40px; } From a54260b5174cca222fb7589d9f582b454b37ef08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 25 Mar 2014 23:35:55 +0100 Subject: [PATCH 22/26] use minimum-scale=1.0 --- core/templates/layout.user.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 3d89750348..b7266d50cf 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -15,7 +15,7 @@ - + From e6392163a437ad5d44cba452edeaba35aeff250b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 25 Mar 2014 23:40:40 +0100 Subject: [PATCH 23/26] adding ellipsis on file names --- apps/files/css/mobile.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css index 087bb1f836..bb71d67c76 100644 --- a/apps/files/css/mobile.css +++ b/apps/files/css/mobile.css @@ -49,5 +49,12 @@ table td.filename .nametext { display: none; } +/* ellipsis on file names */ +.nametext { + width: 60%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} } From d0f84e936f3be2a4622b0b1b8be8d070c970b1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 26 Mar 2014 00:04:11 +0100 Subject: [PATCH 24/26] adding proper notification area for multi line messages --- apps/files/css/mobile.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css index bb71d67c76..3ad7d63483 100644 --- a/apps/files/css/mobile.css +++ b/apps/files/css/mobile.css @@ -57,4 +57,12 @@ table td.filename .nametext { text-overflow: ellipsis; } +/* proper notification area for multi line messages */ +#notification-container { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; +} } From 8958b9147b4134d1732b23882d8bc419572b7bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 26 Mar 2014 00:14:38 +0100 Subject: [PATCH 25/26] adding ellipsis on file names to public file list as well --- apps/files_sharing/css/mobile.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/files_sharing/css/mobile.css b/apps/files_sharing/css/mobile.css index 7d2116d190..988ae862ef 100644 --- a/apps/files_sharing/css/mobile.css +++ b/apps/files_sharing/css/mobile.css @@ -45,5 +45,13 @@ table td.filename .nametext { display: none; } +/* ellipsis on file names */ +.nametext { + width: 60%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + } From eeaefd84c3911a166920084947ad1018c744e6a6 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Wed, 26 Mar 2014 16:32:09 +0100 Subject: [PATCH 26/26] change mobile breakpoint for shared view to 768px as well --- apps/files_sharing/css/mobile.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_sharing/css/mobile.css b/apps/files_sharing/css/mobile.css index 988ae862ef..333c4c77fc 100644 --- a/apps/files_sharing/css/mobile.css +++ b/apps/files_sharing/css/mobile.css @@ -1,4 +1,4 @@ -@media only screen and (max-width: 600px) { +@media only screen and (max-width: 768px) { /* make header scroll up for single shares, more view of content on small screens */ #header.share-file {