diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php index 0d1449ff35..a9d8ba0a1b 100644 --- a/apps/files/appinfo/routes.php +++ b/apps/files/appinfo/routes.php @@ -76,6 +76,66 @@ $application->registerRoutes( 'url' => '/ajax/getstoragestats.php', 'verb' => 'GET', ], + [ + 'name' => 'API#showQuickAccess', + 'url' => '/api/v1/quickaccess/set/showList', + 'verb' => 'GET', + ], + [ + 'name' => 'API#getShowQuickAccess', + 'url' => '/api/v1/quickaccess/get/showList', + 'verb' => 'GET', + ], + [ + 'name' => 'API#getShowQuickaccessSettings', + 'url' => '/api/v1/quickaccess/showsettings', + 'verb' => 'GET', + ], + [ + 'name' => 'API#setShowQuickaccessSettings', + 'url' => '/api/v1/quickaccess/set/showsettings', + 'verb' => 'GET', + ], + [ + 'name' => 'API#setSortingStrategy', + 'url' => '/api/v1/quickaccess/set/SortingStrategy', + 'verb' => 'GET', + ], + [ + 'name' => 'API#setReverseQuickaccess', + 'url' => '/api/v1/quickaccess/set/ReverseList', + 'verb' => 'GET', + ], + [ + 'name' => 'API#getSortingStrategy', + 'url' => '/api/v1/quickaccess/get/SortingStrategy', + 'verb' => 'GET', + ], + [ + 'name' => 'API#getReverseQuickaccess', + 'url' => '/api/v1/quickaccess/get/ReverseList', + 'verb' => 'GET', + ], + [ + 'name' => 'API#getFavoritesFolder', + 'url' => '/api/v1/quickaccess/get/FavoriteFolders/', + 'verb' => 'GET' + ], + [ + 'name' => 'API#setSortingOrder', + 'url' => '/api/v1/quickaccess/set/CustomSortingOrder', + 'verb' => 'GET', + ], + [ + 'name' => 'API#getSortingOrder', + 'url' => '/api/v1/quickaccess/get/CustomSortingOrder', + 'verb' => 'GET', + ], + [ + 'name' => 'API#getNodeType', + 'url' => '/api/v1/quickaccess/get/NodeType', + 'verb' => 'GET', + ], ] ] ); diff --git a/apps/files/js/app.js b/apps/files/js/app.js index 6a21bce975..52c92645b2 100644 --- a/apps/files/js/app.js +++ b/apps/files/js/app.js @@ -53,6 +53,8 @@ this.$showHiddenFiles = $('input#showhiddenfilesToggle'); var showHidden = $('#showHiddenFiles').val() === "1"; this.$showHiddenFiles.prop('checked', showHidden); + + if ($('#fileNotFound').val() === "1") { OC.Notification.show(t('files', 'File could not be found'), {type: 'error'}); } @@ -219,7 +221,7 @@ }, /** - * Persist show hidden preference on ther server + * Persist show hidden preference on the server * * @returns {undefined} */ @@ -237,8 +239,8 @@ var params; if (e && e.itemId) { params = { - view: e.itemId, - dir: '/' + view: typeof e.view === 'string' && e.view !== '' ? e.view : e.itemId, + dir: e.dir ? e.dir : '/' }; this._changeUrl(params.view, params.dir); OC.Apps.hideAppSidebar($('.detailsView')); diff --git a/apps/files/js/navigation.js b/apps/files/js/navigation.js index d213d0467b..d4fa06cb45 100644 --- a/apps/files/js/navigation.js +++ b/apps/files/js/navigation.js @@ -1,8 +1,9 @@ /* - * Copyright (c) 2014 + * @Copyright 2014 Vincent Petry * * @author Vincent Petry - * @copyright 2014 Vincent Petry + * @author Felix Nüsse + * * * This file is licensed under the Affero General Public License version 3 * or later. @@ -11,7 +12,7 @@ * */ -(function() { +(function () { /** * @class OCA.Files.Navigation @@ -19,7 +20,7 @@ * * @param $el element containing the navigation */ - var Navigation = function($el) { + var Navigation = function ($el) { this.initialize($el); }; @@ -38,24 +39,48 @@ */ $currentContent: null, + /** + * Strategy by which the quickaccesslist is sorted + * + * Possible Strategies: + * customorder + * datemodified + * date + * alphabet + * + */ + $sortingStrategy: 'alphabet', + + /** + * Key for the quick-acces-list + */ + $quickAccessListKey: 'sublist-favorites', /** * Initializes the navigation from the given container * * @private * @param $el element containing the navigation */ - initialize: function($el) { + initialize: function ($el) { this.$el = $el; this._activeItem = null; this.$currentContent = null; this._setupEvents(); + + var scope=this; + $.get(OC.generateUrl("/apps/files/api/v1/quickaccess/get/SortingStrategy"), function (data, status) { + scope.$sortingStrategy=data; + scope.setInitialQuickaccessSettings(); + }); + }, /** * Setup UI events */ - _setupEvents: function() { - this.$el.on('click', 'li a', _.bind(this._onClickItem, this)); + _setupEvents: function () { + this.$el.on('click', 'li a', _.bind(this._onClickItem, this)) + this.$el.on('click', 'li button', _.bind(this._onClickMenuButton, this)); }, /** @@ -63,16 +88,16 @@ * * @return app container */ - getActiveContainer: function() { + getActiveContainer: function () { return this.$currentContent; }, /** * Returns the currently active item - * + * * @return item ID */ - getActiveItem: function() { + getActiveItem: function () { return this._activeItem; }, @@ -83,29 +108,42 @@ * @param string itemId id of the navigation item to select * @param array options "silent" to not trigger event */ - setActiveItem: function(itemId, options) { + setActiveItem: function (itemId, options) { + var currentItem = this.$el.find('li[data-id=' + itemId + ']'); + var itemDir = currentItem.data('dir'); + var itemView = currentItem.data('view'); var oldItemId = this._activeItem; if (itemId === this._activeItem) { if (!options || !options.silent) { this.$el.trigger( - new $.Event('itemChanged', {itemId: itemId, previousItemId: oldItemId}) + new $.Event('itemChanged', { + itemId: itemId, + previousItemId: oldItemId, + dir: itemDir, + view: itemView + }) ); } return; } - this.$el.find('li').removeClass('active'); + this.$el.find('li a').removeClass('active'); if (this.$currentContent) { this.$currentContent.addClass('hidden'); this.$currentContent.trigger(jQuery.Event('hide')); } this._activeItem = itemId; - this.$el.find('li[data-id=' + itemId + ']').addClass('active'); - this.$currentContent = $('#app-content-' + itemId); + currentItem.children('a').addClass('active'); + this.$currentContent = $('#app-content-' + (typeof itemView === 'string' && itemView !== '' ? itemView : itemId)); this.$currentContent.removeClass('hidden'); if (!options || !options.silent) { this.$currentContent.trigger(jQuery.Event('show')); this.$el.trigger( - new $.Event('itemChanged', {itemId: itemId, previousItemId: oldItemId}) + new $.Event('itemChanged', { + itemId: itemId, + previousItemId: oldItemId, + dir: itemDir, + view: itemView + }) ); } }, @@ -113,23 +151,206 @@ /** * Returns whether a given item exists */ - itemExists: function(itemId) { + itemExists: function (itemId) { return this.$el.find('li[data-id=' + itemId + ']').length; }, /** * Event handler for when clicking on an item. */ - _onClickItem: function(ev) { + _onClickItem: function (ev) { var $target = $(ev.target); var itemId = $target.closest('li').attr('data-id'); if (!_.isUndefined(itemId)) { this.setActiveItem(itemId); } ev.preventDefault(); + }, + + /** + * Event handler for clicking a button + */ + _onClickMenuButton: function (ev) { + var $target = $(ev.target); + var itemId = $target.closest('button').attr('id'); + + var collapsibleToggles = []; + var dotmenuToggles = []; + + // The collapsibleToggles-Array consists of a list of Arrays. Every subarray must contain the Button to listen to at the 0th index, + // and the parent, which should be toggled at the first arrayindex. + collapsibleToggles.push(["#button-collapse-favorites", "#button-collapse-parent-favorites"]); + + // The dotmenuToggles-Array consists of a list of Arrays. Every subarray must contain the Button to listen to at the 0th index, + // and the parent, which should be toggled at the first arrayindex. + dotmenuToggles.push(["#dotmenu-button-favorites", "dotmenu-content-favorites"]); + + collapsibleToggles.forEach(function foundToggle (item) { + if (item[0] === ("#" + itemId)) { + $(item[1]).toggleClass('open'); + var show = 1; + if (!$(item[1]).hasClass('open')) { + show = 0; + } + $.get(OC.generateUrl("/apps/files/api/v1/quickaccess/set/showList"), {show: show}, function (data, status) { + }); + } + }); + + dotmenuToggles.forEach(function foundToggle (item) { + if (item[0] === ("#" + itemId)) { + document.getElementById(item[1]).classList.toggle('open'); + } + }); + + ev.preventDefault(); + }, + + /** + * Sort initially as setup of sidebar for QuickAccess + */ + setInitialQuickaccessSettings: function () { + + var quickAccesKey = this.$quickAccessListKey; + var list = document.getElementById(quickAccesKey).getElementsByTagName('li'); + + var sort = true; + var reverse = false; + if (this.$sortingStrategy === 'datemodified') { + sort = false; + reverse = false; + + var scope = this; + $.get(OC.generateUrl("/apps/files/api/v1/quickaccess/get/FavoriteFolders/"), function (data, status) { + for (var i = 0; i < data.favoriteFolders.length; i++) { + for (var j = 0; j < list.length; j++) { + if (scope.getCompareValue(list, j, 'alphabet').toLowerCase() === data.favoriteFolders[i].name.toLowerCase()) { + list[j].setAttribute("mtime", data.favoriteFolders[i].mtime); + } + } + } + scope.QuickSort(list, 0, list.length - 1); + scope.reverse(list); + }); + + } else if (this.$sortingStrategy === 'alphabet') { + sort = true; + } else if (this.$sortingStrategy === 'date') { + sort = true; + } else if (this.$sortingStrategy === 'customorder') { + var scope = this; + $.get(OC.generateUrl("/apps/files/api/v1/quickaccess/get/CustomSortingOrder"), function (data, status) { + var ordering = JSON.parse(data); + for (var i = 0; i < ordering.length; i++) { + for (var j = 0; j < list.length; j++) { + if (scope.getCompareValue(list, j, 'alphabet').toLowerCase() === ordering[i].name.toLowerCase()) { + list[j].setAttribute("folderPosition", ordering[i].id); + } + } + } + scope.QuickSort(list, 0, list.length - 1); + }); + sort = false; + } + + if (sort) { + this.QuickSort(list, 0, list.length - 1); + } + if (reverse) { + this.reverse(list); + } + + }, + + /** + * Sorting-Algorithm for QuickAccess + */ + QuickSort: function (list, start, end) { + var lastMatch; + if (list.length > 1) { + lastMatch = this.quicksort_helper(list, start, end); + if (start < lastMatch - 1) { + this.QuickSort(list, start, lastMatch - 1); + } + if (lastMatch < end) { + this.QuickSort(list, lastMatch, end); + } + } + }, + + /** + * Sorting-Algorithm-Helper for QuickAccess + */ + quicksort_helper: function (list, start, end) { + var pivot = Math.floor((end + start) / 2); + var pivotElement = this.getCompareValue(list, pivot); + var i = start; + var j = end; + + while (i <= j) { + while (this.getCompareValue(list, i) < pivotElement) { + i++; + } + while (this.getCompareValue(list, j) > pivotElement) { + j--; + } + if (i <= j) { + this.swap(list, i, j); + i++; + j--; + } + } + return i; + }, + + /** + * Sorting-Algorithm-Helper for QuickAccess + * This method allows easy access to the element which is sorted by. + */ + getCompareValue: function (nodes, int, strategy) { + + if ((typeof strategy === 'undefined')) { + strategy = this.$sortingStrategy; + } + + if (strategy === 'alphabet') { + return nodes[int].getElementsByTagName('a')[0].innerHTML.toLowerCase(); + } else if (strategy === 'date') { + return nodes[int].getAttribute('folderPosition').toLowerCase(); + } else if (strategy === 'datemodified') { + return nodes[int].getAttribute('mtime'); + } else if (strategy === 'customorder') { + return nodes[int].getAttribute('folderPosition'); + } + return nodes[int].getElementsByTagName('a')[0].innerHTML.toLowerCase(); + }, + + /** + * Sorting-Algorithm-Helper for QuickAccess + * This method allows easy swapping of elements. + */ + swap: function (list, j, i) { + list[i].before(list[j]); + list[j].before(list[i]); + }, + + /** + * Reverse QuickAccess-List + */ + reverse: function (list) { + var len = list.length - 1; + for (var i = 0; i < len / 2; i++) { + this.swap(list, i, len - i); + } } + }; OCA.Files.Navigation = Navigation; })(); + + + + + diff --git a/apps/files/js/tagsplugin.js b/apps/files/js/tagsplugin.js index b174aa7d76..bc1396b510 100644 --- a/apps/files/js/tagsplugin.js +++ b/apps/files/js/tagsplugin.js @@ -10,11 +10,11 @@ /* global Handlebars */ -(function(OCA) { +(function (OCA) { _.extend(OC.Files.Client, { - PROPERTY_TAGS: '{' + OC.Files.Client.NS_OWNCLOUD + '}tags', - PROPERTY_FAVORITE: '{' + OC.Files.Client.NS_OWNCLOUD + '}favorite' + PROPERTY_TAGS: '{' + OC.Files.Client.NS_OWNCLOUD + '}tags', + PROPERTY_FAVORITE: '{' + OC.Files.Client.NS_OWNCLOUD + '}favorite' }); var TEMPLATE_FAVORITE_MARK = @@ -30,7 +30,7 @@ * @param {boolean} state true if starred, false otherwise * @return {string} icon class for star image */ - function getStarIconClass(state) { + function getStarIconClass (state) { return state ? 'icon-starred' : 'icon-star'; } @@ -40,7 +40,7 @@ * @param {boolean} state true if starred, false otherwise * @return {Object} jQuery object */ - function renderStar(state) { + function renderStar (state) { if (!this._template) { this._template = Handlebars.compile(TEMPLATE_FAVORITE_MARK); } @@ -57,11 +57,95 @@ * @param {Object} $favoriteMarkEl favorite mark element * @param {boolean} state true if starred, false otherwise */ - function toggleStar($favoriteMarkEl, state) { + function toggleStar ($favoriteMarkEl, state) { $favoriteMarkEl.removeClass('icon-star icon-starred').addClass(getStarIconClass(state)); $favoriteMarkEl.toggleClass('permanent', state); } + /** + * Remove Item from Quickaccesslist + * + * @param {String} appfolder folder to be removed + */ + function removeFavoriteFromList (appfolder) { + + var quickAccessList = 'sublist-favorites'; + var collapsibleButtonId = 'button-collapse-favorites'; + var listULElements = document.getElementById(quickAccessList); + if (!listULElements) { + return; + } + var listLIElements = listULElements.getElementsByTagName('li'); + + var apppath=appfolder; + if(appfolder.startsWith("//")){ + apppath=appfolder.substring(1, appfolder.length); + } + + for (var i = 0; i <= listLIElements.length - 1; i++) { + if (listLIElements[i].getElementsByTagName('a')[0].href.endsWith("dir=" + apppath)) { + listLIElements[i].remove(); + } + } + + if (listULElements.childElementCount === 0) { + var collapsibleButton = document.getElementById("button-collapse-favorites"); + collapsibleButton.style.display = 'none'; + $("#button-collapse-parent-favorites").removeClass('collapsible'); + } + } + + /** + * Add Item to Quickaccesslist + * + * @param {String} appfolder folder to be added + */ + function addFavoriteToList (appfolder) { + var quickAccessList = 'sublist-favorites'; + var collapsibleButtonId = 'button-collapse-favorites'; + var listULElements = document.getElementById(quickAccessList); + if (!listULElements) { + return; + } + var listLIElements = listULElements.getElementsByTagName('li'); + + var appName = appfolder.substring(appfolder.lastIndexOf("/") + 1, appfolder.length); + var apppath=appfolder; + + if(appfolder.startsWith("//")){ + apppath=appfolder.substring(1, appfolder.length); + } + var url=OC.generateUrl('/apps/files/?dir=')+apppath; + + + var innerTagA = document.createElement('A'); + innerTagA.setAttribute("href", url); + innerTagA.setAttribute("class", "nav-icon-files svg"); + innerTagA.innerHTML = appName; + + var length = listLIElements.length + 1; + var innerTagLI = document.createElement('li'); + innerTagLI.setAttribute("data-id", url); + innerTagLI.setAttribute("class", "nav-" + appName); + innerTagLI.setAttribute("folderpos", length.toString()); + innerTagLI.appendChild(innerTagA); + + $.get(OC.generateUrl("/apps/files/api/v1/quickaccess/get/NodeType"),{folderpath: apppath}, function (data, status) { + if (data === "dir") { + if (listULElements.childElementCount <= 0) { + listULElements.appendChild(innerTagLI); + var collapsibleButton = document.getElementById(collapsibleButtonId); + collapsibleButton.style.display = ''; + + $("#button-collapse-parent-favorites").addClass('collapsible'); + } else { + listLIElements[listLIElements.length - 1].after(innerTagLI); + } + } + } + ); + } + OCA.Files = OCA.Files || {}; /** @@ -83,12 +167,12 @@ 'shares.link' ], - _extendFileActions: function(fileActions) { + _extendFileActions: function (fileActions) { var self = this; fileActions.registerAction({ name: 'Favorite', - displayName: function(context) { + displayName: function (context) { var $file = context.$file; var isFavorite = $file.data('favorite') === true; @@ -105,7 +189,7 @@ mime: 'all', order: -100, permissions: OC.PERMISSION_NONE, - iconClass: function(fileName, context) { + iconClass: function (fileName, context) { var $file = context.$file; var isFavorite = $file.data('favorite') === true; @@ -115,12 +199,13 @@ return 'icon-starred'; }, - actionHandler: function(fileName, context) { + actionHandler: function (fileName, context) { var $favoriteMarkEl = context.$file.find('.favorite-mark'); var $file = context.$file; var fileInfo = context.fileList.files[$file.index()]; var dir = context.dir || context.fileList.getCurrentDirectory(); var tags = $file.attr('data-tags'); + if (_.isUndefined(tags)) { tags = ''; } @@ -130,8 +215,10 @@ if (isFavorite) { // remove tag from list tags = _.without(tags, OC.TAG_FAVORITE); + removeFavoriteFromList(dir + '/' + fileName); } else { tags.push(OC.TAG_FAVORITE); + addFavoriteToList(dir + '/' + fileName); } // pre-toggle the star @@ -144,7 +231,7 @@ tags, $favoriteMarkEl, isFavorite - ).then(function(result) { + ).then(function (result) { context.fileInfoModel.trigger('busy', context.fileInfoModel, false); // response from server should contain updated tags var newTags = result.tags; @@ -160,10 +247,10 @@ }); }, - _extendFileList: function(fileList) { + _extendFileList: function (fileList) { // extend row prototype var oldCreateRow = fileList._createRow; - fileList._createRow = function(fileData) { + fileList._createRow = function (fileData) { var $tr = oldCreateRow.apply(this, arguments); var isFavorite = false; if (fileData.tags) { @@ -178,7 +265,7 @@ return $tr; }; var oldElementToFile = fileList.elementToFile; - fileList.elementToFile = function($el) { + fileList.elementToFile = function ($el) { var fileInfo = oldElementToFile.apply(this, arguments); var tags = $el.attr('data-tags'); if (_.isUndefined(tags)) { @@ -191,22 +278,22 @@ }; var oldGetWebdavProperties = fileList._getWebdavProperties; - fileList._getWebdavProperties = function() { + fileList._getWebdavProperties = function () { var props = oldGetWebdavProperties.apply(this, arguments); props.push(OC.Files.Client.PROPERTY_TAGS); props.push(OC.Files.Client.PROPERTY_FAVORITE); return props; }; - fileList.filesClient.addFileInfoParser(function(response) { + fileList.filesClient.addFileInfoParser(function (response) { var data = {}; var props = response.propStat[0].properties; var tags = props[OC.Files.Client.PROPERTY_TAGS]; var favorite = props[OC.Files.Client.PROPERTY_FAVORITE]; if (tags && tags.length) { - tags = _.chain(tags).filter(function(xmlvalue) { + tags = _.chain(tags).filter(function (xmlvalue) { return (xmlvalue.namespaceURI === OC.Files.Client.NS_OWNCLOUD && xmlvalue.nodeName.split(':')[1] === 'tag'); - }).map(function(xmlvalue) { + }).map(function (xmlvalue) { return xmlvalue.textContent || xmlvalue.text; }).value(); } @@ -221,7 +308,7 @@ }); }, - attach: function(fileList) { + attach: function (fileList) { if (this.allowedLists.indexOf(fileList.id) < 0) { return; } @@ -237,7 +324,7 @@ * @param {Object} $favoriteMarkEl favorite mark element * @param {boolean} isFavorite Was the item favorited before */ - applyFileTags: function(fileName, tagNames, $favoriteMarkEl, isFavorite) { + applyFileTags: function (fileName, tagNames, $favoriteMarkEl, isFavorite) { var encodedPath = OC.encodePath(fileName); while (encodedPath[0] === '/') { encodedPath = encodedPath.substr(1); @@ -250,10 +337,10 @@ }), dataType: 'json', type: 'POST' - }).fail(function(response) { + }).fail(function (response) { var message = ''; // show message if it is available - if(response.responseJSON && response.responseJSON.message) { + if (response.responseJSON && response.responseJSON.message) { message = ': ' + response.responseJSON.message; } OC.Notification.show(t('files', 'An error occurred while trying to update the tags' + message), {type: 'error'}); @@ -261,6 +348,7 @@ }); } }; -})(OCA); +}) +(OCA); OC.Plugins.register('OCA.Files.FileList', OCA.Files.TagsPlugin); diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php index 064f2baa98..d4dac5befa 100644 --- a/apps/files/lib/AppInfo/Application.php +++ b/apps/files/lib/AppInfo/Application.php @@ -26,6 +26,7 @@ */ namespace OCA\Files\AppInfo; +use OCA\Files\Activity\Helper; use OCA\Files\Controller\ApiController; use OCP\AppFramework\App; use \OCA\Files\Service\TagService; @@ -65,7 +66,8 @@ class Application extends App { $server->getEventDispatcher(), $server->getUserSession(), $server->getAppManager(), - $server->getRootFolder() + $server->getRootFolder(), + $c->query(Helper::class) ); }); diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php index a66b1b4d56..aae1bec2e7 100644 --- a/apps/files/lib/Controller/ApiController.php +++ b/apps/files/lib/Controller/ApiController.php @@ -11,7 +11,7 @@ * @author Roeland Jago Douma * @author Tobias Kaminsky * @author Vincent Petry - * + * @author Felix Nüsse * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify @@ -45,6 +45,7 @@ use OCP\IPreview; use OCP\Share\IManager; use OC\Files\Node\Node; use OCP\IUserSession; +use Sabre\VObject\Property\Boolean; /** * Class ApiController @@ -54,7 +55,7 @@ use OCP\IUserSession; class ApiController extends Controller { /** @var TagService */ private $tagService; - /** @var IManager **/ + /** @var IManager * */ private $shareManager; /** @var IPreview */ private $previewManager; @@ -107,7 +108,7 @@ class ApiController extends Controller { * @return DataResponse|FileDisplayResponse */ public function getThumbnail($x, $y, $file) { - if($x < 1 || $y < 1) { + if ($x < 1 || $y < 1) { return new DataResponse(['message' => 'Requested size must be numeric and a positive value.'], Http::STATUS_BAD_REQUEST); } @@ -198,6 +199,30 @@ class ApiController extends Controller { return new DataResponse(['files' => $files]); } + /** + * Returns a list of favorites modifed folder. + * + * @NoAdminRequired + * + * @return DataResponse + */ + public function getFavoritesFolder() { + $nodes = $this->userFolder->searchByTag('_$!!$_', $this->userSession->getUser()->getUID()); + + $favorites = []; + $i = 0; + foreach ($nodes as &$node) { + + $favorites[$i]['id'] = $node->getId(); + $favorites[$i]['name'] = $node->getName(); + $favorites[$i]['path'] = $node->getInternalPath(); + $favorites[$i]['mtime'] = $node->getMTime(); + $i++; + } + + return new DataResponse(['favoriteFolders' => $favorites]); + } + /** * Return a list of share types for outgoing shares * @@ -261,8 +286,150 @@ class ApiController extends Controller { * @param bool $show */ public function showHiddenFiles($show) { - $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', (int) $show); + $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', (int)$show); return new Response(); } + /** + * Toggle default for showing/hiding QuickAccess folder + * + * @NoAdminRequired + * + * @param bool $show + * + * @return Response + */ + public function showQuickAccess($show) { + $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_Quick_Access', (int)$show); + return new Response(); + } + + /** + * Toggle default for showing/hiding QuickAccess folder + * + * @NoAdminRequired + * + * @return String + */ + public function getShowQuickAccess() { + + return $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_Quick_Access', 0); + } + + /** + * quickaccess-sorting-strategy + * + * @NoAdminRequired + * + * @param string $strategy + * @return Response + */ + public function setSortingStrategy($strategy) { + $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'quickaccess_sorting_strategy', (String)$strategy); + return new Response(); + } + + /** + * Get reverse-state for quickaccess-list + * + * @NoAdminRequired + * + * @return String + */ + public function getSortingStrategy() { + return $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'quickaccess_sorting_strategy', 'alphabet'); + } + + /** + * Toggle for reverse quickaccess-list + * + * @NoAdminRequired + * + * @param bool $reverse + * @return Response + */ + public function setReverseQuickaccess($reverse) { + $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'quickaccess_reverse_list', (int)$reverse); + return new Response(); + } + + /** + * Get reverse-state for quickaccess-list + * + * @NoAdminRequired + * + * @return bool + */ + public function getReverseQuickaccess() { + if ($this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'quickaccess_reverse_list', false)) { + return true; + } + return false; + } + + /** + * Set state for show sorting menu + * + * @NoAdminRequired + * + * @param bool $show + * @return Response + */ + public function setShowQuickaccessSettings($show) { + $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'quickaccess_show_settings', (int)$show); + return new Response(); + } + + /** + * Get state for show sorting menu + * + * @NoAdminRequired + * + * @return bool + */ + public function getShowQuickaccessSettings() { + if ($this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'quickaccess_show_settings', false)) { + return true; + } + return false; + } + + /** + * Set sorting-order for custom sorting + * + * @NoAdminRequired + * + * @param String $order + * @return Response + */ + public function setSortingOrder($order) { + $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'quickaccess_custom_sorting_order', (String)$order); + return new Response(); + } + + /** + * Get sorting-order for custom sorting + * + * @NoAdminRequired + * + * @return String + */ + public function getSortingOrder() { + return $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'quickaccess_custom_sorting_order', ""); + } + + /** + * Get sorting-order for custom sorting + * + * @NoAdminRequired + * + * @param String + * @return String + */ + public function getNodeType($folderpath) { + $node = $this->userFolder->get($folderpath); + return $node->getType(); + } + + } diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index 7cb0f112f7..f240e04c72 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -8,6 +8,7 @@ * @author Roeland Jago Douma * @author Thomas Müller * @author Vincent Petry + * @author Felix Nüsse * * @license AGPL-3.0 * @@ -27,6 +28,7 @@ namespace OCA\Files\Controller; +use OCA\Files\Activity\Helper; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\RedirectResponse; @@ -67,19 +69,10 @@ class ViewController extends Controller { protected $appManager; /** @var IRootFolder */ protected $rootFolder; + /** @var Helper */ + protected $activityHelper; - /** - * @param string $appName - * @param IRequest $request - * @param IURLGenerator $urlGenerator - * @param IL10N $l10n - * @param IConfig $config - * @param EventDispatcherInterface $eventDispatcherInterface - * @param IUserSession $userSession - * @param IAppManager $appManager - * @param IRootFolder $rootFolder - */ - public function __construct($appName, + public function __construct(string $appName, IRequest $request, IURLGenerator $urlGenerator, IL10N $l10n, @@ -87,7 +80,8 @@ class ViewController extends Controller { EventDispatcherInterface $eventDispatcherInterface, IUserSession $userSession, IAppManager $appManager, - IRootFolder $rootFolder + IRootFolder $rootFolder, + Helper $activityHelper ) { parent::__construct($appName, $request); $this->appName = $appName; @@ -99,6 +93,7 @@ class ViewController extends Controller { $this->userSession = $userSession; $this->appManager = $appManager; $this->rootFolder = $rootFolder; + $this->activityHelper = $activityHelper; } /** @@ -159,28 +154,68 @@ class ViewController extends Controller { // FIXME: Make non static $storageInfo = $this->getStorageInfo(); + $user = $this->userSession->getUser()->getUID(); + + try { + $favElements = $this->activityHelper->getFavoriteFilePaths($this->userSession->getUser()->getUID()); + } catch (\RuntimeException $e) { + $favElements['folders'] = null; + } + + $collapseClasses = ''; + if (count($favElements['folders']) > 0) { + $collapseClasses = 'collapsible'; + } + + $favoritesSublistArray = Array(); + + $navBarPositionPosition = 6; + $currentCount = 0; + foreach ($favElements['folders'] as $dir) { + + $id = substr($dir, strrpos($dir, '/') + 1, strlen($dir)); + $link = $this->urlGenerator->linkToRoute('files.view.index', ['dir' => $dir, 'view' => 'files']); + $sortingValue = ++$currentCount; + $element = [ + 'id' => str_replace('/', '-', $dir), + 'view' => 'files', + 'href' => $link, + 'dir' => $dir, + 'order' => $navBarPositionPosition, + 'folderPosition' => $sortingValue, + 'name' => $id, + 'icon' => 'files', + 'quickaccesselement' => 'true' + ]; + + array_push($favoritesSublistArray, $element); + $navBarPositionPosition++; + } + + + // show_Quick_Access stored as string + $defaultExpandedState = $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_Quick_Access', '0') === '1'; + \OCA\Files\App::getNavigationManager()->add( [ 'id' => 'favorites', 'appname' => 'files', 'script' => 'simplelist.php', + 'classes' => $collapseClasses, 'order' => 5, - 'name' => $this->l10n->t('Favorites') + 'name' => $this->l10n->t('Favorites'), + 'sublist' => $favoritesSublistArray, + 'defaultExpandedState' => $defaultExpandedState, + 'enableMenuButton' => 0, ] ); $navItems = \OCA\Files\App::getNavigationManager()->getAll(); - usort($navItems, function($item1, $item2) { + usort($navItems, function ($item1, $item2) { return $item1['order'] - $item2['order']; }); - $nav->assign('navigationItems', $navItems); - $webdavurl = $this->urlGenerator->linkTo('', 'remote.php') . - '/dav/files/' . - $this->userSession->getUser()->getUID() . - '/'; - $webdavurl = $this->urlGenerator->getAbsoluteURL($webdavurl); - $nav->assign('webdavurl', $webdavurl); + $nav->assign('navigationItems', $navItems); $nav->assign('usage', \OC_Helper::humanFileSize($storageInfo['used'])); if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) { @@ -215,10 +250,9 @@ class ViewController extends Controller { $params['ownerDisplayName'] = $storageInfo['ownerDisplayName']; $params['isPublic'] = false; $params['allowShareWithLink'] = $this->config->getAppValue('core', 'shareapi_allow_links', 'yes'); - $user = $this->userSession->getUser()->getUID(); $params['defaultFileSorting'] = $this->config->getUserValue($user, 'files', 'file_sorting', 'name'); $params['defaultFileSortingDirection'] = $this->config->getUserValue($user, 'files', 'file_sorting_direction', 'asc'); - $showHidden = (bool) $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', false); + $showHidden = (bool)$this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', false); $params['showHiddenFiles'] = $showHidden ? 1 : 0; $params['fileNotFound'] = $fileNotFound ? 1 : 0; $params['appNavigation'] = $nav; @@ -234,6 +268,7 @@ class ViewController extends Controller { $policy->addAllowedFrameDomain('\'self\''); $response->setContentSecurityPolicy($policy); + return $response; } diff --git a/apps/files/templates/appnavigation.php b/apps/files/templates/appnavigation.php index c811ace8ab..0b9ac66590 100644 --- a/apps/files/templates/appnavigation.php +++ b/apps/files/templates/appnavigation.php @@ -1,18 +1,17 @@
-
- +
- - - t('Use this address to access your Files via WebDAV', array(link_to_docs('user-webdav'))));?> + + + t('Use this address to access your Files via WebDAV', array(link_to_docs('user-webdav')))); ?>
+
+ + + +
  • id="button-collapse-parent-" + data-id="" data-dir="" data-view="" + class="nav- open" + folderposition="" > + + + + + + +
      + +
    + +
  • + + + +
    style="display: none"> +
      +
    • + +
    • +
    +
    +
    +
      + +
    +
    + method('getUser') ->will($this->returnValue($this->user)); $this->rootFolder = $this->getMockBuilder('\OCP\Files\IRootFolder')->getMock(); + $this->activityHelper = $this->createMock(Helper::class); $this->viewController = $this->getMockBuilder('\OCA\Files\Controller\ViewController') ->setConstructorArgs([ 'files', @@ -99,7 +103,8 @@ class ViewControllerTest extends TestCase { $this->eventDispatcher, $this->userSession, $this->appManager, - $this->rootFolder + $this->rootFolder, + $this->activityHelper, ]) ->setMethods([ 'getStorageInfo', @@ -120,7 +125,7 @@ class ViewControllerTest extends TestCase { 'owner' => 'MyName', 'ownerDisplayName' => 'MyDisplayName', ])); - $this->config->expects($this->exactly(3)) + $this->config ->method('getUserValue') ->will($this->returnValueMap([ [$this->user->getUID(), 'files', 'file_sorting', 'name', 'name'], @@ -138,7 +143,7 @@ class ViewControllerTest extends TestCase { $nav->assign('usage', '123 B'); $nav->assign('quota', 100); $nav->assign('total_space', '100 B'); - $nav->assign('webdavurl', ''); + //$nav->assign('webdavurl', ''); $nav->assign('navigationItems', [ [ 'id' => 'files', @@ -172,6 +177,9 @@ class ViewControllerTest extends TestCase { 'icon' => '', 'type' => 'link', 'classes' => '', + 'sublist' => [], + 'defaultExpandedState' => false, + 'enableMenuButton' => 0, ], [ 'id' => 'sharingin', @@ -299,6 +307,14 @@ class ViewControllerTest extends TestCase { $policy = new Http\ContentSecurityPolicy(); $policy->addAllowedFrameDomain('\'self\''); $expected->setContentSecurityPolicy($policy); + + $this->activityHelper->method('getFavoriteFilePaths') + ->with($this->user->getUID()) + ->willReturn([ + 'item' => [], + 'folders' => [], + ]); + $this->assertEquals($expected, $this->viewController->index('MyDir', 'MyView')); } diff --git a/apps/files/tests/js/appSpec.js b/apps/files/tests/js/appSpec.js index 5728991e19..15297a029d 100644 --- a/apps/files/tests/js/appSpec.js +++ b/apps/files/tests/js/appSpec.js @@ -239,38 +239,38 @@ describe('OCA.Files.App tests', function() { expect(App.navigation.getActiveItem()).toEqual('other'); expect($('#app-content-files').hasClass('hidden')).toEqual(true); expect($('#app-content-other').hasClass('hidden')).toEqual(false); - expect($('li[data-id=files]').hasClass('active')).toEqual(false); - expect($('li[data-id=other]').hasClass('active')).toEqual(true); + expect($('li[data-id=files] > a').hasClass('active')).toEqual(false); + expect($('li[data-id=other] > a').hasClass('active')).toEqual(true); App._onPopState({view: 'files', dir: '/somedir'}); expect(App.navigation.getActiveItem()).toEqual('files'); expect($('#app-content-files').hasClass('hidden')).toEqual(false); expect($('#app-content-other').hasClass('hidden')).toEqual(true); - expect($('li[data-id=files]').hasClass('active')).toEqual(true); - expect($('li[data-id=other]').hasClass('active')).toEqual(false); + expect($('li[data-id=files] > a').hasClass('active')).toEqual(true); + expect($('li[data-id=other] > a').hasClass('active')).toEqual(false); }); it('clicking on navigation switches the panel visibility', function() { - $('li[data-id=other]>a').click(); + $('li[data-id=other] > a').click(); expect(App.navigation.getActiveItem()).toEqual('other'); expect($('#app-content-files').hasClass('hidden')).toEqual(true); expect($('#app-content-other').hasClass('hidden')).toEqual(false); - expect($('li[data-id=files]').hasClass('active')).toEqual(false); - expect($('li[data-id=other]').hasClass('active')).toEqual(true); + expect($('li[data-id=files] > a').hasClass('active')).toEqual(false); + expect($('li[data-id=other] > a').hasClass('active')).toEqual(true); - $('li[data-id=files]>a').click(); + $('li[data-id=files] > a').click(); expect(App.navigation.getActiveItem()).toEqual('files'); expect($('#app-content-files').hasClass('hidden')).toEqual(false); expect($('#app-content-other').hasClass('hidden')).toEqual(true); - expect($('li[data-id=files]').hasClass('active')).toEqual(true); - expect($('li[data-id=other]').hasClass('active')).toEqual(false); + expect($('li[data-id=files] > a').hasClass('active')).toEqual(true); + expect($('li[data-id=other] > a').hasClass('active')).toEqual(false); }); it('clicking on navigation sends "show" and "urlChanged" event', function() { var handler = sinon.stub(); var showHandler = sinon.stub(); $('#app-content-other').on('urlChanged', handler); $('#app-content-other').on('show', showHandler); - $('li[data-id=other]>a').click(); + $('li[data-id=other] > a').click(); expect(handler.calledOnce).toEqual(true); expect(handler.getCall(0).args[0].view).toEqual('other'); expect(handler.getCall(0).args[0].dir).toEqual('/'); @@ -281,7 +281,7 @@ describe('OCA.Files.App tests', function() { var showHandler = sinon.stub(); $('#app-content-files').on('urlChanged', handler); $('#app-content-files').on('show', showHandler); - $('li[data-id=files]>a').click(); + $('li[data-id=files] > a').click(); expect(handler.calledOnce).toEqual(true); expect(handler.getCall(0).args[0].view).toEqual('files'); expect(handler.getCall(0).args[0].dir).toEqual('/'); diff --git a/core/css/apps.scss b/core/css/apps.scss index 13b4c7eb9f..6645b6868d 100644 --- a/core/css/apps.scss +++ b/core/css/apps.scss @@ -144,11 +144,12 @@ kbd { padding-left: 38px !important; } - &:hover, - &:focus, - &:active, &.active, - a.selected { + a:hover, + a:focus, + a:active, + a.selected , + a.active { &, > a { opacity: 1; @@ -156,13 +157,6 @@ kbd { } } - /* a instead of li is focused by keyboards */ - a:focus, - a:active { - opacity: 1; - box-shadow: inset 4px 0 var(--color-primary); - } - /* align loader */ &.icon-loading-small:after { left: 22px; @@ -691,6 +685,7 @@ kbd { background-color: var(--color-main-background); } + .settings-button { display: block; height: 44px;