diff options
Diffstat (limited to 'apps')
62 files changed, 716 insertions, 299 deletions
diff --git a/apps/dav/l10n/es_CL.js b/apps/dav/l10n/es_CL.js index 433949bf30f..452cd6e77b5 100644 --- a/apps/dav/l10n/es_CL.js +++ b/apps/dav/l10n/es_CL.js @@ -41,6 +41,16 @@ OC.L10N.register( "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> de un calendario fue modificado", "A calendar <strong>todo</strong> was modified" : "Un <strong>pendiente</strong> de un calendario fue modificado", "Contact birthdays" : "Cumpleaños del contacto", + "Invitation canceled" : "Invitación cancelada", + "Hello %s," : "Hola %s,", + "The meeting »%s« with %s was canceled." : "La cita »%s« con %s fue cancelada.", + "Invitation updated" : "Invitación actualizada", + "The meeting »%s« with %s was updated." : "La reunión »%s« con %s ha sido actualizada.", + "%s invited you to »%s«" : "%s te ha invitado a »%s«", + "When:" : "Cuándo:", + "Where:" : "Dónde:", + "Description:" : "Descripción:", + "Link:" : "Enlace:", "Contacts" : "Contactos", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", diff --git a/apps/dav/l10n/es_CL.json b/apps/dav/l10n/es_CL.json index ea72915ea47..918fa4513bc 100644 --- a/apps/dav/l10n/es_CL.json +++ b/apps/dav/l10n/es_CL.json @@ -39,6 +39,16 @@ "A calendar <strong>event</strong> was modified" : "Un <strong>evento</strong> de un calendario fue modificado", "A calendar <strong>todo</strong> was modified" : "Un <strong>pendiente</strong> de un calendario fue modificado", "Contact birthdays" : "Cumpleaños del contacto", + "Invitation canceled" : "Invitación cancelada", + "Hello %s," : "Hola %s,", + "The meeting »%s« with %s was canceled." : "La cita »%s« con %s fue cancelada.", + "Invitation updated" : "Invitación actualizada", + "The meeting »%s« with %s was updated." : "La reunión »%s« con %s ha sido actualizada.", + "%s invited you to »%s«" : "%s te ha invitado a »%s«", + "When:" : "Cuándo:", + "Where:" : "Dónde:", + "Description:" : "Descripción:", + "Link:" : "Enlace:", "Contacts" : "Contactos", "Technical details" : "Detalles técnicos", "Remote Address: %s" : "Dirección remota: %s", diff --git a/apps/dav/l10n/eu.js b/apps/dav/l10n/eu.js new file mode 100644 index 00000000000..d09ac2a7f3b --- /dev/null +++ b/apps/dav/l10n/eu.js @@ -0,0 +1,61 @@ +OC.L10N.register( + "dav", + { + "Calendar" : "Egutegia", + "Todos" : "Egitekoak", + "Personal" : "Pertsonala", + "{actor} created calendar {calendar}" : "{actor}-k sortutako egutegia: {calendar}", + "You created calendar {calendar}" : "{calendar} egutegia sortu duzu", + "{actor} deleted calendar {calendar}" : "{actor}-k {calendar} egutegia borratu du", + "You deleted calendar {calendar}" : "{calendar} egutegia borratu duzu", + "{actor} updated calendar {calendar}" : "{actor} -k {calendar} egutegia eguneratu du", + "You updated calendar {calendar}" : "{calendar} egutegia eguneratu duzu ", + "{actor} shared calendar {calendar} with you" : "{actor} -k zurekin {calendar} egutegia partekatu du", + "You shared calendar {calendar} with {user}" : "{calendar} egutegia {user} erabiltzailearekin partekatu duzu", + "{actor} shared calendar {calendar} with {user}" : "{actor} -k {calendar} egutegia {user}-rekin partekatu du", + "{actor} unshared calendar {calendar} from you" : "{actor} zurekin partekatzen zuen {calendar} egutegia partekatzeari utzi dio", + "You unshared calendar {calendar} from {user}" : "Partekatzen zenuen {calendar} egutegia {user} -rekin partekatzeari utzi diozu ", + "{actor} unshared calendar {calendar} from {user}" : "{actor} {user} erabiltzailearekin partekatzen zuen {calendar} egutegia partekatzeari utzi dio", + "{actor} unshared calendar {calendar} from themselves" : "{actor} {calendar} egutegia partekatzeari utzi dio bere buruarekin", + "You shared calendar {calendar} with group {group}" : "{calendar} egutegia {group} taldearekin partekatu duz", + "{actor} shared calendar {calendar} with group {group}" : "{actor} {group} taldearekin {calendar} egutegia partekatu du", + "You unshared calendar {calendar} from group {group}" : "{group} taldearekin {calendar} egutegia partekatzeari utzi dio.", + "{actor} unshared calendar {calendar} from group {group}" : "{actor}-rk {group} taldearekin {calendar} egutegia partekatzeari utzi dio.", + "{actor} created event {event} in calendar {calendar}" : "{actor} {event} jarduera {calendar} egutegian sortu du.", + "You created event {event} in calendar {calendar}" : "{calendar} egutegia {event} jarduera sortu duzu", + "{actor} deleted event {event} from calendar {calendar}" : "{actor}-rk {event} jarduera {calendar} egutegitik borratu du", + "You deleted event {event} from calendar {calendar}" : "{event} jarduera {calendar} egutegitik borratu duzu", + "{actor} updated event {event} in calendar {calendar}" : "{actor}-rek {event} jarduera {calendar} egutegian eguneratu da", + "You updated event {event} in calendar {calendar}" : "{event} jarduera {calendar} egutegian eguneratu duzu ", + "{actor} created todo {todo} in list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} ekintza sortu du.", + "You created todo {todo} in list {calendar}" : "{calendar} zerrendan {todo} ekintza sortu duzu.", + "{actor} deleted todo {todo} from list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} zeregina borratu du.", + "You deleted todo {todo} from list {calendar}" : " {calendar} zerrendan {todo} zeregina borratu duzu.", + "{actor} updated todo {todo} in list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} zeregina eguneratu du.", + "You updated todo {todo} in list {calendar}" : "{calendar} zerrendan {todo} zeregina eguneratu duzu.", + "{actor} solved todo {todo} in list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} zeregina bukatu du.", + "You solved todo {todo} in list {calendar}" : " {calendar} zerrendan {todo} zeregina bukatu duzu.", + "{actor} reopened todo {todo} in list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} zeregina birreki du.", + "You reopened todo {todo} in list {calendar}" : "{calendar} zerrendan {todo} zeregina birreki duzu. ", + "A <strong>calendar</strong> was modified" : "Egutegia aldatu da", + "A calendar <strong>event</strong> was modified" : "Egutegiaren zeregin bat aldatu da", + "A calendar <strong>todo</strong> was modified" : "Egutegiaren zeregin bat aldatu da", + "Contact birthdays" : "Urtebetetze kontaktua", + "Invitation canceled" : "Gonbidapena ezeztatua", + "Hello %s," : "Kaixo 1%s,", + "The meeting »%s« with %s was canceled." : "1%s-rekin duzun » 1%s « bilera ezeztatu da", + "Invitation updated" : "Gonbidapena eguneratu da", + "%s invited you to »%s«" : "1%s-k »1%s«-ra gonbidatu zaitu", + "When:" : "Noiz:", + "Where:" : "Non:", + "Description:" : "Deskribapena:", + "Link:" : "Esteka:", + "Contacts" : "Kontaktuak", + "Technical details" : "Xehetasun teknikoak", + "Remote Address: %s" : "Urruneko helbidea: 1%s", + "Request ID: %s" : "Eskatutako ID: 1%s", + "CalDAV server" : "CalDAV zerbitzaria", + "Send invitations to attendees" : "Gonbidatutakoei gonbidapenak bidali", + "Please make sure to properly set up the email settings above." : "Mesedez, eposta ezarpenak ondo zehaztuta daudela ziurta ezazu" +}, +"nplurals=2; plural=(n != 1);"); diff --git a/apps/dav/l10n/eu.json b/apps/dav/l10n/eu.json new file mode 100644 index 00000000000..8ab7ee3b892 --- /dev/null +++ b/apps/dav/l10n/eu.json @@ -0,0 +1,59 @@ +{ "translations": { + "Calendar" : "Egutegia", + "Todos" : "Egitekoak", + "Personal" : "Pertsonala", + "{actor} created calendar {calendar}" : "{actor}-k sortutako egutegia: {calendar}", + "You created calendar {calendar}" : "{calendar} egutegia sortu duzu", + "{actor} deleted calendar {calendar}" : "{actor}-k {calendar} egutegia borratu du", + "You deleted calendar {calendar}" : "{calendar} egutegia borratu duzu", + "{actor} updated calendar {calendar}" : "{actor} -k {calendar} egutegia eguneratu du", + "You updated calendar {calendar}" : "{calendar} egutegia eguneratu duzu ", + "{actor} shared calendar {calendar} with you" : "{actor} -k zurekin {calendar} egutegia partekatu du", + "You shared calendar {calendar} with {user}" : "{calendar} egutegia {user} erabiltzailearekin partekatu duzu", + "{actor} shared calendar {calendar} with {user}" : "{actor} -k {calendar} egutegia {user}-rekin partekatu du", + "{actor} unshared calendar {calendar} from you" : "{actor} zurekin partekatzen zuen {calendar} egutegia partekatzeari utzi dio", + "You unshared calendar {calendar} from {user}" : "Partekatzen zenuen {calendar} egutegia {user} -rekin partekatzeari utzi diozu ", + "{actor} unshared calendar {calendar} from {user}" : "{actor} {user} erabiltzailearekin partekatzen zuen {calendar} egutegia partekatzeari utzi dio", + "{actor} unshared calendar {calendar} from themselves" : "{actor} {calendar} egutegia partekatzeari utzi dio bere buruarekin", + "You shared calendar {calendar} with group {group}" : "{calendar} egutegia {group} taldearekin partekatu duz", + "{actor} shared calendar {calendar} with group {group}" : "{actor} {group} taldearekin {calendar} egutegia partekatu du", + "You unshared calendar {calendar} from group {group}" : "{group} taldearekin {calendar} egutegia partekatzeari utzi dio.", + "{actor} unshared calendar {calendar} from group {group}" : "{actor}-rk {group} taldearekin {calendar} egutegia partekatzeari utzi dio.", + "{actor} created event {event} in calendar {calendar}" : "{actor} {event} jarduera {calendar} egutegian sortu du.", + "You created event {event} in calendar {calendar}" : "{calendar} egutegia {event} jarduera sortu duzu", + "{actor} deleted event {event} from calendar {calendar}" : "{actor}-rk {event} jarduera {calendar} egutegitik borratu du", + "You deleted event {event} from calendar {calendar}" : "{event} jarduera {calendar} egutegitik borratu duzu", + "{actor} updated event {event} in calendar {calendar}" : "{actor}-rek {event} jarduera {calendar} egutegian eguneratu da", + "You updated event {event} in calendar {calendar}" : "{event} jarduera {calendar} egutegian eguneratu duzu ", + "{actor} created todo {todo} in list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} ekintza sortu du.", + "You created todo {todo} in list {calendar}" : "{calendar} zerrendan {todo} ekintza sortu duzu.", + "{actor} deleted todo {todo} from list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} zeregina borratu du.", + "You deleted todo {todo} from list {calendar}" : " {calendar} zerrendan {todo} zeregina borratu duzu.", + "{actor} updated todo {todo} in list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} zeregina eguneratu du.", + "You updated todo {todo} in list {calendar}" : "{calendar} zerrendan {todo} zeregina eguneratu duzu.", + "{actor} solved todo {todo} in list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} zeregina bukatu du.", + "You solved todo {todo} in list {calendar}" : " {calendar} zerrendan {todo} zeregina bukatu duzu.", + "{actor} reopened todo {todo} in list {calendar}" : "{actor}-rek {calendar} zerrendan {todo} zeregina birreki du.", + "You reopened todo {todo} in list {calendar}" : "{calendar} zerrendan {todo} zeregina birreki duzu. ", + "A <strong>calendar</strong> was modified" : "Egutegia aldatu da", + "A calendar <strong>event</strong> was modified" : "Egutegiaren zeregin bat aldatu da", + "A calendar <strong>todo</strong> was modified" : "Egutegiaren zeregin bat aldatu da", + "Contact birthdays" : "Urtebetetze kontaktua", + "Invitation canceled" : "Gonbidapena ezeztatua", + "Hello %s," : "Kaixo 1%s,", + "The meeting »%s« with %s was canceled." : "1%s-rekin duzun » 1%s « bilera ezeztatu da", + "Invitation updated" : "Gonbidapena eguneratu da", + "%s invited you to »%s«" : "1%s-k »1%s«-ra gonbidatu zaitu", + "When:" : "Noiz:", + "Where:" : "Non:", + "Description:" : "Deskribapena:", + "Link:" : "Esteka:", + "Contacts" : "Kontaktuak", + "Technical details" : "Xehetasun teknikoak", + "Remote Address: %s" : "Urruneko helbidea: 1%s", + "Request ID: %s" : "Eskatutako ID: 1%s", + "CalDAV server" : "CalDAV zerbitzaria", + "Send invitations to attendees" : "Gonbidatutakoei gonbidapenak bidali", + "Please make sure to properly set up the email settings above." : "Mesedez, eposta ezarpenak ondo zehaztuta daudela ziurta ezazu" +},"pluralForm" :"nplurals=2; plural=(n != 1);" +}
\ No newline at end of file diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss index d1405517f13..1be3c9216f0 100644 --- a/apps/files/css/files.scss +++ b/apps/files/css/files.scss @@ -47,14 +47,6 @@ top: 44px; } -/* make sure there's enough room for the file actions */ -#body-user #filestable { - min-width: 688px; /* 768 (mobile break) - 80 (nav width) */ -} -#body-user #controls { - min-width: 688px; /* 768 (mobile break) - 80 (nav width) */ -} - #filestable tbody tr { height: 51px; } @@ -74,12 +66,16 @@ background-color: rgb(179, 230, 255)!important; } -.app-files #app-content.dir-drop, .file-drag #filestable tbody tr, .file-drag #filestable tbody tr:hover{ - background-color: rgba(0, 0, 0, 0)!important; +.app-files #app-content.dir-drop { + background-color: $color-main-background !important; +} + +.file-drag #filestable tbody tr, .file-drag #filestable tbody tr:hover{ + background-color: transparent !important; } .app-files #app-content.dir-drop #filestable tbody tr.dropping-to-dir{ - background-color: rgb(179, 230, 255)!important; + background-color: rgb(179, 230, 255) !important; } /* icons for sidebar */ @@ -740,9 +736,9 @@ table.dragshadow td.size { margin-bottom: 2px; } -.canDrop, +.breadcrumb .canDrop > a, #filestable tbody tr.canDrop { - background-color: rgba(255, 255, 140, 1); + background-color: rgb(179, 230, 255); } diff --git a/apps/files/img/add-color.png b/apps/files/img/add-color.png Binary files differindex 2211eb6e0bc..f2819c3aca6 100644 --- a/apps/files/img/add-color.png +++ b/apps/files/img/add-color.png diff --git a/apps/files/img/add-color.svg b/apps/files/img/add-color.svg index acf5543c43f..cb4596ac1e5 100644 --- a/apps/files/img/add-color.svg +++ b/apps/files/img/add-color.svg @@ -1,6 +1 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1"> - <g transform="matrix(-.70711 -.70711 .70711 -.70711 -724.85 753.16)" fill="#00d400"> - <path d="m3.7547 1041.6 1.4142-1.4142 3.5355 3.5355 3.5355-3.5355 1.4142 1.4142-3.5355 3.5355 3.5355 3.5356-1.4142 1.4142-3.5355-3.5356-3.5164 3.5547-1.4333-1.4333 3.5355-3.5356z" fill="#00d400"/> - </g> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewbox="0 0 16 16"><path fill="#00d400" d="M9.02 13.98h-2v-5h-5v-2h5v-5h2v5l5-.028V8.98h-5z"/></svg> diff --git a/apps/files/img/delete.svg b/apps/files/img/delete.svg index c20929aaa18..53f0b020eb9 100644 --- a/apps/files/img/delete.svg +++ b/apps/files/img/delete.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M6.5 1L6 2H3c-.554 0-1 .446-1 1v1h12V3c0-.554-.446-1-1-1h-3l-.5-1zM3 5l.875 9c.06.55.573 1 1.125 1h6c.552 0 1.064-.45 1.125-1L13 5z" fill-rule="evenodd"/></svg>
\ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 16 16" width="16" height="16"><path d="M6.5 1L6 2H3c-.554 0-1 .446-1 1v1h12V3c0-.554-.446-1-1-1h-3l-.5-1zM3 5l.875 9c.06.55.573 1 1.125 1h6c.552 0 1.064-.45 1.125-1L13 5z"/></svg> diff --git a/apps/files/img/share.svg b/apps/files/img/share.svg index c0ad9522369..014392d5a57 100644 --- a/apps/files/img/share.svg +++ b/apps/files/img/share.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M12.228 1a2.457 2.457 0 0 0-2.46 2.454c0 .075.01.15.016.224L5.05 6.092a2.445 2.445 0 0 0-1.596-.586A2.453 2.453 0 0 0 1 7.96a2.453 2.453 0 0 0 2.454 2.455 2.45 2.45 0 0 0 1.46-.477l4.865 2.474c-.004.044-.01.09-.01.134a2.457 2.457 0 1 0 .804-1.818l-4.696-2.4c.02-.123.035-.25.035-.378 0-.072-.01-.144-.015-.214l4.74-2.414A2.457 2.457 0 1 0 12.228.99z"/></svg>
\ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16"><circle cx="3.5" cy="8" r="2.5"/><circle cy="12.5" cx="12.5" r="2.5"/><circle cx="12.5" cy="3.5" r="2.5"/><path d="m3.5 8 9 4.5m-9-4.5 9-4.5" stroke="#000" stroke-width="2" fill="none"/></svg> diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js index 2a4c2bc8a52..35aeb8d357d 100644 --- a/apps/files/js/breadcrumb.js +++ b/apps/files/js/breadcrumb.js @@ -33,6 +33,9 @@ */ var BreadCrumb = function(options){ this.$el = $('<div class="breadcrumb"></div>'); + this.$menu = $('<div class="popovermenu menu-center"><ul></ul></div>'); + + this.crumbSelector = '.crumb:not(.hidden):not(.crumbhome):not(.crumbmenu)'; options = options || {}; if (options.onClick) { this.onClick = options.onClick; @@ -47,6 +50,7 @@ } this._detailViews = []; }; + /** * @memberof OCA.Files */ @@ -110,19 +114,32 @@ * Renders the breadcrumb elements */ render: function() { + // Menu is destroyed on every change, we need to init it + OC.unregisterMenu($('.crumbmenu'), $('.crumbmenu > .popovermenu')); + var parts = this._makeCrumbs(this.dir || '/'); var $crumb; + var $menuItem; this.$el.empty(); this.breadcrumbs = []; for (var i = 0; i < parts.length; i++) { var part = parts[i]; var $image; - var $link = $('<a></a>').attr('href', this.getCrumbUrl(part, i)); - $link.text(part.name); + var $link = $('<a></a>'); $crumb = $('<div class="crumb svg"></div>'); + if(part.dir) { + $link.attr('href', this.getCrumbUrl(part, i)); + } + if(part.name) { + $link.text(part.name); + } + $link.addClass(part.linkclass); $crumb.append($link); - $crumb.attr('data-dir', part.dir); + $crumb.data('dir', part.dir); + // Ignore menu button + $crumb.data('crumb-id', i - 1); + $crumb.addClass(part.class); if (part.img) { $image = $('<img class="svg"></img>'); @@ -132,12 +149,27 @@ } this.breadcrumbs.push($crumb); this.$el.append($crumb); - if (this.onClick) { - $crumb.on('click', this.onClick); + // Only add feedback if not menu + if (this.onClick && i !== 0) { + $link.on('click', this.onClick); } } - $crumb.addClass('last'); + // Menu creation + this._createMenu(); + for (var j = 0; j < parts.length; j++) { + var menuPart = parts[j]; + if(menuPart.dir) { + $menuItem = $('<li class="crumblist"><a><span class="icon-folder"></span><span></span></a></li>'); + $menuItem.data('dir', menuPart.dir); + $menuItem.find('a').attr('href', this.getCrumbUrl(part, j)); + $menuItem.find('span:eq(1)').text(menuPart.name); + this.$menu.children('ul').append($menuItem); + if (this.onClick) { + $menuItem.on('click', this.onClick); + } + } + } _.each(this._detailViews, function(view) { view.render({ dirInfo: this.dirInfo @@ -152,16 +184,20 @@ // setup drag and drop if (this.onDrop) { - this.$el.find('.crumb:not(.last)').droppable({ + this.$el.find('.crumb:not(:last-child):not(.crumbmenu), .crumblist:not(:last-child)').droppable({ drop: this.onDrop, over: this.onOver, out: this.onOut, tolerance: 'pointer', - hoverClass: 'canDrop' + hoverClass: 'canDrop', + greedy: true }); } - this._updateTotalWidth(); + // Menu is destroyed on every change, we need to init it + OC.registerMenu($('.crumbmenu'), $('.crumbmenu > .popovermenu')); + + this._resize(); }, /** @@ -179,12 +215,17 @@ if (dir === '') { parts = []; } + // menu part + crumbs.push({ + class: 'crumbmenu hidden', + linkclass: 'icon-more' + }); // root part crumbs.push({ + name: t('core', 'Home'), dir: '/', - name: '', - alt: t('files', 'Home'), - img: OC.imagePath('core', 'places/home.svg') + class: 'crumbhome', + linkclass: 'icon-home' }); for (var i = 0; i < parts.length; i++) { var part = parts[i]; @@ -198,22 +239,9 @@ }, /** - * Calculate the total breadcrumb width when - * all crumbs are expanded - */ - _updateTotalWidth: function () { - this.totalWidth = 0; - for (var i = 0; i < this.breadcrumbs.length; i++ ) { - var $crumb = $(this.breadcrumbs[i]); - $crumb.data('real-width', $crumb.width()); - this.totalWidth += $crumb.width(); - } - this._resize(); - }, - - /** * Show/hide breadcrumbs to fit the given width - * + * Mostly used by tests + * * @param {int} availableWidth available width */ setMaxWidth: function (availableWidth) { @@ -223,74 +251,107 @@ } }, - _resize: function() { - var i, $crumb, $ellipsisCrumb; - - if (!this.availableWidth) { - this.availableWidth = this.$el.width(); + /** + * Calculate real width based on individual crumbs + * More accurate and works with tests + * + * @param {boolean} ignoreHidden ignore hidden crumbs + */ + getTotalWidth: function(ignoreHidden) { + var totalWidth = 0; + for (var i = 0; i < this.breadcrumbs.length; i++ ) { + var $crumb = $(this.breadcrumbs[i]); + if(!$crumb.hasClass('hidden') || ignoreHidden === true) { + totalWidth += $crumb.outerWidth(); + } } + return totalWidth; + }, - if (this.breadcrumbs.length <= 1) { - return; + /** + * Hide the middle crumb + */ + _hideCrumb: function() { + var length = this.$el.find(this.crumbSelector).length; + // Get the middle one floored down + var elmt = Math.floor(length / 2 - 0.5); + this.$el.find(this.crumbSelector+':eq('+elmt+')').addClass('hidden'); + }, + + /** + * Get the crumb to show + */ + _getCrumbElement: function() { + var hidden = this.$el.find('.crumb.hidden').length; + var shown = this.$el.find(this.crumbSelector).length; + // Get the outer one with priority to the highest + var elmt = (1 - shown % 2) * (hidden - 1); + return this.$el.find('.crumb.hidden:eq('+elmt+')'); + }, + + /** + * Show the middle crumb + */ + _showCrumb: function() { + if(this.$el.find('.crumb.hidden').length === 1) { + this.$el.find('.crumb.hidden').removeClass('hidden'); } + this._getCrumbElement().removeClass('hidden'); + }, + + /** + * Create and append the popovermenu + */ + _createMenu: function() { + this.$el.find('.crumbmenu').append(this.$menu); + this.$menu.children('ul').empty(); + }, + + /** + * Update the popovermenu + */ + _updateMenu: function() { + var menuItems = this.$el.find('.crumb.hidden'); + // Hide the crumb menu if no elements + this.$el.find('.crumbmenu').toggleClass('hidden', menuItems.length === 0); - // reset crumbs - this.$el.find('.crumb.ellipsized').remove(); + this.$menu.find('li').addClass('in-breadcrumb'); + for (var i = 0; i < menuItems.length; i++) { + var crumbId = $(menuItems[i]).data('crumb-id'); + this.$menu.find('li:eq('+crumbId+')').removeClass('in-breadcrumb'); + } + }, - // unhide all - this.$el.find('.crumb.hidden').removeClass('hidden'); + _resize: function() { - if (this.totalWidth <= this.availableWidth) { - // no need to compute breadcrumbs, there is enough space + if (this.breadcrumbs.length <= 2) { + // home & menu return; } - // running width, considering the hidden crumbs - var currentTotalWidth = $(this.breadcrumbs[0]).data('real-width'); - var firstHidden = true; - - // insert ellipsis after root part (root part is always visible) - $ellipsisCrumb = $('<div class="crumb ellipsized svg"><span class="ellipsis">...</span></div>'); - $(this.breadcrumbs[0]).after($ellipsisCrumb); - currentTotalWidth += $ellipsisCrumb.width(); - - i = this.breadcrumbs.length - 1; - - // find the first section that would cause the overflow - // then hide everything in front of that - // - // this ensures that the last crumb section stays visible - // for most of the cases and is always the last one to be - // hidden when the screen becomes very narrow - while (i > 0) { - $crumb = $(this.breadcrumbs[i]); - // if the current breadcrumb would cause overflow - if (!firstHidden || currentTotalWidth + $crumb.data('real-width') > this.availableWidth) { - // hide it - $crumb.addClass('hidden'); - if (firstHidden) { - // set the path of this one as title for the ellipsis - this.$el.find('.crumb.ellipsized') - .attr('title', $crumb.attr('data-dir')) - .tooltip(); - this.$el.find('.ellipsis') - .wrap('<a class="ellipsislink" href="' + encodeURI(OC.generateUrl('apps/files/?dir=' + $crumb.attr('data-dir'))) + '"></a>'); - } - // and all the previous ones (going backwards) - firstHidden = false; - } else { - // add to total width - currentTotalWidth += $crumb.data('real-width'); - } - i--; + // Used for testing since this.$el.parent fails + if (!this.availableWidth) { + this.usedWidth = this.$el.parent().width() - (this.$el.parent().find('.button').length + 1) * 44; + } else { + this.usedWidth = this.availableWidth; } - if (!OC.Util.hasSVGSupport()) { - OC.Util.replaceSVG(this.$el); + // If container is smaller than content + // AND if there are crumbs left to hide + while (this.getTotalWidth() > this.usedWidth + && this.$el.find(this.crumbSelector).length > 0) { + this._hideCrumb(); } + // If container is bigger than content + element to be shown + // AND if there is at least one hidden crumb + while (this.$el.find('.crumb.hidden').length > 0 + && this.getTotalWidth() + this._getCrumbElement().width() < this.usedWidth) { + this._showCrumb(); + } + + this._updateMenu(); } }; OCA.Files.BreadCrumb = BreadCrumb; })(); - diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index af5e9c013f0..03e9c138efb 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -322,7 +322,7 @@ this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); - this._onResize = _.debounce(_.bind(this._onResize, this), 100); + this._onResize = _.debounce(_.bind(this._onResize, this), 250); $('#app-content').on('appresized', this._onResize); $(window).resize(this._onResize); @@ -556,7 +556,7 @@ // subtract app navigation toggle when visible containerWidth -= $('#app-navigation-toggle').width(); - this.breadcrumb.setMaxWidth(containerWidth - actionsWidth - 10); + this.breadcrumb._resize(); this.$table.find('>thead').width($('#app-content').width() - OC.Util.getScrollBarWidth()); }, @@ -837,7 +837,8 @@ * Event handler when clicking on a bread crumb */ _onClickBreadCrumb: function(e) { - var $el = $(e.target).closest('.crumb'), + // Select a crumb or a crumb in the menu + var $el = $(e.target).closest('.crumb, .crumblist'), $targetDir = $el.data('dir'); if ($targetDir !== undefined && e.which === 1) { @@ -863,8 +864,8 @@ _onDropOnBreadCrumb: function( event, ui ) { var self = this; var $target = $(event.target); - if (!$target.is('.crumb')) { - $target = $target.closest('.crumb'); + if (!$target.is('.crumb, .crumblist')) { + $target = $target.closest('.crumb, .crumblist'); } var targetPath = $(event.target).data('dir'); var dir = this.getCurrentDirectory(); @@ -1284,7 +1285,7 @@ fileData.extraData = fileData.extraData.substr(1); } nameSpan.addClass('extra-data').attr('title', fileData.extraData); - nameSpan.tooltip({placement: 'right'}); + nameSpan.tooltip({placement: 'top'}); } // dirs can show the number of uploaded files if (mime === 'httpd/unix-directory') { @@ -1713,7 +1714,7 @@ if (status === 500) { // Go home this.changeDirectory('/'); - OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'), + OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'), {type: 'error'} ); return false; @@ -1724,7 +1725,7 @@ if (this.getCurrentDirectory() !== '/') { this.changeDirectory('/'); // TODO: read error message from exception - OC.Notification.show(t('files', 'Storage is temporarily not available'), + OC.Notification.show(t('files', 'Storage is temporarily not available'), {type: 'error'} ); } @@ -2040,11 +2041,11 @@ .fail(function(status) { if (status === 412) { // TODO: some day here we should invoke the conflict dialog - OC.Notification.show(t('files', 'Could not move "{file}", target exists', + OC.Notification.show(t('files', 'Could not move "{file}", target exists', {file: fileName}), {type: 'error'} ); } else { - OC.Notification.show(t('files', 'Could not move "{file}"', + OC.Notification.show(t('files', 'Could not move "{file}"', {file: fileName}), {type: 'error'} ); } @@ -2271,7 +2272,7 @@ // TODO: 409 means current folder does not exist, redirect ? if (status === 404) { // source not found, so remove it from the list - OC.Notification.show(t('files', 'Could not rename "{fileName}", it does not exist any more', + OC.Notification.show(t('files', 'Could not rename "{fileName}", it does not exist any more', {fileName: oldName}), {timeout: 7, type: 'error'} ); @@ -2291,7 +2292,7 @@ ); } else { // restore the item to its previous state - OC.Notification.show(t('files', 'Could not rename "{fileName}"', + OC.Notification.show(t('files', 'Could not rename "{fileName}"', {fileName: oldName}), {type: 'error'} ); } @@ -2376,18 +2377,18 @@ self.addAndFetchFileInfo(targetPath, '', {scrollTo: true}).then(function(status, data) { deferred.resolve(status, data); }, function() { - OC.Notification.show(t('files', 'Could not create file "{file}"', + OC.Notification.show(t('files', 'Could not create file "{file}"', {file: name}), {type: 'error'} ); }); }) .fail(function(status) { if (status === 412) { - OC.Notification.show(t('files', 'Could not create file "{file}" because it already exists', + OC.Notification.show(t('files', 'Could not create file "{file}" because it already exists', {file: name}), {type: 'error'} ); } else { - OC.Notification.show(t('files', 'Could not create file "{file}"', + OC.Notification.show(t('files', 'Could not create file "{file}"', {file: name}), {type: 'error'} ); } @@ -2426,7 +2427,7 @@ self.addAndFetchFileInfo(targetPath, '', {scrollTo:true}).then(function(status, data) { deferred.resolve(status, data); }, function() { - OC.Notification.show(t('files', 'Could not create folder "{dir}"', + OC.Notification.show(t('files', 'Could not create folder "{dir}"', {dir: name}), {type: 'error'} ); }); @@ -2437,20 +2438,20 @@ // add it to the list, for completeness self.addAndFetchFileInfo(targetPath, '', {scrollTo:true}) .done(function(status, data) { - OC.Notification.show(t('files', 'Could not create folder "{dir}" because it already exists', + OC.Notification.show(t('files', 'Could not create folder "{dir}" because it already exists', {dir: name}), {type: 'error'} ); // still consider a failure deferred.reject(createStatus, data); }) .fail(function() { - OC.Notification.show(t('files', 'Could not create folder "{dir}"', + OC.Notification.show(t('files', 'Could not create folder "{dir}"', {dir: name}), {type: 'error'} ); deferred.reject(status); }); } else { - OC.Notification.show(t('files', 'Could not create folder "{dir}"', + OC.Notification.show(t('files', 'Could not create folder "{dir}"', {dir: name}), {type: 'error'} ); deferred.reject(createStatus); @@ -2507,7 +2508,7 @@ deferred.resolve(status, data); }) .fail(function(status) { - OC.Notification.show(t('files', 'Could not create file "{file}"', + OC.Notification.show(t('files', 'Could not create file "{file}"', {file: name}), {type: 'error'} ); deferred.reject(status); @@ -2616,7 +2617,7 @@ removeFromList(file); } else { // only reset the spinner for that one file - OC.Notification.show(t('files', 'Error deleting file "{fileName}".', + OC.Notification.show(t('files', 'Error deleting file "{fileName}".', {fileName: file}), {type: 'error'} ); var deleteAction = self.findFileEl(file).find('.action.delete'); diff --git a/apps/files/js/files.js b/apps/files/js/files.js index e34d7fe2550..017bf7ecf41 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -117,32 +117,32 @@ ownerDisplayName = $('#ownerDisplayName').val(); if (usedSpacePercent > 98) { if (owner !== oc_current_user) { - OC.Notification.show(t('files', 'Storage of {owner} is full, files can not be updated or synced anymore!', + OC.Notification.show(t('files', 'Storage of {owner} is full, files can not be updated or synced anymore!', {owner: ownerDisplayName}), {type: 'error'} ); return; } - OC.Notification.show(t('files', - 'Your storage is full, files can not be updated or synced anymore!'), + OC.Notification.show(t('files', + 'Your storage is full, files can not be updated or synced anymore!'), {type : 'error'} ); return; } if (usedSpacePercent > 90) { if (owner !== oc_current_user) { - OC.Notification.show(t('files', 'Storage of {owner} is almost full ({usedSpacePercent}%)', + OC.Notification.show(t('files', 'Storage of {owner} is almost full ({usedSpacePercent}%)', { - usedSpacePercent: usedSpacePercent, + usedSpacePercent: usedSpacePercent, owner: ownerDisplayName }), - { + { type: 'error' } ); return; } OC.Notification.show(t('files', 'Your storage is almost full ({usedSpacePercent}%)', - {usedSpacePercent: usedSpacePercent}), + {usedSpacePercent: usedSpacePercent}), {type : 'error'} ); } @@ -396,6 +396,8 @@ var dragOptions={ } $selectedFiles.closest('tr').addClass('animate-opacity dragging'); $selectedFiles.closest('tr').filter('.ui-droppable').droppable( 'disable' ); + // Show breadcrumbs menu + $('.crumbmenu').addClass('canDropChildren'); }, stop: function(event, ui) { @@ -411,6 +413,8 @@ var dragOptions={ setTimeout(function() { $tr.removeClass('animate-opacity'); }, 300); + // Hide breadcrumbs menu + $('.crumbmenu').removeClass('canDropChildren'); }, drag: function(event, ui) { var scrollingArea = FileList.$container; diff --git a/apps/files/l10n/da.js b/apps/files/l10n/da.js index 8c82974369f..0c8fea1c3ff 100644 --- a/apps/files/l10n/da.js +++ b/apps/files/l10n/da.js @@ -19,6 +19,8 @@ OC.L10N.register( "Uploading …" : "Uploader ...", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} af {totalSize} ({bitrate})", + "Target folder does not exist any more" : "Destinations mappen findes ikke længere", + "Error when assembling chunks, status code {status}" : "Fejl ved montering af klumper, statuskode {status}", "Actions" : "Handlinger", "Download" : "Hent", "Rename" : "Omdøb", @@ -125,6 +127,7 @@ OC.L10N.register( "Settings" : "Indstillinger", "Show hidden files" : "Vis skjulte filer", "WebDAV" : "WebDAV", + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Brug denne adresse til at <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">få adgang til dine filer via WebDAV</a>", "Cancel upload" : "Annuller upload ", "No files in here" : "Her er ingen filer", "Upload some content or sync with your devices!" : "Overfør indhold eller synkronisér med dine enheder!", diff --git a/apps/files/l10n/da.json b/apps/files/l10n/da.json index 4d83d9fa6c0..ae52158a78d 100644 --- a/apps/files/l10n/da.json +++ b/apps/files/l10n/da.json @@ -17,6 +17,8 @@ "Uploading …" : "Uploader ...", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} af {totalSize} ({bitrate})", + "Target folder does not exist any more" : "Destinations mappen findes ikke længere", + "Error when assembling chunks, status code {status}" : "Fejl ved montering af klumper, statuskode {status}", "Actions" : "Handlinger", "Download" : "Hent", "Rename" : "Omdøb", @@ -123,6 +125,7 @@ "Settings" : "Indstillinger", "Show hidden files" : "Vis skjulte filer", "WebDAV" : "WebDAV", + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Brug denne adresse til at <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">få adgang til dine filer via WebDAV</a>", "Cancel upload" : "Annuller upload ", "No files in here" : "Her er ingen filer", "Upload some content or sync with your devices!" : "Overfør indhold eller synkronisér med dine enheder!", diff --git a/apps/files/l10n/el.js b/apps/files/l10n/el.js index 064bdd838e2..b5b0738d85b 100644 --- a/apps/files/l10n/el.js +++ b/apps/files/l10n/el.js @@ -16,10 +16,13 @@ OC.L10N.register( "Not enough free space, you are uploading {size1} but only {size2} is left" : "Δεν υπάρχει αρκετός ελεύθερος χώρος, μεταφορτώνετε μέγεθος {size1} αλλά υπάρχει χώρος μόνο {size2}", "Target folder \"{dir}\" does not exist any more" : "Φάκελος προορισμού \"{dir}\" δεν υπάρχει πια", "Not enough free space" : "Δεν υπάρχει αρκετός ελεύθερος χώρος.", + "Uploading …" : "Γίνεται μεταφόρτωση ...", + "…" : "…", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} από {totalSize} ({bitrate})", "Actions" : "Ενέργειες", "Download" : "Λήψη", "Rename" : "Μετονομασία", + "Move or copy" : "Μετακίνηση ή αντιγραφή", "Target folder" : "Φάκελος προορισμού", "Delete" : "Διαγραφή", "Disconnect storage" : "Αποσυνδεδεμένος αποθηκευτικός χώρος", @@ -70,6 +73,8 @@ OC.L10N.register( "Favorite" : "Αγαπημένο", "New folder" : "Νέος φάκελος", "Upload file" : "Αποστολή αρχείου", + "Remove from favorites" : "Αφαίρεση από τα αγαπημένα", + "Add to favorites" : "Προσθήκη στα αγαπημένα", "An error occurred while trying to update the tags" : "Ένα σφάλμα προέκυψε κατά τη διάρκεια ενημέρωσης των ετικετών", "Added to favorites" : "Προσθήκη στα αγαπημένα", "Removed from favorites" : "Αφαίρεση από τα αγαπημένα", @@ -115,6 +120,7 @@ OC.L10N.register( "Settings" : "Ρυθμίσεις", "Show hidden files" : "Εμφάνιση κρυφών αρχείων", "WebDAV" : "WebDAV", + "Cancel upload" : "Ακύρωση μεταφόρτωσης", "No files in here" : "Δεν υπάρχουν αρχεία", "Upload some content or sync with your devices!" : "Μεταφόρτωση περιεχομένου ή συγχρονισμός με τις συσκευές σας!", "No entries found in this folder" : "Δεν βρέθηκαν καταχωρήσεις σε αυτόν το φάκελο", diff --git a/apps/files/l10n/el.json b/apps/files/l10n/el.json index 588c0169920..59acab268be 100644 --- a/apps/files/l10n/el.json +++ b/apps/files/l10n/el.json @@ -14,10 +14,13 @@ "Not enough free space, you are uploading {size1} but only {size2} is left" : "Δεν υπάρχει αρκετός ελεύθερος χώρος, μεταφορτώνετε μέγεθος {size1} αλλά υπάρχει χώρος μόνο {size2}", "Target folder \"{dir}\" does not exist any more" : "Φάκελος προορισμού \"{dir}\" δεν υπάρχει πια", "Not enough free space" : "Δεν υπάρχει αρκετός ελεύθερος χώρος.", + "Uploading …" : "Γίνεται μεταφόρτωση ...", + "…" : "…", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} από {totalSize} ({bitrate})", "Actions" : "Ενέργειες", "Download" : "Λήψη", "Rename" : "Μετονομασία", + "Move or copy" : "Μετακίνηση ή αντιγραφή", "Target folder" : "Φάκελος προορισμού", "Delete" : "Διαγραφή", "Disconnect storage" : "Αποσυνδεδεμένος αποθηκευτικός χώρος", @@ -68,6 +71,8 @@ "Favorite" : "Αγαπημένο", "New folder" : "Νέος φάκελος", "Upload file" : "Αποστολή αρχείου", + "Remove from favorites" : "Αφαίρεση από τα αγαπημένα", + "Add to favorites" : "Προσθήκη στα αγαπημένα", "An error occurred while trying to update the tags" : "Ένα σφάλμα προέκυψε κατά τη διάρκεια ενημέρωσης των ετικετών", "Added to favorites" : "Προσθήκη στα αγαπημένα", "Removed from favorites" : "Αφαίρεση από τα αγαπημένα", @@ -113,6 +118,7 @@ "Settings" : "Ρυθμίσεις", "Show hidden files" : "Εμφάνιση κρυφών αρχείων", "WebDAV" : "WebDAV", + "Cancel upload" : "Ακύρωση μεταφόρτωσης", "No files in here" : "Δεν υπάρχουν αρχεία", "Upload some content or sync with your devices!" : "Μεταφόρτωση περιεχομένου ή συγχρονισμός με τις συσκευές σας!", "No entries found in this folder" : "Δεν βρέθηκαν καταχωρήσεις σε αυτόν το φάκελο", diff --git a/apps/files/l10n/es.js b/apps/files/l10n/es.js index 4f01be175f7..a4666bd7bdf 100644 --- a/apps/files/l10n/es.js +++ b/apps/files/l10n/es.js @@ -19,6 +19,8 @@ OC.L10N.register( "Uploading …" : "Subiendo...", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})", + "Target folder does not exist any more" : "La carpeta destino yano existe", + "Error when assembling chunks, status code {status}" : "Error al reunir las partes, código de estado {status}", "Actions" : "Acciones", "Download" : "Descargar", "Rename" : "Renombrar", diff --git a/apps/files/l10n/es.json b/apps/files/l10n/es.json index 2b3701aba18..44db92002f2 100644 --- a/apps/files/l10n/es.json +++ b/apps/files/l10n/es.json @@ -17,6 +17,8 @@ "Uploading …" : "Subiendo...", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})", + "Target folder does not exist any more" : "La carpeta destino yano existe", + "Error when assembling chunks, status code {status}" : "Error al reunir las partes, código de estado {status}", "Actions" : "Acciones", "Download" : "Descargar", "Rename" : "Renombrar", diff --git a/apps/files/l10n/es_CL.js b/apps/files/l10n/es_CL.js index 831187b5e11..793abaf101b 100644 --- a/apps/files/l10n/es_CL.js +++ b/apps/files/l10n/es_CL.js @@ -19,6 +19,8 @@ OC.L10N.register( "Uploading …" : "Cargando...", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})", + "Target folder does not exist any more" : "La carpeta destino ya no existe", + "Error when assembling chunks, status code {status}" : "Se presentó un error al ensamblar los bloques, código de estatus {status}", "Actions" : "Acciones", "Download" : "Descargar", "Rename" : "Renombrar", @@ -125,6 +127,7 @@ OC.L10N.register( "Settings" : "Configuraciones ", "Show hidden files" : "Mostrar archivos ocultos", "WebDAV" : "WebDAV", + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Usa esta dirección para <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">acceder a tus Archivos vía WebDAV</a>", "Cancel upload" : "Cancelar carga", "No files in here" : "No hay archivos aquí", "Upload some content or sync with your devices!" : "¡Carga algún contenido o sincroniza con tus dispositivos!", diff --git a/apps/files/l10n/es_CL.json b/apps/files/l10n/es_CL.json index 374239112ca..4083caedd5e 100644 --- a/apps/files/l10n/es_CL.json +++ b/apps/files/l10n/es_CL.json @@ -17,6 +17,8 @@ "Uploading …" : "Cargando...", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})", + "Target folder does not exist any more" : "La carpeta destino ya no existe", + "Error when assembling chunks, status code {status}" : "Se presentó un error al ensamblar los bloques, código de estatus {status}", "Actions" : "Acciones", "Download" : "Descargar", "Rename" : "Renombrar", @@ -123,6 +125,7 @@ "Settings" : "Configuraciones ", "Show hidden files" : "Mostrar archivos ocultos", "WebDAV" : "WebDAV", + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Usa esta dirección para <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">acceder a tus Archivos vía WebDAV</a>", "Cancel upload" : "Cancelar carga", "No files in here" : "No hay archivos aquí", "Upload some content or sync with your devices!" : "¡Carga algún contenido o sincroniza con tus dispositivos!", diff --git a/apps/files/l10n/es_MX.js b/apps/files/l10n/es_MX.js index c4a03ff1225..793abaf101b 100644 --- a/apps/files/l10n/es_MX.js +++ b/apps/files/l10n/es_MX.js @@ -19,6 +19,8 @@ OC.L10N.register( "Uploading …" : "Cargando...", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})", + "Target folder does not exist any more" : "La carpeta destino ya no existe", + "Error when assembling chunks, status code {status}" : "Se presentó un error al ensamblar los bloques, código de estatus {status}", "Actions" : "Acciones", "Download" : "Descargar", "Rename" : "Renombrar", diff --git a/apps/files/l10n/es_MX.json b/apps/files/l10n/es_MX.json index 2adfe559742..4083caedd5e 100644 --- a/apps/files/l10n/es_MX.json +++ b/apps/files/l10n/es_MX.json @@ -17,6 +17,8 @@ "Uploading …" : "Cargando...", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} de {totalSize} ({bitrate})", + "Target folder does not exist any more" : "La carpeta destino ya no existe", + "Error when assembling chunks, status code {status}" : "Se presentó un error al ensamblar los bloques, código de estatus {status}", "Actions" : "Acciones", "Download" : "Descargar", "Rename" : "Renombrar", diff --git a/apps/files/l10n/fr.js b/apps/files/l10n/fr.js index 68540aac8d8..c17791b5cbd 100644 --- a/apps/files/l10n/fr.js +++ b/apps/files/l10n/fr.js @@ -20,6 +20,7 @@ OC.L10N.register( "…" : "…", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} sur {totalSize} ({bitrate})", "Target folder does not exist any more" : "Le dossier cible n'existe plus", + "Error when assembling chunks, status code {status}" : "Erreur lors de l'assemblage des blocs, code d'état {status}", "Actions" : "Actions", "Download" : "Télécharger", "Rename" : "Renommer", diff --git a/apps/files/l10n/fr.json b/apps/files/l10n/fr.json index 7f675056eb4..77256638126 100644 --- a/apps/files/l10n/fr.json +++ b/apps/files/l10n/fr.json @@ -18,6 +18,7 @@ "…" : "…", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} sur {totalSize} ({bitrate})", "Target folder does not exist any more" : "Le dossier cible n'existe plus", + "Error when assembling chunks, status code {status}" : "Erreur lors de l'assemblage des blocs, code d'état {status}", "Actions" : "Actions", "Download" : "Télécharger", "Rename" : "Renommer", diff --git a/apps/files/l10n/nl.js b/apps/files/l10n/nl.js index c009bb7496f..fd30a95fb8e 100644 --- a/apps/files/l10n/nl.js +++ b/apps/files/l10n/nl.js @@ -19,6 +19,8 @@ OC.L10N.register( "Uploading …" : "Uploaden …", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} van {totalSize} ({bitrate})", + "Target folder does not exist any more" : "Doelmap bestaat niet meer", + "Error when assembling chunks, status code {status}" : "Fout tijdens samenvoegen van brokken, status code {status}", "Actions" : "Acties", "Download" : "Downloaden", "Rename" : "Naam wijzigen", @@ -125,6 +127,7 @@ OC.L10N.register( "Settings" : "Instellingen", "Show hidden files" : "Verborgen bestanden tonen", "WebDAV" : "WebDAV", + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Gebruik deze link <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">om je bestanden te benaderen via WebDAV</a>", "Cancel upload" : "Stop upload", "No files in here" : "Hier geen bestanden", "Upload some content or sync with your devices!" : "Upload je inhoud of synchroniseer met je apparaten!", diff --git a/apps/files/l10n/nl.json b/apps/files/l10n/nl.json index 9a526124372..a74f3f329e4 100644 --- a/apps/files/l10n/nl.json +++ b/apps/files/l10n/nl.json @@ -17,6 +17,8 @@ "Uploading …" : "Uploaden …", "…" : "...", "{loadedSize} of {totalSize} ({bitrate})" : "{loadedSize} van {totalSize} ({bitrate})", + "Target folder does not exist any more" : "Doelmap bestaat niet meer", + "Error when assembling chunks, status code {status}" : "Fout tijdens samenvoegen van brokken, status code {status}", "Actions" : "Acties", "Download" : "Downloaden", "Rename" : "Naam wijzigen", @@ -123,6 +125,7 @@ "Settings" : "Instellingen", "Show hidden files" : "Verborgen bestanden tonen", "WebDAV" : "WebDAV", + "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Gebruik deze link <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">om je bestanden te benaderen via WebDAV</a>", "Cancel upload" : "Stop upload", "No files in here" : "Hier geen bestanden", "Upload some content or sync with your devices!" : "Upload je inhoud of synchroniseer met je apparaten!", diff --git a/apps/files/tests/js/breadcrumbSpec.js b/apps/files/tests/js/breadcrumbSpec.js index a26f0176f15..dd3eac017ec 100644 --- a/apps/files/tests/js/breadcrumbSpec.js +++ b/apps/files/tests/js/breadcrumbSpec.js @@ -43,80 +43,93 @@ describe('OCA.Files.BreadCrumb tests', function() { var $crumbs; bc.render(); $crumbs = bc.$el.find('.crumb'); - expect($crumbs.length).toEqual(1); - expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0'); - expect($crumbs.eq(0).find('img').length).toEqual(1); - expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); + // menu and home + expect($crumbs.length).toEqual(2); + expect($crumbs.eq(0).find('a').hasClass('icon-more')).toEqual(true); + expect($crumbs.eq(0).find('div.popovermenu').length).toEqual(1); + expect($crumbs.eq(0).data('dir')).not.toBeDefined(); + expect($crumbs.eq(1).find('a').attr('href')).toEqual('/#1'); + expect($crumbs.eq(1).find('a').hasClass('icon-home')).toEqual(true); + expect($crumbs.eq(1).data('dir')).toEqual('/'); }); it('Renders root when switching to root', function() { var $crumbs; bc.setDirectory('/somedir'); bc.setDirectory('/'); $crumbs = bc.$el.find('.crumb'); - expect($crumbs.length).toEqual(1); - expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); - }); - it('Renders last crumb with "last" class', function() { - bc.setDirectory('/abc/def'); - expect(bc.$el.find('.crumb:last').hasClass('last')).toEqual(true); + expect($crumbs.length).toEqual(2); + expect($crumbs.eq(1).data('dir')).toEqual('/'); }); it('Renders single path section', function() { var $crumbs; bc.setDirectory('/somedir'); $crumbs = bc.$el.find('.crumb'); - expect($crumbs.length).toEqual(2); - expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0'); - expect($crumbs.eq(0).find('img').length).toEqual(1); - expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); - expect($crumbs.eq(1).find('a').attr('href')).toEqual('/somedir#1'); - expect($crumbs.eq(1).find('img').length).toEqual(0); - expect($crumbs.eq(1).attr('data-dir')).toEqual('/somedir'); + expect($crumbs.length).toEqual(3); + expect($crumbs.eq(0).find('a').hasClass('icon-more')).toEqual(true); + expect($crumbs.eq(0).find('div.popovermenu').length).toEqual(1); + expect($crumbs.eq(0).data('dir')).not.toBeDefined(); + + expect($crumbs.eq(1).find('a').attr('href')).toEqual('/#1'); + expect($crumbs.eq(1).find('a').hasClass('icon-home')).toEqual(true); + expect($crumbs.eq(1).data('dir')).toEqual('/'); + + expect($crumbs.eq(2).find('a').attr('href')).toEqual('/somedir#2'); + expect($crumbs.eq(2).find('img').length).toEqual(0); + expect($crumbs.eq(2).data('dir')).toEqual('/somedir'); }); it('Renders multiple path sections and special chars', function() { var $crumbs; bc.setDirectory('/somedir/with space/abc'); $crumbs = bc.$el.find('.crumb'); - expect($crumbs.length).toEqual(4); - expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0'); - expect($crumbs.eq(0).find('img').length).toEqual(1); - expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); + expect($crumbs.length).toEqual(5); + expect($crumbs.eq(0).find('a').hasClass('icon-more')).toEqual(true); + expect($crumbs.eq(0).find('div.popovermenu').length).toEqual(1); + expect($crumbs.eq(0).data('dir')).not.toBeDefined(); - expect($crumbs.eq(1).find('a').attr('href')).toEqual('/somedir#1'); - expect($crumbs.eq(1).find('img').length).toEqual(0); - expect($crumbs.eq(1).attr('data-dir')).toEqual('/somedir'); + expect($crumbs.eq(1).find('a').attr('href')).toEqual('/#1'); + expect($crumbs.eq(1).find('a').hasClass('icon-home')).toEqual(true); + expect($crumbs.eq(1).data('dir')).toEqual('/'); - expect($crumbs.eq(2).find('a').attr('href')).toEqual('/somedir/with space#2'); + expect($crumbs.eq(2).find('a').attr('href')).toEqual('/somedir#2'); expect($crumbs.eq(2).find('img').length).toEqual(0); - expect($crumbs.eq(2).attr('data-dir')).toEqual('/somedir/with space'); + expect($crumbs.eq(2).data('dir')).toEqual('/somedir'); - expect($crumbs.eq(3).find('a').attr('href')).toEqual('/somedir/with space/abc#3'); + expect($crumbs.eq(3).find('a').attr('href')).toEqual('/somedir/with space#3'); expect($crumbs.eq(3).find('img').length).toEqual(0); - expect($crumbs.eq(3).attr('data-dir')).toEqual('/somedir/with space/abc'); + expect($crumbs.eq(3).data('dir')).toEqual('/somedir/with space'); + + expect($crumbs.eq(4).find('a').attr('href')).toEqual('/somedir/with space/abc#4'); + expect($crumbs.eq(4).find('img').length).toEqual(0); + expect($crumbs.eq(4).data('dir')).toEqual('/somedir/with space/abc'); }); it('Renders backslashes as regular directory separator', function() { var $crumbs; bc.setDirectory('/somedir\\with/mixed\\separators'); $crumbs = bc.$el.find('.crumb'); - expect($crumbs.length).toEqual(5); - expect($crumbs.eq(0).find('a').attr('href')).toEqual('/#0'); - expect($crumbs.eq(0).find('img').length).toEqual(1); - expect($crumbs.eq(0).attr('data-dir')).toEqual('/'); + expect($crumbs.length).toEqual(6); + expect($crumbs.eq(0).find('a').hasClass('icon-more')).toEqual(true); + expect($crumbs.eq(0).find('div.popovermenu').length).toEqual(1); + expect($crumbs.eq(0).data('dir')).not.toBeDefined(); - expect($crumbs.eq(1).find('a').attr('href')).toEqual('/somedir#1'); - expect($crumbs.eq(1).find('img').length).toEqual(0); - expect($crumbs.eq(1).attr('data-dir')).toEqual('/somedir'); + expect($crumbs.eq(1).find('a').attr('href')).toEqual('/#1'); + expect($crumbs.eq(1).find('a').hasClass('icon-home')).toEqual(true); + expect($crumbs.eq(1).data('dir')).toEqual('/'); - expect($crumbs.eq(2).find('a').attr('href')).toEqual('/somedir/with#2'); + expect($crumbs.eq(2).find('a').attr('href')).toEqual('/somedir#2'); expect($crumbs.eq(2).find('img').length).toEqual(0); - expect($crumbs.eq(2).attr('data-dir')).toEqual('/somedir/with'); + expect($crumbs.eq(2).data('dir')).toEqual('/somedir'); - expect($crumbs.eq(3).find('a').attr('href')).toEqual('/somedir/with/mixed#3'); + expect($crumbs.eq(3).find('a').attr('href')).toEqual('/somedir/with#3'); expect($crumbs.eq(3).find('img').length).toEqual(0); - expect($crumbs.eq(3).attr('data-dir')).toEqual('/somedir/with/mixed'); + expect($crumbs.eq(3).data('dir')).toEqual('/somedir/with'); - expect($crumbs.eq(4).find('a').attr('href')).toEqual('/somedir/with/mixed/separators#4'); + expect($crumbs.eq(4).find('a').attr('href')).toEqual('/somedir/with/mixed#4'); expect($crumbs.eq(4).find('img').length).toEqual(0); - expect($crumbs.eq(4).attr('data-dir')).toEqual('/somedir/with/mixed/separators'); + expect($crumbs.eq(4).data('dir')).toEqual('/somedir/with/mixed'); + + expect($crumbs.eq(5).find('a').attr('href')).toEqual('/somedir/with/mixed/separators#5'); + expect($crumbs.eq(5).find('img').length).toEqual(0); + expect($crumbs.eq(5).data('dir')).toEqual('/somedir/with/mixed/separators'); }); }); describe('Events', function() { @@ -126,14 +139,15 @@ describe('OCA.Files.BreadCrumb tests', function() { onClick: handler }); bc.setDirectory('/one/two/three/four'); - bc.$el.find('.crumb:eq(3)').click(); - expect(handler.calledOnce).toEqual(true); - expect(handler.getCall(0).thisValue).toEqual(bc.$el.find('.crumb').get(3)); + // Click on crumb does not work, only link + bc.$el.find('.crumb:eq(4)').click(); + expect(handler.calledOnce).toEqual(false); handler.reset(); - bc.$el.find('.crumb:eq(0) a').click(); + // Click on crumb link works + bc.$el.find('.crumb:eq(1) a').click(); expect(handler.calledOnce).toEqual(true); - expect(handler.getCall(0).thisValue).toEqual(bc.$el.find('.crumb').get(0)); + expect(handler.getCall(0).thisValue).toEqual(bc.$el.find('.crumb > a').get(1)); }); it('Calls onDrop handler when dropping on a crumb', function() { var droppableStub = sinon.stub($.fn, 'droppable'); @@ -154,8 +168,75 @@ describe('OCA.Files.BreadCrumb tests', function() { droppableStub.restore(); }); }); + + describe('Menu tests', function() { + var bc, dummyDir, $crumbmenuLink, $popovermenu; + + beforeEach(function() { + dummyDir = '/one/two/three/four/five' + + $('div.crumb').each(function(index){ + $(this).css('width', 50); + }); + + bc = new BreadCrumb(); + // append dummy navigation and controls + // as they are currently used for measurements + $('#testArea').append( + '<div id="controls"></div>' + ); + $('#controls').append(bc.$el); + + // Shrink to show popovermenu + bc.setMaxWidth(300); + + // triggers resize implicitly + bc.setDirectory(dummyDir); + + $crumbmenuLink = bc.$el.find('.crumbmenu > a'); + $popovermenu = $crumbmenuLink.next('.popovermenu'); + }); + afterEach(function() { + bc = null; + }); + + it('Opens and closes the menu on click', function() { + // Menu exists + expect($popovermenu.length).toEqual(1); + + // Disable jQuery delay + jQuery.fx.off = true + + // Click on menu + $crumbmenuLink.click(); + expect($popovermenu.is(':visible')).toEqual(true); + + // Click on home + $(document).mouseup(); + expect($popovermenu.is(':visible')).toEqual(false); + + // Change directory and reset elements + bc.setDirectory('/one/two/three/four/five/six/seven/eight/nine/ten'); + $crumbmenuLink = bc.$el.find('.crumbmenu > a'); + $popovermenu = $crumbmenuLink.next('.popovermenu'); + + // Click on menu again + $crumbmenuLink.click(); + expect($popovermenu.is(':visible')).toEqual(true); + + // Click on home again + $(document).mouseup(); + expect($popovermenu.is(':visible')).toEqual(false); + + }); + it('Shows only items not in the breadcrumb', function() { + var hiddenCrumbs = bc.$el.find('.crumb:not(.crumbmenu).hidden'); + expect($popovermenu.find('li:not(.in-breadcrumb)').length).toEqual(hiddenCrumbs.length); + }); + }); + describe('Resizing', function() { - var bc, dummyDir, widths, oldUpdateTotalWidth; + var bc, dummyDir, widths; beforeEach(function() { dummyDir = '/short name/longer name/looooooooooooonger/' + @@ -163,17 +244,12 @@ describe('OCA.Files.BreadCrumb tests', function() { // using hard-coded widths (pre-measured) to avoid getting different // results on different browsers due to font engine differences - widths = [41, 106, 112, 160, 257, 251, 91]; + // 51px is default size for menu and home + widths = [51, 51, 106, 112, 160, 257, 251, 91]; - oldUpdateTotalWidth = BreadCrumb.prototype._updateTotalWidth; - BreadCrumb.prototype._updateTotalWidth = function() { - // pre-set a width to simulate consistent measurement - $('div.crumb').each(function(index){ - $(this).css('width', widths[index]); - }); - - return oldUpdateTotalWidth.apply(this, arguments); - }; + $('div.crumb').each(function(index){ + $(this).css('width', widths[index]); + }); bc = new BreadCrumb(); // append dummy navigation and controls @@ -184,30 +260,26 @@ describe('OCA.Files.BreadCrumb tests', function() { $('#controls').append(bc.$el); }); afterEach(function() { - BreadCrumb.prototype._updateTotalWidth = oldUpdateTotalWidth; bc = null; }); it('Hides breadcrumbs to fit max allowed width', function() { var $crumbs; bc.setMaxWidth(500); + // triggers resize implicitly bc.setDirectory(dummyDir); $crumbs = bc.$el.find('.crumb'); - // first one is always visible + // Menu and home are always visible expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); - // second one has ellipsis expect($crumbs.eq(1).hasClass('hidden')).toEqual(false); - expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1); - // there is only one ellipsis in total - expect($crumbs.find('.ellipsis').length).toEqual(1); - // subsequent elements are hidden - expect($crumbs.eq(2).hasClass('hidden')).toEqual(true); + + expect($crumbs.eq(2).hasClass('hidden')).toEqual(false); expect($crumbs.eq(3).hasClass('hidden')).toEqual(true); expect($crumbs.eq(4).hasClass('hidden')).toEqual(true); expect($crumbs.eq(5).hasClass('hidden')).toEqual(true); - expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(6).hasClass('hidden')).toEqual(true); expect($crumbs.eq(7).hasClass('hidden')).toEqual(false); }); it('Updates the breadcrumbs when reducing max allowed width', function() { @@ -215,56 +287,27 @@ describe('OCA.Files.BreadCrumb tests', function() { // enough space bc.setMaxWidth(1800); + $crumbs = bc.$el.find('.crumb'); - expect(bc.$el.find('.ellipsis').length).toEqual(0); + // Menu is hidden + expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); // triggers resize implicitly bc.setDirectory(dummyDir); - // simulate increase + // simulate decrease bc.setMaxWidth(950); - $crumbs = bc.$el.find('.crumb'); - // first one is always visible + // Menu and home are always visible expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); - // second one has ellipsis expect($crumbs.eq(1).hasClass('hidden')).toEqual(false); - expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1); - // there is only one ellipsis in total - expect($crumbs.find('.ellipsis').length).toEqual(1); - // subsequent elements are hidden - expect($crumbs.eq(2).hasClass('hidden')).toEqual(true); - expect($crumbs.eq(3).hasClass('hidden')).toEqual(true); - // the rest is visible - expect($crumbs.eq(4).hasClass('hidden')).toEqual(false); - expect($crumbs.eq(5).hasClass('hidden')).toEqual(false); - expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); - }); - it('Removes the ellipsis when there is enough space', function() { - var $crumbs; - - bc.setMaxWidth(500); - // triggers resize implicitly - bc.setDirectory(dummyDir); - $crumbs = bc.$el.find('.crumb'); - - // ellipsis - expect(bc.$el.find('.ellipsis').length).toEqual(1); - - // simulate increase - bc.setMaxWidth(1800); - // no ellipsis - expect(bc.$el.find('.ellipsis').length).toEqual(0); - - // all are visible - expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); - expect($crumbs.eq(1).hasClass('hidden')).toEqual(false); expect($crumbs.eq(2).hasClass('hidden')).toEqual(false); expect($crumbs.eq(3).hasClass('hidden')).toEqual(false); expect($crumbs.eq(4).hasClass('hidden')).toEqual(false); expect($crumbs.eq(5).hasClass('hidden')).toEqual(false); expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(7).hasClass('hidden')).toEqual(false); }); }); }); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index fd011474eb1..c590275e1cf 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -1657,7 +1657,7 @@ describe('OCA.Files.FileList tests', function() { fileList.changeDirectory('/subdir/two/three with space/four/five'); deferredList.resolve(200, [testRoot].concat(testFiles)); var changeDirStub = sinon.stub(fileList, 'changeDirectory'); - fileList.breadcrumb.$el.find('.crumb:eq(0)').trigger({type: 'click', which: 1}); + fileList.breadcrumb.$el.find('.crumb:eq(1) > a').trigger({type: 'click', which: 1}); expect(changeDirStub.calledOnce).toEqual(true); expect(changeDirStub.getCall(0).args[0]).toEqual('/'); @@ -1667,7 +1667,7 @@ describe('OCA.Files.FileList tests', function() { fileList.changeDirectory('/subdir/two/three with space/four/five'); deferredList.resolve(200, [testRoot].concat(testFiles)); var changeDirStub = sinon.stub(fileList, 'changeDirectory'); - fileList.breadcrumb.$el.find('.crumb:eq(3)').trigger({type: 'click', which: 1}); + fileList.breadcrumb.$el.find('.crumb:eq(4) > a').trigger({type: 'click', which: 1}); expect(changeDirStub.calledOnce).toEqual(true); expect(changeDirStub.getCall(0).args[0]).toEqual('/subdir/two/three with space'); @@ -1678,7 +1678,7 @@ describe('OCA.Files.FileList tests', function() { var moveStub = sinon.stub(filesClient, 'move').returns($.Deferred().promise()); fileList.changeDirectory(testDir); deferredList.resolve(200, [testRoot].concat(testFiles)); - var $crumb = fileList.breadcrumb.$el.find('.crumb:eq(3)'); + var $crumb = fileList.breadcrumb.$el.find('.crumb:eq(4)'); // no idea what this is but is required by the handler var ui = { helper: { @@ -3013,7 +3013,7 @@ describe('OCA.Files.FileList tests', function() { it('drop on a breadcrumb inside the table triggers upload to target folder', function() { var ev; fileList.changeDirectory('a/b/c/d'); - ev = dropOn(fileList.$el.find('.crumb:eq(2)'), uploadData); + ev = dropOn(fileList.$el.find('.crumb:eq(3)'), uploadData); expect(ev).not.toEqual(false); expect(uploadData.targetDir).toEqual('/a/b'); diff --git a/apps/files_external/3rdparty/composer.json b/apps/files_external/3rdparty/composer.json index f1613f0c70b..307a062c7e0 100644 --- a/apps/files_external/3rdparty/composer.json +++ b/apps/files_external/3rdparty/composer.json @@ -8,7 +8,7 @@ "classmap-authoritative": true }, "require": { - "icewind/smb": "2.0.2", + "icewind/smb": "2.0.3", "icewind/streams": "0.5.2" } } diff --git a/apps/files_external/3rdparty/composer.lock b/apps/files_external/3rdparty/composer.lock index 0cf5cabdc19..e00a7bd80e4 100644 --- a/apps/files_external/3rdparty/composer.lock +++ b/apps/files_external/3rdparty/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "85f8c3519f909ded38d917d3901f2709", + "content-hash": "b6a304e8ab2effa3791b513007fadcbc", "packages": [ { "name": "icewind/smb", - "version": "v2.0.2", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "6691355d9314ac3a8cb9ec9446e4c26e8aab09d0" + "reference": "8394551bf29a37b884edb33dae8acde369177f32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/6691355d9314ac3a8cb9ec9446e4c26e8aab09d0", - "reference": "6691355d9314ac3a8cb9ec9446e4c26e8aab09d0", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/8394551bf29a37b884edb33dae8acde369177f32", + "reference": "8394551bf29a37b884edb33dae8acde369177f32", "shasum": "" }, "require": { @@ -45,7 +45,7 @@ } ], "description": "php wrapper for smbclient and libsmbclient-php", - "time": "2017-08-16T16:08:57+00:00" + "time": "2017-10-18T16:21:10+00:00" }, { "name": "icewind/streams", diff --git a/apps/files_external/3rdparty/composer/autoload_classmap.php b/apps/files_external/3rdparty/composer/autoload_classmap.php index 4c596a44418..257bdb64eb5 100644 --- a/apps/files_external/3rdparty/composer/autoload_classmap.php +++ b/apps/files_external/3rdparty/composer/autoload_classmap.php @@ -22,6 +22,7 @@ return array( 'Icewind\\SMB\\Exception\\ForbiddenException' => $vendorDir . '/icewind/smb/src/Exception/ForbiddenException.php', 'Icewind\\SMB\\Exception\\HostDownException' => $vendorDir . '/icewind/smb/src/Exception/HostDownException.php', 'Icewind\\SMB\\Exception\\InvalidHostException' => $vendorDir . '/icewind/smb/src/Exception/InvalidHostException.php', + 'Icewind\\SMB\\Exception\\InvalidParameterException' => $vendorDir . '/icewind/smb/src/Exception/InvalidParameterException.php', 'Icewind\\SMB\\Exception\\InvalidPathException' => $vendorDir . '/icewind/smb/src/Exception/InvalidPathException.php', 'Icewind\\SMB\\Exception\\InvalidRequestException' => $vendorDir . '/icewind/smb/src/Exception/InvalidRequestException.php', 'Icewind\\SMB\\Exception\\InvalidResourceException' => $vendorDir . '/icewind/smb/src/Exception/InvalidResourceException.php', @@ -30,6 +31,8 @@ return array( 'Icewind\\SMB\\Exception\\NoRouteToHostException' => $vendorDir . '/icewind/smb/src/Exception/NoRouteToHostException.php', 'Icewind\\SMB\\Exception\\NotEmptyException' => $vendorDir . '/icewind/smb/src/Exception/NotEmptyException.php', 'Icewind\\SMB\\Exception\\NotFoundException' => $vendorDir . '/icewind/smb/src/Exception/NotFoundException.php', + 'Icewind\\SMB\\Exception\\OutOfSpaceException' => $vendorDir . '/icewind/smb/src/Exception/OutOfSpaceException.php', + 'Icewind\\SMB\\Exception\\RevisionMismatchException' => $vendorDir . '/icewind/smb/src/Exception/RevisionMismatchException.php', 'Icewind\\SMB\\Exception\\TimedOutException' => $vendorDir . '/icewind/smb/src/Exception/TimedOutException.php', 'Icewind\\SMB\\FileInfo' => $vendorDir . '/icewind/smb/src/FileInfo.php', 'Icewind\\SMB\\IFileInfo' => $vendorDir . '/icewind/smb/src/IFileInfo.php', diff --git a/apps/files_external/3rdparty/composer/autoload_static.php b/apps/files_external/3rdparty/composer/autoload_static.php index 459de971c4e..62be2df6a0d 100644 --- a/apps/files_external/3rdparty/composer/autoload_static.php +++ b/apps/files_external/3rdparty/composer/autoload_static.php @@ -52,6 +52,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3 'Icewind\\SMB\\Exception\\ForbiddenException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/ForbiddenException.php', 'Icewind\\SMB\\Exception\\HostDownException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/HostDownException.php', 'Icewind\\SMB\\Exception\\InvalidHostException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidHostException.php', + 'Icewind\\SMB\\Exception\\InvalidParameterException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidParameterException.php', 'Icewind\\SMB\\Exception\\InvalidPathException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidPathException.php', 'Icewind\\SMB\\Exception\\InvalidRequestException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidRequestException.php', 'Icewind\\SMB\\Exception\\InvalidResourceException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/InvalidResourceException.php', @@ -60,6 +61,8 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3 'Icewind\\SMB\\Exception\\NoRouteToHostException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NoRouteToHostException.php', 'Icewind\\SMB\\Exception\\NotEmptyException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NotEmptyException.php', 'Icewind\\SMB\\Exception\\NotFoundException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/NotFoundException.php', + 'Icewind\\SMB\\Exception\\OutOfSpaceException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/OutOfSpaceException.php', + 'Icewind\\SMB\\Exception\\RevisionMismatchException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/RevisionMismatchException.php', 'Icewind\\SMB\\Exception\\TimedOutException' => __DIR__ . '/..' . '/icewind/smb/src/Exception/TimedOutException.php', 'Icewind\\SMB\\FileInfo' => __DIR__ . '/..' . '/icewind/smb/src/FileInfo.php', 'Icewind\\SMB\\IFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/IFileInfo.php', diff --git a/apps/files_external/3rdparty/composer/installed.json b/apps/files_external/3rdparty/composer/installed.json index aafe1591fa8..6855971a9f1 100644 --- a/apps/files_external/3rdparty/composer/installed.json +++ b/apps/files_external/3rdparty/composer/installed.json @@ -44,17 +44,17 @@ }, { "name": "icewind/smb", - "version": "v2.0.2", - "version_normalized": "2.0.2.0", + "version": "v2.0.3", + "version_normalized": "2.0.3.0", "source": { "type": "git", "url": "https://github.com/icewind1991/SMB.git", - "reference": "6691355d9314ac3a8cb9ec9446e4c26e8aab09d0" + "reference": "8394551bf29a37b884edb33dae8acde369177f32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/icewind1991/SMB/zipball/6691355d9314ac3a8cb9ec9446e4c26e8aab09d0", - "reference": "6691355d9314ac3a8cb9ec9446e4c26e8aab09d0", + "url": "https://api.github.com/repos/icewind1991/SMB/zipball/8394551bf29a37b884edb33dae8acde369177f32", + "reference": "8394551bf29a37b884edb33dae8acde369177f32", "shasum": "" }, "require": { @@ -64,7 +64,7 @@ "require-dev": { "phpunit/phpunit": "^4.8" }, - "time": "2017-08-16T16:08:57+00:00", + "time": "2017-10-18T16:21:10+00:00", "type": "library", "installation-source": "source", "autoload": { diff --git a/apps/files_external/3rdparty/icewind/smb/src/ErrorCodes.php b/apps/files_external/3rdparty/icewind/smb/src/ErrorCodes.php index 03bd574c185..d36cdaf2c7d 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/ErrorCodes.php +++ b/apps/files_external/3rdparty/icewind/smb/src/ErrorCodes.php @@ -15,7 +15,7 @@ class ErrorCodes { const BadHostName = 'NT_STATUS_BAD_NETWORK_NAME'; const Unsuccessful = 'NT_STATUS_UNSUCCESSFUL'; const ConnectionRefused = 'NT_STATUS_CONNECTION_REFUSED'; - const NoLogonServers = 'NT_STATUS_NO_LOGON_SERVERS'; + const NoLogonServers = 'NT_STATUS_NO_LOGON_SERVERS'; const PathNotFound = 'NT_STATUS_OBJECT_PATH_NOT_FOUND'; const NoSuchFile = 'NT_STATUS_NO_SUCH_FILE'; @@ -26,4 +26,6 @@ class ErrorCodes { const FileIsADirectory = 'NT_STATUS_FILE_IS_A_DIRECTORY'; const NotADirectory = 'NT_STATUS_NOT_A_DIRECTORY'; const SharingViolation = 'NT_STATUS_SHARING_VIOLATION'; + const InvalidParameter = 'NT_STATUS_INVALID_PARAMETER'; + const RevisionMismatch = 'NT_STATUS_REVISION_MISMATCH'; } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Exception/Exception.php b/apps/files_external/3rdparty/icewind/smb/src/Exception/Exception.php index 7ac528198a9..93f2c1b3e2e 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Exception/Exception.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Exception/Exception.php @@ -14,7 +14,7 @@ class Exception extends \Exception { $message .= ' for ' . $path; } - return new Exception($message, $error); + return new Exception($message, is_string($error) ? 0 : $error); } /** diff --git a/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidParameterException.php b/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidParameterException.php new file mode 100644 index 00000000000..163b571183d --- /dev/null +++ b/apps/files_external/3rdparty/icewind/smb/src/Exception/InvalidParameterException.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Licensed under the MIT license: + * http://opensource.org/licenses/MIT + */ + +namespace Icewind\SMB\Exception; + +class InvalidParameterException extends InvalidRequestException {} diff --git a/apps/files_external/3rdparty/icewind/smb/src/Exception/OutOfSpaceException.php b/apps/files_external/3rdparty/icewind/smb/src/Exception/OutOfSpaceException.php new file mode 100644 index 00000000000..4c5517a1404 --- /dev/null +++ b/apps/files_external/3rdparty/icewind/smb/src/Exception/OutOfSpaceException.php @@ -0,0 +1,11 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Licensed under the MIT license: + * http://opensource.org/licenses/MIT + */ + +namespace Icewind\SMB\Exception; + +class OutOfSpaceException extends InvalidRequestException { +} diff --git a/apps/files_external/3rdparty/icewind/smb/src/Exception/RevisionMismatchException.php b/apps/files_external/3rdparty/icewind/smb/src/Exception/RevisionMismatchException.php new file mode 100644 index 00000000000..e898b5a2347 --- /dev/null +++ b/apps/files_external/3rdparty/icewind/smb/src/Exception/RevisionMismatchException.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Licensed under the MIT license: + * http://opensource.org/licenses/MIT + */ + +namespace Icewind\SMB\Exception; + +use Throwable; + +class RevisionMismatchException extends Exception { + public function __construct($message = 'Protocol version mismatch', $code = 0, Throwable $previous = null) { + parent::__construct($message, $code, $previous); + } +} diff --git a/apps/files_external/3rdparty/icewind/smb/src/NativeState.php b/apps/files_external/3rdparty/icewind/smb/src/NativeState.php index 7ddce831853..45e0441d252 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/NativeState.php +++ b/apps/files_external/3rdparty/icewind/smb/src/NativeState.php @@ -31,6 +31,7 @@ class NativeState { 17 => '\Icewind\SMB\Exception\AlreadyExistsException', 20 => '\Icewind\SMB\Exception\InvalidTypeException', 21 => '\Icewind\SMB\Exception\InvalidTypeException', + 28 => '\Icewind\SMB\Exception\OutOfSpaceException', 39 => '\Icewind\SMB\Exception\NotEmptyException', 110 => '\Icewind\SMB\Exception\TimedOutException', 111 => '\Icewind\SMB\Exception\ConnectionRefusedException', diff --git a/apps/files_external/3rdparty/icewind/smb/src/NotifyHandler.php b/apps/files_external/3rdparty/icewind/smb/src/NotifyHandler.php index 6ad565555bf..20ada5cf320 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/NotifyHandler.php +++ b/apps/files_external/3rdparty/icewind/smb/src/NotifyHandler.php @@ -9,6 +9,8 @@ namespace Icewind\SMB; +use Icewind\SMB\Exception\Exception; + class NotifyHandler implements INotifyHandler { /** * @var Connection @@ -22,6 +24,12 @@ class NotifyHandler implements INotifyHandler { private $listening = true; + // todo replace with static once <5.6 support is dropped + // see error.h + private static $exceptionMap = [ + ErrorCodes::RevisionMismatch => '\Icewind\SMB\Exception\RevisionMismatchException', + ]; + /** * @param Connection $connection * @param string $path @@ -43,6 +51,7 @@ class NotifyHandler implements INotifyHandler { stream_set_blocking($this->connection->getOutputStream(), 0); $lines = []; while (($line = $this->connection->readLine())) { + $this->checkForError($line); $lines[] = $line; } stream_set_blocking($this->connection->getOutputStream(), 1); @@ -59,6 +68,7 @@ class NotifyHandler implements INotifyHandler { public function listen($callback) { if ($this->listening) { $this->connection->read(function ($line) use ($callback) { + $this->checkForError($line); $change = $this->parseChangeLine($line); if ($change) { return $callback($change); @@ -80,6 +90,13 @@ class NotifyHandler implements INotifyHandler { } } + private function checkForError($line) { + if (substr($line, 0, 16) === 'notify returned ') { + $error = substr($line, 16); + throw Exception::fromMap(self::$exceptionMap, $error, 'Notify is not supported with the used smb version'); + } + } + public function stop() { $this->listening = false; $this->connection->close(); diff --git a/apps/files_external/3rdparty/icewind/smb/src/Parser.php b/apps/files_external/3rdparty/icewind/smb/src/Parser.php index 5cc5acbdf56..3142f9c29e0 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Parser.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Parser.php @@ -30,6 +30,7 @@ class Parser { // todo replace with static once <5.6 support is dropped // see error.h private static $exceptionMap = [ + ErrorCodes::LogonFailure => '\Icewind\SMB\Exception\AuthenticationException', ErrorCodes::PathNotFound => '\Icewind\SMB\Exception\NotFoundException', ErrorCodes::ObjectNotFound => '\Icewind\SMB\Exception\NotFoundException', ErrorCodes::NoSuchFile => '\Icewind\SMB\Exception\NotFoundException', @@ -38,7 +39,8 @@ class Parser { ErrorCodes::DirectoryNotEmpty => '\Icewind\SMB\Exception\NotEmptyException', ErrorCodes::FileIsADirectory => '\Icewind\SMB\Exception\InvalidTypeException', ErrorCodes::NotADirectory => '\Icewind\SMB\Exception\InvalidTypeException', - ErrorCodes::SharingViolation => '\Icewind\SMB\Exception\FileInUseException' + ErrorCodes::SharingViolation => '\Icewind\SMB\Exception\FileInUseException', + ErrorCodes::InvalidParameter => '\Icewind\SMB\Exception\InvalidParameterException' ]; /** diff --git a/apps/files_external/3rdparty/icewind/smb/src/RawConnection.php b/apps/files_external/3rdparty/icewind/smb/src/RawConnection.php index e9349716430..42923f09eda 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/RawConnection.php +++ b/apps/files_external/3rdparty/icewind/smb/src/RawConnection.php @@ -7,6 +7,7 @@ namespace Icewind\SMB; +use Icewind\SMB\Exception\ConnectException; use Icewind\SMB\Exception\ConnectionException; class RawConnection { @@ -25,6 +26,9 @@ class RawConnection { * * $pipes[0] holds STDIN for smbclient * $pipes[1] holds STDOUT for smbclient + * $pipes[3] holds the authfile for smbclient + * $pipes[4] holds the stream for writing files + * $pipes[5] holds the stream for reading files */ private $pipes; @@ -33,32 +37,44 @@ class RawConnection { */ private $process; - public function __construct($command, $env = array()) { + /** + * @var resource|null $authStream + */ + private $authStream = null; + + private $connected = false; + + public function __construct($command, array $env = []) { $this->command = $command; $this->env = $env; - $this->connect(); } - private function connect() { - $descriptorSpec = array( - 0 => array('pipe', 'r'), // child reads from stdin - 1 => array('pipe', 'w'), // child writes to stdout - 2 => array('pipe', 'w'), // child writes to stderr - 3 => array('pipe', 'r'), // child reads from fd#3 - 4 => array('pipe', 'r'), // child reads from fd#4 - 5 => array('pipe', 'w') // child writes to fd#5 - ); + public function connect() { + if (is_null($this->getAuthStream())) { + throw new ConnectException('Authentication not set before connecting'); + } + + $descriptorSpec = [ + 0 => ['pipe', 'r'], // child reads from stdin + 1 => ['pipe', 'w'], // child writes to stdout + 2 => ['pipe', 'w'], // child writes to stderr + 3 => $this->getAuthStream(), // child reads from fd#3 + 4 => ['pipe', 'r'], // child reads from fd#4 + 5 => ['pipe', 'w'] // child writes to fd#5 + ]; + setlocale(LC_ALL, Server::LOCALE); - $env = array_merge($this->env, array( + $env = array_merge($this->env, [ 'CLI_FORCE_INTERACTIVE' => 'y', // Needed or the prompt isn't displayed!! - 'LC_ALL' => Server::LOCALE, - 'LANG' => Server::LOCALE, - 'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output - )); + 'LC_ALL' => Server::LOCALE, + 'LANG' => Server::LOCALE, + 'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output + ]); $this->process = proc_open($this->command, $descriptorSpec, $this->pipes, '/', $env); if (!$this->isValid()) { throw new ConnectionException(); } + $this->connected = true; } /** @@ -129,7 +145,7 @@ class RawConnection { } public function getAuthStream() { - return $this->pipes[3]; + return $this->authStream; } public function getFileInputStream() { @@ -143,14 +159,10 @@ class RawConnection { public function writeAuthentication($user, $password) { $auth = ($password === false) ? "username=$user" - : "username=$user\npassword=$password"; + : "username=$user\npassword=$password\n"; - if (fwrite($this->getAuthStream(), $auth) === false) { - fclose($this->getAuthStream()); - return false; - } - fclose($this->getAuthStream()); - return true; + $this->authStream = fopen('php://temp', 'w+'); + fwrite($this->getAuthStream(), $auth); } public function close($terminate = true) { @@ -163,8 +175,8 @@ class RawConnection { $status = proc_get_status($this->process); $ppid = $status['pid']; $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid $ppid`); - foreach($pids as $pid) { - if(is_numeric($pid)) { + foreach ($pids as $pid) { + if (is_numeric($pid)) { //9 is the SIGKILL signal posix_kill($pid, 9); } diff --git a/apps/files_external/3rdparty/icewind/smb/src/Server.php b/apps/files_external/3rdparty/icewind/smb/src/Server.php index 12692eb4c6e..21cc605ed1f 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Server.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Server.php @@ -134,6 +134,7 @@ class Server { ); $connection = new RawConnection($command); $connection->writeAuthentication($this->getUser(), $this->getPassword()); + $connection->connect(); $output = $connection->readAll(); $parser = new Parser($this->timezoneProvider); diff --git a/apps/files_external/3rdparty/icewind/smb/src/Share.php b/apps/files_external/3rdparty/icewind/smb/src/Share.php index ba8bbbbb8ca..542eaf0887e 100644 --- a/apps/files_external/3rdparty/icewind/smb/src/Share.php +++ b/apps/files_external/3rdparty/icewind/smb/src/Share.php @@ -68,8 +68,9 @@ class Share extends AbstractShare { ); $connection = new Connection($command, $this->parser); $connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); + $connection->connect(); if (!$connection->isValid()) { - throw new ConnectionException(); + throw new ConnectionException($connection->readLine()); } return $connection; } @@ -88,7 +89,6 @@ class Share extends AbstractShare { protected function reconnect() { $this->connection->reconnect(); - $this->connection->writeAuthentication($this->server->getUser(), $this->server->getPassword()); if (!$this->connection->isValid()) { throw new ConnectionException(); } @@ -318,9 +318,9 @@ class Share extends AbstractShare { $modeString = ''; $modeMap = array( FileInfo::MODE_READONLY => 'r', - FileInfo::MODE_HIDDEN => 'h', - FileInfo::MODE_ARCHIVE => 'a', - FileInfo::MODE_SYSTEM => 's' + FileInfo::MODE_HIDDEN => 'h', + FileInfo::MODE_ARCHIVE => 'a', + FileInfo::MODE_SYSTEM => 's' ); foreach ($modeMap as $modeByte => $string) { if ($mode & $modeByte) { @@ -413,6 +413,7 @@ class Share extends AbstractShare { } $path = str_replace('/', '\\', $path); $path = str_replace('"', '^"', $path); + $path = ltrim($path, '\\'); return '"' . $path . '"'; } diff --git a/apps/files_external/templates/settings.php b/apps/files_external/templates/settings.php index 1d703e0c1b3..237985bfd0f 100644 --- a/apps/files_external/templates/settings.php +++ b/apps/files_external/templates/settings.php @@ -169,10 +169,10 @@ <?php if ($_['visibilityType'] === BackendService::VISIBILITY_ADMIN): ?> <input type="checkbox" name="allowUserMounting" id="allowUserMounting" class="checkbox" - value="1" <?php if ($_['allowUserMounting'] === 'yes') print_unescaped(' checked="checked"'); ?> /> + value="1" <?php if ($_['allowUserMounting']) print_unescaped(' checked="checked"'); ?> /> <label for="allowUserMounting"><?php p($l->t('Allow users to mount external storage')); ?></label> <span id="userMountingMsg" class="msg"></span> - <p id="userMountingBackends"<?php if ($_['allowUserMounting'] !== 'yes'): ?> class="hidden"<?php endif; ?>> + <p id="userMountingBackends"<?php if (!$_['allowUserMounting']): ?> class="hidden"<?php endif; ?>> <?php p($l->t('Allow users to mount the following external storage')); ?><br /> <?php $userBackends = array_filter($_['backends'], function($backend) { diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js index 5e9a4cf27d1..04ff243d07b 100644 --- a/apps/files_trashbin/tests/js/filelistSpec.js +++ b/apps/files_trashbin/tests/js/filelistSpec.js @@ -132,12 +132,12 @@ describe('OCA.Trashbin.FileList tests', function() { fileList.changeDirectory('/subdir', false, true); fakeServer.respond(); var $crumbs = fileList.$el.find('#controls .crumb'); - expect($crumbs.length).toEqual(2); - expect($crumbs.eq(0).find('a').text()).toEqual(''); - expect($crumbs.eq(0).find('a').attr('href')) - .toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/'); - expect($crumbs.eq(1).find('a').text()).toEqual('subdir'); + expect($crumbs.length).toEqual(3); + expect($crumbs.eq(1).find('a').text()).toEqual('Home'); expect($crumbs.eq(1).find('a').attr('href')) + .toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/'); + expect($crumbs.eq(2).find('a').text()).toEqual('subdir'); + expect($crumbs.eq(2).find('a').attr('href')) .toEqual(OC.webroot + '/index.php/apps/files?view=trashbin&dir=/subdir'); }); }); diff --git a/apps/theming/appinfo/info.xml b/apps/theming/appinfo/info.xml index ee3126c0781..883dfbbb2ec 100644 --- a/apps/theming/appinfo/info.xml +++ b/apps/theming/appinfo/info.xml @@ -5,7 +5,7 @@ <description>Adjust the Nextcloud theme</description> <licence>AGPL</licence> <author>Nextcloud</author> - <version>1.4.0</version> + <version>1.4.1</version> <namespace>Theming</namespace> <category>other</category> diff --git a/apps/theming/l10n/es_CL.js b/apps/theming/l10n/es_CL.js index 4f98160b142..fba2c6263c0 100644 --- a/apps/theming/l10n/es_CL.js +++ b/apps/theming/l10n/es_CL.js @@ -33,6 +33,7 @@ OC.L10N.register( "Login image" : "Imágen de inicio de sesión", "Upload new login background" : "Cargar nueva imagen de fondo para inicio de sesión", "Remove background image" : "Eliminar imagen de fondo", + "Install the Imagemagick PHP extension with support for SVG images to automatically generate favicons based on the uploaded logo and color." : "Instala la extensión Imagemagick de PHP con soporte a imagenes SVG para generar los favicons en automático con base en el logotipo cargado y el color.", "reset to default" : "restaurar a predeterminado", "Log in image" : "Imagen de inicio de sesión" }, diff --git a/apps/theming/l10n/es_CL.json b/apps/theming/l10n/es_CL.json index 866eb07f7fa..d0be021859a 100644 --- a/apps/theming/l10n/es_CL.json +++ b/apps/theming/l10n/es_CL.json @@ -31,6 +31,7 @@ "Login image" : "Imágen de inicio de sesión", "Upload new login background" : "Cargar nueva imagen de fondo para inicio de sesión", "Remove background image" : "Eliminar imagen de fondo", + "Install the Imagemagick PHP extension with support for SVG images to automatically generate favicons based on the uploaded logo and color." : "Instala la extensión Imagemagick de PHP con soporte a imagenes SVG para generar los favicons en automático con base en el logotipo cargado y el color.", "reset to default" : "restaurar a predeterminado", "Log in image" : "Imagen de inicio de sesión" },"pluralForm" :"nplurals=2; plural=(n != 1);" diff --git a/apps/theming/l10n/ru.js b/apps/theming/l10n/ru.js index 2a46add3c0d..18c907f6cea 100644 --- a/apps/theming/l10n/ru.js +++ b/apps/theming/l10n/ru.js @@ -33,6 +33,7 @@ OC.L10N.register( "Login image" : "Изображение экрана входа в систему", "Upload new login background" : "Загрузить новый фон для экрана входа в систему", "Remove background image" : "Убрать фоновое изображение ", + "Install the Imagemagick PHP extension with support for SVG images to automatically generate favicons based on the uploaded logo and color." : "Для автоматической генерации favicon на основе загруженного логотипа и цвета нужно установить PHP расширение Imagemagick с поддержкой изображений SVG ", "reset to default" : "сброс до настроек по-умолчанию", "Log in image" : "Изображение экрана входа в систему" }, diff --git a/apps/theming/l10n/ru.json b/apps/theming/l10n/ru.json index f363f3ab151..2d996c8f3e5 100644 --- a/apps/theming/l10n/ru.json +++ b/apps/theming/l10n/ru.json @@ -31,6 +31,7 @@ "Login image" : "Изображение экрана входа в систему", "Upload new login background" : "Загрузить новый фон для экрана входа в систему", "Remove background image" : "Убрать фоновое изображение ", + "Install the Imagemagick PHP extension with support for SVG images to automatically generate favicons based on the uploaded logo and color." : "Для автоматической генерации favicon на основе загруженного логотипа и цвета нужно установить PHP расширение Imagemagick с поддержкой изображений SVG ", "reset to default" : "сброс до настроек по-умолчанию", "Log in image" : "Изображение экрана входа в систему" },"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);" diff --git a/apps/theming/lib/IconBuilder.php b/apps/theming/lib/IconBuilder.php index 83258341985..ad44dd7ed6c 100644 --- a/apps/theming/lib/IconBuilder.php +++ b/apps/theming/lib/IconBuilder.php @@ -55,14 +55,38 @@ class IconBuilder { * @return string|false image blob */ public function getFavicon($app) { + if (!$this->themingDefaults->shouldReplaceIcons()) { + return false; + } try { - $icon = $this->renderAppIcon($app, 32); + $favicon = new Imagick(); + $favicon->setFormat("ico"); + $icon = $this->renderAppIcon($app, 128); if ($icon === false) { return false; } $icon->setImageFormat("png32"); - $data = $icon->getImageBlob(); + + $clone = clone $icon; + $clone->scaleImage(16,0); + $favicon->addImage($clone); + + $clone = clone $icon; + $clone->scaleImage(32,0); + $favicon->addImage($clone); + + $clone = clone $icon; + $clone->scaleImage(64,0); + $favicon->addImage($clone); + + $clone = clone $icon; + $clone->scaleImage(128,0); + $favicon->addImage($clone); + + $data = $favicon->getImagesBlob(); + $favicon->destroy(); $icon->destroy(); + $clone->destroy(); return $data; } catch (\ImagickException $e) { return false; diff --git a/apps/theming/tests/IconBuilderTest.php b/apps/theming/tests/IconBuilderTest.php index f25f76a8180..4f5078d4c56 100644 --- a/apps/theming/tests/IconBuilderTest.php +++ b/apps/theming/tests/IconBuilderTest.php @@ -71,6 +71,9 @@ class IconBuilderTest extends TestCase { if (count($checkImagick->queryFormats('SVG')) < 1) { $this->markTestSkipped('No SVG provider present.'); } + if (count($checkImagick->queryFormats('PNG')) < 1) { + $this->markTestSkipped('No PNG provider present.'); + } } public function dataRenderAppIcon() { @@ -147,16 +150,22 @@ class IconBuilderTest extends TestCase { public function testGetFavicon($app, $color, $file) { $this->checkImagick(); $this->themingDefaults->expects($this->once()) + ->method('shouldReplaceIcons') + ->willReturn(true); + $this->themingDefaults->expects($this->once()) ->method('getColorPrimary') ->willReturn($color); $expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file); + $actualIcon = $this->iconBuilder->getFavicon($app); + $icon = new \Imagick(); - $icon->readImageBlob($this->iconBuilder->getFavicon($app)); + $icon->setFormat('ico'); + $icon->readImageBlob($actualIcon); $this->assertEquals(true, $icon->valid()); - $this->assertEquals(32, $icon->getImageWidth()); - $this->assertEquals(32, $icon->getImageHeight()); + $this->assertEquals(128, $icon->getImageWidth()); + $this->assertEquals(128, $icon->getImageHeight()); $icon->destroy(); $expectedIcon->destroy(); // FIXME: We may need some comparison of the generated and the test images @@ -167,8 +176,12 @@ class IconBuilderTest extends TestCase { * @expectedException \PHPUnit_Framework_Error_Warning */ public function testGetFaviconNotFound() { + $this->checkImagick(); $util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock(); $iconBuilder = new IconBuilder($this->themingDefaults, $util); + $this->themingDefaults->expects($this->once()) + ->method('shouldReplaceIcons') + ->willReturn(true); $util->expects($this->once()) ->method('getAppIcon') ->willReturn('notexistingfile'); @@ -179,6 +192,7 @@ class IconBuilderTest extends TestCase { * @expectedException \PHPUnit_Framework_Error_Warning */ public function testGetTouchIconNotFound() { + $this->checkImagick(); $util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock(); $iconBuilder = new IconBuilder($this->themingDefaults, $util); $util->expects($this->once()) @@ -191,6 +205,7 @@ class IconBuilderTest extends TestCase { * @expectedException \PHPUnit_Framework_Error_Warning */ public function testColorSvgNotFound() { + $this->checkImagick(); $util = $this->getMockBuilder(Util::class)->disableOriginalConstructor()->getMock(); $iconBuilder = new IconBuilder($this->themingDefaults, $util); $util->expects($this->once()) diff --git a/apps/user_ldap/js/wizard/wizardTabElementary.js b/apps/user_ldap/js/wizard/wizardTabElementary.js index 7ce1009565d..3c6eb1adc45 100644 --- a/apps/user_ldap/js/wizard/wizardTabElementary.js +++ b/apps/user_ldap/js/wizard/wizardTabElementary.js @@ -214,6 +214,7 @@ OCA = OCA || {}; onConfigSwitch: function(view, configuration) { this.baseDNTestTriggered = false; view.disableElement(view.managedItems.ldap_port.$relatedElements); + view.managedItems.ldap_dn.$saveButton.removeClass('primary'); view.onConfigLoaded(view, configuration); }, diff --git a/apps/user_ldap/js/wizard/wizardTabGeneric.js b/apps/user_ldap/js/wizard/wizardTabGeneric.js index 57ac375e321..9997d6e1b04 100644 --- a/apps/user_ldap/js/wizard/wizardTabGeneric.js +++ b/apps/user_ldap/js/wizard/wizardTabGeneric.js @@ -359,6 +359,10 @@ OCA = OCA || {}; item.$saveButton.click(function(event) { event.preventDefault(); view._requestSave(item.$element); + item.$saveButton.removeClass('primary'); + }); + item.$element.change(function () { + item.$saveButton.addClass('primary'); }); })(this.managedItems[id]); } diff --git a/apps/user_ldap/l10n/es_CL.js b/apps/user_ldap/l10n/es_CL.js index 63d3d71e0a9..f87d7051878 100644 --- a/apps/user_ldap/l10n/es_CL.js +++ b/apps/user_ldap/l10n/es_CL.js @@ -99,6 +99,7 @@ OC.L10N.register( "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "El DN del cliente del usuario con el que se vinculará, ejem. uid=agente,dc=ejemplo,dc=com. Para tener un acceso anónimo, deja el DN y la contraseña vacíos.", "Password" : "Contraseña", "For anonymous access, leave DN and Password empty." : "Para acceso anónimo, deja la contraseña y DN vacíos.", + "Save Credentials" : "Guardar credenciales", "One Base DN per line" : "Un DN Base por línea", "You can specify Base DN for users and groups in the Advanced tab" : "Puedes especificar el DN Base para usuarios y grupos en la pestaña Avanzado", "Detect Base DN" : "Detectar DN Base", diff --git a/apps/user_ldap/l10n/es_CL.json b/apps/user_ldap/l10n/es_CL.json index eefeb9a037f..d92be60abe0 100644 --- a/apps/user_ldap/l10n/es_CL.json +++ b/apps/user_ldap/l10n/es_CL.json @@ -97,6 +97,7 @@ "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "El DN del cliente del usuario con el que se vinculará, ejem. uid=agente,dc=ejemplo,dc=com. Para tener un acceso anónimo, deja el DN y la contraseña vacíos.", "Password" : "Contraseña", "For anonymous access, leave DN and Password empty." : "Para acceso anónimo, deja la contraseña y DN vacíos.", + "Save Credentials" : "Guardar credenciales", "One Base DN per line" : "Un DN Base por línea", "You can specify Base DN for users and groups in the Advanced tab" : "Puedes especificar el DN Base para usuarios y grupos en la pestaña Avanzado", "Detect Base DN" : "Detectar DN Base", diff --git a/apps/user_ldap/l10n/ru.js b/apps/user_ldap/l10n/ru.js index d1d4d0331ea..09e300528e9 100644 --- a/apps/user_ldap/l10n/ru.js +++ b/apps/user_ldap/l10n/ru.js @@ -99,6 +99,7 @@ OC.L10N.register( "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "DN пользователя, под которым выполняется подключение, например, uid=agent,dc=example,dc=com. Для анонимного доступа оставьте DN и пароль пустыми.", "Password" : "Пароль", "For anonymous access, leave DN and Password empty." : "Для анонимного доступа оставьте DN и пароль пустыми.", + "Save Credentials" : "Сохранить учётные данные", "One Base DN per line" : "По одной базе поиска (Base DN) в строке.", "You can specify Base DN for users and groups in the Advanced tab" : "Вы можете задать Base DN для пользователей и групп на вкладке \"Расширенные\"", "Detect Base DN" : "Определить базу поиска DN", diff --git a/apps/user_ldap/l10n/ru.json b/apps/user_ldap/l10n/ru.json index 6ae07d14b68..eb23609a805 100644 --- a/apps/user_ldap/l10n/ru.json +++ b/apps/user_ldap/l10n/ru.json @@ -97,6 +97,7 @@ "The DN of the client user with which the bind shall be done, e.g. uid=agent,dc=example,dc=com. For anonymous access, leave DN and Password empty." : "DN пользователя, под которым выполняется подключение, например, uid=agent,dc=example,dc=com. Для анонимного доступа оставьте DN и пароль пустыми.", "Password" : "Пароль", "For anonymous access, leave DN and Password empty." : "Для анонимного доступа оставьте DN и пароль пустыми.", + "Save Credentials" : "Сохранить учётные данные", "One Base DN per line" : "По одной базе поиска (Base DN) в строке.", "You can specify Base DN for users and groups in the Advanced tab" : "Вы можете задать Base DN для пользователей и групп на вкладке \"Расширенные\"", "Detect Base DN" : "Определить базу поиска DN", diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php index afd43999c7f..c93d2a77d80 100644 --- a/apps/user_ldap/lib/User/User.php +++ b/apps/user_ldap/lib/User/User.php @@ -387,8 +387,7 @@ class User { $lastChecked = $this->config->getUserValue($this->uid, 'user_ldap', self::USER_PREFKEY_LASTREFRESH, 0); - //TODO make interval configurable - if((time() - intval($lastChecked)) < 86400 ) { + if((time() - intval($lastChecked)) < intval($this->config->getAppValue('user_ldap', 'updateAttributesInterval', 86400)) ) { return false; } return true; diff --git a/apps/user_ldap/templates/part.wizardcontrols.php b/apps/user_ldap/templates/part.wizardcontrols.php index 89eb96827e6..bd84b23c76d 100644 --- a/apps/user_ldap/templates/part.wizardcontrols.php +++ b/apps/user_ldap/templates/part.wizardcontrols.php @@ -5,7 +5,7 @@ type="button"> <?php p($l->t('Back'));?> </button> - <button class="ldap_action_continue" name="ldap_action_continue" type="button"> + <button class="ldap_action_continue primary" name="ldap_action_continue" type="button"> <?php p($l->t('Continue'));?> </button> <a href="<?php p(link_to_docs('admin-ldap')); ?>" diff --git a/apps/user_ldap/tests/User/UserTest.php b/apps/user_ldap/tests/User/UserTest.php index 5e911159285..27bd7762e39 100644 --- a/apps/user_ldap/tests/User/UserTest.php +++ b/apps/user_ldap/tests/User/UserTest.php @@ -871,7 +871,14 @@ class UserTest extends \Test\TestCase { ->with($this->equalTo('alice'), $this->equalTo('user_ldap'), $this->equalTo(User::USER_PREFKEY_LASTREFRESH), $this->equalTo(0)) - ->will($this->returnValue(time())); + ->will($this->returnValue(time() - 10)); + + $config->expects($this->once()) + ->method('getAppValue') + ->with($this->equalTo('user_ldap'), + $this->equalTo('updateAttributesInterval'), + $this->anything()) + ->will($this->returnValue(1800)); $config->expects($this->exactly(2)) ->method('getUserValue'); $config->expects($this->never()) |