summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files/css/files.css4
-rw-r--r--apps/files/css/mobile.css68
-rw-r--r--apps/files/index.php1
-rw-r--r--apps/files/js/fileactions.js37
-rw-r--r--apps/files/js/files.js13
-rw-r--r--apps/files_sharing/css/mobile.css10
-rw-r--r--apps/files_versions/js/versions.js3
-rw-r--r--core/css/mobile.css85
-rw-r--r--core/css/styles.css1
-rw-r--r--core/js/js.js108
-rw-r--r--core/js/share.js8
-rw-r--r--core/js/tests/specs/coreSpec.js104
-rw-r--r--core/templates/layout.user.php2
13 files changed, 418 insertions, 26 deletions
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index af863aca33e..1bac5d2b7db 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: 750px;
+ 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; }
diff --git a/apps/files/css/mobile.css b/apps/files/css/mobile.css
new file mode 100644
index 00000000000..3ad7d634838
--- /dev/null
+++ b/apps/files/css/mobile.css
@@ -0,0 +1,68 @@
+@media only screen and (max-width: 768px) {
+
+/* don’t require a minimum width for files table */
+#body-user #filestable {
+ 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,
+table th#headerDate,
+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;
+}
+
+/* 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;
+}
+/* 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;
+}
+/* hide text of the actions on mobile */
+#fileList a.action span {
+ display: none;
+}
+
+/* ellipsis on file names */
+.nametext {
+ width: 60%;
+ white-space: nowrap;
+ overflow: hidden;
+ 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;
+}
+}
diff --git a/apps/files/index.php b/apps/files/index.php
index c66cd40fb56..73601d26217 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');
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 9a69d7b3688..a7d1fa9d8a2 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 = '<a href="#" class="action" data-action="' + name + '">';
+ var html = '<a href="#" class="action action-' + name.toLowerCase() + '" data-action="' + name + '">';
if (img) {
html += '<img class ="svg" src="' + img + '" />';
}
@@ -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/js/files.js b/apps/files/js/files.js
index 1186a72a44f..1137364db4a 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('<span>...</span>');
- 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('<span>...</span>');
+ Files.breadcrumbsWidth += bc.offsetWidth;
+ Files.hiddenBreadcrumbs = 2;
+ }
}
var i = Files.hiddenBreadcrumbs;
while (width < Files.breadcrumbsWidth && i > 1 && i < Files.breadcrumbs.length - 1) {
diff --git a/apps/files_sharing/css/mobile.css b/apps/files_sharing/css/mobile.css
index 7d2116d190d..333c4c77fc9 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 {
@@ -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;
+}
+
}
diff --git a/apps/files_versions/js/versions.js b/apps/files_versions/js/versions.js
index 4adf14745de..b452bc25b13 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')
);
}
diff --git a/core/css/mobile.css b/core/css/mobile.css
index a63aa902d34..c67ac3e5ecf 100644
--- a/core/css/mobile.css
+++ b/core/css/mobile.css
@@ -1,4 +1,12 @@
-@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 {
+ 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"] {
@@ -18,5 +26,80 @@
display: none;
}
+/* toggle navigation */
+#content-wrapper {
+ padding-left: 0;
+}
+
+#navigation {
+ top: 45px;
+ bottom: initial;
+ width: 255px;
+ 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: 80px;
+ 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: 80px;
+}
+#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;
+}
+
+/* position share dropdown */
+#dropdown {
+ margin-right: 10% !important;
+ width: 80% !important;
+}
+
}
diff --git a/core/css/styles.css b/core/css/styles.css
index 89f58828cf9..2c043ab724c 100644
--- a/core/css/styles.css
+++ b/core/css/styles.css
@@ -248,6 +248,7 @@ input[type="submit"].enabled {
-webkit-box-sizing: border-box;
box-sizing: border-box;
position: fixed;
+ top:45px;
right: 0;
left: 0;
height: 44px;
diff --git a/core/js/js.js b/core/js/js.js
index 3d3185f12c1..721a5a2e927 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -482,6 +482,53 @@ var OC={
}).show();
}, 'html');
}
+ },
+
+ // for menu toggling
+ registerMenu: function($toggle, $menuEl) {
+ $menuEl.addClass('menu');
+ $toggle.addClass('menutoggle');
+ $toggle.on('click.menu', 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
+ });
+ },
+
+ 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');
+ },
+
+ /**
+ * 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={};
@@ -940,6 +987,67 @@ 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;
+ });
+
+
+ /**
+ * 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 (!OC._matchMedia) {
+ return;
+ }
+ var mq = OC._matchMedia('(max-width: 768px)');
+ 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 (window.matchMedia) {
+ setupMainMenu();
+ }
}
$(document).ready(initCore);
diff --git a/core/js/share.js b/core/js/share.js
index 9ee50ff6963..e769edd0a21 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(' <span>'+t('core', 'Shared')+'</span>').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(' <span>'+t('core', 'Shared')+'</span>').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(' <span>'+ escapeHTML(t('core', 'Shared'))+'</span>').prepend(img);
} else {
action.removeClass('permanent');
- action.html(' '+ escapeHTML(t('core', 'Share'))).prepend(img);
+ action.html(' <span>'+ escapeHTML(t('core', 'Share'))+'</span>').prepend(img);
}
}
}
diff --git a/core/js/tests/specs/coreSpec.js b/core/js/tests/specs/coreSpec.js
index 069546387c7..57ea5be8be0 100644
--- a/core/js/tests/specs/coreSpec.js
+++ b/core/js/tests/specs/coreSpec.js
@@ -279,5 +279,109 @@ 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 = 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('<div id="header">' +
+ '<a id="owncloud" href="#"></a>' +
+ '</div>' +
+ '<div id="navigation"></div>');
+ $toggle = $('#owncloud');
+ $navigation = $('#navigation');
+ });
+
+ afterEach(function() {
+ OC._matchMedia = oldMatchMedia;
+ });
+ it('Sets up menu toggle in mobile mode', function() {
+ 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() {
+ 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};
+ OC._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};
+ OC._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() {
+ OC._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() {
+ OC._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};
+ OC._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};
+ OC._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};
+ OC._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);
+ });
+ });
});
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index 789b4dcf902..ba5f6ef9b54 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -15,7 +15,7 @@
</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=1.0">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
<meta name="apple-itunes-app" content="app-id=543672169">
<link rel="shortcut icon" href="<?php print_unescaped(image_path('', 'favicon.png')); ?>" />
<link rel="apple-touch-icon-precomposed" href="<?php print_unescaped(image_path('', 'favicon-touch.png')); ?>" />