diff --git a/CHANGELOG.md b/CHANGELOG.md index 58f8215..6856abc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,95 @@ +## 3.2.0 +**Maintainer**: balloon-team \ +**Date**: Tue Jun 25 07:41:04 CEST 2019 + +* [FIX] Reset view bar when menu is changed #224 +* [FIX] Avoid doubled text in file handler chooser #225 +* [FIX] menu swipe upwards opens the menu #229 +* [FIX] Remove fs-fullscreen-window-open on icon close click #230 + + +## 3.2.0-beta3 +**Maintainer**: balloon-team \ +**Date**: Mon Jun 17 17:02:02 CEST 2019 + +* [CHANGE] Use real input[type="checkbox"] + label for webauthn setup wizard #182 +* [CHANGE] Use real input[type="checkbox"] + label for remember handler #149 +* [CHANGE] Changed translations for in-built file handler apps +* [CHANGE] removed preview file handler +* [CHANGE] Desktop download popup should contain more information about proper installation way on linux #77 + + +## 3.2.0-beta2 +**Maintainer**: balloon-team \ +**Date**: Fri Jun 14 15:17:02 CEST 2019 + +* [FIX] Allow scrolling in kendo autocompletes #207 +* [FIX] Balloon file editor can only be opened once #200 +* [FIX] Allow application/x-sql to be edited in text editor +* [CHANGE] Change Icons for shared links +* [FIX] Ignore deleted nodes when adding new nodes #121 +* [CHANGE] Only show webauthn dialog on touch devices #182 +* [FEATURE] Allow to activate/deactivate webauthn from user settings #182 +* [FIX] move/clone should prompt user to merge if conflict=19 #118 +* [FIX] Fix getting index of nodes in multiselect array #44 +* [FIX] Fix this.balloon is undefined in burl handler #149 +* [CHANGE] Switch position of login button and webauthn #182 +* [CHANGE] Change event search query to $regex #143 +* [FIX] Avoid displaying event results from an old search #143 +* [FIX] Clearing event search input should display all events #143 +* [FIX] Avoid stacking of multiple infinite scroll requests #143 +* [CHANGE] Only activate change password if user.has_password === true +* [CHANGE] Added translations for in-built default balloon file handler apps + + +## 3.2.0-beta1 +**Maintainer**: balloon-team \ +**Date**: Wed Jun 05 14:47:01 CEST 2019 + +* [FEATURE] Display mount information #73 +* [CHANGE] Implement paged node listing #133 +* [FEATURE] Undo move/clone via snackbar #42 +* [FIX] Undo event "addCollectionShare" #157 +* [FEATURE] Support for multiple office clients #149 +* [FEATURE] Various snackbar undo actions #62 +* [FIX] prompt user to save unsaved changes in text file #159 +* [FIX] Not visible if a resource is a user or group in share dropdown #168 +* [FIX] Share link from search context, no ui response #144 +* [FIX] App aborts if received a server error #108 +* [FEATURE] Implement webauthn authentification #182 +* [FEATURE] Support for progressive web app (pwa) #82 +* [FIX] Various actions reload the tree, without respecting search results #78 +* [FIX] FOLDERUP in a search result ends in parent menu #177 +* [FIX] Search does not get correctly initialized #176 +* [FIX] reloadTree should work in search mode as well #173 +* [FEATURE] Refresh tree with scrolling down #162 +* [FEATURE] swipe left menu from right to left to open #82 +* [FIX] Adding new node reloads root instead of current collection #158 +* [FEATURE] Full WOPI support and support for multiple file handler apps #149 +* [FEATURE] Implement recaptcha v2 ui #148 +* [CHANGE] NTH: Own app for elastic search #129 +* [FIX] Searching by tag resets search filter #126 +* [FEATURE] Implement all features from the desktop version into the mobile version #81 +* [FIX] Rename node: mouse usage on input field #63 +* [FEATURE] New KendoBalloonFullscreenWindow #179 +* [FIX] File view close should be top right #170 +* [FIX] Filename with `&` gets cut off #194 +* [FIX] selectAll should not trigger multiselect when only one node is present #201 +* [CHANGE] Increase snackbar timeout #199 +* [CHANGE] Use slideout.js for the menu #209 +* [FIX] open a file on iPad landscape does not work #211 +* [FIX] Restoring a version results in status 422 #212 +* [FEATURE] Search over event log #143 +* [CHANGE] Only display search result after a search occured #198 +* [FIX] Use fake password field for external storage to avoid autocomplete #76 +* [CHANGE] alternative tipps icon #70 +* [FIX] Infinite scroll on event log is truly infinite #214 +* [FIX] add burl form url input on new line #216 +* [FIX] Typo in search menu #208 +* [CHANGE] Prompt user for certain actions when file is open #206 +* [FEATURE] Drag and Drop upload of images into md files #205 + + ## 3.1.3 **Maintainer**: balloon-team \ **Date**: Tue May 14 16:37:12 CEST 2019 diff --git a/config.example.json b/config.example.json index 579dc4a..62ba507 100755 --- a/config.example.json +++ b/config.example.json @@ -1,10 +1,11 @@ { "localScript": "/path/to/local_script.js", - "default_lang": "en", + "defaultLang": "en", + "recaptchaKey": null, "apps": { - "Balloon.App.Office": {""enabled": true, "config": {{}, - "Balloon.App.Convert": {""enabled": true, "config": {}}, - "Balloon.App.Notification": {""enabled": true, "config": {}}, + "Balloon.App.Office": {"enabled": true, "config": {}}, + "Balloon.App.Convert": {"enabled": true, "config": {}}, + "Balloon.App.Notification": {"enabled": true, "config": {}}, "Balloon.App.ClamAv": {"enabled": true, "config": {}}, "Balloon.App.DesktopClient": {"enabled": true, "config": {}}, "Balloon.App.Burl": {"enabled": true, "config": {}}, diff --git a/package.json b/package.json index c7df781..0d58bca 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,15 @@ "title": "balloon", "scripts": { "start": "webpack-dev-server --open --config webpack.dev.js", + "start-any": "webpack-dev-server --open --config webpack.dev.js --https --host 0.0.0.0", "build": "NODE_ENV=production webpack --config webpack.prod.js", "install": "napa StefH/bower-kendo-ui-web:kendo-ui-web", "postinstall": "echo > node_modules/kendo-ui-web/scripts/kendo.data.min.js; echo > node_modules/kendo-ui-web/scripts/kendo.draganddrop.min.js" }, "dependencies": { - "@gyselroth/icon-collection": "^1.0.9", - "@openid/appauth": "^0.2.2", + "@babel/polyfill": "^7.4.4", + "@gyselroth/icon-collection": "^1.0.12", + "@openid/appauth": "^1.2.4", "bundle-loader": "^0.5.6", "i18next": "3.x", "i18next-browser-languagedetector": "^2.2.4", @@ -18,19 +20,29 @@ "i18next-sprintf-postprocessor": "*", "i18next-xhr-backend": "^1.5.1", "jquery-i18next": "*", - "js-base64": "^2.5.1", "kendo-ui-core": "^2018.3.1219", - "merge-jsons-webpack-plugin": "^1.0.18", + "merge-jsons-webpack-plugin": "^1.0.19", + "mobile-pull-to-refresh": "^0.2.1", "qrcode.es": "^1.0.0", + "showdown": "^1.9.0", + "showdown-highlight": "^2.1.3", + "simplemde": "^1.11.2", + "slideout": "https://github.com/raffis/slideout/tarball/direction_fix", + "sw-precache-webpack-plugin": "^0.11.5", "ubuntu-fontface": "^0.1.13", - "uglifyjs-webpack-plugin": "^2.1.1" + "uglifyjs-webpack-plugin": "^2.1.3", + "webpack-pwa-manifest": "^4.0.0", + "workbox-webpack-plugin": "^4.3.1" }, "devDependencies": { - "@babel/core": "^7.2.2", - "@babel/preset-env": "^7.3.1", - "babel-loader": "^8.0.5", + "@babel/core": "^7.4.5", + "@babel/preset-env": "^7.4.5", + "babel-loader": "^8.0.6", + "babel-plugin-transform-async-to-generator": "^6.24.1", + "babel-polyfill": "^6.26.0", + "babel-preset-env": "^1.7.0", "css-loader": "^1.0.1", - "eslint": "^4.19.1", + "eslint": "^5.16.0", "eslint-loader": "1.7.1", "extract-text-webpack-plugin": "^3.0.2", "favicons-webpack-plugin": "0.0.9", @@ -39,15 +51,15 @@ "html-webpack-plugin": "^3.2.0", "mini-css-extract-plugin": "^0.4.5", "napa": "^3.0.0", - "node-sass": "^4.11.0", + "node-sass": "^4.12.0", "sass-loader": "^7.1.0", "source-map-loader": "^0.2.4", "style-loader": "^0.23.1", "svgxuse": "^1.2.6", "url-loader": "^1.1.2", - "webpack": "^4.29.0", - "webpack-cli": "^3.2.1", - "webpack-dev-server": "^3.1.14", + "webpack": "^4.35.0", + "webpack-cli": "^3.3.4", + "webpack-dev-server": "^3.7.2", "webpack-merge": "^4.2.1" }, "main": "index.html", diff --git a/src/app/Balloon.App.Burl/lib/lib.js b/src/app/Balloon.App.Burl/lib/lib.js index fdb4799..bda4133 100644 --- a/src/app/Balloon.App.Burl/lib/lib.js +++ b/src/app/Balloon.App.Burl/lib/lib.js @@ -25,40 +25,18 @@ var app = { preInit: function(core) { this.balloon = core; - this.balloon.addNew(app.BURL_EXTENSION, 'app.burl.tree.burl_file', 'hyperlink', this.addBurl.bind(this)); + this.balloon.addNew(app.BURL_EXTENSION, 'app.burl.tree.burl_file', 'file-burl', this.addBurl.bind(this)); - this.balloon.fileExtIconMap[app.BURL_EXTENSION] = 'gr-i-language'; + this.balloon.fileExtIconMap[app.BURL_EXTENSION] = 'gr-i-file-burl'; this.balloon.mimeFileExtMap['application/vnd.balloon.burl'] = app.BURL_EXTENSION; - this.balloon.addPreviewHandler('burl', this._handlePreview); - }, - - /** - * Checks if "preview" for a given node can be handled by this app. - * If it can handle it, return a handler to preview the file - * - * @param string mime - * @return void|function - */ - _handlePreview: function(node) { - if (app.isBurlFile(node)) { - return function(node) { - app.handleBurl(node); - } - } - }, - - /** - * Check if file is .burl - * - * @param object node - * @return bool - */ - isBurlFile: function(node) { - return this.BURL_EXTENSION === this.balloon.getFileExtension(node); + app.balloon.addFileHandler({ + app: 'balloon burl', + ext: 'burl', + handler: app.handleBurl + }); }, - addBurl: function() { var $d = $.Deferred(); var $div = $('
'); @@ -67,8 +45,8 @@ var app = { $div.html( '
'+ '
'+ - ''+ - ''+ + '
'+ + '
'+ '
'+ '
'+ ''+ @@ -115,7 +93,7 @@ var app = { return; } - if(app.balloon.nodeExists(name+'.'+app.BURL_EXTENSION) || name === '') { + if(app.balloon.nodeExists(name+'.'+app.BURL_EXTENSION, true) || name === '') { $input_name.addClass('error-input'); fieldsValid.name = false; } else { @@ -183,6 +161,16 @@ var app = { url: this.balloon.base+'/files?name='+name+'&'+this.balloon.param('collection', this.balloon.getCurrentCollectionId()), type: 'PUT', data: url, + snackbar: { + message: 'app.burl.snackbar.file_created', + values: { + name: name + }, + icon: 'undo', + iconAction: function(response) { + app.balloon.remove(response, true, true); + } + }, success: function(data) { this.balloon.refreshTree('/collections/children', {id: this.balloon.getCurrentCollectionId()}); app.balloon.added_rename = data.id; @@ -206,20 +194,20 @@ var app = { * @return void */ handleBurl: function(node) { - this.balloon.xmlHttpRequest({ - url: this.balloon.base+'/files/content', + app.balloon.xmlHttpRequest({ + url: app.balloon.base+'/files/content', type: 'GET', data: { - id: this.balloon.id(node), + id: app.balloon.id(node), }, dataType: 'text', success: function (data) { try { let url = new URL(data); var msg = i18next.t('app.burl.prompt.open_burl', url.href); - this.balloon.promptConfirm(msg, this._handleBurl, [url]); + app.balloon.promptConfirm(msg, app._handleBurl, [url]); } catch (error) { - this.balloon.displayError(error); + app.balloon.displayError(error); } }.bind(this) }); diff --git a/src/app/Balloon.App.Burl/locale/de.json b/src/app/Balloon.App.Burl/locale/de.json index 4eec245..a04d732 100644 --- a/src/app/Balloon.App.Burl/locale/de.json +++ b/src/app/Balloon.App.Burl/locale/de.json @@ -8,6 +8,9 @@ }, "prompt": { "open_burl": "Willst du %s in einem neuen Tab öffnen?" + }, + "snackbar": { + "file_created": "Die URL-Verknüpfung {{name}} wurde erstellt." } } } diff --git a/src/app/Balloon.App.Burl/locale/en.json b/src/app/Balloon.App.Burl/locale/en.json index 8e9d49e..c4a349b 100644 --- a/src/app/Balloon.App.Burl/locale/en.json +++ b/src/app/Balloon.App.Burl/locale/en.json @@ -8,6 +8,9 @@ }, "prompt": { "open_burl": "Do you want to open %s in a new tab?" + }, + "snackbar": { + "file_created": "The URL Shortcut {{name}} has been created." } } } diff --git a/src/app/Balloon.App.DesktopClient/lib/lib.js b/src/app/Balloon.App.DesktopClient/lib/lib.js index 5246cc8..0936943 100755 --- a/src/app/Balloon.App.DesktopClient/lib/lib.js +++ b/src/app/Balloon.App.DesktopClient/lib/lib.js @@ -46,6 +46,7 @@ var app = { + '
  • '+i18next.t('app.desktopclient.debian')+''+i18next.t('app.desktopclient.download')+'
  • ' + '
  • '+i18next.t('app.desktopclient.redhat')+''+i18next.t('app.desktopclient.download')+'
  • ' + '' + + '
    '+i18next.t('app.desktopclient.documentation')+'
    ' +'
    '); $div.off('click').on('click', 'li', this.download); diff --git a/src/app/Balloon.App.DesktopClient/locale/de.json b/src/app/Balloon.App.DesktopClient/locale/de.json index 1a5e2b1..af805bb 100755 --- a/src/app/Balloon.App.DesktopClient/locale/de.json +++ b/src/app/Balloon.App.DesktopClient/locale/de.json @@ -9,7 +9,8 @@ "debian": "DEB based (.deb)", "redhat": "RPM based (.rpm)", "download": "Download", - "hint": "Es gibt eine App, welche deine ganze Cloud auf deinen Desktop bringt. Die App gibt es für Windows, Mac OS X und Linux. Du kannst diese herunterladen, indem du auf {icon-app-download} klickst unten links." + "hint": "Es gibt eine App, welche deine ganze Cloud auf deinen Desktop bringt. Die App gibt es für Windows, Mac OS X und Linux. Du kannst diese herunterladen, indem du auf {icon-app-download} klickst unten links.", + "documentation": "In der Dokumentation (EN) sind weitere Instruktionen wie der Desktop-Client unter Linux von Paketquellen installiert werden kann." } } } diff --git a/src/app/Balloon.App.DesktopClient/locale/en.json b/src/app/Balloon.App.DesktopClient/locale/en.json index 43a0298..139c621 100755 --- a/src/app/Balloon.App.DesktopClient/locale/en.json +++ b/src/app/Balloon.App.DesktopClient/locale/en.json @@ -9,7 +9,8 @@ "debian": "DEB based (.deb)", "redhat": "RPM based (.rpm)", "download": "Download", - "hint": "There is full featured desktop client app which brings your cloud to your desktop. The desktop client is available for Windows, Mac OS X and Linux. You can get if you hit the icon {icon-app-download} on the bottom left." + "hint": "There is full featured desktop client app which brings your cloud to your desktop. The desktop client is available for Windows, Mac OS X and Linux. You can get if you hit the icon {icon-app-download} on the bottom left.", + "documentation": "Also see the desktop client installation documentation if you want further information or instructions how to install the desktop client on Linux via package sources." } } } diff --git a/src/app/Balloon.App.Elasticsearch/lib/lib.js b/src/app/Balloon.App.Elasticsearch/lib/lib.js new file mode 100644 index 0000000..7061f25 --- /dev/null +++ b/src/app/Balloon.App.Elasticsearch/lib/lib.js @@ -0,0 +1,149 @@ +/** + * balloon + * + * @copyright Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com) + * @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 + */ + +import $ from "jquery"; +import i18next from 'i18next'; +import css from '../styles/style.scss'; + +var app = { + id: 'Balloon.App.Elasticsearch', + + balloon: null, + + render: function() { + }, + + preInit: function(core) { + this.balloon = core; + + core.search_modes['fulltext'] = { + label: 'app.elasticsearch.fulltext', + buildQuery: app.buildQuery, + executeQuery: app.executeQuery + }; + }, + + /** + * execute a query + * + * @param object query + * @return boolean + */ + executeQuery: function(query) { + return app.balloon.refreshTree('/files/search', {query: query}); + }, + + /** + * build query + * + * @param string value + * @param object filters + * @return object + */ + buildQuery: function(value, filters) { + var should = []; + var must = app._buildFilterQuery(filters); + + var query = { + body: { + from: 0, + size: 500, + query: {bool: {}} + } + }; + + if(value) { + should.push({ + match: { + name: { + query: value, + minimum_should_match: "90%" + } + } + }); + + should.push({ + match: { + 'content.content': { + query: value, + minimum_should_match: "90%" + } + } + }); + } + + if(must.length === 0 && should.length === 0) { + return undefined; + } else if(must.length === 0) { + query.body.query.bool.should = should; + } else if(should.length === 0) { + query.body.query.bool.must = must; + } else { + query.body.query.bool.should = should; + query.body.query.bool.minimum_should_match = 1; + query.body.query.bool.must = must; + } + + return query; + }, + + /** + * build filters query + * + * @param object filters + * @return array + */ + _buildFilterQuery(filters) { + var must = [], i; + + if(filters && filters.tags && filters.tags.length > 0) { + var should = []; + + for(i=0; i 0) { + var should = []; + + for(i=0; i 0) { + var should = []; + + for(i=0; i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.ttf b/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.ttf new file mode 100755 index 0000000..7a9d826 Binary files /dev/null and b/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.ttf differ diff --git a/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.woff b/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.woff new file mode 100755 index 0000000..2eb4cbb Binary files /dev/null and b/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.woff differ diff --git a/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.woff2 b/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.woff2 new file mode 100755 index 0000000..c2d1142 Binary files /dev/null and b/src/app/Balloon.App.ExternalStorage/dotsfont/dotsfont.woff2 differ diff --git a/src/app/Balloon.App.ExternalStorage/lib/lib.js b/src/app/Balloon.App.ExternalStorage/lib/lib.js index 0e9b205..d7b99e5 100755 --- a/src/app/Balloon.App.ExternalStorage/lib/lib.js +++ b/src/app/Balloon.App.ExternalStorage/lib/lib.js @@ -48,8 +48,8 @@ var app = { ''+ ''+ ''+ - ''+ - ''+ + ''+ + ''+ ''+ ''+ '
    '+ @@ -95,7 +95,7 @@ var app = { return; } - if(app.balloon.nodeExists(name) || name === '') { + if(app.balloon.nodeExists(name, true) || name === '') { $input_name.addClass('error-input'); fieldsValid.name = false; } else { @@ -181,6 +181,16 @@ var app = { app.balloon.xmlHttpRequest({ url: app.balloon.base+'/collections', type: 'POST', + snackbar: { + message: 'app.externalstorage.snackbar.created', + values: { + name: name + }, + icon: 'undo', + iconAction: function(response) { + app.balloon.remove(response); + } + }, data: { name: name, id: app.balloon.getCurrentCollectionId(), diff --git a/src/app/Balloon.App.ExternalStorage/locale/de.json b/src/app/Balloon.App.ExternalStorage/locale/de.json index e079b63..eb98944 100644 --- a/src/app/Balloon.App.ExternalStorage/locale/de.json +++ b/src/app/Balloon.App.ExternalStorage/locale/de.json @@ -13,6 +13,9 @@ "invalid_share": "Die Freigabe auf dem Server kann nicht gefunden werden. Bitte kontrolliere deine Eingabe.", "invalid_credentials": "Die Authentifizierung ist fehlgeschlagen. Bitte kontrolliere deine Eingabe." }, + "snackbar": { + "created": "Der Externe Speicher {{name}} wurde erstellt." + }, "hint": "Es ist möglich externen Speicher in balloon zu verbinden. So kannst du Daten auf diesem Speicher direkt oder via balloon bearbeiten. Bis jetzt wird nur SMB/CIFS unterstützt. Externer Speicher ist als {icon-folder-storage} sichtbar. Externer Speicher kann auch via balloon geteilt werden." } } diff --git a/src/app/Balloon.App.ExternalStorage/locale/en.json b/src/app/Balloon.App.ExternalStorage/locale/en.json index 8fa4389..0cfbb11 100644 --- a/src/app/Balloon.App.ExternalStorage/locale/en.json +++ b/src/app/Balloon.App.ExternalStorage/locale/en.json @@ -13,6 +13,9 @@ "invalid_share": "The share can not be found, please verify your input.", "invalid_credentials": "Authentication failed, please verify your credentials." }, + "snackbar": { + "created": "The External Storage {{name}} has been created." + }, "hint": "It is possible to mount external storage into balloon. Therefore you can manage files on the external storage itself or via balloon. Currently only SMB/CIFS is supported. An external storage mount is visible as {icon-folder-storage}. You may also share a mount via balloon itself." } } diff --git a/src/app/Balloon.App.ExternalStorage/styles/style.scss b/src/app/Balloon.App.ExternalStorage/styles/style.scss index fb3430e..ab36d93 100755 --- a/src/app/Balloon.App.ExternalStorage/styles/style.scss +++ b/src/app/Balloon.App.ExternalStorage/styles/style.scss @@ -1,6 +1,19 @@ @import '../../../themes/default/scss/includes/colors.scss'; @import '../../../themes/default/scss/includes/mixins.scss'; +$dotfontsPath: '../dotsfont'; +@font-face { + font-family: 'dotsfont'; + src: url('#{$dotfontsPath}/dotsfont.eot'); + src: url('#{$dotfontsPath}/dotsfont.eot?#iefix') format('embedded-opentype'), + url('#{$dotfontsPath}/dotsfont.woff2') format('woff2'), + url('#{$dotfontsPath}/dotsfont.woff') format('woff'), + url('#{$dotfontsPath}/dotsfont.ttf') format('truetype'), + url('#{$dotfontsPath}/dotsfont.svg#dotsfontregular') format('svg'); + font-weight: 400; + font-style: normal; +} + #fs-external-storage { display: none; width: 560px; @@ -10,6 +23,11 @@ display: block; } + input[name=password] { + font-family: 'dotsfont' !important; + font-size: 8px; + } + .fs-external-storage-select { position: relative; } diff --git a/src/app/Balloon.App.IntelligentCollection/lib/lib.js b/src/app/Balloon.App.IntelligentCollection/lib/lib.js index 0107464..e9bd245 100644 --- a/src/app/Balloon.App.IntelligentCollection/lib/lib.js +++ b/src/app/Balloon.App.IntelligentCollection/lib/lib.js @@ -123,7 +123,7 @@ var app = { return; } - if(app.balloon.nodeExists(name) || name === '') { + if(app.balloon.nodeExists(name, true) || name === '') { $input_name.addClass('error-input'); app.fieldsValid.name = false; } else { @@ -208,6 +208,16 @@ var app = { type: 'POST', contentType: 'application/json', data: JSON.stringify(data), + snackbar: { + message: 'app.intelligentCollection.snackbar.created', + values: { + name: name + }, + icon: 'undo', + iconAction: function(response) { + app.balloon.remove(response, true, true); + } + }, success: function(data) { $d.resolve(data); }.bind(this), diff --git a/src/app/Balloon.App.IntelligentCollection/locale/de.json b/src/app/Balloon.App.IntelligentCollection/locale/de.json index d905aef..7cfd5f1 100644 --- a/src/app/Balloon.App.IntelligentCollection/locale/de.json +++ b/src/app/Balloon.App.IntelligentCollection/locale/de.json @@ -59,6 +59,9 @@ "years": "Jahre" } } + }, + "snackbar": { + "created": "Der Intelligente Ordner {{name}} wurde erstellt." } } } diff --git a/src/app/Balloon.App.IntelligentCollection/locale/en.json b/src/app/Balloon.App.IntelligentCollection/locale/en.json index a1a7f77..fea688e 100644 --- a/src/app/Balloon.App.IntelligentCollection/locale/en.json +++ b/src/app/Balloon.App.IntelligentCollection/locale/en.json @@ -60,6 +60,9 @@ "years": "Years" } } + }, + "snackbar": { + "created": "The intelligent folder {{name}} has been created." } } } diff --git a/src/app/Balloon.App.Markdown/lib/lib.js b/src/app/Balloon.App.Markdown/lib/lib.js new file mode 100644 index 0000000..b49b63b --- /dev/null +++ b/src/app/Balloon.App.Markdown/lib/lib.js @@ -0,0 +1,441 @@ +/** + * balloon + * + * @copyright Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com) + * @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 + */ + +import $ from "jquery"; +import i18next from 'i18next'; +import SimpleMDE from 'simplemde'; +import balloonWindow from '../../../lib/widget-balloon-window.js'; +import css from '../styles/style.scss'; +import iconsSvg from '@gyselroth/icon-collection/src/icons.svg'; + +const showdown = require('showdown'); +const showdownHighlight = require("showdown-highlight"); +import login from '../../../lib/auth.js'; + +var app = { + id: 'Balloon.App.Markdown', + + /** + * File extension for Markdown files + */ + MARKDOWN_EXTENSION: 'md', + + balloon: null, + + editor: {}, + + render: function() { + this.initExtensions(); + }, + + preInit: function(core) { + this.balloon = core; + + this.balloon.addNew(app.MARKDOWN_EXTENSION, 'app.markdown.markdownFile', 'file-markdown', function(type) { + return core.showNewNode(type, core.addFile); + }); + + this.balloon.fileExtIconMap[app.MARKDOWN_EXTENSION] = 'gr-i-file-markdown'; + this.balloon.mimeFileExtMap['text/markdown'] = app.MARKDOWN_EXTENSION; + + app.balloon.addFileHandler({ + app: 'Markdown Editor (SimpleMDE)', + appIcon: null, + ext: 'md', + handler: app.editMarkdownFile + }); + }, + + /** + * Edit a markdown file + * + * @param object node + * @return void + */ + editMarkdownFile: function(node) { + app._initializeMarkdownEditor(); + + app.editor.node = node; + + var ext = app.balloon.getFileExtension(node); + var winTitle = node.name + + if(ext != null && node.directory == false) { + winTitle = node.name.substr(0, node.name.length-ext.length-1) + ' (' + ext.toUpperCase() + ')'; + } + + app.balloon.xmlHttpRequest({ + url: app.balloon.base+'/files/content', + type: 'GET', + data: { + id: app.balloon.id(node), + }, + dataType: 'text', + success: function (data) { + app.editor.data = data; + + app.editor.k_window = app.$windowHtml.kendoBalloonWindow({ + title: winTitle, + resizable: false, + modal: false, + draggable: false, + fullscreen: true, + deactivate: function(e) { + e.sender.destroy(); + }, + close: function(e) { + if(e.userTriggered && app._editorCancel() === false) { + e.preventDefault(); + } + }, + open: function(e) { + app.editor.simplemde.value(app.editor.data); + + if(app.editor.data.length > 0) { + app._togglePreview(true); + } else { + app._togglePreview(false); + } + } + }).data("kendoBalloonWindow").center(); + } + }); + + app.$windowHtml.find('input[type=submit]').off('click').on('click', function(e) { + app._editorSave(); + }); + }, + + /** + * Toglgle the preview view + * + * @return void + */ + _togglePreview: function(forcePreview) { + if(forcePreview || app.$windowHtml.hasClass('preview-active') === false) { + app.$windowHtml.find('#app-markdown-edit-live-preview-content') + .html(app._renderMarkdown(app.editor.data)); + app.$windowHtml.addClass('preview-active'); + app.$windowHtml.find('#app-markdown-edit-preview-button-wrapper input[name="edit"]') + .off('click').on('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + + app._togglePreview(); + }); + } else { + app.$windowHtml.removeClass('preview-active'); + app.editor.simplemde.value(app.editor.data); + } + }, + + /** + * Initializes the editor + * + * @return void + */ + _initializeMarkdownEditor: function() { + app.editor = { + simplemde: null, + data: '', + k_window: null, + node: null + }; + + $('#app-markdown-edit-live').remove(); + + this.$windowHtml = $( + '
    '+ + '
    '+ + ''+ + '
    '+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    '+ + '
    '+ + '
    ' + ); + + $('body').append(this.$windowHtml); + + app.editor.simplemde = new SimpleMDE({ + element: this.$windowHtml.find('textarea')[0], + autofocus: true, + toolbar: [ + { + name: 'bold', + action: SimpleMDE.toggleBold, + className: 'gr-icon-bold', + title: 'Bold' + }, { + name: 'italic', + action: SimpleMDE.toggleItalic, + className: 'gr-icon-italic', + title: 'Italic' + }, + + '|', + + { + name: 'heading-1', + action: SimpleMDE.toggleHeading1, + className: 'gr-icon-heading-big', + title: 'Heading 1' + }, { + name: 'heading-2', + action: SimpleMDE.toggleHeading2, + className: 'gr-icon-heading-medium', + title: 'Heading 2' + }, { + name: 'heading-3', + action: SimpleMDE.toggleHeading3, + className: 'gr-icon-heading-small', + title: 'Heading 3' + }, + + '|', + + { + name: 'code', + action: SimpleMDE. toggleCodeBlock, + className: 'gr-icon-html', + title: 'Code' + }, { + name: 'quote', + action: SimpleMDE.toggleBlockquote, + className: 'gr-icon-quote', + title: 'Quote' + }, { + name: 'unordered-list', + action: SimpleMDE.toggleUnorderedList, + className: 'gr-icon-insert-unordered-list', + title: 'Unordered list' + }, { + name: 'ordered-list', + action: SimpleMDE.toggleOrderedList, + className: 'gr-icon-insert-ordered-list', + title: 'Numbered list' + }, + + '|', + + { + name: 'link', + action: SimpleMDE.drawLink, + className: 'gr-icon-hyperlink', + title: 'Create link' + }, { + name: 'image', + action: SimpleMDE.drawImage, + className: 'gr-icon-image', + title: 'Insert image' + }, + + '|', + + { + name: 'preview', + action: SimpleMDE.togglePreview, + className: 'gr-icon-preview', + title: 'Toggle preview' + }, + + '|', + + { + name: 'guide', + action: 'https://simplemde.com/markdown-guide', + className: 'gr-icon-help', + title: 'Markdown guide' + } + ], + spellChecker: false, + previewRender: app._renderMarkdown, + autoDownloadFontAwesome: false, + shortcuts: { + 'toggleSideBySide': null, + 'toggleFullScreen': null, + }, + }); + + this.$windowHtml.find('.editor-toolbar a').map(function(i, el) { + var icon = el.className.substring(8); + + $(el).append(''); + }); + + this.$windowHtml.unbind('drop').on('drop', function(e) { + var $fs_browser_tree = $('#fs-browser-tree'); + + $fs_browser_tree.removeClass('fs-file-dropable'); + $fs_browser_tree.find('.fs-file-drop').removeClass('fs-file-drop'); + $('#fs-upload').removeClass('fs-file-dropable'); + + app.balloon._handleFileSelect(e, app.balloon.getCurrentCollectionId()).done(function($dFiles) { + if($dFiles) { + $dFiles.map(function($dFile) { + $dFile.then(function(file) { + + var node = file.node; + var insertText = ''; + + switch(node.mime) { + case 'image/png': + case 'image/jpeg': + case 'image/png': + case 'image/gif': + case 'image/svg+xml': + insertText = '![](balloon/' + node.id + ') '; + break; + default: + insertText = '[' + node.name + '](balloon/' + node.id + ') '; + break; + } + + var pos = app.editor.simplemde.codemirror.getCursor(); + app.editor.simplemde.codemirror.setSelection(pos, pos); + app.editor.simplemde.codemirror.replaceSelection(insertText); + }); + }); + } + }); + }); + }, + + /** + * Renders given markup to html + * + * @return string rendered html + */ + _renderMarkdown: function(markdown) { + + let converter = new showdown.Converter({ + extensions: [showdownHighlight, 'href', 'balloon-image'] + }); + + let html = converter.makeHtml(markdown); + + $('#app-markdown-edit-live-preview-content, .editor-preview').off('click', 'a').on('click', 'a', function(e) { + var href = $(this).attr('href'); + + if(href.match(/^balloon\/[0-9a-fA-F]{24}$/)) { + e.preventDefault(); + + app.balloon.xmlHttpRequest({ + url: app.balloon.base+'/nodes/'+href.substr(8), + success: function(node) { + app.editor.k_window.close(); + app.balloon.openFile(node); + + if(app.balloon.id(node) !== app.balloon.id(app.balloon.last)) { + app.balloon.deselectAll(); + app.balloon._updateLastNode(node); + app.balloon._updateContentView(node); + } + } + }); + } + }); + + return html; + }, + + initExtensions: function() { + showdown.extension('href', function () { + return [{ + type: "output", + filter: function (html, converter, options) { + var $liveHtml = $('
    ').html(html); + $liveHtml.find('a').each(function(){ + $(this).attr('target', '_blank'); + }); + + return $liveHtml.html(); + } + }]; + }); + + showdown.extension('balloon-image', function () { + return [{ + type: "output", + filter: function (html, converter, options) { + var $liveHtml = $('
    ').html(html); + $liveHtml.find('img').each(function(){ + + var href = $(this).attr('src'); + + if(href.match(/^balloon\/[0-9a-fA-F]{24}$/)) { + var url = app.balloon.base+'/files/'+href.substr(8)+'/content'; + if(typeof(login) === 'object' && login.getAccessToken()) { + url += '?access_token='+login.getAccessToken(); + } + + $(this).attr('src', url); + } + }); + + return $liveHtml.html(); + } + }]; + }); + }, + + /** + * Save the content of the editor + * + * @return void + */ + _editorSave: function() { + app.balloon.saveFile(app.editor.node, app.editor.simplemde.value()); + app._closeEditorWindow(); + }, + + /** + * Check if editor has unsaved changes, display prompt if unsaved changes have been detected + * + * @return void + */ + _editorCancel: function() { + if(app.editor.simplemde === undefined) { + return; + } + + if(app.editor.data == app.editor.simplemde.value()) { + app._closeEditorWindow(); + return true; + } + + var msg = i18next.t('prompt.close_save_file', app.editor.node.name); + app.balloon.promptConfirm(msg, function(){ + app._editorSave(); + }); + + $("#fs-prompt-window").find('input[name=cancel]').unbind('click').bind('click', function(){ + $("#fs-prompt-window").data('kendoBalloonWindow').close(); + app._closeEditorWindow(); + }); + + return false; + }, + + /** + * Close the editor window + * + * @return void + */ + _closeEditorWindow: function() { + app.editor.k_window.close(); + app.editor = {}; + } +} + +export default app; diff --git a/src/app/Balloon.App.Markdown/locale/de.json b/src/app/Balloon.App.Markdown/locale/de.json new file mode 100644 index 0000000..e76e46b --- /dev/null +++ b/src/app/Balloon.App.Markdown/locale/de.json @@ -0,0 +1,14 @@ +{ + "app": { + "markdown": { + "markdownFile": "Markdown-Dokument", + "edit": "Bearbeiten" + } + }, + + "new_node": { + "title": { + "md": "Neues Markdown-Dokument" + } + } +} diff --git a/src/app/Balloon.App.Markdown/locale/en.json b/src/app/Balloon.App.Markdown/locale/en.json new file mode 100644 index 0000000..60db32b --- /dev/null +++ b/src/app/Balloon.App.Markdown/locale/en.json @@ -0,0 +1,14 @@ +{ + "app": { + "markdown": { + "markdownFile": "Markdown Document", + "edit": "Edit" + } + }, + + "new_node": { + "title": { + "md": "New Markdown Document" + } + } +} diff --git a/src/app/Balloon.App.Markdown/styles/_preview.scss b/src/app/Balloon.App.Markdown/styles/_preview.scss new file mode 100644 index 0000000..5e554d0 --- /dev/null +++ b/src/app/Balloon.App.Markdown/styles/_preview.scss @@ -0,0 +1,126 @@ +@import '../../../themes/default/scss/includes/colors.scss'; + +.CodeMirror .editor-preview, +#app-markdown-edit-live-preview { + background-color: $colorWhite; + + h1 { + font-size: 2em; + line-height: 2.4em; + margin: 1.2em 0 0.6em; + } + + h2 { + font-size: 1.8em; + line-height: 2.2em; + margin: 1.1em 0 0.55em; + } + + h3 { + font-size: 1.6em; + line-height: 2em; + margin: 1em 0 0.5em; + } + + h4 { + font-size: 1.4em; + line-height: 1.8em; + margin: 0.9em 0 0.45em; + } + + h5 { + font-size: 1.2em; + line-height: 1.6em; + margin: 0.8em 0 0.4em; + } + + h6, p, ul, ol, pre, blockquote { + font-size: 1em; + line-height: 1.4em; + margin-top: 0.7em; + color: $colorDarkGrey; + } + + h6 { + font-weight: bold; + } + + ul { + padding-left: 1.3em; + + li { + list-style-type: disc; + list-style-position: outside; + } + } + + ol { + padding-left: 1.3em; + + li { + list-style-type: decimal; + list-style-position: outside; + } + } + + pre { + background-color: transparent; + padding: 0; + margin: 1.4em 0; + + code { + display: inline-block; + width: 100%; + box-sizing: border-box; + } + } + + blockquote { + font-style: italic; + color: $colorMediumGrey; + + p { + display: inline; + color: inherit; + } + + &:before, + &:after { + content: "\201E"; + font-size: 1em; + } + + &:after { + content: "\201C"; + margin-left: -3px; + } + } + + img { + width: auto; + max-width: 100%; + margin-top: 0.7em; + } + + h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, img { + &:first-child { + margin-top: 0; + } + } + + code { + font-family: monospace; + background-color: $colorBrightGrey; + border-radius: 4px; + padding: 0.2em 0.4em; + } + + a, + a:visited { + color: $colorActiveBlue; + + &:hover { + text-decoration: underline; + } + } +} diff --git a/src/app/Balloon.App.Markdown/styles/style.scss b/src/app/Balloon.App.Markdown/styles/style.scss new file mode 100644 index 0000000..f330b75 --- /dev/null +++ b/src/app/Balloon.App.Markdown/styles/style.scss @@ -0,0 +1,237 @@ +@import '../../../themes/default/scss/includes/colors'; +@import '../../../themes/default/scss/includes/mixins'; + +@import '../../../../node_modules/codemirror/lib/codemirror.css'; + +#app-markdown-edit-live { + #app-markdown-edit-live-editor { + display: block; + height: 100%; + } + + #app-markdown-edit-live-preview { + display: none; + } + + &.preview-active { + #app-markdown-edit-live-editor { + display: none; + } + + #app-markdown-edit-live-preview { + display: block; + height: 100%; + + #app-markdown-edit-live-preview-content { + height: calc(100% - 77px); + overflow-y: auto; + overflow-x: hidden; + } + } + } +} + +.editor-toolbar { + $buttonHeight: 30px; + + a { + @include icon-button-small($buttonHeight, $buttonHeight); + + &.active { + color: $colorWhite; + border-color: $colorActiveBlue; + background-color: $colorActiveBlue; + } + } + + i.separator { + display: inline-block; + text-indent: -10px; + border-left: 1px solid $colorLightGrey; + color: transparent; + height: 22px; + position: relative; + top: -12px; + } + + a, + i.separator { + margin-right: 5px; + + &:last-child { + margin-right: 0; + } + } +} + +.CodeMirror { + height: calc(100% - 51px - 77px - 22px - 29px); + border: 1px solid #ddd; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + padding: 10px; + font: inherit; + z-index: 1; +} + +.CodeMirror-scroll { + height: 100%; +} + +.CodeMirror-fullscreen { + background: #fff; + position: fixed !important; + top: 50px; + left: 0; + right: 0; + bottom: 0; + height: auto; + z-index: 9; +} + +.CodeMirror-sided { + width: 50% !important; +} + +.editor-statusbar { + padding: 8px 10px; + font-size: 12px; + color: #959694; + text-align: right; +} + +.editor-statusbar span { + display: inline-block; + min-width: 4em; + margin-left: 1em; +} + +.editor-statusbar .lines:before { + content: 'lines: ' +} + +.editor-statusbar .words:before { + content: 'words: ' +} + +.editor-statusbar .characters:before { + content: 'characters: ' +} + +.editor-preview { + padding: 10px; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: #fafafa; + z-index: 7; + overflow: auto; + display: none; + box-sizing: border-box; +} + +.editor-preview-side { + padding: 10px; + position: fixed; + bottom: 0; + width: 50%; + top: 50px; + right: 0; + background: #fafafa; + z-index: 9; + overflow: auto; + display: none; + box-sizing: border-box; + border: 1px solid #ddd; +} + +.editor-preview-active-side { + display: block +} + +.editor-preview-active { + display: block +} + +.editor-preview>p, +.editor-preview-side>p { + margin-top: 0 +} + +.editor-preview pre, +.editor-preview-side pre { + background: #eee; + margin-bottom: 10px; +} + +.editor-preview table td, +.editor-preview table th, +.editor-preview-side table td, +.editor-preview-side table th { + border: 1px solid #ddd; + padding: 5px; +} + +.CodeMirror .CodeMirror-code .cm-tag { + color: #63a35c; +} + +.CodeMirror .CodeMirror-code .cm-attribute { + color: #795da3; +} + +.CodeMirror .CodeMirror-code .cm-string { + color: #183691; +} + +.CodeMirror .CodeMirror-selected { + background: #d9d9d9; +} + +.CodeMirror .CodeMirror-code .cm-header-1 { + font-size: 200%; + line-height: 200%; +} + +.CodeMirror .CodeMirror-code .cm-header-2 { + font-size: 160%; + line-height: 160%; +} + +.CodeMirror .CodeMirror-code .cm-header-3 { + font-size: 125%; + line-height: 125%; +} + +.CodeMirror .CodeMirror-code .cm-header-4 { + font-size: 110%; + line-height: 110%; +} + +.CodeMirror .CodeMirror-code .cm-comment { + background: rgba(0, 0, 0, .05); + border-radius: 2px; +} + +.CodeMirror .CodeMirror-code .cm-link { + color: #7f8c8d; +} + +.CodeMirror .CodeMirror-code .cm-url { + color: #aab2b3; +} + +.CodeMirror .CodeMirror-code .cm-strikethrough { + text-decoration: line-through; +} + +.CodeMirror .CodeMirror-placeholder { + opacity: .5; +} + + +@import '../../../../node_modules/codemirror-spell-checker/src/css/spell-checker.css'; +@import './preview'; +@import '../../../../node_modules/highlight.js/styles/hybrid.css'; diff --git a/src/app/Balloon.App.Notification/styles/messages/_dropdown-header.scss b/src/app/Balloon.App.Notification/styles/messages/_dropdown-header.scss index b867b47..756d439 100644 --- a/src/app/Balloon.App.Notification/styles/messages/_dropdown-header.scss +++ b/src/app/Balloon.App.Notification/styles/messages/_dropdown-header.scss @@ -20,10 +20,23 @@ right: 0; } - @media screen and (min-width: $breakpointMenuVisible) { + @media screen and (min-width: $breakpointNotificationsL) { h3 { font-size: 24px; line-height: 27px; } } } + +@media screen and (min-width: $breakpointPannelVisible) { + .fs-fullscreen-window-open { + #fs-notifications-header { + margin-bottom: 26px; + + h3 { + font-size: 15px; + line-height: 27px; + } + } + } +} diff --git a/src/app/Balloon.App.Notification/styles/messages/_dropdown-messages.scss b/src/app/Balloon.App.Notification/styles/messages/_dropdown-messages.scss index 27e8b52..308e974 100644 --- a/src/app/Balloon.App.Notification/styles/messages/_dropdown-messages.scss +++ b/src/app/Balloon.App.Notification/styles/messages/_dropdown-messages.scss @@ -106,7 +106,7 @@ } - @media screen and (min-width: $breakpointIncreasePadding) { + @media screen and (min-width: $breakpointNotificationsXL) { &:after { bottom: 40px; left: 56px; @@ -114,3 +114,13 @@ } } } + +@media screen and (min-width: $breakpointPannelVisible) { + #fs-notifications-messages { + &:after { + bottom: 20px; + left: 20px; + width: calc(100% - #{2 * 20px}); + } + } +} diff --git a/src/app/Balloon.App.Notification/styles/messages/_dropdown.scss b/src/app/Balloon.App.Notification/styles/messages/_dropdown.scss index bd7f2c2..b607789 100644 --- a/src/app/Balloon.App.Notification/styles/messages/_dropdown.scss +++ b/src/app/Balloon.App.Notification/styles/messages/_dropdown.scss @@ -1,4 +1,5 @@ -$breakpointIncreasePadding: $breakpointSearchVisible; +$breakpointNotificationsL: 550px; +$breakpointNotificationsXL: $breakpointPannelVisible; @import './dropdown-header'; @import './dropdown-messages'; @@ -12,11 +13,11 @@ Styles for "message center" & > li { #fs-notifications-dropdown-wrap { width: 640px; - max-width: calc(100vw - 17px); - right: -52px; + max-width: calc(100vw - 20px); + right: -109px; .bln-dropdown-spike { - right: 54px; + right: 111px; } #fs-notifications-dropdown { @@ -35,13 +36,18 @@ Styles for "message center" } } - @media screen and (min-width: $breakpointMenuVisible) { + @media screen and (min-width: $breakpointNotificationsL) { #fs-notifications-dropdown-wrap { - max-width: calc(100vw - 87px); + max-width: calc(100vw - 107px); + right: -52px; + + .bln-dropdown-spike { + right: 54px; + } } } - @media screen and (min-width: $breakpointIncreasePadding) { + @media screen and (min-width: $breakpointNotificationsXL) { #fs-notifications-dropdown-wrap { #fs-notifications-dropdown { padding: 37px 56px 40px; @@ -52,3 +58,28 @@ Styles for "message center" } } } + +@media screen and (min-width: $breakpointPannelVisible) { + .fs-fullscreen-window-open { + #fs-browser-top-bar { + #fs-identity { + & > ul { + & > li { + #fs-notifications-dropdown-wrap { + width: 400px; + right: -90px; + + .bln-dropdown-spike { + right: 92px; + } + + #fs-notifications-dropdown { + padding: 27px 20px 20px; + } + } + } + } + } + } + } +} diff --git a/src/app/Balloon.App.Office/lib/lib.js b/src/app/Balloon.App.Office/lib/lib.js index 024bd8c..5911341 100755 --- a/src/app/Balloon.App.Office/lib/lib.js +++ b/src/app/Balloon.App.Office/lib/lib.js @@ -11,152 +11,92 @@ import css from '../styles/style.scss'; var app = { id: 'Balloon.App.Office', - - OFFICE_EXTENSIONS: ['csv', 'odt','ott','ott','docx','doc','dot','rtf','xls','xlsx','xlt','ods','ots','ppt','pptx','odp','otp','potm'], + wopiHosts: [], + handlers: [], + supported: [], render: function() { }, - preInit: function(core) { - this.balloon = core; - - var callback = function(type) { - return core.showNewNode(type, app.addOfficeFile); - }; - - app.balloon.addNew('docx', 'app.office.word_document', 'file-word', callback); - app.balloon.addNew('xlsx', 'app.office.excel_document', 'file-excel', callback); - app.balloon.addNew('pptx', 'app.office.powerpoint_document', 'file-powerpoint', callback); - - app.balloon.addPreviewHandler('office', this._handlePreview); - }, - - edit: function(node) { - $('#fs-edit-office').remove(); - + refreshWopiHosts: function() { app.balloon.xmlHttpRequest({ - url: app.balloon.base+'/office/documents?id='+app.balloon.id(node), - success: function(doc) { - if(doc.session.length === 0) { - app.newSession(node, doc); - } else { - if(doc.session.length === 1) { - app.promptSingleSessionJoin(node, doc, doc.session[0]); - } else { - app.promptSelectSessionJoin(node, doc); + url: app.balloon.base+'/office/hosts', + success: function(data) { + app.wopiHosts = data.data; + + for(let host of app.wopiHosts) { + for(let zone of app.toArray(host.discovery['net-zone'])) { + if(zone['@attributes'].name === 'external-https' || zone['@attributes'].name === 'external-http') { + for(let wopiApp of app.toArray(zone.app)) { + for(let action of app.toArray(wopiApp.action)) { + if(app.supported.indexOf(host.name+'-'+action['@attributes'].ext) === -1) { + app.supported.push(host.name+'-'+action['@attributes'].ext); + + app.balloon.addFileHandler({ + app: host.name+' - '+wopiApp['@attributes'].name, + appIcon: wopiApp['@attributes'].favIconUrl, + ext: action['@attributes'].ext, + handler: app.fileHandler, + context: { + url: action['@attributes'].urlsrc, + } + }); + } + } + } + } } } } }); }, - promptSelectSessionJoin: function(node, doc) { - $("#fs-office-join-prompt").remove(); - - var msg = i18next.t('app.office.session.prompt.message_select', node.name); - msg += '
      '; - for(var i in doc.session) { - msg += '
    • ' - +i18next.t('app.office.session.prompt.message_select_by_user', doc.session[i].user.name, app.balloon.timeSince(new Date((doc.session[i].created*1000))))+'
    • '; - } - msg += '
    '; - - var $div = $('
    ' - +'
    '+msg+'
    ' - +'
    ' - +' ' - +' ' - +'
    ' - +'
    '); - - $("#fs-namespace").append($div); - $div.find('input[name=session]:first').attr('checked', true); - - $div.find('input[name=join]').unbind('click').click(function(e) { - e.stopImmediatePropagation(); - $div.data('kendoBalloonWindow').close(); - app.joinSession(node, doc, $div.find('input[name=session]:checked').val()); - }); - - app.sessionPrompt($div, node, doc); - }, - - sessionPrompt: function($div, node, doc) { - var $k_prompt = $div.kendoBalloonWindow({ - title: $div.attr('title'), - resizable: false, - modal: true, - activate: function() { - setTimeout(function() { - $div.find('input[name=join]').focus() - },200); - } - }).data("kendoBalloonWindow").center().open(); - - $div.unbind('keydown').keydown(function(e) { - if(e.keyCode === 27) { - e.stopImmediatePropagation(); - $k_prompt.close(); + toArray: function(haystack) { + if(Array.isArray(haystack)) { + return haystack; } - }); - $div.find('input[name=new]').unbind('click').click(function(e) { - e.stopImmediatePropagation(); - $k_prompt.close(); - app.newSession(node, doc); - }); + return [haystack]; }, - promptSingleSessionJoin: function(node, doc, session) { - $("#fs-office-join-prompt").remove(); - var $div = $('
    ' - +'
    '+i18next.t('app.office.session.prompt.message_one', node.name, session.user.name, app.balloon.timeSince(new Date((session.created*1000))))+'
    ' - +'
    ' - +' ' - +' ' - +'
    ' - +'
    '); - $("#fs-namespace").append($div); - - $div.find('input[name=join]').unbind('click').click(function(e) { - e.stopImmediatePropagation(); - $div.data('kendoBalloonWindow').close(); - app.joinSession(node, doc, session.id); - }); + preInit: function(core) { + this.balloon = core; + this.refreshWopiHosts(); - app.sessionPrompt($div, node, doc); - }, + var callback = function(type) { + return core.showNewNode(type, app.addOfficeFile); + }; - newSession: function(node, doc) { - app.balloon.xmlHttpRequest({ - url: app.balloon.base+'/office/sessions?id='+app.balloon.id(node), - type: 'POST', - success: function(session) { - app.initLibreOffice(node, doc, session); - } - }); + app.balloon.addNew('docx', 'app.office.word_document', 'file-word', callback); + app.balloon.addNew('xlsx', 'app.office.excel_document', 'file-excel', callback); + app.balloon.addNew('pptx', 'app.office.powerpoint_document', 'file-powerpoint', callback); }, - joinSession: function(node, doc, session_id) { + fileHandler: function(node, context) { + $('#fs-edit-office').remove(); + app.balloon.xmlHttpRequest({ - url: app.balloon.base+'/office/sessions/join?id='+session_id, + url: app.balloon.base+'/files/'+app.balloon.id(node)+'/tokens', type: 'POST', success: function(session) { - session.id = session_id; - app.initLibreOffice(node, doc, session); + app.wopiClient(node, session, context); } }); }, - initLibreOffice: function(node, doc, session) { + wopiClient: function(node, session, context) { var $div = $('
    '); $('body').append($div); var $k_display = $div.kendoBalloonWindow({ resizable: false, title: node.name, - modal: true, + modal: false, draggable: false, + fullscreen: true, + deactivate: function(e) { + e.sender.destroy(); + }, keydown: function(e) { if(e.originalEvent.keyCode !== 27) { return; @@ -165,135 +105,109 @@ var app = { e.stopImmediatePropagation(); var msg = i18next.t('app.office.close_edit_file', node.name); app.balloon.promptConfirm(msg, function(){ - app.balloon.xmlHttpRequest({ - url: app.balloon.base+'/office/sessions?id='+session.id+'&access_token='+session.access_token, - type: 'DELETE', - error: function(){}, - complete: function() { - $k_display.close(); - $div.remove(); - } - }); + $k_display.close(); + $div.remove(); }); }, open: function(e) { - app.showStartupPrompt(); - $('#fs-edit-office_wnd_title').html( $('#fs-browser-tree').find('li[gr-id="'+node.id+'"]').find('.k-in').find('> span').clone() ); - var src = session.wopi_url+app.balloon.base+'/office/wopi/document/'+session.id, - src = encodeURIComponent(src), - url = doc.loleaflet+'?WOPISrc='+src+'&title='+node.name+'&lang='+i18next.language+'&closebutton=0&revisionhistory=0'; + var src = window.location.protocol + '//' + window.location.hostname + app.balloon.base+'/office/wopi/files/'+session.node; + //var src = window.location.protocol + '//' + '10.242.2.9' + ':' + '8084'+app.balloon.base+'/office/wopi/files/'+session.node; + src = encodeURIComponent(src); + var url = app.parseUrl(context.url, src, node); $div.append( - '
    '+ - ''+ - ''+ - '
    '+ - '
    @@ -786,7 +943,7 @@

    - +
    @@ -832,7 +989,7 @@


    @@ -952,8 +1109,8 @@

    - - + +
    @@ -968,6 +1125,19 @@

    +
    +
    +
      +
      + + +
      +
      + + +
      +
      +
        @@ -976,6 +1146,20 @@

        +
        +
        +
        + + + +
        + +
        + + +
        +
        +
        diff --git a/src/lib/app.js b/src/lib/app.js index e394d5e..e2ef653 100755 --- a/src/lib/app.js +++ b/src/lib/app.js @@ -13,6 +13,8 @@ import desktop from '../app/Balloon.App.DesktopClient/lib/lib.js'; import burl from '../app/Balloon.App.Burl/lib/lib.js'; import external from '../app/Balloon.App.ExternalStorage/lib/lib.js'; import intelligentCollection from '../app/Balloon.App.IntelligentCollection/lib/lib.js'; +import elasticsearch from '../app/Balloon.App.Elasticsearch/lib/lib.js'; +import markdown from '../app/Balloon.App.Markdown/lib/lib.js'; const map = { 'Balloon.App.Office': office, @@ -23,6 +25,8 @@ const map = { 'Balloon.App.Burl': burl, 'Balloon.App.ExternalStorage': external, 'Balloon.App.IntelligentCollection': intelligentCollection, + 'Balloon.App.Elasticsearch': elasticsearch, + 'Balloon.App.Markdown': markdown, }; var app = { @@ -34,7 +38,9 @@ var app = { 'Balloon.App.DesktopClient': {enabled: true, config: {}}, 'Balloon.App.Burl': {enabled: true, config: {}}, 'Balloon.App.ExternalStorage': {enabled: true, config: {}}, - 'Balloon.App.IntelligentCollection': {enabled: true, config: {}} + 'Balloon.App.IntelligentCollection': {enabled: true, config: {}}, + 'Balloon.App.Elasticsearch': {enabled: true, config: {}}, + 'Balloon.App.Markdown': {enabled: true, config: {}} }, init: function(config) { @@ -52,7 +58,7 @@ var app = { var app = this.apps[name]; if(app.enabled === true && map[name]['render']) { - map[name].render(); + map[name].render(app.config); } } }, diff --git a/src/lib/auth.js b/src/lib/auth.js index a3338a9..a3ad8fb 100755 --- a/src/lib/auth.js +++ b/src/lib/auth.js @@ -23,8 +23,11 @@ var login = { handler: null, mayHideLoader: true, internalIdp: true, + recaptchaKey: null, init: function(config) { + this.recaptchaKey = config.recaptchaKey; + if(config && config.auth) { if(config.auth.credentials) { this.credentials = config.auth.credentials; @@ -90,6 +93,115 @@ var login = { return obj; }, + webauthnAuth: function() { + var userId = localStorage.getItem('userId'); + var $webauth_error = $('#login-error-webauthn').hide(); + + if(userId === null) { + return; + } + + login.xmlHttpRequest({ + url: '/api/v2/users/'+userId+'/request-challenges?domain='+window.location.hostname, + type: 'POST', + error: function(e) { + $webauth_error.show(); + }, + success: function(resource) { + let publicKey = resource.key; + publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c=>c.charCodeAt(0)); + publicKey.allowCredentials = publicKey.allowCredentials.map(function(data) { + return { + ...data, + 'id': Uint8Array.from(atob(data.id), c=>c.charCodeAt(0)) + }; + }); + + navigator.credentials.get({publicKey}).then(data => { + let publicKeyCredential = { + id: data.id, + type: data.type, + rawId: login.arrayToBase64String(new Uint8Array(data.rawId)), + response: { + authenticatorData: login.arrayToBase64String(new Uint8Array(data.response.authenticatorData)), + clientDataJSON: login.arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), + signature: login.arrayToBase64String(new Uint8Array(data.response.signature)), + userHandle: data.response.userHandle ? login.arrayToBase64String(new Uint8Array(data.response.userHandle)) : null + } + }; + + login.doTokenAuth({ + grant_type: 'webauthn', + public_key: publicKeyCredential, + challenge: resource.id, + user: userId, + }); + }).catch(e => { + $webauth_error.show(); + }); + } + }); + }, + + arrayToBase64String: function(a) { + return btoa(String.fromCharCode(...a)); + }, + + setupWebauthn: function() { + var $d = $.Deferred(); + + login.xmlHttpRequest({ + url: '/api/v2/creation-challenges?domain='+window.location.hostname, + type: 'POST', + success: function(resource) { + let publicKey = resource.key; + publicKey.challenge = Uint8Array.from(window.atob(publicKey.challenge), c=>c.charCodeAt(0)); + publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), c=>c.charCodeAt(0)); + + if (publicKey.excludeCredentials) { + publicKey.excludeCredentials = publicKey.excludeCredentials.map(function(data) { + return { + ...data, + 'id': Uint8Array.from(window.atob(data.id), c=>c.charCodeAt(0)) + }; + }); + } + + navigator.credentials.create({publicKey}).then(function(data) { + let publicKeyCredential = { + id: data.id, + type: data.type, + rawId: login.arrayToBase64String(new Uint8Array(data.rawId)), + response: { + clientDataJSON: login.arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), + attestationObject: login.arrayToBase64String(new Uint8Array(data.response.attestationObject)) + } + }; + + login.xmlHttpRequest({ + url: '/api/v2/devices?challenge='+resource.id, + data: publicKeyCredential, + type: 'POST', + success: function(publicKey) { + localStorage.setItem('webauthn', 'true'); + $d.resolve(); + }, + error: function(e) { + $d.reject(e); + } + }); + }).catch((e) => { + $d.reject(e); + }); + }, + error: function(e) { + $d.reject(e); + } + }); + + return $d; + }, + checkOidcAuth: function() { this.notifier = new AuthorizationNotifier(); this.handler = new RedirectRequestHandler(); @@ -122,7 +234,7 @@ var login = { var $input_username = $login.find('input[name=username]').focus(); $('#fs-namespace').hide(); - $login.find('input[type=submit]').off('click').on('click', login.initUsernamePasswordAuth); + $login.find('input[type=submit]').off('click.login').on('click.login', login.initUsernamePasswordAuth); if(localStorage.username !== undefined) { $input_username.val(localStorage.username); @@ -138,6 +250,11 @@ var login = { $login.find('.error-message').hide(); login.initOidcAuth($(this).attr('alt')); }); + + if(localStorage.getItem('webauthn') === 'true') { + $('#login-webauthn').show().off('click').on('click', login.webauthnAuth); + login.webauthnAuth(); + } }; var options = { @@ -213,6 +330,8 @@ var login = { } }); } + } else { + login.checkAuth(); } }, @@ -232,6 +351,7 @@ var login = { case 200: login.user = response.responseJSON; localStorage.username = login.user.username; + localStorage.userId = login.user.id; if(login.user.namespace) { localStorage.namespace = login.user.namespace; @@ -240,7 +360,6 @@ var login = { } login.updateFsIdentity(); - login.initBrowser(); break; @@ -262,6 +381,7 @@ var login = { success: function(body) { login.user = body; localStorage.username = login.user.username; + localStorage.userId = login.user.id; if(login.user.namespace) { localStorage.namespace = login.user.namespace; @@ -343,8 +463,16 @@ var login = { localStorage.lastIdpUrl = provider_url; AuthorizationServiceConfiguration.fetchFromIssuer(idp.providerUrl).then(configuration => { - var request = new AuthorizationRequest( - idp.clientId, idp.redirectUri, idp.scope, 'id_token token', undefined, {'nonce': Math.random().toString(36).slice(2)}); + var config = { + client_id: idp.clientId, + redirect_uri: idp.redirectUri, + scope: idp.scope, + response_type: 'id_token token', + state: undefined, + extras: {nonce: Math.random().toString(36).slice(2)} + } + + var request = new AuthorizationRequest(config); login.handler.performAuthorizationRequest(configuration, request); }); @@ -378,7 +506,11 @@ var login = { return login.doBasicAuth(username, password); } - return login.doTokenAuth(username, password); + return login.doTokenAuth({ + username: username, + password: password, + grant_type: 'password' + }); }, verifyBasicIdentity: function() { @@ -386,12 +518,18 @@ var login = { var $username_input = $login.find('input[name=username]'); var $password_input = $login.find('input[name=password]'); window.location.hash = ''; + $('#login-recaptcha').html(''); $.ajax({ type: 'GET', dataType: 'json', url: '/api/auth', complete: function(response) { + if(response.responseJSON.error === 'Balloon\\App\\Recaptcha\\Exception\\InvalidRecaptchaToken') { + login.displayRecaptcha(); + return; + } + switch(response.status) { case 401: case 403: @@ -418,7 +556,7 @@ var login = { }); }, - verifyTokenIdentity: function(response, username, password, mfa) { + verifyTokenIdentity: function(response, context, mfa) { var $login = $('#login'); var $login_mfa = $('#login-mfa'); var $username_input = $login.find('input[name=username]'); @@ -430,22 +568,25 @@ var login = { case 400: case 401: case 403: - if(response.responseJSON.error === 'Balloon\\App\\Idp\\Exception\\MultiFactorAuthenticationRequired') { + if(response.responseJSON.error === 'Balloon\\App\\Recaptcha\\Exception\\InvalidRecaptchaToken') { + login.displayRecaptcha(); + } else if(response.responseJSON.error === 'Balloon\\App\\Idp\\Exception\\MultiFactorAuthenticationRequired') { $username_input.hide(); $password_input.hide(); $login_mfa.show(); + context.grant_type = context.grant_type +'_mfa'; $login.find('input[type=submit]').focus().unbind('click').on('click', function() { - let code = $code_input.val(); + context.code = $code_input.val(); $code_input.val(''); - login.doMultiFactorTokenAuth(username, password, code); + login.doTokenAuth(context, true); }); $(document).off('keydown.token').on('keydown.token', function(e) { if(e.keyCode === 13) { - let code = $code_input.val(); + context.code = $code_input.val(); $code_input.val(''); - login.doMultiFactorTokenAuth(username, password, code); + login.doTokenAuth(context, true); } }); } else if(mfa === true) { @@ -465,8 +606,7 @@ var login = { login.adapter = 'oidc'; login.accessToken = response.responseJSON.access_token; login.refreshToken = response.responseJSON.refresh_token; - login.fetchIdentity(); - login.initBrowser(); + login.initApp(); break; default: @@ -477,6 +617,47 @@ var login = { } }, + initApp: function() { + if( + localStorage.getItem('webauthn') === null && "credentials" in navigator + && + ('ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch) + ) { + var $login_setup_webauthn = $('#login-setup-webauthn').show(); + var $webauthn_error = $('#login-webauthn-setup-error').hide(); + var $login_basic = $('#login-basic').hide(); + var $login_oidc = $('#login-oidc').hide(); + + $login_setup_webauthn.find('input[type=submit]').off('click').on('click', function() { + if($(this).attr('name') === 'ignore') { + if($login_setup_webauthn.find('input[name="webauthn-reminder"]').is(':checked')) { + localStorage.setItem('webauthn', 'false'); + } + + login.fetchIdentity(); + login.initBrowser(); + $login_setup_webauthn.hide(); + $login_basic.show(); + $login_oidc.show(); + $webauthn_error.hide(); + } else { + login.setupWebauthn().then(() => { + $login_setup_webauthn.hide(); + $login_basic.show(); + $login_oidc.show(); + login.fetchIdentity(); + login.initBrowser(); + }).catch(error => { + $webauthn_error.show(); + }); + } + }); + } else { + login.fetchIdentity(); + login.initBrowser(); + } + }, + renewToken: function() { var $d = $.Deferred(); var $spinner = $('#fs-spinner').show(); @@ -507,51 +688,40 @@ var login = { return $d; }, - doMultiFactorTokenAuth: function(username, password, code) { - var $spinner = $('#fs-spinner').show(); + getRecaptchaString: function() { + var captcha = $('.g-recaptcha-response').val() + if(captcha) { + return '?g-recaptcha-response='+captcha; + } - $.ajax({ - type: 'POST', - data: { - username: username, - password: password, - code: code, - grant_type: 'password_mfa', - }, - url: '/api/v2/tokens', - beforeSend: function (xhr) { - xhr.setRequestHeader("Authorization", "Basic " + btoa('balloon-client-web:')); - }, - complete: function(response) { - login.verifyTokenIdentity(response, username, password, true) - } - }).always(function() { - $spinner.hide(); - }); + return ''; }, - doTokenAuth: function(username, password) { + doTokenAuth: function(data, mfa) { var $spinner = $('#fs-spinner').show(); $.ajax({ type: 'POST', - data: { - username: username, - password: password, - grant_type: 'password', - }, - url: '/api/v2/tokens', + data: data, + url: '/api/v2/tokens'+login.getRecaptchaString(), beforeSend: function (xhr) { xhr.setRequestHeader("Authorization", "Basic " + btoa('balloon-client-web:')); }, complete: function(response) { - login.verifyTokenIdentity(response, username, password, false) + login.verifyTokenIdentity(response, data, mfa); + $('#login-recaptcha').html(''); } }).always(function() { $spinner.hide(); }); }, + displayRecaptcha: function() { + $.getScript('https://www.google.com/recaptcha/api.js', function() { + $('.g-recaptcha').attr('data-sitekey', login.recaptchaKey); + }); + }, + doBasicAuth: function(username, password) { var $spinner = $('#fs-spinner').show(); @@ -560,7 +730,7 @@ var login = { username: username, password: password, dataType: 'json', - url: '/api/basic-auth', + url: '/api/basic-auth'+login.getRecaptchaString(), complete: login.verifyBasicIdentity }).always(function() { $spinner.hide(); @@ -580,6 +750,8 @@ var login = { $('#login').hide(); $('#fs-namespace').show(); + $(document).off('keydown.password'); + balloon.init(); }, diff --git a/src/lib/core.js b/src/lib/core.js index 2aaeafa..05b0564 100755 --- a/src/lib/core.js +++ b/src/lib/core.js @@ -21,6 +21,10 @@ import fileExtIconMap from './file-ext-icon-map.js'; import {mimeFileExtMap} from './mime-file-ext-map.js'; import iconsSvg from '@gyselroth/icon-collection/src/icons.svg'; +import Slideout from 'slideout'; +import pullToRefresh from 'mobile-pull-to-refresh' +import ptrAnimatesIos from 'mobile-pull-to-refresh/dist/styles/ios/animates' + window.$ = $; $.ajaxSetup({ beforeSend:function(jqXHR,settings){ @@ -65,6 +69,11 @@ var balloon = { */ URL_PARAM_SELECTED_SEPARATOR: ',', + /** + * Event request limit + */ + EVENTS_PER_REQUEST: 50, + /** * Map with [FILE EXTENSION]: [SPRITE ICON CLASS] */ @@ -149,6 +158,23 @@ var balloon = { collection: null }, + /** + * Available search modes + * + * @var object + */ + search_modes: { + 'nodename': { + label: 'search.mode.nodename', + buildQuery: function(value, filters) { + return balloon._buildQueryNodename(value, filters); + }, + executeQuery: function(query) { + return balloon.refreshTree('/nodes', {query: query}); + } + } + }, + /** * Add file menu * @@ -174,11 +200,11 @@ var balloon = { }, /** - * Handlers for file previews + * File handlers * - * @var object + * @var array */ - preview_file_handlers: {}, + file_handlers: [], /** * Menu handlers @@ -213,7 +239,7 @@ var balloon = { 'shared_link': { name: 'shared_link', label: 'menu.shared_link', - icon: 'hyperlink', + icon: 'language', callback: function() { return balloon.refreshTree('/nodes', {query: {"app.Balloon\\App\\Sharelink.token": {$exists: 1}}}, {}); } @@ -239,12 +265,23 @@ var balloon = { 'help': { name: 'help', label: 'login.help', - icon: 'help', + icon: 'hint', hasDropDown: false, hasCount: false, callback: function() { balloon.displayHelpWindow(); } + }, + 'search': { + name: 'search-trigger-mobile', + label: 'nav.action.search', + icon: 'search', + hasDropDown: false, + hasCount: false, + callback: function() { + $('#fs-search').addClass('fs-search-mobile-visible'); + $('#fs-search-input').focus(); + } } }, @@ -340,10 +377,10 @@ var balloon = { var $fs_events_all = $('#fs-events-all').hide(); var $view_list = $('#fs-events ul'); - $view_list.children().remove(); - - var req = balloon.displayEvents($view_list, node, {skip: 0, limit: 3}) + $view_list.empty(); + balloon._event_limit = false; + var req = balloon.displayEvents($view_list, node, {offset: 0, limit: 3}); req.done(function(body) { if(body && body.count < body.total) { @@ -374,6 +411,24 @@ var balloon = { */ toggle_fs_browser_action_hooks: {}, + /** + * The different possible states of fs-content overlays in mobile + * + * @var array + */ + fs_content_mobile_states: ['fs-content-mobile-menu', 'fs-content-mobile-more', 'fs-content-mobile-detail'], + + /** + * Current state of fs-content overlays in mobile + * + * @var array + */ + fs_content_mobile_current_state: 0, + + /** + * Slideout + */ + slideout: null, /** * Generates a uuid v4. @@ -394,6 +449,7 @@ var balloon = { balloon.resetDom(); } else { this.base = this.base+'/v'+this.BALLOON_API_VERSION; + if(balloon.isTouchDevice()) $('body').addClass('is-touch'); } @@ -464,8 +520,32 @@ var balloon = { balloon.createDatasource(); balloon.initCrumb(); + balloon._initSwipeEvents(); + $(".fs-action-element").unbind('click').click(balloon.doAction); - $("#fs-browser-header").find("> div.fs-browser-column-sortable").unbind('click').click(balloon._sortTree); + $('#fs-browser-action-mobile').off('click').on('click', function() { + if($('#fs-browser-action').hasClass('fs-browser-action-mobile-open')) { + $('#fs-browser-action').removeClass('fs-browser-action-mobile-open'); + $(document).off('click.fs-browser-action-mobile'); + } else { + $('#fs-browser-action').addClass('fs-browser-action-mobile-open'); + + $(document).off('click.fs-browser-action-mobile').on('click.fs-browser-action-mobile', function(event){ + var $target = $(event.target); + + if($target.attr('id') === 'fs-browser-action' || $target.parents('#fs-browser-action').length > 0) return; + + $('#fs-browser-action').removeClass('fs-browser-action-mobile-open'); + }); + } + + }); + + $("#fs-browser-header").find("> div.fs-browser-column-sortable").unbind('click').click(function(event) { + var field = $(this).attr('id').substr(18); + + balloon._sortTree(field); + }); $(document).unbind('drop').on('drop', function(e) { e.stopPropagation(); @@ -502,14 +582,13 @@ var balloon = { if(balloon.isTouchDevice()) { $fs_browser_tree - .off('touchstart', '.k-in').on('touchstart', '.k-in', balloon._treeTouch) .off('touchend', '.k-in').on('touchend', '.k-in', balloon._treeTouchEnd) .off('touchmove', '.k-in').on('touchmove', '.k-in', balloon._treeTouchMove); } - if(!balloon.isTouchDevice() && balloon.isMobileViewPort()) { + if(balloon.isTouchDevice()) { $fs_browser_tree - .off('dblclick', '.k-in').on('click', '.k-in', balloon._treeDblclick); + .off('click', '.k-in').on('click', '.k-in', balloon._treeDblclick); } else { $fs_browser_tree .off('click', '.k-in').on('click', '.k-in', balloon._treeClick) @@ -522,14 +601,10 @@ var balloon = { var $fs_search_input = $fs_search.find('#fs-search-input'); var $fs_search_filter_toggle = $fs_search.find('#fs-search-toggle-filter'); var $fs_search_mode_toggle = $fs_search.find('#fs-search-mode-toggle'); + var $fs_search_mode_dropdown_ul = $fs_search.find('#fs-search-mode-dropdown ul'); $fs_search_input.off('focus').on('focus', function() { $fs_search.addClass('fs-search-focused'); - - if($('#fs-search-filter').data('initialized') !== true) { - //populate filters before opening - balloon.advancedSearch(); - } }); $fs_search_input.off('blur').on('blur', function() { @@ -566,30 +641,57 @@ var balloon = { } }); - function toggleSearchMode() { - $fs_search.toggleClass('fs-search-mode-dropdown-open'); - $(document).off('click.fs-search-mode-toggle'); + var searchModes = Object.keys(balloon.search_modes); + if(searchModes.length <= 1) { + $fs_search_mode_toggle.hide(); + } else { + $fs_search_mode_toggle.show(); - if($fs_search.hasClass('fs-search-mode-dropdown-open')) { - $(document).on('click.fs-search-mode-toggle', function(event){ - var $target = $(event.target); - var parentId = 'fs-search-mode-toggle'; + function toggleSearchMode() { + $fs_search.toggleClass('fs-search-mode-dropdown-open'); + $(document).off('click.fs-search-mode-toggle'); - if($target.attr('id') === parentId || $target.parents('#'+parentId).length > 0) return; + if($fs_search.hasClass('fs-search-mode-dropdown-open')) { + $(document).on('click.fs-search-mode-toggle', function(event){ + var $target = $(event.target); + var parentId = 'fs-search-mode-toggle'; - toggleSearchMode(); - }); + if($target.attr('id') === parentId || $target.parents('#'+parentId).length > 0) return; + + toggleSearchMode(); + }); + } } - } - $fs_search_mode_toggle.off('click').on('click', toggleSearchMode); + $fs_search_mode_toggle.off('click').on('click', toggleSearchMode); - $('input[name="fs-search-mode"]').off('change').change(function() { - $fs_search_mode_toggle.find('span').contents().last().replaceWith(i18next.t('search.mode.' + this.value)); - $fs_search.removeClass('fs-search-mode-dropdown-open'); - balloon.advancedSearch(); - $fs_search_input.focus(); - }); + $fs_search_mode_dropdown_ul.empty(); + + var i; + for(i=0; i'+ + ''+ + ''+ + '' + ); + } + + $('input[name="fs-search-mode"]').off('change').change(function() { + var value = $(this).val(); + $fs_search_mode_toggle.find('span').contents().last().replaceWith(i18next.t(balloon.search_modes[value].label)); + $fs_search.removeClass('fs-search-mode-dropdown-open'); + + balloon._refreshSearchResult(); + + $fs_search_input.focus(); + }); + + } $('#fs-namespace').unbind('dragover').on('dragover', function(e) { e.preventDefault(); @@ -637,13 +739,58 @@ var balloon = { } balloon._updateCheckAllState(); + balloon._updateFsContentSelectedState(); + }); + + $('#fs-content-close').off('click').click(function(event) { + event.preventDefault(); + event.stopPropagation(); + balloon.deselectAll(); + balloon._updateCheckAllState(); + balloon._updateFsContentSelectedState(); + balloon.selected_action = {nodes: null, command: null, collection: null}; + $('body').removeClass('fs-content-paste-active'); + }); + + $('#fs-crumb-back').off('click').on('click', function(event) { + balloon._folderUp(); + balloon.last = null; }); for(let i=1; i<=25; i++) { this.addHint("hint.hint_"+i); } + balloon.addFileHandler({ + app: i18next.t('app.file_editor'), + test: balloon.isEditable, + handler: balloon.editFile + }); + + balloon.addFileHandler({ + app: i18next.t('app.file_viewer'), + test: balloon.isViewable, + handler: balloon.displayFile + }); + + balloon.addFileHandler({ + app: i18next.t('app.file_downloader'), + test: function(node){return true;}, + handler: balloon.downloadNode + }); + balloon.showHint() + + pullToRefresh({ + container: document.getElementById('fs-browser'), + scrollable: document.getElementById('fs-layout-left'), + animates: ptrAnimatesIos, + + refresh: function() { + return balloon.reloadTree(); + } + }); + balloon.initialized = true; app.postInit(this); }, @@ -657,6 +804,8 @@ var balloon = { var i; var $fs_content_view_template = $('#fs-content-view'); var $fs_content_view = $('
        '); + var $fs_content_nav_small = $('#fs-content-nav-small'); + var keys = Object.keys(balloon.fs_content_views); for(i=0; i' + + '' + i18next.t(viewConfig.title) + '' + + ''+ + '' + ); + + $fs_content_nav_small.append($navItem); } $fs_content_view_template.replaceWith($fs_content_view); @@ -709,16 +867,7 @@ var balloon = { $menu.off('click').on('click', 'li', balloon.menuLeftAction); $('#fs-menu-left-toggl').off('click').on('click', function(event) { event.preventDefault(); - var $menu = $('#fs-menu-left'); - var $toggl = $('#fs-menu-left-toggl'); - - if($menu.hasClass('fs-menu-left-open')) { - $menu.removeClass('fs-menu-left-open'); - $toggl.removeClass('fs-menu-left-open'); - } else { - $menu.addClass('fs-menu-left-open'); - $toggl.addClass('fs-menu-left-open'); - } + balloon.slideout.toggle(); }); }, @@ -797,9 +946,11 @@ var balloon = { initAddFileMenu() { var i; var $menu = $('#fs-action-add-select ul'); + var $menuMobile = $('#fs-action-add-select-mobile ul'); var keys = Object.keys(balloon.add_file_menu_items); $menu.empty(); + $menuMobile.empty(); for(i=0; i'+ + ' ' + label + ''+ + ''+ + ''+ + ''+ + '' + ); + + $menuMobile.append($itemMobile); } }, @@ -936,7 +1098,7 @@ var balloon = { show = options.suppressSnackbar !== true && (valid.indexOf(options.type) > -1); if(show && jqXHR.status.toString().substr(0, 1) === '2') { - balloon.showSnackbar((options.snackbar || {})); + balloon.showSnackbar((options.snackbar || {}), jqXHR.responseJSON); } if(complete !== undefined) { @@ -966,7 +1128,7 @@ var balloon = { * @param object options * @return void */ - showSnackbar: function(options) { + showSnackbar: function(options, response) { var options = options || {}; var $snackbar = $('#fs-snackbar'); @@ -984,7 +1146,7 @@ var balloon = { if(iconAction) $iconWrap.addClass('has-action'); $iconWrap.off('click').on('click', function() { - if(iconAction) iconAction(); + if(iconAction) iconAction(response); }); var i18nextOptions = $.extend(values, {'interpolation': {'escapeValue': false}}); @@ -993,7 +1155,7 @@ var balloon = { setTimeout(function() { $snackbar.removeClass('show'); - }, 2900); + }, 3900); }, @@ -1300,7 +1462,25 @@ var balloon = { } } - balloon.move(balloon.getSelected(src), dest); + var source = balloon.getSelected(src); + var parentId = balloon.getCurrentCollectionId(); + + if(!Array.isArray(source)) { + source = [source]; + } + + var moveSrc = []; + + for(var i=0; i'+size+'
        '); break; @@ -1391,7 +1565,7 @@ var balloon = { switch(metaOrder[metaProp]) { case 'sharelink_token': if(node.sharelink_token) { - meta_html_children.push('
        '); + meta_html_children.push('
        '); } break; case 'deleted': @@ -1436,6 +1610,13 @@ var balloon = { $name_el = $('
        '+node.name+'
        '); } + if(balloon.id(node) !== '_FOLDERUP') { + var sizeAndChanged = balloon.nodeSize(node) + ', ' + balloon.timeSince(new Date(node.changed), true); + $name_el.append($( + '

        ' + sizeAndChanged + '

        ' + )); + } + if(balloon.isSearch() && balloon.id(node) !== '_FOLDERUP') { var path = node.path.split('/').slice(1, -1); if(path.length === 0) path = ['']; @@ -1445,7 +1626,7 @@ var balloon = { path.unshift('...'); } - var $path_el = $('

        '); + var $path_el = $('

        '); path.forEach(function(item) { $path_el.append(' / ' + item + ''); @@ -1463,7 +1644,7 @@ var balloon = { break; case 'checkbox': - var $checkbox = $('
         
        '); + var $checkbox = $('
         
        '); $checkbox.on('click', function(event) { event.stopPropagation(); @@ -1513,6 +1694,7 @@ var balloon = { balloon._updateCheckAllState(); balloon.pushState(); + balloon._updateFsContentSelectedState(); }); $node_el.append($checkbox); @@ -1549,10 +1731,27 @@ var balloon = { } balloon._updateCheckAllState(); + balloon._updateFsContentSelectedState(); balloon.fileUpload(balloon.getCurrentCollectionId(), $('#fs-layout-left')); }, + _updateFsContentSelectedState: function() { + var currentCollectionId = balloon.getCurrentCollectionId(); + + $('body').removeClass('fs-content-multiselect-active fs-content-select-active'); + + if(balloon.isMultiSelect()) { + if(balloon.multiselect.length === 1) { + $('body').addClass('fs-content-select-active'); + } else { + $('body').addClass('fs-content-multiselect-active'); + } + } else if(balloon.last && balloon.last.id !== currentCollectionId && balloon.isSystemNode(balloon.last) === false) { + $('body').addClass('fs-content-select-active'); + } + }, + /** * Kendo tree: select event * @@ -1560,10 +1759,13 @@ var balloon = { * @return void */ _treeSelect: function(e) { - $('.k-in').removeClass('fs-rename'); - var id = $(e.node).attr('data-uid'), - node = balloon.datasource.getByUid(id)._childrenOptions.data; + dataSourceNode = balloon.datasource.getByUid(id), + node; + + if(!dataSourceNode) return; + + node = dataSourceNode._childrenOptions.data; if(balloon.id(node) === balloon.id(balloon.last)) { balloon.last = node; @@ -1575,12 +1777,7 @@ var balloon = { 'history','share','share-link' ]); - var copy = balloon.last; - balloon.last = node; - - if(!balloon.isSystemNode(copy)) { - balloon.previous = copy; - } + balloon._updateLastNode(node); if(balloon.isSystemNode(node) || balloon.isMultiSelect()) { e.preventDefault(); @@ -1594,6 +1791,63 @@ var balloon = { $(e.node).find('.k-in').addClass('k-state-selected'); + balloon._updateContentView(node); + }, + + + /** + * Pop state + * + * @param object e + * @return void + */ + _statePop: function(e) { + balloon.resetDom('multiselect'); + balloon.resetDom('breadcrumb'); + balloon.previous = null; + balloon.last = null; + + balloon._resolveHash(window.location.hash.substr(1)).then(function() { + var collection = balloon.getURLParam('collection'), + menu = balloon.getURLParam('menu'); + + if(collection !== null) { + balloon.menuLeftAction(menu, false); + balloon.refreshTree('/collections/children', {id: collection}, null, {nostate: true}); + } else { + balloon.menuLeftAction(menu, true, false); + } + + if(e.originalEvent.state === null) { + balloon.buildCrumb(collection); + } else { + balloon._repopulateCrumb(e.originalEvent.state.parents); + } + }); + }, + + /** + * Update last/previous node state + * + * @param object node + * @return void + */ + _updateLastNode: function(node) { + var copy = balloon.last; + balloon.last = node; + + if(!balloon.isSystemNode(copy)) { + balloon.previous = copy; + } + }, + + /** + * Update the content view with a given node + * + * @param object node + * @return void + */ + _updateContentView: function(node) { balloon.resetDom([ 'selected', 'metadata', @@ -1626,10 +1880,7 @@ var balloon = { balloon.switchView(view); $('#fs-properties-name').show(); - - if(!balloon.isMobileViewPort()) { - balloon.updatePannel(true); - } + balloon.updatePannel(true); $('#fs-content-view dt').unbind('click').not('.disabled').click(function() { var $that = $(this), @@ -1639,37 +1890,30 @@ var balloon = { balloon.switchView(action); } }); - }, + $('#fs-content-nav-small li').off('click').not('.disabled').click(function(event) { + event.stopPropagation(); + event.preventDefault(); - /** - * Pop state - * - * @param object e - * @return void - */ - _statePop: function(e) { - balloon.resetDom('multiselect'); - balloon.resetDom('breadcrumb'); - balloon.previous = null; - balloon.last = null; + var action = $(this).attr('id').substr(15); + balloon.switchView(action, true); + balloon.fsContentMobileNext(); + }); - balloon._resolveHash(window.location.hash.substr(1)).then(function() { - var collection = balloon.getURLParam('collection'), - menu = balloon.getURLParam('menu'); + $('.fs-content-view-back').off('click').click(function(event) { + event.stopPropagation(); + event.preventDefault(); - if(collection !== null) { - balloon.menuLeftAction(menu, false); - balloon.refreshTree('/collections/children', {id: collection}, null, {nostate: true}); - } else { - balloon.menuLeftAction(menu, true, false); - } + $('#fs-content-view dd.active').removeClass('active'); - if(e.originalEvent.state === null) { - balloon.buildCrumb(collection); - } else { - balloon._repopulateCrumb(e.originalEvent.state.parents); - } + balloon.fsContentMobilePrev(); + }); + + $('#fs-content-nav-small-more li').off('click').on('click', function(event) { + event.stopPropagation(); + event.preventDefault(); + + balloon.fsContentMobileNext(); }); }, @@ -1706,7 +1950,9 @@ var balloon = { var collection = balloon.getCurrentCollectionId(); var node = balloon.getCurrentNode(); - if(!collection) { + if(balloon.isSearchResult() && collection === null) { + return balloon.buildExtendedSearchQuery(); + } else if(!collection) { return balloon.menuLeftAction(menu, true).then(function() { if(node) { balloon.last = node; @@ -1728,14 +1974,13 @@ var balloon = { scrollToNode: function(node) { var $node = $('li[fs-id="' + balloon.id(node) + '"]'); - if(!$node) return; + if(!$node || $node.length === 0) return; $('#fs-layout-left').animate({ scrollTop: ($node.offset().top - 70) }, 1000); }, - /** * Build breadcrumb with parents * @@ -1811,7 +2056,7 @@ var balloon = { //remove current collection from selection selected = selected.filter(function(node) { - return node.id !== collection; + return node !== null && node.id !== collection; }); for(var node in selected) { @@ -2014,13 +2259,17 @@ var balloon = { balloon.resetDom('user-profile'); //change password should only be possible for internal users - var mayChangePassword = (login.user && login.user.auth === 'internal'); + var mayChangePassword = (login.user && login.user.auth === 'internal' && login.user.has_password === true); $('#fs-profile-window-title-change-password').toggleClass('disabled', !mayChangePassword); $('#fs-profile-window-change-password').toggleClass('disabled', !mayChangePassword); var mayActivate2FA = (login.getAdapter() !== 'basic' && login.user && login.internalIdp === true); $('#fs-profile-window-title-google-authenticator').toggleClass('disabled', !mayActivate2FA); - $('#fs-profile-window-change-google-authenticator').toggleClass('disabled', !mayActivate2FA); + $('#fs-profile-window-google-authenticator').toggleClass('disabled', !mayActivate2FA); + + var maySetupWebauthn = ('credentials' in navigator && mayActivate2FA); + $('#fs-profile-window-title-webauthn').toggleClass('disabled', !maySetupWebauthn); + $('#fs-profile-window-change-webauthn').toggleClass('disabled', !maySetupWebauthn); $('#fs-profile-window dl dt').not('.disabled').off('click').on('click', function(event) { var view = $(this).attr('id').substr(24); @@ -2062,6 +2311,9 @@ var balloon = { case 'google-authenticator': balloon._displayUserProfileGoogleAuthenticator(); break; + case 'webauthn': + balloon._displayUserProfileWebauthn(); + break; } }, @@ -2262,13 +2514,16 @@ var balloon = { _displayUserProfileGoogleAuthenticator: function() { var $view = $('#fs-profile-window-google-authenticator'); var $buttons = $view.find('#fs-profile-window-google-authenticator-buttons'); - var $code = $view.find('#fs-profile-window-google-authenticator-code'); + var $code = $view.find('#fs-profile-window-google-authenticator-qr'); + var $secret = $view.find('#fs-profile-window-google-authenticator-secret'); var $hintInactive = $view.find('#fs-profile-window-google-authenticator-hint-incative').hide(); var $hintActive = $view.find('#fs-profile-window-google-authenticator-hint-active').hide(); var $btnActivate = $buttons.find('input[name="activate"]').hide(); var $btnDeactivate = $buttons.find('input[name="deactivate"]').hide(); $code.find('canvas').remove(); + $secret.html(''); + $secret.off('click'); if(login.user.multi_factor_auth === false) { $btnActivate.show(); @@ -2313,6 +2568,15 @@ var balloon = { var qrCode = new qrcode($code[0]); qrCode.generate(body.multi_factor_uri, qrCodeSetting); + var secret = body.multi_factor_uri.match(/secret=([A-Z0-9]*)&/)[1]; + $secret.html(secret); + + $secret.on('click', function() { + balloon.copyToClipboard(secret); + + balloon.showSnackbar({message: 'profile.google-authenticator.secret_copied'}); + }); + $btnDeactivate.show(); } }); @@ -2331,6 +2595,8 @@ var balloon = { $btnDeactivate.hide(); $code.find('canvas').remove(); + $secret.html(''); + $secret.off('click'); balloon.xmlHttpRequest({ url: balloon.base+'/users/' + login.user.id, @@ -2348,37 +2614,86 @@ var balloon = { }); }); }); - - - }, + /** - * Get and display event log + * Displays the webauthn screen * - * @param object $dom - * @param object|string node * @return void */ - displayEvents: function($dom, node, params) { - if(balloon._event_limit === true) { - return; - } + _displayUserProfileWebauthn: function() { + var $view = $('#fs-profile-window-webauthn'); + var $buttons = $view.find('#fs-profile-window-webauthn-buttons'); + var $hintInactive = $view.find('#fs-profile-window-webauthn-hint-incative').hide(); + var $hintActive = $view.find('#fs-profile-window-webauthn-hint-active').hide(); + var $errorSetup = $view.find('#fs-profile-window-webauthn-error-setup').hide(); + var $btnActivate = $buttons.find('input[name="activate"]').hide(); + var $btnDeactivate = $buttons.find('input[name="deactivate"]').hide(); - var $elements = $dom.find('li'); + var webauthnLS = localStorage.getItem('webauthn'); - if(params === undefined) { - $elements.remove(); - params = {limit: 50}; + if(webauthnLS === 'false' || !webauthnLS) { + $btnActivate.show(); + $hintInactive.show(); + } else { + $btnDeactivate.show(); + $hintActive.show(); } - if(node !== undefined) { - params.id = balloon.id(node); - } + $btnActivate.off('click').on('click', function(event) { + event.preventDefault(); - var share_events = [ - 'deleteCollectionReference', - 'deleteCollectionShare', + login.setupWebauthn() + .then(() => { + $btnDeactivate.show(); + $hintActive.show(); + + $btnActivate.hide(); + $hintInactive.hide(); + }) + .catch(error => { + $errorSetup.show(); + }); + }); + + $btnDeactivate.off('click').on('click', function(event) { + event.preventDefault(); + + var msg = i18next.t('profile.webauthn.confirm.deactivate'); + + balloon.promptConfirm(msg, function() { + localStorage.setItem('webauthn', 'false'); + + $btnDeactivate.hide(); + $hintActive.hide(); + + $btnActivate.show(); + $hintInactive.show(); + }); + }); + }, + + /** + * Get and display event log + * + * @param object $dom + * @param object|string node + * @param object params request params + * @return void + */ + displayEvents: function($dom, node, params) { + if(balloon._event_limit === true) return; + + var data = params || {}; + + if(data.limit === undefined) data.limit = balloon.EVENTS_PER_REQUEST; + + if(node !== undefined) data.id = balloon.id(node); + + var share_events = [ + 'deleteCollectionReference', + 'deleteCollectionShare', 'forceDeleteCollectionReference', 'forceDeleteCollectionShare', 'undeleteCollectionReference', @@ -2392,11 +2707,18 @@ var balloon = { 'copyCollectionReference' ]; + var curSearch = Object.assign({}, params); + delete curSearch.offset; + $dom.data('curSearch', curSearch); + return balloon.xmlHttpRequest({ url: balloon.base+'/nodes/event-log', - data: params, + data: data, type: 'GET', success: function(body) { + //return if result is from an outdated search query + if($dom.data('curSearch') !== curSearch) return; + var $node, $icon, $text, @@ -2413,7 +2735,7 @@ var balloon = { balloon._event_limit = true; } - if(body.data.length === 0 && $elements.length === 0) { + if(body.data.length === 0 && (data.offset === undefined || data.offset === 0)) { $dom.append('
      • '+i18next.t('events.no_events')+'
      • '); return; } @@ -2575,7 +2897,9 @@ var balloon = { var that = this; $undo = $('
        ').unbind('click').bind('click', body.data[log], function(e) { - balloon._undoEvent.apply(that, [e, node]); + balloon.alertOpenFile(function() { + balloon._undoEvent.apply(that, [e, node]); + }); }); $node.append($undo); } @@ -2590,17 +2914,28 @@ var balloon = { /** * Infinite scroll events * - * @param object $window - * @param object node + * @param object $list + * @param object|string node + * @param object params * @return void */ - displayEventsInfiniteScroll: function($window, node) { + displayEventsInfiniteScroll: function($list, node, params) { balloon._event_limit = false; - var skip = 0; - $window.unbind('scroll').bind('scroll', function() { - if(($window.scrollTop() + 700) >= $window[0].scrollHeight) { - skip = skip + 50; - balloon.displayEvents($window.find('ul'), node, {skip: skip, limit: 50}); + var offset = 0; + var currentlyLoading = false; + + params = params || {}; + + $list.unbind('scroll').bind('scroll', function() { + if(currentlyLoading === false && ($list.scrollTop() + 700) >= $list[0].scrollHeight) { + currentlyLoading = true; + offset = offset + balloon.EVENTS_PER_REQUEST; + params.offset = offset; + params.limit = balloon.EVENTS_PER_REQUEST; + balloon.displayEvents($list.find('ul'), node, params) + .always(function() { + currentlyLoading = false; + }); } }); }, @@ -2609,33 +2944,78 @@ var balloon = { /** * Display events * + * @param object node (optional) * @return void */ displayEventsWindow: function(node) { var $fs_event_win = $('#fs-event-window'), - $fs_event_list = $fs_event_win.find('ul'), - datastore = []; + $fs_event_list = $fs_event_win.find('#fs-events-window-list'), + $fs_event_list_ul = $fs_event_list.find('ul'), + $fs_event_search = $fs_event_win.find('input[name=event-log-search]'); + + $fs_event_list_ul.empty(); + $fs_event_list.data('last-search-value', ''); + $fs_event_search.val(''); if($fs_event_win.is(':visible')) { - balloon.displayEventsInfiniteScroll($fs_event_win, node); - balloon.displayEvents($fs_event_list, node); + balloon.displayEventsInfiniteScroll($fs_event_list, node); + balloon.displayEvents($fs_event_list_ul, node); } else { - balloon.resetDom('events-win'); - $fs_event_win = $('#fs-event-window'), - $fs_event_list = $fs_event_win.find('ul'), - balloon.displayEventsInfiniteScroll($fs_event_win, node); + balloon.displayEventsInfiniteScroll($fs_event_list, node); $fs_event_win.kendoBalloonWindow({ title: $fs_event_win.attr('title'), resizable: false, modal: true, open: function() { - balloon.displayEvents($fs_event_list, node); + balloon.displayEvents($fs_event_list_ul, node); } }).data("kendoBalloonWindow").center().open(); } + + $fs_event_search.off('keyup').on('keyup', function(e) { + balloon._eventsWindowSearch.bind(this)(node); + }); + + $fs_event_search.on('search', function (e) { + balloon._eventsWindowSearch.bind(this)(node); + }); }, + /** + * Event search event handler + * + * @param object node (optional) + * @return void + */ + _eventsWindowSearch: function(node) { + var value = $(this).val(); + + var $fs_event_list = $('#fs-event-window #fs-events-window-list'), + $fs_event_list_ul = $fs_event_list.find('ul'); + + var lastSearchValue = $fs_event_list.data('last-search-value'); + + if(value.length >= 3) { + //do not search for same query twice + if(value === lastSearchValue) return; + + $fs_event_list.data('last-search-value', value); + var params = {query: {'$or': [ + {name: {$regex:value, $options:'i'}}, + {'previous.name': {$regex:value, $options:'i'}} + ]}}; + + $fs_event_list_ul.empty(); + balloon.displayEventsInfiniteScroll($fs_event_list, node, params); + balloon.displayEvents($fs_event_list_ul, node, params); + } else if(lastSearchValue !== '') { + $fs_event_list.data('last-search-value', ''); + $fs_event_list_ul.empty(); + balloon.displayEventsInfiniteScroll($fs_event_list, node); + balloon.displayEvents($fs_event_list_ul, node); + } + }, /** * Undo event @@ -2675,8 +3055,8 @@ var balloon = { var msg = i18next.t('events.prompt.unshare', e.data.node.name); balloon.promptConfirm(msg, [ { - action: '_shareCollection', - params: [e.data.node, {options: {shared: false}}] + action: '_deleteShare', + params: [e.data.node] }, successAction ]); break; @@ -2720,7 +3100,7 @@ var balloon = { balloon.promptConfirm(msg, [ { action: 'rename', - params: [e.data.node.id, e.data.previous.name] + params: [e.data.node.id, e.data.previous.name, true] }, successAction ]); break; @@ -2840,32 +3220,118 @@ var balloon = { }, /** - * Add preview handler + * Add file handler + * + * @param string app + * @param string|function ext + * @param function handler + * @param object context + */ + addFileHandler: function(handler) { + var checksum = JSON.stringify(handler); + + for(let handle of balloon.file_handlers) { + if(JSON.stringify(handle) === checksum) { + return; + } + } + + balloon.file_handlers.push(handler); + }, + + /** + * Open file * - * @param function + * @var object node */ - addPreviewHandler: function(name, handler) { - balloon.preview_file_handlers[name] = handler; + openFile: function(node) { + var ext = this.getFileExtension(node); + var result = []; + + for(let handler of this.file_handlers) { + if(typeof handler.test === 'function' && handler.test(node) === true || handler.ext === ext) { + result.push(handler); + } + } + + let defaultApps = {}; + if(localStorage.defaultApps) { + defaultApps = JSON.parse(localStorage.defaultApps); + } + + if(result.length === 0) { + balloon.promptConfirm(i18next.t('tree.no_handler_found', node.name), function() { + balloon.downloadNode(node); + }); + } else if(result.length === 1) { + result[0].handler(node, result[0].context); + } else if(defaultApps[ext] && result[defaultApps[ext]]) { + let handler = result[defaultApps[ext]]; + handler.handler(node, handler.context); + } else { + balloon.chooseFileHandler(node, result); + } }, /** - * Get preview handler + * Choose file handler * * @param object node + * @param array handlers */ - getPreviewHandler(node) { - var i; - var handlerFn; - var keys = Object.keys(balloon.preview_file_handlers); + chooseFileHandler: function(node, handlers) { + var $div = $('#fs-file-handler-window'); + $div.find('li').remove(); + var $ul = $div.find('ul'); + $div.find('#fs-file-handler-window-text').html(i18next.t('tree.choose_handler_text', node.name)); - for(i=0; i'; + } - if(handlerFn) break; + $ul.append('
      • '+img+handlers[i].app+'
      • '); } - return handlerFn; + var $k_display = $div.kendoBalloonWindow({ + resizable: false, + title: i18next.t('tree.choose_handler'), + modal: true, + draggable: false, + }).data('kendoBalloonWindow').center().open(); + + $ul.find('li:first-child').addClass('active'); + + $ul.off('click').on('click', 'li', function() { + $ul.find('li').removeClass('active'); + $(this).toggleClass('active'); + }); + + $div.find('input[type=button]').off('click').on('click', function() { + $k_display.close(); + }); + + var $checkbox = $div.find('input[name="choose-handler-remember"]').prop('checked', false); + + $div.find('input[type=submit]').off('click').on('click', function() { + let index = $ul.find('li.active').attr('data-handler'); + let handler = handlers[index]; + $k_display.close(); + handler.handler(node, handler.context); + + if($checkbox.is(':checked')) { + let defaultApps = {}; + if(localStorage.defaultApps) { + defaultApps = JSON.parse(localStorage.defaultApps); + } + + defaultApps[handler.ext || balloon.getFileExtension(node)] = index; + + localStorage.defaultApps = JSON.stringify(defaultApps); + } + }); }, /** @@ -2927,8 +3393,9 @@ var balloon = { $that.parent().find('li').removeClass('fs-menu-left-active'); $that.addClass('fs-menu-left-active'); + balloon.updatePannel(false); - balloon.resetDom(['search']); + balloon.resetDom(['search', 'view-bar']); if(action === 'cloud') { balloon.resetDom('breadcrumb-home'); @@ -2952,6 +3419,8 @@ var balloon = { return $.Deferred().resolve().promise(); } + balloon.slideout.close(); + if(action in balloon.menu_left_items) { $d = balloon.menu_left_items[action].callback(); } else { @@ -2973,7 +3442,6 @@ var balloon = { title = i18next.t('menu.' + menu); } - $('#fs-crumb-search').html(title); }, @@ -2983,11 +3451,16 @@ var balloon = { * * @return void */ - switchView: function(view) { + switchView: function(view, visibleMobile) { + $('#fs-content-view-wrap').removeClass('mobile-overlay-in-from-right').removeClass('mobile-overlay-out-to-right'); $('#fs-content-view').find('dt,dd').removeClass('active'); - var $title = $('#fs-content-view-title-'+view).addClass('active'); + var $title = $('#fs-content-view-title-'+view).addClass(['active']); $title.next().addClass('active'); + if(visibleMobile) { + $('#fs-content-view-wrap').addClass('mobile-overlay-in-from-right'); + } + var viewConfig = balloon._getViewConfig(view); if(viewConfig.onActivate) viewConfig.onActivate.call(this); @@ -3235,40 +3708,6 @@ var balloon = { balloon.touch_move = true; }, - - /** - * Tree touch start on tree node - * - * @param object e - * @return void - */ - _treeTouch: function(e) { - var id = $(e.target).attr('fs-id'); - - if(!id) { - id = $(e.target).parents('[role="treeitem"]').attr('fs-id'); - } - - if(id === '_FOLDERUP') { - return balloon._folderUp(); - } - - balloon.touch_move = false; - balloon.long_touch = false; - - if(balloon.lock_touch_timer){ - return; - } - - balloon.touch_timer = setTimeout(function(){ - if(balloon.touch_move !== true) { - setTimeout(balloon._treeLongtouch(e), 50); - } - }, 650); - balloon.lock_touch_timer = true; - }, - - /** * touch end from a tree node * @@ -3276,59 +3715,13 @@ var balloon = { * @return void */ _treeTouchEnd: function(e) { - if(balloon.touch_move === true) { - clearTimeout(balloon.touch_timer); - balloon.lock_touch_timer = false; - return; - } - - if(balloon.touch_timer) { - clearTimeout(balloon.touch_timer); - balloon.lock_touch_timer = false; - } - - if(!balloon.long_touch) { - //call dblclick with a timeout of 50ms, otherwise balloon._treeSelect() would be fired after - setTimeout(function(){ - balloon._treeDblclick(e); - }, 50); + if(balloon.touch_move) { + e.preventDefault(); } - }, - - - /** - * Long toch event on a tree node - * - * @param object e - * @return void - */ - _treeLongtouch: function(e) { - balloon.long_touch = true; - var $node = $(e.target).parents('li'), - $k_tree = $('#fs-browser-tree').data('kendoTreeView'); - - //need to fire balloon._treeSelect() since select() would not be fired when _treeLongtouch is called - $k_tree.select($node); - $k_tree.trigger('select', {node: $node}); - - balloon.long_touch = true; - //TODO pixtron - should pannel really open here? - balloon.updatePannel(true); - - if(!balloon.isSystemNode(balloon.last)) { - $('#fs-browser-tree').find('.k-in').removeClass('k-state-selected'); - - if(balloon.isMultiSelect()) { - balloon.multiSelect(balloon.getCurrentNode()); - } else { - balloon.multiSelect(balloon.getCurrentNode()); - } - balloon.pushState(); - } + balloon.touch_move = false; }, - /** * treeview select click event (triggered after select()) * @@ -3341,7 +3734,6 @@ var balloon = { } var $target = $(e.target); - balloon.previous_clicked_id = balloon.last_clicked_id; balloon.last_clicked_id = $target.attr('fs-id') || $target.parents('[fs-id]').attr('fs-id'); @@ -3476,6 +3868,9 @@ var balloon = { if(id !== null) { params.id = id; balloon.refreshTree('/collections/children', params, null, {action: '_FOLDERUP'}); + } else if(balloon.isSearchResult()) { + balloon.resetDom('breadcrumb-search'); + return balloon.buildExtendedSearchQuery(); } else { balloon.menuLeftAction(balloon.getCurrentMenu()); } @@ -3493,12 +3888,12 @@ var balloon = { return; } + $('body').removeClass('fs-content-multiselect-active fs-content-select-active'); + if(balloon.last.directory === true) { balloon.resetDom('selected'); } - var previewHandler = balloon.getPreviewHandler(balloon.last); - if(balloon.last !== null && balloon.last.directory) { balloon.updatePannel(false); @@ -3514,36 +3909,12 @@ var balloon = { ['selected','metadata','preview','multiselect','view-bar', 'history','share','share-link'] ); - } else if(previewHandler) { - previewHandler(balloon.last); - } else if(balloon.isEditable(balloon.last.mime)) { - balloon.editFile(balloon.getCurrentNode()); - } else if(balloon.isViewable(balloon.last.mime)) { - balloon.displayFile(balloon.getCurrentNode()); - } else { - balloon.downloadNode(balloon.getCurrentNode()); - } - - balloon.pushState(); - }, - - - /** - * Keyup in search (when a char was entered) - * - * @param object e - * @return void - */ - _searchKeyup: function(e){ - //TODO pixtron search - is this still needed? - return; - var $that = $(this); - $('.fs-search-reset-button').show(); - if(e.keyCode == 13) { - balloon.search($(this).val()); return; } + + balloon.openFile(balloon.last); + balloon.pushState(); }, @@ -3571,11 +3942,16 @@ var balloon = { * Does node exists? * * @param string name + * @param boolean ignoreDeleted * @return bool */ - nodeExists: function(name) { + nodeExists: function(name, ignoreDeleted) { for(var node=0; node < balloon.datasource._data.length; node++) { - if(balloon.datasource._data[node].name.toLowerCase() === name.toLowerCase()) { + if( + balloon.datasource._data[node].name.toLowerCase() === name.toLowerCase() + && + (ignoreDeleted !== true || balloon.datasource._data[node].deleted === undefined) + ){ return true; } } @@ -3662,78 +4038,15 @@ var balloon = { } operation.data.limit = 1000; + operation.data.offset = 0; - balloon.xmlHttpRequest({ - url: balloon.datasource._url, - type: 'GET', - dataType: 'json', - contentType: 'application/json', - data: JSON.stringify(operation.data), - processData: false, - success: function(pool, msg, http) { - /*for(var node in pool.data) { - pool.data[node].spriteCssClass = balloon.getSpriteClass(pool.data[node]); - }*/ - - if(balloon.datasource._ds_params.action == '_FOLDERDOWN') { - balloon.addCrumbRegister(balloon.getCurrentNode()); - } else if(balloon.datasource._ds_params.action == '_FOLDERUP') { - var crumbs = balloon.getCrumb().find('li').filter(':hidden').get()/*.reverse()*/; - crumbs = crumbs.slice(-1); - $(crumbs).show(); - balloon.getCrumb().find('li:last-child').remove(); - - if(balloon.getCrumb().find('li').get().length <= 5) { - balloon.getCrumb().find('li.removed').remove(); - } - } - - if(balloon.datasource._ds_params.nostate !== true && balloon.getCurrentNode() !== null) { - balloon.pushState(); - } - balloon.datasource._ds_params.nostate = false; - - - var depth = balloon.getFolderDepth(), - param_col = balloon.getURLParam('collection'); - - if(pool.count == 0 && depth == 1 && param_col === null) { - $('#fs-browser-fresh').show(); - } else { - $('#fs-browser-fresh').hide(); - } - - if(depth != 1 && balloon.isSearch() === false || 'id' in operation.data && operation.data.id !== null && operation.id !== null) { - pool.data.unshift({ - id: '_FOLDERUP', - name: i18next.t('tree.folderup'), - directory: true, - spriteCssClass: 'gr-i-arrow-w', - }); - } - - balloon.datasource._raw_data = pool.data; - var sorted = balloon._sortDatasource( - balloon._filterDatasource(pool.data, balloon.tree.filter), - balloon.tree.sort.field, - balloon.tree.sort.dir - ); - balloon._rebuildTree(sorted, operation) - }, - error: function(e) { - if(balloon.datasource._raw_data === undefined) { - operation.success([]); - } else { - balloon._sortDatasource( - balloon._filterDatasource(balloon.datasource._raw_data, balloon.tree.filter), - balloon.tree.sort.field, - balloon.tree.sort.dir, - operation - ); - } + balloon._readDataSource(operation).done(function(data) { + var pool = { + data: data, + count: data.length + }; - balloon.displayError(e); - }, + balloon._dataSourceSuccess(pool, operation); }); } }, @@ -3746,6 +4059,104 @@ var balloon = { }); }, + _readDataSource: function(operation) { + var $d = $.Deferred(); + + balloon.xmlHttpRequest({ + url: balloon.datasource._url, + type: 'GET', + dataType: 'json', + contentType: 'application/json', + data: JSON.stringify(operation.data), + processData: false, + success: function(pool, msg, http) { + if(pool._links.next) { + operation.data.offset = operation.data.offset + operation.data.limit; + + balloon._readDataSource(operation) + .done(function(data) { + var combined = pool.data.concat(data); + + $d.resolve(combined); + }) + .fail(function(e) { + $d.resolve([]); + }); + } else { + $d.resolve(pool.data); + } + }, + error: function(e) { + if(balloon.datasource._raw_data === undefined) { + operation.success([]); + } else { + balloon._sortDatasource( + balloon._filterDatasource(balloon.datasource._raw_data, balloon.tree.filter), + balloon.tree.sort.field, + balloon.tree.sort.dir, + operation + ); + } + + balloon.displayError(e); + + $d.resolve([]); + }, + }); + + return $d; + }, + + _dataSourceSuccess: function(pool, operation) { + if(balloon.datasource._ds_params.action == '_FOLDERDOWN') { + balloon.addCrumbRegister(balloon.getCurrentNode()); + } else if(balloon.datasource._ds_params.action == '_FOLDERUP') { + var crumbs = balloon.getCrumb().find('li').filter(':hidden').get()/*.reverse()*/; + crumbs = crumbs.slice(-1); + $(crumbs).show(); + balloon.getCrumb().find('li:last-child').remove(); + + if(balloon.getCrumb().find('li').get().length <= 5) { + balloon.getCrumb().find('li.removed').remove(); + } + } + + if(balloon.datasource._ds_params.nostate !== true && balloon.getCurrentNode() !== null) { + balloon.pushState(); + } + balloon.datasource._ds_params.nostate = false; + + + var depth = balloon.getFolderDepth(), + param_col = balloon.getURLParam('collection'); + + if(pool.count == 0 && depth == 1 && param_col === null) { + $('#fs-browser-fresh').show(); + } else { + $('#fs-browser-fresh').hide(); + } + + if(depth != 1 && balloon.isSearch() === false || 'id' in operation.data && operation.data.id !== null && operation.id !== null) { + $('#fs-crumb').addClass('is-child'); + pool.data.unshift({ + id: '_FOLDERUP', + name: i18next.t('tree.folderup'), + directory: true, + spriteCssClass: 'gr-i-arrow-w', + }); + } else { + $('#fs-crumb').removeClass('is-child'); + } + + balloon.datasource._raw_data = pool.data; + var sorted = balloon._sortDatasource( + balloon._filterDatasource(pool.data, balloon.tree.filter), + balloon.tree.sort.field, + balloon.tree.sort.dir + ); + balloon._rebuildTree(sorted, operation); + }, + /** * Check if node has a hidden name @@ -3784,28 +4195,29 @@ var balloon = { /** * Sort tree (click callback) * + * @param string field field to sort by + * @param string dir (optional) sort direction * @return void */ - _sortTree: function() { - var field = $(this).attr('id').substr(18); - - $('#fs-browser-header').find('span').empty(); - - var dir; + _sortTree: function(field, dir) { + var $fs_browser_header = $('#fs-browser-header'); - if(balloon.tree.sort.field == field) { - if(balloon.tree.sort.dir == 'asc') { - dir = 'desc'; + if(dir === undefined) { + if(balloon.tree.sort.field == field) { + dir = balloon.tree.sort.dir == 'asc' ? 'desc' : 'asc'; } else { dir = 'asc'; } - } else { - dir = 'asc'; } + $fs_browser_header.find('.fs-browser-column-sortable span').empty(); + var iconId = dir === 'asc' ? 'expand' : 'collapse'; - $(this).find('span').append(''); + $fs_browser_header.find('.fs-browser-column-' + field + ' span').append(''); + + $('#fs-sorting-' + field + '-' + dir).prop('checked', true); + $('#fs-action-sorting').html(i18next.t('tree.sorting.' + field + '_' + dir)); balloon.sortTree(field, dir); }, @@ -3957,15 +4369,16 @@ var balloon = { return; } - var node = balloon.getSelected(), - $target = $('#fs-browser').find('li[fs-id='+node.id+']').find('.fs-browser-column-name'), - $input = $(''); + var $fs_rename_window = $('#fs-rename-window'); + $fs_rename_window.addClass('is-open'); + + var node = balloon.getSelected(); + + //wrapping $(find()[0]) is necessary that $input.focus() works + var $input = $($fs_rename_window.find('input')[0]).val(node.name); balloon.rename_node = node; balloon.rename_input = $input; - balloon.rename_original = $target.html(); - - $target.html($input); $input.focus(); @@ -3977,10 +4390,6 @@ var balloon = { $input[0].setSelectionRange(0, length); } - $input.focusout(function(e) { - balloon._rename(); - }); - $input.keyup(function(e) { e.stopImmediatePropagation(); if(e.which === 27) { @@ -3989,6 +4398,20 @@ var balloon = { balloon._rename(); } }); + + $fs_rename_window.find('input[name="cancel"], #fs-rename-window-close').off('click').on('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + + balloon._resetRenameView(); + }); + + $fs_rename_window.find('input[name="save"]').off('click').on('click', function() { + event.preventDefault(); + event.stopPropagation(); + + balloon._rename(); + }); }, /** @@ -3996,17 +4419,10 @@ var balloon = { * * @return void */ - _resetRenameView: function(){ - if(balloon.rename_input && balloon.rename_original) { - balloon.rename_input.parent().append(balloon.rename_original); - } - - if(balloon.rename_input) { - balloon.rename_input.remove(); - } + _resetRenameView: function() { + $('#fs-rename-window').removeClass('is-open'); balloon.rename_node = undefined; - balloon.rename_original = undefined; balloon.rename_input = null; }, @@ -4038,10 +4454,11 @@ var balloon = { * * @param object node * @param string new_name + * @param boolean suppressUndo * @return void */ - rename: function(node, new_name) { - balloon.xmlHttpRequest({ + rename: function(node, new_name, suppressUndo) { + var options = { url: balloon.base+'/nodes?id='+balloon.id(node), type: 'PATCH', dataType: 'json', @@ -4053,9 +4470,11 @@ var balloon = { if(typeof(newNode) === 'object') { newNode.spriteCssClass = balloon.getSpriteClass(node); + balloon._resetRenameView(); balloon.displayName(newNode); } + //rename can only be initialized from "cloud" therefore it is not necessary to use reloadTree here balloon.refreshTree('/collections/children', {id: balloon.getCurrentCollectionId()}); balloon.rename_node = null; }, @@ -4064,7 +4483,24 @@ var balloon = { balloon._resetRenameView(); balloon.displayError(response); } - }); + } + + if(suppressUndo !== true) { + options.snackbar = { + message: 'snackbar.node_renamed', + values: { + old_name: node.name, + new_name: new_name + }, + icon: 'undo', + iconAction: function(response) { + balloon.rename(response, node.name, true); + } + }; + } + + + balloon.xmlHttpRequest(options); }, @@ -4092,7 +4528,7 @@ var balloon = { balloon.resetDom(['upload', 'preview', 'metadata', 'history', 'selected', 'view-bar']); balloon.updatePannel(true); - var index = balloon.multiselect.indexOf(node); + var index = balloon.getMultiselectIndex(node); var $selected = $('#fs-browser-tree').find('li[fs-id='+balloon.id(node)+']'); if(index >= 0 && stay === false) { @@ -4104,9 +4540,26 @@ var balloon = { $selected.addClass('fs-multiselected'); } - $('#fs-browser-summary').html(i18next.t('tree.selected', {count: balloon.multiselect.length})).show(); + $('#fs-browser-summary div:first-child').html(i18next.t('tree.selected', {count: balloon.multiselect.length})); + $('#fs-browser-summary').show(); }, + /** + * Find the index of a given node in the multiselect array + * Note: always use this method over balloon.multiselect.indexOf(node), + * as multiselect may contain a kendo objects or another node object + * with the same id, instead of the given node object. + * + * @param object|string node + * @return integer + */ + getMultiselectIndex: function(node) { + var id = balloon.id(node); + + return balloon.multiselect.findIndex(function(element) { + return id === balloon.id(element); + }); + }, /** * Deselects all currently selected nodes @@ -4121,6 +4574,9 @@ var balloon = { var $node = $('#fs-browser-tree').find('li[fs-id='+balloon.id(node)+']'); $node.removeClass('fs-multiselected'); } + + $('#fs-browser-header-checkbox').removeClass('fs-browser-header-checkbox-undetermined').removeClass('fs-browser-header-checkbox-checked'); + $k_tree.select($()); balloon.multiselect = []; @@ -4138,18 +4594,30 @@ var balloon = { var $fs_browser_tree = $('#fs-browser-tree'); var $k_tree = $fs_browser_tree.data('kendoTreeView'); - for(var i=0; i 1) { + for(var i=0; i= 0); - } else { + return (balloon.getMultiselectIndex(node) >= 0); + } else { return (node.id === balloon.getCurrentNode().id); } }, @@ -4449,7 +4917,10 @@ var balloon = { if($that.hasClass('removed')) return; - if(id === '') { + if(balloon.isSearchResult() && id === '') { + balloon.resetDom('breadcrumb-search'); + return balloon.buildExtendedSearchQuery(); + } else if(id === '') { balloon.menuLeftAction(balloon.getCurrentMenu()); } else { balloon.refreshTree('/collections/children', {id: id}, null, {action: false}); @@ -4666,7 +5137,7 @@ var balloon = { return; } - if(balloon.nodeExists(name) || name === '') { + if(balloon.nodeExists(name, true) || name === '') { mayCreate = false; $(this).addClass('error-input'); $submit.attr('disabled', true); @@ -4701,25 +5172,37 @@ var balloon = { * Add node */ addNode: function() { + var $select = $('#fs-action-add-select'); + var $selectMobile = $('#fs-action-add-select-mobile'); + var $spike = $select.find('.fs-action-dropdown-spike'); + + $('#fs-browser-action').removeClass('fs-browser-action-mobile-open'); + $('body').off('click').on('click', function(e){ var $target = $(e.target); if($target.attr('id') != "fs-action-add") { - $('#fs-action-add-select').hide(); + $select.removeClass('is-open'); } }); - var $select = $('#fs-action-add-select'); - var $spike = $select.find('.fs-action-dropdown-spike'); - - $select.show(); + $select.addClass('is-open'); + $selectMobile.addClass('is-open'); var spikeLeft = ($(this).offset().left + $(this).width() / 2) - $select.offset().left - ($spike.outerWidth() / 2); $spike.css('left', spikeLeft+'px'); - $select.off('click', 'li').on('click', 'li', function() { + function actionClicked() { var type = $(this).attr('data-type'); balloon.handleAddNode(type); + $selectMobile.removeClass('is-open'); + } + + $select.off('click', 'li').on('click', 'li', actionClicked); + $selectMobile.off('click', 'li').on('click', 'li', actionClicked); + + $selectMobile.find('button').off('click').on('click', function() { + $selectMobile.removeClass('is-open'); }); }, @@ -4737,6 +5220,7 @@ var balloon = { $d.done(function(node) { balloon.added = node.id; + //as handleAddNode can only be executed from within cloud:root and child collections no need for reloadTree here balloon.refreshTree('/collections/children', {id: balloon.getCurrentCollectionId()}).then(function() { balloon.scrollToNode(node); }); @@ -4764,11 +5248,21 @@ var balloon = { addFile: function(name) { var $d = $.Deferred(); - name = encodeURI(name); + name = encodeURIComponent(name); balloon.xmlHttpRequest({ url: balloon.base+'/files?name='+name+'&'+balloon.param('collection', balloon.getCurrentCollectionId()), type: 'PUT', + snackbar: { + message: 'snackbar.file_created', + values: { + name: name + }, + icon: 'undo', + iconAction: function(response) { + balloon.remove(response, true, true); + } + }, complete: function(jqXHR, textStatus) { switch(textStatus) { case 'success': @@ -4843,9 +5337,9 @@ var balloon = { var $fs_share_edit = $fs_share.find('#fs-share-edit'); var $fs_share_delete = $fs_share.find('#fs-share-delete'); - $fs_share_create.off('click').on('click', balloon.showShare); + $fs_share_create.off('click').on('click', balloon._onShowShare); - $fs_share_edit.off('click').on('click', balloon.showShare); + $fs_share_edit.off('click').on('click', balloon._onShowShare); $fs_share_delete.off('click').on('click', function() { balloon.deleteShare(node); @@ -4890,7 +5384,7 @@ var balloon = { var $li_owner = $('
      • '); $fs_share_consumers_ul.append($li_owner); - $fs_share_consumers_ul.off('click').on('click', balloon.showShare); + $fs_share_consumers_ul.off('click').on('click', balloon._onShowShare); balloon.displayAvatar($li_owner, login.user.id); @@ -4921,14 +5415,25 @@ var balloon = { } }, + /** + * Event handler wrapper for showShare + * + * @param object event + * @return void + */ + _onShowShare: function(event) { + var node = balloon.getCurrentNode(); + balloon.showShare(node); + }, + /** * Shows popup for share creating/edting * - * @return bool + * @param object node + * @return void */ - showShare: function() { + showShare: function(node) { var acl = []; - var node = balloon.getCurrentNode(); if(!node || !node.directory) return; @@ -4992,19 +5497,7 @@ var balloon = { $fs_share_win_remove_btn.off('click').on('click', function() { balloon.deleteShare(node); - $k_win.close(); - }); - - $fs_share_win_create.off('click').on('click', function() { - if(balloon._saveShare(acl, node)) { - $k_win.close(); - } - }); - - $fs_share_win_save.off('click').on('click', function() { - if(balloon._saveShare(acl, node)) { - $k_win.close(); - } + $k_win.close(); }); $fs_share_win_cancel.off('click').on('click', function() { @@ -5024,10 +5517,18 @@ var balloon = { var $share_consumer_search = $fs_share_win.find('input[name=share_consumer_search]'); var $share_name = $fs_share_win.find('input[name=share_name]'); var $privilegeSelectorTrigger = $fs_share_win.find('#fs-share-window-search-role .fs-share-window-selected-privilege'); - + var $saveBtns = $fs_share_win.find('.fs-window-secondary-actions input[type="submit"]'); balloon._setToggleConsumersVisibility(acl); - $fs_share_win.find('.fs-window-secondary-actions input[type="submit"]').prop('disabled', ($share_name.val() === '' || acl.length === 0)); + $saveBtns.prop('disabled', ($share_name.val() === '' || acl.length === 0)); + + var oldAcl = $.extend(true, [], acl); + + $saveBtns.off('click').on('click', function() { + if(balloon._saveShare(acl, node, oldAcl)) { + $fs_share_win.data('kendoBalloonWindow').close(); + } + }); $share_name.off('change').on('change', function() { if($(this).val() !== '') { @@ -5227,6 +5728,7 @@ var balloon = { dataTextField: "name", filter: "contains", highlightFirst: true, + template: '#: name #', noDataTemplate: i18next.t('error.autocomplete.no_user_groups_found'), dataSource: new kendo.data.DataSource({ serverFiltering: true, @@ -5263,6 +5765,7 @@ var balloon = { success: function(data) { for(var i in data.data) { data.data[i].type = 'group'; + data.data[i].icon = 'group'; data.data[i].role = $.extend({}, data.data[i]); } @@ -5299,6 +5802,7 @@ var balloon = { success: function(data) { for(var i in data.data) { data.data[i].type = 'user'; + data.data[i].icon = 'person'; data.data[i].role = $.extend({}, data.data[i]); } @@ -5478,9 +5982,10 @@ var balloon = { * * @param Array acl * @param object node + * @param Array oldAcl * @return object */ - _saveShare: function(acl, node) { + _saveShare: function(acl, node, oldAcl) { var $share_name = $('#fs-share-window input[name=share_name]'); if($share_name.val() === '') { @@ -5490,7 +5995,7 @@ var balloon = { $('#fs-share-window input[name=share_consumer_search]').focus(); return false; } else { - balloon._shareCollection(node, acl, $share_name.val()); + balloon._shareCollection(node, acl, $share_name.val(), oldAcl); return true; } }, @@ -5545,12 +6050,13 @@ var balloon = { * @param object node * @param array acl * @param string name + * @param array oldAcl * @return void */ - _shareCollection: function(node, acl, name) { + _shareCollection: function(node, acl, name, oldAcl) { var url = balloon.base+'/collections/share?id='+balloon.id(node); - balloon.xmlHttpRequest({ + var options = { url: url, type: 'POST', dataType: 'json', @@ -5560,13 +6066,40 @@ var balloon = { }, success: function(data) { node.shared = true; - balloon.refreshTree('/collections/children', {id: balloon.getCurrentCollectionId()}); + balloon.reloadTree(); if(balloon.id(node) == balloon.id(balloon.last)) { balloon.last.shareowner = data.shareowner; balloon.switchView('share'); } }, - }); + }; + + if(oldAcl.length === 0) { + options.snackbar = { + message: 'snackbar.share_added', + values: { + name: node.name + }, + icon: 'undo', + iconAction: function(response) { + balloon._deleteShare(node) + } + }; + } else { + options.snackbar = { + message: 'snackbar.share_updated', + values: { + name: node.name + }, + icon: 'undo', + iconAction: function(response) { + var oldName = node.sharename || node.name; + balloon._shareCollection(response, oldAcl, oldName, acl); + } + }; + } + + balloon.xmlHttpRequest(options); }, /** @@ -5752,7 +6285,12 @@ var balloon = { var shareLinkRequest = balloon.xmlHttpRequest({ type: 'POST', url: balloon.base+'/nodes/share-link', - data: data + data: data, + statusCode: { + 200: function(data) { + balloon.last = data; + } + } }); $.when(shareLinkRequest, destroyRequest).done(function() { @@ -5824,9 +6362,9 @@ var balloon = { $fs_share_link.val(window.location.origin+'/share/'+node.sharelink_token); $fs_share_link.unbind('click').bind('click', function() { - this.select(); - document.execCommand('copy'); - this.selectionEnd = this.selectionStart; + + balloon.copyToClipboard($(this).val()); + balloon.showSnackbar({message: 'view.share_link.link_copied'}); }); @@ -6134,7 +6672,7 @@ var balloon = { url += "&download=true"; $iframe.attr('src', url).load(url); } else { - window.location.href = url; + window.open(url, '_blank'); } }, @@ -6142,24 +6680,14 @@ var balloon = { /** * Extended search popup * - * @param object e + * @param object filters initialy selected filters (eg: {tags: ['tag1', 'tag2']}) * @return void */ - advancedSearch: function(e) { - balloon.resetDom(['breadcrumb-search']); - $('#fs-crumb-home-list').hide(); - $('#fs-browser-header .fs-browser-column-icon').children().hide(); - $('#fs-crumb-search-list').show(); - - $('#fs-crumb-search').find('li:first-child').html(i18next.t('search.results')); - + advancedSearch: function(filters) { var $fs_search = $('#fs-search'); var $fs_search_input = $fs_search.find('#fs-search-input'); var $fs_search_filter = $('#fs-search-filter'); - if(!$fs_search_input.is(':focus')) $fs_search_input; - $fs_search_input.off('keyup').on('keyup', balloon.buildExtendedSearchQuery); - balloon.xmlHttpRequest({ url: balloon.base+'/users/' + login.user.id + '/node-attribute-summary', type: 'GET', @@ -6174,7 +6702,14 @@ var balloon = { for(var i in colors) { if(balloon.isValidColor(colors[i]._id)) { - children.push('
      • '); + var color = colors[i]._id; + var classes = ['fs-color-'+color]; + + if(filters && filters.color && filters.color.includes(color)) { + classes.push('fs-search-filter-selected'); + } + + children.push('
      • '); } } @@ -6187,7 +6722,14 @@ var balloon = { children = []; for(var i in tags) { - children.push('
      • '+tags[i]._id+' ('+tags[i].sum+')
      • '); + var classes = []; + var tag = tags[i]._id; + + if(filters && filters.tags && filters.tags.includes(tag)) { + classes.push('fs-search-filter-selected'); + } + + children.push('
      • '+tag+' ('+tags[i].sum+')
      • '); } if(children.length >= 1) { @@ -6197,14 +6739,22 @@ var balloon = { var $mime_list = $('#fs-search-filter-mime').find('div:first'), mimes = body['mime'], children = []; + for(var i in mimes) { - var ext = balloon.mapMimeToExtension(mimes[i]._id); + var mime = mimes[i]._id; + var ext = balloon.mapMimeToExtension(mime); var spriteClass = ext !== false ? balloon.getSpriteClass(ext) : 'gr-i-file'; + + var classes = []; + + if(filters && filters.mime && filters.mime.includes(mime)) { + classes.push('fs-search-filter-selected'); + } children.push( - '
      • '+ + '
      • '+ ''+ - '
        ['+mimes[i]._id+']
      • ' + '
        ['+mime+']
        ' ); } @@ -6226,11 +6776,13 @@ var balloon = { var filter = $this.parent().attr('id').replace('fs-search-filter-', ''); balloon.resetSearchFilter(filter); + balloon._refreshSearchResult(); + $fs_search_filter.hide(); }); $fs_search_filter.find('input[name=fs-search-filter-reset]').off('click').on('click', function() { balloon.resetSearchFilter(['tags', 'color', 'mime']); - balloon.buildExtendedSearchQuery(); + balloon._refreshSearchResult(); $fs_search_filter.hide(); }); @@ -6241,6 +6793,24 @@ var balloon = { }); }, + /** + * Initializes the search breadcrumb + * + * @param object filters initialy selected filters (eg: {tags: ['tag1', 'tag2']}) + * @return void + */ + initSearchResultBreadCrumb: function() { + balloon.resetDom(['breadcrumb-search']); + $('#fs-crumb-home-list').hide(); + $('#fs-browser-header .fs-browser-column-icon').children().hide(); + $('#fs-crumb-search-list').show(); + + $('#fs-crumb-search').find('li:first-child').html(i18next.t('search.results')); + $('#fs-crumb-search').data('is-search-result', true); + + balloon.datasource.data([]); + }, + /** * Resets given search filter * @@ -6257,70 +6827,56 @@ var balloon = { }); }, + /** + * Refreshes search result. Resets search if no query resulted + * + * @return void + */ + _refreshSearchResult() { + if(balloon.buildExtendedSearchQuery() === false) { + balloon.resetSearch(); + } + }, + /** * Build query & search * * @return void */ buildExtendedSearchQuery: function() { - var must = []; - - var should1 = []; - $('#fs-search-filter-mime').find('li.fs-search-filter-selected').each(function(){ - should1.push({ - 'query_string': { - 'query': '(mime:"'+$(this).attr('data-item')+'")' - } - }); - }); - - if(should1.length > 0) must.push({bool: {should: should1}}); + var filters = {tags: [], color:[], mime: []}; - var should2 = []; $('#fs-search-filter-tags').find('li.fs-search-filter-selected').each(function(){ - should2.push({ - term: { - 'meta.tags': $(this).attr('data-item') - } - }); + filters.tags.push($(this).attr('data-item')); }); - if(should2.length > 0) must.push({bool: {should: should2}}); + $('#fs-search-filter-mime').find('li.fs-search-filter-selected').each(function(){ + filters.mime.push($(this).attr('data-item')); + }); - var should3 = []; $('#fs-search-filter-color').find('li.fs-search-filter-selected').each(function(){ - should3.push({ - match: { - 'meta.color': $(this).attr('data-item') - } - }); + filters.color.push($(this).attr('data-item')); }); - if(should3.length > 0) must.push({bool: {should: should3}}); - var content = $('#fs-search-input').val(); - var query = balloon.buildQuery(content, must); + if(content.length < 3) content = undefined; + $('.fs-search-reset-button').show(); - if(should1.length > 0 || should2.length > 0 || should3.length > 0) { + if(filters.tags.length > 0 || filters.color.length > 0 || filters.mime.length > 0) { $('#fs-search').addClass('fs-search-filtered'); } else { $('#fs-search').removeClass('fs-search-filtered'); } - if(content.length < 3 && should1.length == 0 && should2.length == 0 && should3.length == 0) { - query = undefined; - } + var query = balloon.buildQuery(content, filters); - if(query == undefined) { - balloon.datasource.data([]); - return; - } else if(query.useV2nodes) { - balloon.refreshTree('/nodes', {query: query.query}); - } else { - balloon.refreshTree('/files/search', {query: query}); + if(query === undefined) { + return false } + balloon.initSearchResultBreadCrumb(); + return balloon.executeQuery(query); }, @@ -6328,113 +6884,92 @@ var balloon = { * build query * * @param string value - * @param object filter + * @param object filters * @return object */ - buildQuery: function(value, filter) { - var a = value.split(':'); - var attr, type; + buildQuery: function(value, filters) { var mode = $('input[name="fs-search-mode"]:checked').val(); - if(a.length > 1) { - attr = a[0]; - value = a[1]; - } - - if(filter && filter.length === 0) { - filter = undefined; + if(balloon.search_modes && balloon.search_modes[mode] && balloon.search_modes[mode].buildQuery) { + return balloon.search_modes[mode].buildQuery(value, filters); } - var query = { - body: { - from: 0, - size: 500, - query: {bool: {}} - } - }; - - if(attr == undefined && value == "" && filter !== undefined) { - query.body.query.bool.must = filter; - } else if(attr == undefined) { - if(filter === undefined && mode !== 'fulltext') { - query = { - useV2nodes: true, - query: {'name': {$regex:value, $options:'i'} }, - }; - } else { - var should = [{ - match: { - name: { - query:value, - minimum_should_match: "90%" - } - } - }]; - - if(mode === 'fulltext') { - should.push({ - match: { - "content.content": { - query:value, - minimum_should_match: "90%" - } - } - }); - } + return undefined; + }, - if(filter === undefined) { - query.body.query.bool.should = should; - } else { - query.body.query.bool.should = should; - query.body.query.bool.minimum_should_match = 1; - query.body.query.bool.must = filter; - } - } - } else{ - query.body.query.bool = {must:{term:{}}}; - query.body.query.bool.must.term[attr] = value; + /** + * execute query + * + * @param object query + * @return object + */ + executeQuery: function(query) { + var mode = $('input[name="fs-search-mode"]:checked').val(); - if(filter !== undefined) { - query.body.query.bool.must = filter; - } + if(balloon.search_modes && balloon.search_modes[mode] && balloon.search_modes[mode].executeQuery) { + return balloon.search_modes[mode].executeQuery(query); + } else { + return $.Deferred().reject().promise(); } - return query; }, - /** - * Search node + * build query for nodename mode * - * @param string search_query - * @return void + * @param string value + * @param object filters + * @return object */ - search: function(search_query) { - //TODO pixtron - is this method still needed? - var value = search_query, query; - if(value == '') { - return balloon.resetSearch(); + _buildQueryNodename: function(value, filters) { + var queryParts = []; + + if(value) { + queryParts.push({'name': {$regex:value, $options:'i'} }); } - if(typeof(search_query) === 'object') { - query = search_query; - } else { - query = balloon.buildQuery(search_query); + if(filters && filters.tags && filters.tags.length > 0) { + var $or = []; + var i; + + for(i=0; i 0) { + var $or = []; + var i; - if(query === undefined) { - return; + for(i=0; i 0) { + var $or = []; + var i; + + for(i=0; i'; + } + + list += ''; + + if(typeof(balloon.id(source)) === 'string') { + id = balloon.id(source); + } + + balloon.promptConfirm(list, 'move', [id, destination, 2, clone]); + } else { + balloon.displayError(xhr); + } + break; + default: + balloon.displayError(xhr); + break; + } + }, + }); + }, + + /** + * undo a move issued by balloon.move + * + * params are the same as passed to balloon.move() + * + * @param string|object|array source (moved elements) + * @param string|object|array destination (the destination where source(s) have been moved to) + * @param int conflict + * @return void + */ + _undoMove: function(source, destination, conflict, clone) { + var requests = []; + + if(!Array.isArray(source)) { + source = [source]; + } + + for(var i=0; i'; - } + /** + * undo a clone issued by balloon.move + * + * + * @param string|object|array source (source elements) + * @param object response (response from clone call) + * @return void + */ + _undoClone: function(source, response) { + var requests = []; - list += ''; + if(!Array.isArray(source)) { + source = [source]; + } - if(typeof(balloon.id(source)) === 'string') { - id = balloon.id(source); - } + for(var i=0; i 1) { + var id = balloon.id(src); + clone = response[id].data; + } else { + clone = response; + } + + var request = balloon.xmlHttpRequest({ + url: balloon.base+'/nodes', + type: 'DELETE', + dataType: 'json', + data: { + id: balloon.id(clone), + force: true, + }, + complete: function() { + balloon.resetDom('multiselect'); + balloon.deselectAll(); + }, + error: function(data) { balloon.displayError(data); } - } + }); + + requests.push(request); + } + + $.when.apply($, requests).always(function() { + //undoClone can only be executed in cloud:root and child collections, therefore no need for reloatTree + balloon.refreshTree('/collections/children', {id: balloon.getCurrentCollectionId()}); }); }, @@ -6868,9 +7594,9 @@ var balloon = { if(node.filter) { if(node.shared === true && node.reference === true) { return 'gr-i-folder-filter-received'; - } else if(node.shared === true) { + } else if(node.shared === true) { return 'gr-i-folder-filter-shared'; - } else { + } else { return 'gr-i-folder-filter'; } } else if(node.mount) { @@ -6951,45 +7677,13 @@ var balloon = { var $k_display = $div.kendoBalloonWindow({ title: winTitle, resizable: false, - modal: true, - keydown: function(e) { - if(e.originalEvent.keyCode !== 27) { - return; - } - - if(data == $textarea.val()) { - $k_display.close(); - return; - } - - e.stopImmediatePropagation(); - var msg = i18next.t('prompt.close_save_file', node.name); - balloon.promptConfirm(msg, function(){ - balloon.saveFile(node, $textarea.val()); - $k_display.close(); - }); - - $("#fs-prompt-window").find('input[name=cancel]').unbind('click').bind('click', function(){ - $("#fs-prompt-window").data('kendoBalloonWindow').close(); - $k_display.close(); - }); - }, - open: function(e) { - setTimeout(function(){ - e.sender.wrapper.find('textarea').focus(); - }, 600); - - e.sender.wrapper.find('textarea').unbind('change').bind('change',function(){ - data = $textarea.val(); - }); - - $(this.wrapper).find('.gr-i-close').unbind('click.fix').bind('click.fix', function(e){ - e.stopImmediatePropagation(); - - if(data == $textarea.val()) { - $k_display.close(); - return; - } + modal: false, + fullscreen: true, + draggable: false, + close: function(e) { + if(e.userTriggered && data != $textarea.val()) { + //user tries to close window with unsaved changes + e.preventDefault(); var msg = i18next.t('prompt.close_save_file', node.name); balloon.promptConfirm(msg, function(){ balloon.saveFile(node, $textarea.val()); @@ -7000,7 +7694,12 @@ var balloon = { $("#fs-prompt-window").data('kendoBalloonWindow').close(); $k_display.close(); }); - }); + } + }, + open: function(e) { + setTimeout(function(){ + $textarea.focus(); + }, 600); } }).data("kendoBalloonWindow").center().open(); } @@ -7024,8 +7723,35 @@ var balloon = { url: balloon.base+'/files?id='+balloon.id(node), type: 'PUT', data: content, + snackbar: { + message: 'snackbar.file_saved', + values: { + name: node.name + }, + icon: 'undo', + iconAction: function(response) { + balloon.restoreVersion(node.id, node.version, response.version); + } + }, success: function(data) { balloon.resetDom('edit'); + + if(balloon.last && balloon.last.id === data.id) { + balloon.last = data; + } + + switch(balloon.getURLParam('view')) { + case 'history': + balloon.displayHistoryView(); + + if($('#fs-history-window').is(':visible')) { + balloon.displayHistoryWindow(node); + } + break; + case 'events': + balloon.switchView('events'); + break; + } } }); }, @@ -7106,7 +7832,7 @@ var balloon = { var data = balloon.datasource._pristineData; for(var i=++index; i<=data.length; i++) { - if(i in data && data[i].mime && balloon.isViewable(data[i].mime)) { + if(i in data && data[i].mime && balloon.isViewable(data[i])) { $('#fs-display-right').show().unbind('click').bind('click', function(){ balloon.displayFile(data[i]); }); @@ -7117,7 +7843,7 @@ var balloon = { index = data.indexOf(node) ; for(var i2=--index; i2!=-1; i2--) { - if(i2 in data && data[i2].mime && balloon.isViewable(data[i2].mime)) { + if(i2 in data && data[i2].mime && balloon.isViewable(data[i2])) { $('#fs-display-left').show().unbind('click').bind('click', function(){ balloon.displayFile(data[i2]); }); @@ -7211,13 +7937,30 @@ var balloon = { }, + /** + * Get a nodes size as a human readable string + * + * @param object node + * @return string + */ + nodeSize: function(node) { + var size = ''; + if(node.directory) { + size = i18next.t('view.prop.data.childcount', {count: node.size}) + } else { + size = balloon.getReadableFileSizeString(node.size || 0); + } + + return size; + }, + /** * Get time since * * @param Date date * @return string */ - timeSince: function(date) { + timeSince: function(date, includeAgo) { var seconds = Math.floor((new Date() - date) / 1000); if(seconds < -1) { @@ -7227,23 +7970,23 @@ var balloon = { var interval = Math.floor(seconds / 31536000); if (interval >= 1) { - return i18next.t('time.year', {count: interval}); + return i18next.t(includeAgo ? 'time.year_ago' : 'time.year', {count: interval}); } interval = Math.floor(seconds / 2592000); if (interval >= 1) { - return i18next.t('time.month', {count: interval}); + return i18next.t(includeAgo ? 'time.month_ago' : 'time.month', {count: interval}); } interval = Math.floor(seconds / 86400); if (interval >= 1) { - return i18next.t('time.day', {count: interval}); + return i18next.t(includeAgo ? 'time.day_ago' : 'time.day', {count: interval}); } interval = Math.floor(seconds / 3600); if (interval >= 1) { - return i18next.t('time.hour', {count: interval}); + return i18next.t(includeAgo ? 'time.hour_ago' : 'time.hour', {count: interval}); } interval = Math.floor(seconds / 60); if (interval >= 1) { - return i18next.t('time.minute', {count: interval}); + return i18next.t(includeAgo ? 'time.minute_ago' : 'time.minute', {count: interval}); } seconds = Math.round(seconds); @@ -7258,10 +8001,12 @@ var balloon = { /** * Check if can edit a file via browser * - * @param string mime + * @param object node * @return bool */ - isEditable: function(mime) { + isEditable: function(node) { + let mime = node.mime; + if(balloon.isMobileViewPort()) { return false; } @@ -7274,7 +8019,8 @@ var balloon = { var valid = [ 'application/xml', 'application/json', - 'inode/x-empty' + 'inode/x-empty', + 'application/x-sql' ]; return valid.indexOf(mime) > -1; @@ -7284,14 +8030,11 @@ var balloon = { /** * Check if we can display the file live * - * @param string + * @param object node * @return bool */ - isViewable: function(mime) { - if(balloon.isMobileViewPort()) { - return false; - } - + isViewable: function(node) { + let mime = node.mime; var type = mime.substr(0, mime.indexOf('/')); if(type == 'image' || type == 'video' || type == 'audio') { @@ -7444,7 +8187,9 @@ var balloon = { $submit.off('click').on('click', function(){ var version = $fs_history.find('input[name=version]:checked').val(); if(version !== undefined) { - balloon.restoreVersion(node, version); + balloon.alertOpenFile(function() { + balloon.restoreVersion(node.id, version, node.version); + }); } }); } @@ -7471,13 +8216,6 @@ var balloon = { open: function() { balloon.displayHistory($fs_history_win, node); - $fs_history_win.find('input[name="apply"]').off('click').on('click', function(){ - var version = $fs_history_win.find('input[name=version]:checked').val(); - if(version !== undefined) { - balloon.restoreVersion(node, version); - } - }); - $fs_history_win.find('input[name="cancel"]').off('click').on('click', function(){ $fs_history_win.data("kendoBalloonWindow").close(); }); @@ -7491,25 +8229,48 @@ var balloon = { * * @param string|object node * @param int version + * @param int prevVersion * @return void */ - restoreVersion: function(node, version) { - balloon.xmlHttpRequest({ - url: balloon.base+'/files/restore?id='+balloon.id(node), + restoreVersion: function(node, version, prevVersion) { + var options = { + url: balloon.base+'/files/restore', type: 'POST', dataType: 'json', data: { - version: version + version: version, + id: balloon.id(node), + }, + snackbar: { + message: 'snackbar.restore_file', + values: { + version: version + }, + icon: 'undo', + iconAction: function(response) { + balloon.restoreVersion(node, prevVersion, version); + } }, success: function(data) { - balloon.refreshTree('/collections/children', {id: balloon.getCurrentCollectionId()}); + if(balloon.last && balloon.last.id === data.id) { + balloon.last = data; + } + balloon.displayHistoryView(); + balloon.reloadTree(); if($('#fs-history-window').is(':visible')) { balloon.displayHistoryWindow(node); } } - }); + }; + + if(prevVersion === undefined) { + delete options.snackbar.icon; + delete options.snackbar.iconAction; + } + + balloon.xmlHttpRequest(options); }, @@ -7545,20 +8306,31 @@ var balloon = { * @return void */ displayName: function(node) { - var $fs_prop_name = $('#fs-properties-name'); - var $field = $fs_prop_name.find('.fs-value'); + var $fs_content_nodename = $('.fs-content-nodename'); + + $fs_content_nodename.find('span').remove(); + $fs_content_nodename.find('input').remove(); + + $fs_content_nodename.append(''); + var $field = $fs_content_nodename.find('.fs-value'); var ext = balloon.getFileExtension(node); var name = node.name; - $fs_prop_name.find('.fs-ext').remove(); + var displayName = name; if(ext != null && node.directory == false) { - $fs_prop_name.append('('+ext+')'); - $field.html(name.substr(0, name.length-ext.length-1)); + $fs_content_nodename.append('('+ext+')'); + var filename = name.substr(0, name.length-ext.length-1); + $field.html(filename); + displayName = filename + ' (' + ext.toUpperCase() + ')'; } else { $field.html(name); } + + if($('body').hasClass('fs-fullscreen-window-open')) { + $('.fs-fullscreen-window .k-window-title').html(displayName); + } }, /** @@ -7626,6 +8398,15 @@ var balloon = { var iconId = node.shareowner.name == login.username ? 'folder-shared' : 'folder-received'; $icon.replaceWith(''); } + break; + case 'mount': + var $fs_metadata_mount = $('#fs-metadata-mount'); + + value = i18next.t('view.prop.head.mount_value', node.mount.share, node.mount.username, node.mount.adapter, node.mount.host, node.mount.workgroup); + + $field = $fs_metadata_mount.find('.fs-value'); + $fs_metadata_mount.parent().show(); + break; default: if($field.length != 0 && prop !== 'access' && prop != 'shareowner') { @@ -7675,12 +8456,7 @@ var balloon = { * @return void */ _copyPermaLink: function(id) { - var tmpEl = document.createElement('textarea'); - tmpEl.value = balloon._buildPermaLink(id, false); - document.body.appendChild(tmpEl); - tmpEl.select(); - document.execCommand('copy'); - document.body.removeChild(tmpEl); + balloon.copyToClipboard(balloon._buildPermaLink(id, false)); balloon.showSnackbar({message: 'view.share_link.link_copied'}); }, @@ -7756,7 +8532,6 @@ var balloon = { }); }, - /** * Display preview * @@ -7832,14 +8607,7 @@ var balloon = { $fs_preview.removeClass('fs-loader'); $fs_preview.find('*').unbind('click').bind('click', function() { - var previewHandler = balloon.getPreviewHandler(node); - if(previewHandler) { - previewHandler(node); - } else if(balloon.isViewable(node.mime)) { - balloon.displayFile(node); - } else { - balloon.downloadNode(node); - } + balloon.openFile(node); }); }, success: function(data) { @@ -7897,11 +8665,10 @@ var balloon = { return; } - //TODO pixtron search - fix advanced search - balloon.advancedSearch(); - var value = 'meta.tags:'+$(this).find('.tag-name').text(); - //TODO pixtron search - why advancedSearch and search? - balloon.search(value); + balloon.resetSearchFilter(['tags', 'color', 'mime']); + $('#fs-search-input').val(''); + + balloon.advancedSearch({tags: [$(this).find('.tag-name').text()]}); }); $fs_prop_tags_parent.find('input[name=add_tag]').unbind('keyup').on('keyup', function(e) { @@ -8010,6 +8777,7 @@ var balloon = { for(var element in elements) { var $title = $('#fs-content-view-title-'+elements[element]).removeClass('disabled'); $title.next().removeClass('disabled'); + $('#fs-content-nav-' + elements[element]).removeClass('disabled'); } }, @@ -8028,6 +8796,10 @@ var balloon = { var i; for(i=0; i'+i18next.t('search.results')+''); + $('#fs-crumb-search').data('is-search-result', false); break; case 'shortcuts': @@ -8553,6 +9359,19 @@ var balloon = { $.extend(true, reqOptions, options || {}); + if(reqOptions.suppressSnackbar !== true) { + reqOptions.snackbar = { + message: 'snackbar.collection_created', + values: { + name: name + }, + icon: 'undo', + iconAction: function(response) { + balloon.remove(response, true, true); + } + }; + } + balloon.xmlHttpRequest(reqOptions); return $d; @@ -8636,9 +9455,11 @@ var balloon = { */ _handleFileSelect: function(e, parent_node) { if(balloon.isSearch() && balloon.getCurrentCollectionId() === null) { - return; + return $.Deferred().reject().promise(); } + var $dHandleFileSelect = $.Deferred(); + e.stopPropagation(); e.preventDefault(); @@ -8651,13 +9472,17 @@ var balloon = { var $d = handler.handleItems(e.originalEvent.dataTransfer.items, balloon.id(parent_node)); $d.done(function(files) { + // successfully added items to queue, files is an array of promisses to be fullfilled, when given file/directory upload finishes balloon.hideSpinner(); + $dHandleFileSelect.resolve(files); }); $d.fail(function(err) { + // failed adding to queue balloon.hideSpinner(); blobs = e.originalEvent.dataTransfer.files; - balloon._handleFileSelectFilesOnly(blobs, parent_node); + var files = balloon._handleFileSelectFilesOnly(blobs, parent_node); + $dHandleFileSelect.resolve(files); }); } catch(err) { balloon.hideSpinner(); @@ -8669,8 +9494,11 @@ var balloon = { if(blobs) { //if blobs are set - either browser does not support directory upload, or only files have been selected - balloon._handleFileSelectFilesOnly(blobs, parent_node); + var files = balloon._handleFileSelectFilesOnly(blobs, parent_node); + $dHandleFileSelect.resolve(files); } + + return $dHandleFileSelect; }, /** @@ -8689,8 +9517,8 @@ var balloon = { parent: parent }]; - balloon.uploadFiles(files); - $d.resolve(); + var $filesD = balloon.uploadFiles(files); + $d.resolve($filesD); }, function(err) { $d.reject(err); }); @@ -8715,7 +9543,7 @@ var balloon = { }); } - balloon.uploadFiles(files); + return balloon.uploadFiles(files); }, /** @@ -8725,6 +9553,7 @@ var balloon = { * @return void */ uploadFiles: function(files) { + var filePromises = []; balloon.resetDom(['upload-progress', 'uploadmgr-progress']); if(balloon.upload_manager === null || @@ -8781,6 +9610,10 @@ var balloon = { }, }); + var $dFile = $.Deferred(); + + filePromises.push($dFile); + balloon.upload_manager.files[uuid] = { progress: progressnode, blob: file.blob, @@ -8796,6 +9629,7 @@ var balloon = { request: null, status: 1, id: uuid, + dFile: $dFile, }; balloon.upload_manager.queue.push(uuid); @@ -8808,7 +9642,7 @@ var balloon = { } if(Object.keys(balloon.upload_manager.files).length <= 0) { - return; + return filePromises; } $('#fs-upload-list').off('click', '.fs-progress-icon').on('click', '.fs-progress-icon', function() { @@ -8840,6 +9674,7 @@ var balloon = { balloon._uploadManagerNext(); + return filePromises; }, /** @@ -8890,10 +9725,24 @@ var balloon = { * Upload done, removes it from pending uploads, and schedules next * * @param string uuid, id of the file + * @param boolean successfull * @return void */ - _uploadManagerDone: function(uuid) { + _uploadManagerDone: function(uuid, successfull) { delete balloon.upload_manager.pending[uuid]; + + var file = balloon.upload_manager.files[uuid]; + if(successfull) { + file.dFile.resolve({ + type: 'file', + node: file.node, + name: file.name, + parent: file.parent + }); + } else { + file.dFile.reject(); + } + balloon._uploadManagerNext(); }, @@ -8973,7 +9822,7 @@ var balloon = { file.manager.count.success.toString(), file.manager.count.upload) ); - balloon._uploadManagerDone(file.id); + balloon._uploadManagerDone(file.id, true); } return; @@ -8995,7 +9844,7 @@ var balloon = { file.manager.count.transfer++; - balloon._uploadManagerDone(file.id); + balloon._uploadManagerDone(file.id, false); } else { file.end = file.start + balloon.BYTES_PER_CHUNK; @@ -9042,7 +9891,7 @@ var balloon = { * @return void */ _chunkUpload: function(file) { - var url = balloon.base + '/files/chunk?name=' + encodeURI(file.name) + '&index=' + + var url = balloon.base + '/files/chunk?name=' + encodeURIComponent(file.name) + '&index=' + file.index + '&chunks=' + file.slices + '&size=' + file.blob.size; if(file.session) { @@ -9131,6 +9980,11 @@ var balloon = { $('#fs-upload-info > span').html(balloon.getReadableFileSizeString(file.manager.transfered_bytes)); }, success: function(response) { + //add node from final chunk response + if(response.id) { + file.node = response; + } + file.session = response.session; balloon._chunkUploadManager(file); balloon._checkUploadEnd(); @@ -9158,7 +10012,7 @@ var balloon = { var data = balloon.parseError(e); if(data === false || data.status != 403) { - balloon.displayError(response); + balloon.displayError(e); } else { if(data.code === 40) { var new_name = balloon.getCloneName(file.blob.name); @@ -9215,6 +10069,147 @@ var balloon = { return balloon.toggle_fs_browser_action_hooks[key](); }); }, + + _initSwipeEvents: function() { + balloon._initMenuLeftSwipeEvents(); + balloon._initMobileOverlaySwipeEvents(); + }, + + _unifyTouchEvent: function(e) { + return e.changedTouches ? e.changedTouches[0] : e; + }, + + _initMobileOverlaySwipeEvents: function() { + var $overlays = $('#fs-content-nav-small-wrap, #fs-content-view-wrap'); + + var $body = $('body'); + + var threshhold = $body.width() / 4; + var direction; + var x0; + + function touchstart(e) { + x0 = balloon._unifyTouchEvent(e).clientX; + } + + function touchemove(e) { + if(direction === undefined) { + direction = (balloon._unifyTouchEvent(e).clientX - x0) > 0 ? 'right' : 'left'; + } + } + + function touchend(e) { + var xDiff = balloon._unifyTouchEvent(e).clientX - x0; + + switch(direction) { + case 'right': + if(xDiff > threshhold) { + balloon.fsContentMobilePrev(); + e.preventDefault(); + e.stopPropagation(); + } + break; + } + + direction = undefined; + }; + + $overlays.off('touchstart').on('touchstart', touchstart); + $overlays.off('touchmove').on('touchmove', touchemove); + $overlays.off('touchend').on('touchend', touchend); + }, + + _initMenuLeftSwipeEvents: function() { + var $fs_menu_left = $('#fs-menu-left'); + var $fs_layout = $('#fs-browser-layout'); + + this.slideout = new Slideout({ + 'panel': $fs_layout[0], + 'menu': $fs_menu_left[0], + 'padding': 240, + 'tolerance': 70 + }); + + var $toggle = $('#fs-menu-left-toggl'); + + this.slideout.on('beforeopen', function(){ + $toggle.addClass('fs-menu-left-open'); + $fs_menu_left.addClass('fs-menu-left-open'); + $fs_menu_left.css('z-index', '15'); + }); + + this.slideout.on('beforeclose', function() { + $toggle.removeClass('fs-menu-left-open'); + $fs_menu_left.removeClass('fs-menu-left-open'); + $fs_menu_left.css('z-index', '0'); + }); + + this.slideout.on('translatestart', function() { + $fs_menu_left.css('z-index', '0'); + }); + }, + + /** + * Copies a value to clipboard + * + * @param string value value to copy + * @return void + */ + copyToClipboard: function(value) { + var tmpEl = document.createElement('textarea'); + + document.body.appendChild(tmpEl); + + tmpEl.contentEditable = true; + tmpEl.readOnly = false; + tmpEl.value = value; + + var range = document.createRange(); + range.selectNodeContents(tmpEl); + + var s = window.getSelection(); + s.removeAllRanges(); + s.addRange(range); + + var userAgent = window.navigator.userAgent; + if (userAgent.match(/iPad/i) || userAgent.match(/iPhone/i)) { + tmpEl.setSelectionRange(0, 999999); + } else { + tmpEl.select(); + } + + document.execCommand('copy'); + + document.body.removeChild(tmpEl); + }, + + fsContentMobileNext: function() { + var $body = $('body'); + if(balloon.fs_content_mobile_current_state < balloon.fs_content_mobile_states.length-1) { + $body.addClass('fs-content-mobile-animating'); + $body.removeClass(balloon.fs_content_mobile_states[balloon.fs_content_mobile_current_state]); + balloon.fs_content_mobile_current_state ++; + $body.addClass(balloon.fs_content_mobile_states[balloon.fs_content_mobile_current_state]); + + window.setTimeout(function() { + $body.removeClass('fs-content-mobile-animating'); + }, 250); + } + }, + + fsContentMobilePrev: function() { + var $body = $('body'); + if(balloon.fs_content_mobile_current_state > 0) { + $body.addClass('fs-content-mobile-animating'); + $body.removeClass(balloon.fs_content_mobile_states[balloon.fs_content_mobile_current_state]); + balloon.fs_content_mobile_current_state --; + $body.addClass(balloon.fs_content_mobile_states[balloon.fs_content_mobile_current_state]); + + window.setTimeout(function() { + $body.removeClass('fs-content-mobile-animating'); + }, 250); + } + }, }; import './app.js'; diff --git a/src/lib/data-transfer-items-handler.js b/src/lib/data-transfer-items-handler.js index 458befe..8415f2e 100644 --- a/src/lib/data-transfer-items-handler.js +++ b/src/lib/data-transfer-items-handler.js @@ -51,7 +51,14 @@ dataTransferItemsHandler.prototype._handleEntries = function(entries, parent) { $.when.apply(this, promises) .done(function() { - $d.resolve(); + var $dFiles = []; + var i; + + for(i=0; i < arguments.length; i++) { + $dFiles = $dFiles.concat(arguments[i]); + } + + $d.resolve($dFiles); }) .fail(function(err) { $d.reject(err); diff --git a/src/lib/translate.js b/src/lib/translate.js index af8fafc..61688c3 100755 --- a/src/lib/translate.js +++ b/src/lib/translate.js @@ -48,7 +48,7 @@ var translate = { var locales = [ ['en', 'English'], ['en-US', 'English (USA)'], - ['en-AU', 'English (Australia'], + ['en-AU', 'English (Australia)'], ['en-GB', 'English (UK)'], ['de', 'Deutsch'], ['de-CH', 'Deutsch (Schweiz)'], @@ -92,7 +92,7 @@ var translate = { prefix: 'i18next_res_', expirationTime: 60*60*120 }, - fallbackLng: translate.config.default_lang, + fallbackLng: translate.config.defaultLang, backend: { ajax: translate.load, loadPath: function(lng,ns){ diff --git a/src/lib/widget-balloon-window.js b/src/lib/widget-balloon-window.js index 4ac54ae..1c2971a 100644 --- a/src/lib/widget-balloon-window.js +++ b/src/lib/widget-balloon-window.js @@ -31,6 +31,11 @@ import iconsSvg from '@gyselroth/icon-collection/src/icons.svg'; } } + if(options.fullscreen === true) { + $parent.addClass('fs-fullscreen-window'); + $('body').addClass('fs-fullscreen-window-open'); + } + this.title(options.title); }, @@ -87,6 +92,29 @@ import iconsSvg from '@gyselroth/icon-collection/src/icons.svg'; return that; }, + _actionForIcon: function(icon) { + if(icon.hasClass('k-i-close')) { + // remove the class, when window is close via action icon close. + // a bit of a hack though, but unfourtanetly the action handler only + // calls `_close`, which we can't extend without running into circular loops + $('body').removeClass('fs-fullscreen-window-open'); + } + + return Window.fn._actionForIcon.call(this, icon); + }, + + close: function() { + var that = this; + + if(this.options.fullscreen === true) { + $('body').removeClass('fs-fullscreen-window-open'); + } + + Window.fn.close.call(this); + + return that; + }, + options: { name: 'BalloonWindow', }, diff --git a/src/locale/de.json b/src/locale/de.json index caf8e45..d102e98 100755 --- a/src/locale/de.json +++ b/src/locale/de.json @@ -6,7 +6,9 @@ "login": "Login", "logout": "Logout", "code": "Code", - "multi_factor_auth": "Dieses Konto ist mit multi factor authentication geschützt. Bitte gib den Code vom google authenticator ein:", + "multi_factor_auth": "Dieses Konto ist mit Multi-Faktor-Authentisierung geschützt. Bitte gib den Code vom google authenticator ein:", + "dont_remind_webauthn": "Nicht mehr danach fragen.", + "setup_webauthn": "Dein Gerät unterstützt eine Form von Authentifzierung ohne Passwort und kann jetzt eingerichtet werden oder später in den Benutzereinstellungen.", "events": "Events", "profile": "Profil", "settings": "Einstellungen", @@ -15,7 +17,9 @@ "server": "Der balloon-Server meldet aktuell eine Störung, bitte versuche es später erneut.", "credentials": "Der Benutzername oder das Passwort ist falsch.", "code": "Der Code ist ungültig.", - "oidc": " Ein Fehler ist aufgetreten bei der Authentifizierung deines Kontos über den gewählten Identity Provider." + "oidc": " Ein Fehler ist aufgetreten bei der Authentifizierung deines Kontos über den gewählten Identity Provider.", + "webauthn_setup": "Ein Fehler ist aufgetreten bei der Einrichtung der Authentifzierung ohne Passwort. Du kannst diesen Schritt ignorieren oder es nocheinmal versuchen.", + "webauthn": "Ein Fehler ist aufgetreten bei der Authentifzierung ohne Passwort. Du kannst Benutzername/Passwort eingeben oder es nochmals versuchen." } }, "profile": { @@ -28,7 +32,8 @@ "nav": { "overview": "Übersicht", "change-password": "Passwort ändern", - "google-authenticator": "Multi-Faktor-Authentisierung" + "google-authenticator": "Multi-Faktor-Authentisierung", + "webauthn": "Authentifizierung ohne Passwort" }, "attribute": { "id": "Benutzer-ID", @@ -40,6 +45,7 @@ "google-authenticator": { "activate": "Aktivieren", "deactivate": "Deaktivieren", + "secret_copied": "Der code wurde in die Zwischenablage kopiert", "confirm": { "activate": "Bist du sicher, dass du Multi-Faktor-Authentisierung aktivieren willst? Nach Aktivierung ist der Zugang nur noch mithilfe der Google Authenticator App möglich.", "deactivate": "Bist du sicher, dass du Multi-Faktor-Authentisierung deaktivieren willst?" @@ -48,6 +54,17 @@ "inactive": "Um Multi-Faktor-Authentisierung nutzen zu können benötigst du ein Smartphone und die App Google Authenticator. Nach dem Aktivieren wird dir einmalig ein QR Code angezeigt, welchen du mit der App scannen musst. Achtung: Nach Aktivierung von Multi-Faktor-Authentisierung ist der Zugang nur noch mit dieser App möglich.", "active": "Multi-Faktor-Authentisierung ist aktiv." } + }, + "webauthn": { + "activate": "Aktivieren", + "deactivate": "Deaktivieren", + "confirm": { + "deactivate": "Bist du sicher, dass du Authentifizierung ohne Passwort deaktiviern willst?" + }, + "hint": { + "inactive": "Die Authentifizierung ohne Passwort ist deaktiviert.", + "active": "Die Authentifizierung ohne Passwort ist aktiviert." + } } }, "nav": { @@ -64,6 +81,7 @@ "cut": "Ausschneiden", "paste": "Einfügen", "filter": "Filter", + "sorting": "Sortierung", "rename": "Umbenennen", "perma-link": "Perma Link kopieren" }, @@ -74,7 +92,8 @@ "history": "Verlauf", "share_folder": "Ordner freigeben", "share_link": "Link teilen", - "events": "Events" + "events": "Events", + "more": "Weitere Optionen" } }, "menu": { @@ -85,12 +104,16 @@ "trash": "Gelöschte Objekte", "search": "Suchen" }, + "app": { + "file_editor": "Text Editor", + "file_viewer": "Vorschau", + "file_downloader": "Download" + }, "search": { "results": "Suchresultate", "reset_filter": "Zurücksetzen", "apply_filter": "Anwenden", "mode": { - "fulltext": "Volltext", "nodename": "Dateiname" }, "filter": { @@ -123,6 +146,14 @@ "shares": "Freigaben", "hidden": "Versteckt" }, + "sorting": { + "name_asc": "Name aufsteigend", + "name_desc": "Name absteigend", + "size_asc": "Grösse aufsteigend", + "size_desc": "Grösse absteigend", + "changed_asc": "Geändert aufsteigend", + "changed_desc": "Geändert absteigend" + }, "open_folder": "Ordner öffnen", "folder": "Ordner", "txt_file": "Text-Dokument", @@ -132,7 +163,11 @@ "error": { "folder_exists": "Ein Ordner mit dem Name %s existiert bereits." }, - "folderup": "Zurück" + "folderup": "Zurück", + "choose_handler": "Wähle eine App", + "choose_handler_text": "Mit welcher App soll die Datei %s geöffnet werden?", + "choose_handler_remember": "Diesen Dateityp immer mit dieser App öffnen.", + "no_handler_found": "Es wurde keine App gefunden welche die Datei %s öffnen kann. Möchtest du die Datei herunterladen?" }, "user": { "disk_space": "Speicherplatz:", @@ -152,17 +187,32 @@ "minute": "{{count}} Minute", "minute_plural": "{{count}} Minuten", "second": "{{count}} Sekunde", - "second_plural": "{{count}} Sekunden" + "second_plural": "{{count}} Sekunden", + "year_ago": "vor einem Jahr", + "year_ago_plural": "vor {{count}} Jahren", + "month_ago": "vor einem Monat", + "month_ago_plural": "vor {{count}} Monaten", + "day_ago": "vor einem Tag", + "day_ago_plural": "vor {{count}} Tagen", + "hour_ago": "vor einer Stunde", + "hour_ago_plural": "vor {{count}} Stunden", + "minute_ago": "vor einer Minute", + "minute_ago_plural": "vor {{count}} Minuten", + "second_ago": "vor einer Sekunde", + "second_ago_plural": "vor {{count}} Sekunden" }, "button": { "save": "Speichern", - "cancel": "Abbrechen" + "setup": "Einrichten", + "ignore": "Ignorieren", + "cancel": "Abbrechen", + "open": "Öffnen" }, "view": { "prop": { "head": { - "mime": "Dateityp:", - "id": "ID:", + "mime": "Dateityp", + "id": "ID", "contains": "Enthält", "changed": "Geändert", "deleted": "Gelöscht", @@ -181,7 +231,9 @@ "path": "Pfad", "coordinate": "Koordinaten", "author": "Autor", - "perma-link": "Perma Link" + "perma-link": "Perma Link", + "mount": "Externer Speicher", + "mount_value": "%s (%s@%s://%s) [%s]" }, "data": { "size": "%s (%s Bytes)", @@ -288,7 +340,8 @@ } }, "events": { - "show_all": "Alle Events anzeigen" + "show_all": "Alle Events anzeigen", + "placeholder_search": "Events durchsuchen" }, "change-password": { "password": "Passwort", @@ -325,6 +378,7 @@ }, "prompt": { "confirm": "Bestätigen", + "alert": "Warnung", "cancel": "Nein", "continue": "Ja", "ok": "OK", @@ -336,7 +390,8 @@ "restore_to_root": "Der ursprüngliche Ordner existiert nicht mehr oder ist gelöscht, willst du die Dateien/Ordner in deinen obersten Ordner wiederherstellen?", "open_big_file": "Die Datei %s ist sehr gross und das Öffnen kann einige Zeit in Anspruch nehmen, willst du dennoch fortfahren?", "close_save_file": "Willst du deine Änderungen speichern?", - "auto_rename_node": "Eine Datei/ein Ordner mit dem Name %s existiert bereits, möchtest du sie/ihn umbenennen in %s?" + "auto_rename_node": "Eine Datei/ein Ordner mit dem Name %s existiert bereits, möchtest du sie/ihn umbenennen in %s?", + "open_file_disables_action": "Die gewählte Aktion kann nicht ausgeführt werden, wenn eine Datei offen ist" }, "events": { "unknown": "Unbekannt", @@ -423,7 +478,18 @@ "node_moved": "{{count}} Element wurde nach {{dest}} verschoben.", "node_moved_plural": "{{count}} Elemente wurden nach {{dest}} verschoben.", "node_moved_folderup": "{{count}} Element wurde in den übergeordneten Ordner verschoben.", - "node_moved_folderup_plural": "{{count}} Elemente wurden in den übergeordneten Ordner verschoben." + "node_moved_folderup_plural": "{{count}} Elemente wurden in den übergeordneten Ordner verschoben.", + "node_deleted": "{{count}} Element wurde in den Papierkorb verschoben.", + "node_deleted_plural": "{{count}} Elemente wurden in den Papierkorb verschoben.", + "collection_created": "Der Ordner {{name}} wurde erstellt.", + "file_created": "Die Datei {{name}} wurde erstellt.", + "node_renamed": "Das Element {{old_name}} wurde nach {{new_name}} umbenannt.", + "node_undeleted": "{{count}} Element wurde wiederhergestellt.", + "node_undeleted_plural": "{{count}} Elemente wurde wiederhergestellt.", + "file_saved": "Die Datei {{name}} wurde gespeichert.", + "share_added": "Der Ordner {{name}} wurde freigegeben.", + "share_updated": "Die Freigabe für den Ordner {{name}} wurde angepasst.", + "restore_file": "Die Version {{version}} wurde wiederhergestellt." }, "new_node": { "title": { @@ -431,5 +497,8 @@ "txt": "Neues Text-Dokument" }, "name": "Name" + }, + "misc": { + "rename_node": "Datei umbenennen" } } diff --git a/src/locale/en.json b/src/locale/en.json index 3b52fb8..5703a90 100755 --- a/src/locale/en.json +++ b/src/locale/en.json @@ -7,6 +7,8 @@ "logout": "Logout", "code": "Code", "multi_factor_auth": "Your account has been protected with multi factor authentication. Please enter the code from your google authenticator:", + "dont_remind_webauthn": "Do not ask me again.", + "setup_webauthn": "Your device is compatible to use a form of passwordless authentication. You may setup this now or at any time in the user settings.", "events": "Events", "profile": "Profile", "settings": "Settings", @@ -15,7 +17,9 @@ "server": "The balloon server reports an ongoing incident, please try it again later.", "credentials": "Wrong username or password given.", "code": "Invalid code provided.", - "oidc": "There was an error authenticate you via the choosen identity provider." + "oidc": "There was an error authenticate you via the choosen identity provider.", + "webauthn_setup": "There was an error to setup passwordless authentication. You may proceed by ignoring this step or try again.", + "webauthn": "There was an error to auhenticate using passwordless authentication. You may process by using username/password or try again." } }, "profile": { @@ -28,7 +32,8 @@ "nav": { "overview": "Overview", "change-password": "Change password", - "google-authenticator": "Two factor authentication" + "google-authenticator": "Two factor authentication", + "webauthn": "Passwordless authentication" }, "attribute": { "id": "User Id", @@ -40,6 +45,7 @@ "google-authenticator": { "activate": "Activate", "deactivate": "Deactivate", + "secret_copied": "The code has been copied to the clipboard", "confirm": { "activate": "Do you really want to activate multi factor authentication? After the activation the login will only be possible using the google authenticator app.", "deactivate": "Do you really want to deactivate multi factor authentication?" @@ -48,6 +54,17 @@ "inactive": "In order to activate multi factor authentication, you need a smartphone and the app Google Authenticator. After activating multi factor authentication a QR code, which you need to scan with the app, will be displayed. Attention, after the activation the login will only be possible using the google authenticator app.", "active": "Multi factor authentication is enabled." } + }, + "webauthn": { + "activate": "Activate", + "deactivate": "Deactivate", + "confirm": { + "deactivate": "Do you really want to deactivate multi passwordless authentication?" + }, + "hint": { + "inactive": "Passwordless authentication is inactive.", + "active": "Passwordless authentication is active." + } } }, "nav": { @@ -64,6 +81,7 @@ "cut": "Cut", "paste": "Paste", "filter": "Filter", + "sorting": "Sorting", "rename": "Rename", "perma-link": "Copy permalink" }, @@ -74,7 +92,8 @@ "history": "History", "share_folder": "Share folder", "share_link": "Share link", - "events": "Events" + "events": "Events", + "more": "More options" } }, "menu": { @@ -85,12 +104,16 @@ "trash": "Trash", "search": "Search" }, + "app": { + "file_editor": "Text Editor", + "file_viewer": "Preview", + "file_downloader": "Download" + }, "search": { - "results": "Searchresults", + "results": "Search results", "reset_filter": "Reset", "apply_filter": "Apply", "mode": { - "fulltext": "Fulltext", "nodename": "Filename" }, "filter": { @@ -123,6 +146,14 @@ "shares": "Shares", "hidden": "Hidden" }, + "sorting": { + "name_asc": "Name ascending", + "name_desc": "Name descending", + "size_asc": "Size ascending", + "size_desc": "Size descending", + "changed_asc": "Changed ascending", + "changed_desc": "Changed descending" + }, "open_folder": "Open folder", "folder": "Folder", "txt_file": "Text Document", @@ -132,7 +163,11 @@ "error": { "folder_exists": "A folder named %s does already exists." }, - "folderup": "Back" + "folderup": "Back", + "choose_handler": "Choose app", + "choose_handler_text": "Choose an app to open file file %s", + "choose_handler_remember": "Always open this file type with the choosen app.", + "no_handler_found": "There was no app found which can open %s. Would you like to download the file instead?" }, "user": { "disk_space": "Disk space:", @@ -152,17 +187,32 @@ "minute": "{{count}} minute", "minute_plural": "{{count}} minutes", "second": "{{count}} second", - "second_plural": "{{count}} seconds" + "second_plural": "{{count}} seconds", + "year_ago": "one year ago", + "year_ago_plural": "{{count}} years ago", + "month_ago": "one month ago", + "month_ago_plural": "{{count}} months ago", + "day_ago": "one day ago", + "day_ago_plural": "{{count}} days ago", + "hour_ago": "one hour ago", + "hour_ago_plural": "{{count}} hours ago", + "minute_ago": "one minute ago", + "minute_ago_plural": "{{count}} minutes ago", + "second_ago": "one second ago", + "second_ago_plural": "{{count}} seconds ago" }, "button": { "save": "Save", - "cancel": "Cancel" + "setup": "Setup", + "ignore": "Ignore", + "cancel": "Cancel", + "open": "Open" }, "view": { "prop": { "head": { - "mime": "Mime:", - "id": "ID:", + "mime": "Mime", + "id": "ID", "contains": "Contains", "changed": "Changed", "deleted": "Deleted", @@ -181,7 +231,9 @@ "path": "Path", "coordinate": "Coordinate", "author": "Author", - "perma-link": "Permalink" + "perma-link": "Permalink", + "mount": "Mount", + "mount_value": "%s (%s@%s://%s) [%s]" }, "data": { "size": "%s (%s Bytes)", @@ -287,7 +339,8 @@ } }, "events": { - "show_all": "Show all events" + "show_all": "Show all events", + "placeholder_search": "Search events" }, "change-password": { "password": "Password", @@ -324,6 +377,7 @@ }, "prompt": { "confirm": "Confirm", + "alert": "Warning", "cancel": "No", "continue": "Yes", "ok": "Ok", @@ -335,7 +389,8 @@ "restore_to_root": "The parent node in which you want to restore does not exists or is deleted, would you like to restore the node(s) to your root directory?", "open_big_file": "The file %s is large and open it could take a long time, would you like to continue anyway?", "close_save_file": "Would you like to save your changes?", - "auto_rename_node": "A file or directory called %s already exists. Would you like to rename yours to %s?" + "auto_rename_node": "A file or directory called %s already exists. Would you like to rename yours to %s?", + "open_file_disables_action": "The choosen action can't be executed, while a file is open" }, "events": { "unknown": "unknown", @@ -422,7 +477,18 @@ "node_moved": "{{count}} Element has been moved to {{dest}}.", "node_moved_plural": "{{count}} Elements have been moved to {{dest}}.", "node_moved_folderup": "{{count}} Element has been moved to the parent folder.", - "node_moved_folderup_plural": "{{count}} Elements have been moved to the parent folder." + "node_moved_folderup_plural": "{{count}} Elements have been moved to the parent folder.", + "node_deleted": "{{count}} Element has been moved to Trash.", + "node_deleted_plural": "{{count}} Elemente have been moved to Trash.", + "collection_created": "The folder {{name}} has been created.", + "file_created": "The file {{name}} has been created.", + "node_renamed": "The element {{old_name}} has been renamed to {{new_name}}.", + "node_undeleted": "{{count}} element has been recovered.", + "node_undeleted_plural": "{{count}} elements have been recovered.", + "file_saved": "The file {{name}} has been saved.", + "share_added": "The collection {{name}} has been shared.", + "share_updated": "The share for the collection {{name}} has been updated.", + "restore_file": "The version {{version}} has been restored." }, "new_node": { "title": { @@ -430,5 +496,8 @@ "txt": "New Text Document" }, "name": "Name" + }, + "misc": { + "rename_node": "Rename file" } } diff --git a/src/main.js b/src/main.js index 6a8caf0..31ffc32 100644 --- a/src/main.js +++ b/src/main.js @@ -5,13 +5,13 @@ * @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 */ +import "@babel/polyfill"; import iconsSvg from '@gyselroth/icon-collection/src/icons.svg'; import $ from "jquery"; import translate from './lib/translate.js'; import svgxuse from 'svgxuse'; import balloonCss from './themes/default/scss/balloon.scss'; import { polyfill } from 'es6-promise'; polyfill(); - window.jquery = $; $.ajax({ @@ -27,3 +27,9 @@ $.ajax({ translate.init({}); } }); + +if ('serviceWorker' in navigator) { + window.addEventListener('load', function() { + navigator.serviceWorker.register('/service-worker.js', { scope: '/' }); + }); +} diff --git a/src/themes/default/img/icon_512x512.png b/src/themes/default/img/icon_512x512.png new file mode 100644 index 0000000..5961c9d Binary files /dev/null and b/src/themes/default/img/icon_512x512.png differ diff --git a/src/themes/default/scss/_base.scss b/src/themes/default/scss/_base.scss index 9525175..33af6e4 100644 --- a/src/themes/default/scss/_base.scss +++ b/src/themes/default/scss/_base.scss @@ -1,8 +1,19 @@ html, body, #fs-namespace { - height: 100%; - position: relative; + @include fs-content-mobile-overlay; + + right: 0; + -webkit-overflow-scrolling: touch; + + .fs-content-mobile-more &, + .fs-content-mobile-detail & { + @include fs-content-mobile-overlay-left; + } + + @media screen and (min-width: $breakpointPannelVisible) { + right: 0 !important; + } } svg, @@ -83,7 +94,7 @@ select { border: 2px solid $colorActiveBlue; } - &::-ms-extend { + &::-ms-expand { display: none; } @@ -119,6 +130,7 @@ input[type="checkbox"] { & + label { position: relative; padding-left: 30px; + padding-top: 2px; line-height: 17px; display: inline-block; vertical-align: text-top; diff --git a/src/themes/default/scss/_fsBrowserLayout.scss b/src/themes/default/scss/_fsBrowserLayout.scss index 0998fb1..2ad5e5d 100644 --- a/src/themes/default/scss/_fsBrowserLayout.scss +++ b/src/themes/default/scss/_fsBrowserLayout.scss @@ -1,34 +1,30 @@ #fs-browser-layout { position: absolute; - top: 60px; - bottom: 0; left: 0; - right: 0; + top: $headerHeight; overflow: hidden; z-index: 10; - #fs-layout-left { - width: 100%; - } - - #fs-content { - width: 0; - } + height: calc(100% - #{$headerHeight}); + width: 100%; #fs-layout-left, #fs-content { + width: 100%; transition: width 0.3s ease-in-out; } @media screen and (min-width: $breakpointPannelVisible) { - $pannelWidth: 400px; + right: 0 !important; + transform: inherit !important; + #fs-layout-left { - width: calc(100% - #{$pannelWidth}); + width: calc(100% - #{$pannelWidthM}); } #fs-content { - width: $pannelWidth; + width: $pannelWidthM; } } } diff --git a/src/themes/default/scss/_fsContent.scss b/src/themes/default/scss/_fsContent.scss index 145527f..be57d5e 100644 --- a/src/themes/default/scss/_fsContent.scss +++ b/src/themes/default/scss/_fsContent.scss @@ -1,25 +1,62 @@ -$fsContentSidePadding: 30px; - @import './fsContent/fsBrowserSelectAction'; @import './fsContent/fsPropertiesName'; @import './fsContent/fsContentView'; +@import './fsContent/fsContentNavSmallWrap'; +@import './fsContent/fsContentNavSmallMore'; #fs-content { - + @include shadow-default; position: absolute; - right: 0; - top: 0; + left: 0; + bottom: 0; border-top: 1px solid $colorBrightGrey; background-color: $colorWhite; - height: 100%; + height: auto; overflow: hidden; + display: none; + z-index: 17; + + #fs-content-close { + @include icon-button(28px); + + position: absolute; + top: 16px; + right: 16px; + padding: 2px; + color: $colorMediumGrey; + } #fs-content-inner { height: 100%; - overflow-x: hidden; - overflow-y: auto; + overflow: hidden; border-bottom: 1px solid $colorBrightGrey; box-sizing: border-box; - padding: 22px $fsContentSidePadding 0; + padding: 47px $fsContentSidePaddingS 0; + } + + .fs-content-paste-active &, + .fs-content-select-active &, + .fs-content-multiselect-active & { + display: block; + } + + @media screen and (min-width: $breakpointPannelVisible) { + left: auto; + bottom: auto; + right: 0; + top: 0; + height: 100%; + display: block; + box-shadow: none; + + #fs-content-close { + display: none; + } + + #fs-content-inner { + overflow-x: hidden; + overflow-y: auto; + padding: 22px $fsContentSidePaddingM 0; + } } } diff --git a/src/themes/default/scss/_fsLayoutLeft.scss b/src/themes/default/scss/_fsLayoutLeft.scss index dea3527..3255fa1 100644 --- a/src/themes/default/scss/_fsLayoutLeft.scss +++ b/src/themes/default/scss/_fsLayoutLeft.scss @@ -20,7 +20,7 @@ width: 100%; box-sizing: border-box; - @media screen and (min-width: $breakpointMenuVisible) { + @media screen and (min-width: $breakpointPannelVisible) { padding: 0 30px 0 90px; } } diff --git a/src/themes/default/scss/_fsMenuLeft.scss b/src/themes/default/scss/_fsMenuLeft.scss index a88d073..0777670 100644 --- a/src/themes/default/scss/_fsMenuLeft.scss +++ b/src/themes/default/scss/_fsMenuLeft.scss @@ -1,21 +1,17 @@ +@import '../../../../node_modules/slideout/index.css'; + #fs-menu-left { position: absolute; - top: 60px; + top: $headerHeight; bottom: 0; left: 0; + width: 0; overflow-x: hidden; overflow-y: auto; background-color: $colorWhite; border-right: 1px solid $colorBrightGrey; - - transition: width 0.3s ease-in-out; - - z-index: 15; - - &.fs-menu-left-open { - width: 240px; - } + width: 240px; #fs-menu-left-top, #fs-menu-left-bottom { @@ -112,9 +108,16 @@ } } - @media screen and (min-width: $breakpointMenuVisible) { + @media screen and (min-width: $breakpointPannelVisible) { width: 60px; overflow-y: hidden; + display: block; + z-index: 15 !important; + transition: width 0.1s ease-in-out; + + &.fs-menu-left-open { + width: 240px; + } } @media screen and (min-height:547px) { diff --git a/src/themes/default/scss/_fsSearchFilter.scss b/src/themes/default/scss/_fsSearchFilter.scss index 9eca065..d89f030 100644 --- a/src/themes/default/scss/_fsSearchFilter.scss +++ b/src/themes/default/scss/_fsSearchFilter.scss @@ -1,15 +1,16 @@ #fs-search-filter { @include shadow-default; display: none; - position: absolute; + position: fixed; left: 0; right: 0; - height: auto; + top: 0; + height: 100%; width: auto; - padding: 0 30px 0 90px; + padding: 0 10px; background-color: $colorWhite; - border-top: 1px solid $colorBrightGrey; - z-index: 11; + overflow: auto; + z-index: 12; #fs-search-filter-content, #fs-search-filter-buttons { @@ -210,4 +211,15 @@ margin-left: 10px; } } + + @media screen and (min-width: $breakpointPannelVisible) { + position: absolute; + left: 0; + right: 0; + top: $headerHeight; + height: auto; + padding: 0 30px 0 90px; + z-index: 11; + border-top: 1px solid $colorBrightGrey; + } } diff --git a/src/themes/default/scss/_fsSnackbar.scss b/src/themes/default/scss/_fsSnackbar.scss index a3af7ff..b32c817 100644 --- a/src/themes/default/scss/_fsSnackbar.scss +++ b/src/themes/default/scss/_fsSnackbar.scss @@ -16,10 +16,11 @@ $snackBarVisibleDuration: 2s; transform: translate(-50%,0); width: auto; min-width: 300px; + max-width: calc(100vw - 20px); height: auto; background-color: $colorWhite; border-radius: 4px; - padding: 8px 11px 8px 15px; + padding: 8px 45px 8px 15px; margin: 0 auto; z-index: 12001; @@ -31,20 +32,24 @@ $snackBarVisibleDuration: 2s; #fs-snackbar-message, #fs-snackbar-icon { display: inline-block; - height: 24px; line-height: 24px; vertical-align: middle; } #fs-snackbar-message { - float: left; + height: auto; + width: auto; } #fs-snackbar-icon { - float: right; height: 24px; width: 24px; - margin-left: 10px; + + display: block; + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 11px; &.has-action { cursor: pointer; diff --git a/src/themes/default/scss/_header.scss b/src/themes/default/scss/_header.scss index e17edaa..c26369d 100644 --- a/src/themes/default/scss/_header.scss +++ b/src/themes/default/scss/_header.scss @@ -1,6 +1,5 @@ +$searchModeDropdownWidth: 220px; #fs-browser-top-bar { - $headerHeight: 60px; - @include clearfix; position: relative; @@ -49,7 +48,7 @@ @include clearfix; - margin-left: 16px; + margin-left: 0; margin-top: ( $headerHeight - $logoHeight ) / 2; svg { @@ -77,7 +76,9 @@ $height: 36px; position: absolute; - top: 12px; + top: $headerHeight; + padding: 0 5px 10px 5px; + width: 100%; left: 0; right: 0; @@ -85,22 +86,19 @@ margin: auto; background-color: $colorWhite; - width: 400px; height: $height; transition: width 0.5s ease-in-out; - @media screen and (min-width: $breakpointSearchVisible) { + &.fs-search-mobile-visible { display: block; } - & > .gr-icon { - position: absolute; - color: $colorMediumGrey; + .fs-fullscreen-window-open & { + display: none; } .gr-i-search { - top: 6px; - left: 7px; + display: none; } #fs-search-reset { @@ -108,7 +106,7 @@ position: absolute; width: 30px; height: $height; - right: 37px; + right: 47px; top: 0; cursor: pointer; @@ -127,7 +125,7 @@ position: absolute; width: auto; height: $height; - right: 67px; + right: 77px; top: 0; cursor: pointer; color: $colorMediumGrey; @@ -148,11 +146,10 @@ #fs-search-mode-dropdown { display: none; - $dropdownWidth: 220px; - @include dropdown($dropdownWidth, 52px); + @include dropdown($searchModeDropdownWidth, 52px); - left: calc(100% - #{$dropdownWidth + 42px}); + left: calc(100% - #{$searchModeDropdownWidth + 52px}); top: 41px; display: none; @@ -214,9 +211,9 @@ } #fs-search-input { - padding: 6px 120px 6px 45px; + padding: 6px 120px 6px 12px; float: left; - width: calc(100% - 40px); + width: calc(100% - 50px); height: 100%; background-color: $colorWhite; @@ -250,16 +247,11 @@ &.fs-search-focused { #fs-search-input { - @include shadow-selected; - padding: 4px 120px 6px 44px; + padding: 5px 120px 5px 11px; border-width: 2px; border-color: $colorActiveBlue; } - - @media screen and (min-width: $breakpointSearchBig) { - width: 600px; - } } &.fs-search-filtered { @@ -439,5 +431,72 @@ } } + @media screen and (min-width: $breakpointPannelVisible) { + #fs-logo { + margin-left: 16px; + } + + #fs-search { + display: block; + top: 12px; + width: 400px; + padding: 0; + + & > .gr-icon { + position: absolute; + color: $colorMediumGrey; + } + + .gr-i-search { + display: block; + top: 6px; + left: 7px; + } + + #fs-search-input { + padding-left: 45px; + width: calc(100% - 40px); + } + + &.fs-search-focused { + #fs-search-input { + @include shadow-selected; + padding-left: 44px; + } + } + + #fs-search-reset { + right: 37px; + } + + #fs-search-mode-toggle { + right: 67px; + } + + #fs-search-mode-dropdown { + left: calc(100% - #{$searchModeDropdownWidth + 42px}); + } + } + + #fs-identity { + #fs-search-trigger-mobile { + display: none; + } + } + } + + @media screen and (min-width: $breakpointSearchBig) { + #fs-search { + display: block; + top: 12px; + width: 400px; + padding: 0; + + &.fs-search-focused { + width: 600px; + } + } + } + @include clearfix; } diff --git a/src/themes/default/scss/_kendo-ui-override.scss b/src/themes/default/scss/_kendo-ui-override.scss index 8af3ca2..31c22b0 100644 --- a/src/themes/default/scss/_kendo-ui-override.scss +++ b/src/themes/default/scss/_kendo-ui-override.scss @@ -44,12 +44,29 @@ .k-overlay { opacity: 1 !important; filter: none !important; - background-color: rgba(0, 0, 0, 0.4); + background-color: $overlayBackgroundColor; &.bln-overlay-clickable { cursor: pointer; } } +.k-list-container.k-popup ul.k-list li { + svg { + position: relative; + top: 6px; + left: -3px; + } +} + +.k-list-scroller { + overflow-x: hidden; + overflow-y: auto; +} + +div.k-window-content { + z-index: auto; +} + @import './kendoUiOverride/date-and-time-picker'; @import './kendoUiOverride/k-calendar'; diff --git a/src/themes/default/scss/_login.scss b/src/themes/default/scss/_login.scss index 2ce95dc..1083a7a 100644 --- a/src/themes/default/scss/_login.scss +++ b/src/themes/default/scss/_login.scss @@ -18,6 +18,30 @@ display: none; } + #login-setup-webauthn { + display: none; + + input[type=submit] { + margin-top: 10px; + } + + input[name=submit] { + margin-bottom: 25px; + } + } + + #login-webauthn { + display: none; + width: 50px; + height: 50px; + margin: 20px auto; + + svg.gr-icon { + width: 100%; + height: 100%; + } + } + #login-box { position: absolute; background-color: #eaeaea; @@ -40,6 +64,10 @@ margin-bottom: 10px; } + input[type="submit"] { + padding: 7px 22px 8px 22px; + } + .login-select { position: relative; @@ -64,6 +92,11 @@ } } + #login-recaptcha { + transform: scale(0.82); + margin-left: -25px; + } + #login-oidc { text-align: center; diff --git a/src/themes/default/scss/balloon.scss b/src/themes/default/scss/balloon.scss index 6c1c4a4..232f360 100644 --- a/src/themes/default/scss/balloon.scss +++ b/src/themes/default/scss/balloon.scss @@ -1,5 +1,9 @@ @import '../../../../node_modules/ubuntu-fontface/ubuntu.css'; +@import '../../../../node_modules/mobile-pull-to-refresh/dist/styles/ios/style.css'; + +@import 'includes/variables'; +@import 'includes/fsContentMobileOverlay'; @import 'includes/breakpoints'; @import 'includes/colors'; @import 'includes/fonts'; diff --git a/src/themes/default/scss/fsContent/_fsBrowserSelectAction.scss b/src/themes/default/scss/fsContent/_fsBrowserSelectAction.scss index 4bdba3a..cf76a39 100644 --- a/src/themes/default/scss/fsContent/_fsBrowserSelectAction.scss +++ b/src/themes/default/scss/fsContent/_fsBrowserSelectAction.scss @@ -1,15 +1,72 @@ #fs-browser-select-action { - $buttonHeight: 30px; + $buttonHeightS: 40px; + $buttonHeightM: 30px; - margin-top: 16px; - height: $buttonHeight; - width: 345px; + white-space: nowrap; + overflow-x: auto; + overflow-y: hidden; - li { - @include icon-button-small($buttonHeight, $buttonHeight); + margin: 9px -1*$fsContentSidePaddingS 10px; + padding: 16px 0 $fsContentSidePaddingS; + height: $buttonHeightS; + width: auto; - &:last-child { + ul { + padding: 0 $fsContentSidePaddingS; + display: block; + box-sizing: border-box; + + li { + @include icon-button-small($buttonHeightS, $buttonHeightS); + + margin-right: 8px; + + &.fs-action-disabled { + display: none; + } + } + + &:after { + //needed for spacing while scrolling the menu + content: ""; + display: inline-block; + border: none; margin-right: 0; + width: $fsContentSidePaddingS - 8px; + height: $buttonHeightS; + } + } + + + @media screen and (min-width: $breakpointPannelVisible) { + margin: 0; + padding-bottom: 0; + + width: 345px; + height: $buttonHeightM; + + ul { + margin: 0; + padding: 0; + + li { + width: $buttonHeightM; + height: $buttonHeightM; + margin-right: 13px; + + + &:last-child { + margin-right: 0; + } + + &.fs-action-disabled { + display: inline-block; + } + } + + &:after { + display: none; + } } } } diff --git a/src/themes/default/scss/fsContent/_fsContentNavSmallMore.scss b/src/themes/default/scss/fsContent/_fsContentNavSmallMore.scss new file mode 100644 index 0000000..f50735c --- /dev/null +++ b/src/themes/default/scss/fsContent/_fsContentNavSmallMore.scss @@ -0,0 +1,14 @@ +#fs-content-nav-small-more { + + display: none; + + .fs-content-select-active & { + margin: 0 -1*$fsContentSidePaddingS; + padding: 0 $fsContentSidePaddingS; + display: block; + } + + @media screen and (min-width: $breakpointPannelVisible) { + display: none !important; + } +} diff --git a/src/themes/default/scss/fsContent/_fsContentNavSmallWrap.scss b/src/themes/default/scss/fsContent/_fsContentNavSmallWrap.scss new file mode 100644 index 0000000..c5b62db --- /dev/null +++ b/src/themes/default/scss/fsContent/_fsContentNavSmallWrap.scss @@ -0,0 +1,59 @@ +#fs-content-nav-small-wrap { + @include fs-content-mobile-overlay; + + padding: 0 $fsContentSidePaddingS; + + .fs-content-mobile-more & { + right: 0; + } + + .fs-content-mobile-detail & { + @include fs-content-mobile-overlay-left; + } + + #fs-content-nav-small { + overflow-x: hidden; + overflow-y: auto; + + max-height: calc(100% - 80px); + + margin: 0 -1*$fsContentSidePaddingS; + padding: 0 $fsContentSidePaddingS; + } + + @media screen and (min-width: $breakpointPannelVisible) { + display: none; + } +} + +.fs-content-nav-small { + li { + position: relative; + cursor: pointer; + font-size: $fontSizeLead; + font-weight: $fontWeightLead; + line-height: 20px; + color: $colorDarkGrey; + border-top: 1px solid $colorBrightGrey; + margin: 0 -1*$fsContentSidePaddingS 0; + padding: 16px $fsContentSidePaddingS 18px; + + &:last-child { + border-bottom: 1px solid $colorBrightGrey; + } + + &.disabled { + display: none; + } + + .gr-icon { + display: block; + position: absolute; + top: 15px; + right: $fsContentSidePaddingS; + width: 24px; + height: 24px; + color: $colorMediumGrey; + } + } +} diff --git a/src/themes/default/scss/fsContent/_fsContentView.scss b/src/themes/default/scss/fsContent/_fsContentView.scss index 66f688d..2aff125 100644 --- a/src/themes/default/scss/fsContent/_fsContentView.scss +++ b/src/themes/default/scss/fsContent/_fsContentView.scss @@ -6,8 +6,146 @@ @import './fsContentView/fsShare'; @import './fsContentView/fsShareLink'; -#fs-content-view { - margin-top: 20px; - @include accordion($fsContentSidePadding); +#fs-content-view-wrap { + @include fs-content-mobile-overlay; + + padding: 0 $fsContentSidePaddingS; + + .fs-content-mobile-detail & { + right: 0; + } + + #fs-content-view { + @include accordion($fsContentSidePaddingS); + + position: relative; + background-color: $colorWhite; + height: calc(100% - 65px); + + dt { + display: none; + + &.active { + display: block; + + &:before { + display: none; + } + + .gr-i-arrowhead-n { + display: none; + } + } + + .gr-icon { + display: none; + } + } + + dd { + display: none; + padding: 0; + + &.active { + &:before { + display: none; + } + + max-height: calc(100% - 55px); + overflow: auto; + } + + &.disabled { + display: none !important; + } + } + } + + @media screen and (min-width: $breakpointPannelVisible) { + $top: 168px; + + position: fixed; + width: $pannelWidthM; + height: calc(100% - #{$top}); + top: $top; + right: 0; + bottom: auto; + left: auto; + padding: 0 $fsContentSidePaddingM; + overflow-x: hidden; + overflow-y: scroll; + z-index: 10; + + &.active-mobile { + right: auto; + animation: none; + } + + #fs-content-view-header { + display: none; + } + + #fs-content-view { + padding: 0; + + dt { + display: block; + position: relative; + cursor: pointer; + margin: 0 -1*$fsContentSidePaddingM 0; + padding: 16px $fsContentSidePaddingM 18px; + + .gr-icon { + display: block; + position: absolute; + top: 13px; + right: 20px; + color: $colorMediumGrey; + } + + .gr-i-arrowhead-n { + display: none; + } + + &.active { + &:before { + display: block; + } + + .gr-i-arrowhead-s { + display: none; + } + .gr-i-arrowhead-n { + display: block; + } + } + + &.disabled { + color: $colorLightGrey; + + .gr-i-arrowhead-s { + color: $colorLightGrey; + } + } + } + + dd { + position: relative; + padding-top: 2px; + padding: 2px $fsContentSidePaddingM 65px; + margin: 0 -1*$fsContentSidePaddingM; + + &.active { + &:before { + display: block; + left: 0; + } + + max-height: auto; + overflow: auto; + } + } + } + } } diff --git a/src/themes/default/scss/fsContent/_fsPropertiesName.scss b/src/themes/default/scss/fsContent/_fsPropertiesName.scss index 6f9a892..9e98abf 100644 --- a/src/themes/default/scss/fsContent/_fsPropertiesName.scss +++ b/src/themes/default/scss/fsContent/_fsPropertiesName.scss @@ -1,34 +1,71 @@ #fs-browser-summary, #fs-properties-name { + $extWidth: 53px; + $extMargin: 3px; + @include clearfix; @include font-paragraph-bold; - span { - $extWidth: 53px; - $extMargin: 3px; + position: relative; + + div:first-child { + height: 20px; - display: block; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - float: left; + span { + display: block; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + float: left; - &.fs-value { - max-width: calc(100% - #{$extWidth + $extMargin}); + &.fs-value { + max-width: calc(100% - #{$extWidth + $extMargin + 30px)}); + + &:last-child { + max-width: calc(100% - 30px); + } + } - &:last-child { - max-width: 100%; + &.fs-ext { + margin-left: $extMargin; + text-transform: uppercase; + max-width: $extWidth; } } + } + + @media screen and (min-width: $breakpointPannelVisible) { + div:first-child { + line-height: 20px; + + span { + &.fs-value { + max-width: calc(100% - #{$extWidth + $extMargin}); - &.fs-ext { - margin-left: $extMargin; - text-transform: uppercase; - max-width: $extWidth; + &:last-child { + max-width: 100%; + } + } + } } } } +#fs-browser-summary, +#fs-properties-name { + position: absolute; + top: 25px; + left: 20px; + width: calc(100% - #{$fsContentSidePaddingM * 2 + 45px}); + + @media screen and (min-width: $breakpointPannelVisible) { + position: relative; + top: auto; + left: auto; + width: auto; + } +} + #fs-browser-summary { color: $colorMediumGrey; font-weight: 400; diff --git a/src/themes/default/scss/fsContent/fsContentView/_fsHistory.scss b/src/themes/default/scss/fsContent/fsContentView/_fsHistory.scss index 373d7b1..fa1138d 100644 --- a/src/themes/default/scss/fsContent/fsContentView/_fsHistory.scss +++ b/src/themes/default/scss/fsContent/fsContentView/_fsHistory.scss @@ -7,14 +7,23 @@ } #fs-history-actions { - @include clearfix; - input { - float: left; + margin-bottom: 10px; } + } + + @media screen and (min-width: 350px) { + #fs-history-actions { + @include clearfix; + + input { + float: left; + margin-bottom: 0; + } - button { - float: right; + button { + float: right; + } } } } diff --git a/src/themes/default/scss/fsContent/fsContentView/_fsMetadata.scss b/src/themes/default/scss/fsContent/fsContentView/_fsMetadata.scss index 4b4f044..da6ef45 100644 --- a/src/themes/default/scss/fsContent/fsContentView/_fsMetadata.scss +++ b/src/themes/default/scss/fsContent/fsContentView/_fsMetadata.scss @@ -67,7 +67,8 @@ z-index: 1; } - &#fs-metadata-share { + &#fs-metadata-share, + &#fs-metadata-mount { @include clearfix; .fs-value { @@ -93,4 +94,26 @@ } } } + + @media screen and (max-width: 390px) { + table { + display: block; + + th, + td { + display: block; + padding-left: 0; + width: 100%; + max-width: 100%; + } + + th { + padding-bottom: 0; + } + + td { + color: $colorDarkGrey; + } + } + } } diff --git a/src/themes/default/scss/fsLayoutLeft/_fsBrowser.scss b/src/themes/default/scss/fsLayoutLeft/_fsBrowser.scss index b37c5fa..098f091 100644 --- a/src/themes/default/scss/fsLayoutLeft/_fsBrowser.scss +++ b/src/themes/default/scss/fsLayoutLeft/_fsBrowser.scss @@ -1,13 +1,13 @@ #fs-browser { $headerHeight: 28px; //height of the header $headerBorderColor: $colorLightGrey; - $nodeHeight: 50px; //height of a single node + $nodeHeight: 54px; //height of a single node - $lineHeight: 30px; + $lineHeight: 46px; $iconWidth: 26px; $metaWidth: 120px; - $sizeWidth: 110px; + $sizeWidth: 115px; $changedWidth: 150px; $checkboxWidth: 18px; @@ -15,12 +15,32 @@ $border-radius: 4px; - $breakpointMetaVisible: 659px; - $breakpointSizeVisible: 720px; - $breakpointChangedVisible: 875px; + $breakpointMetaVisible: 414px; + $breakpointSizeChangedVisible: 750px; + + $fsContentHeightMultiselectS: 144px; + $fsContentHeightSelectS: 200px; + + $bottomPadding: 90px; + + $fsActionRefreshTop: 30px; + $fsActionRefreshButtonSize: 30px; + position: relative; - padding-bottom: 90px; + padding-bottom: $bottomPadding; + + .fs-content-paste-active & { + padding-bottom: #{$bottomPadding + $fsContentHeightMultiselectS}; + } + + .fs-content-multiselect-active & { + padding-bottom: #{$bottomPadding + $fsContentHeightMultiselectS}; + } + + .fs-content-select-active & { + padding-bottom: #{$bottomPadding + $fsContentHeightSelectS}; + } .fs-browser-column { display: inline-block; @@ -49,7 +69,7 @@ width: $iconWidth; height: $lineHeight; margin: 0; - padding: 3px 0; + padding: 7px 0; } .fs-browser-column-changed { @@ -61,70 +81,82 @@ } .fs-browser-column-checkbox { - @include checkbox($checkboxWidth); + margin: -15px -17px -15px -14px; + padding: 15px 17px 15px 14px; + + span { + @include checkbox($checkboxWidth); + display: block; + } } #fs-browser-header { - border: 1px solid $headerBorderColor; - border-radius: $border-radius; - padding: 0 17px 0 12px; + padding: 0; margin-bottom: 20px; position: relative; .fs-browser-column { - $paddingTopBottom: ($headerHeight - $lineHeight) / 2; + $paddingTopBottom: 0; position: relative; + line-height: 30px; + height: 30px; padding-top: $paddingTopBottom; padding-bottom: $paddingTopBottom; + div { display: inline-block; width: auto; } } - .fs-browser-column-name { - width: calc(100% - #{$iconWidth} - #{$checkboxWidth}); - } - + .fs-browser-column-icon, + .fs-browser-column-name, .fs-browser-column-size, .fs-browser-column-changed { - border-left: 1px dotted $headerBorderColor; - padding-left: $columnPaddingLeftRight - 1px; display: none; } #fs-browser-header-checkbox { - width: $checkboxWidth; - height: $checkboxWidth; - border: 1px solid $colorMediumGrey; - border-radius: 2px; cursor: pointer; + display: inline-block; + + padding: 5px 16px 5px 15px; + + position: absolute; + top: 15px; + right: 18px; + + span { + display: block; + width: $checkboxWidth; + height: $checkboxWidth; + border: 1px solid $colorMediumGrey; + border-radius: 2px; + } &.fs-browser-header-checkbox-checked { - @include checkboxChecked; + span { + @include checkboxChecked; - background-size: 20px; + background-size: 20px; + } } &.fs-browser-header-checkbox-undetermined { - @include checkboxUndetermined; + span { + @include checkboxUndetermined; + } } } - .fs-browser-column-icon { - .gr-icon { - $iconSize: 13px; - - position: relative; - top: 0; - left: 3px; - width: $iconSize; - height: $iconSize; - } + #fs-browser-action-sorting { + padding: 2px 0; + margin-left: -2px; } - #fs-action-filter { + #fs-action-filter, + #fs-action-sorting { position: absolute; left: 0; top: 0; @@ -133,7 +165,14 @@ cursor: pointer; } - #fs-action-filter-select { + #fs-action-sorting { + width: auto; + padding-left: 100%; + white-space: nowrap; + } + + #fs-action-filter-select, + #fs-action-sorting-select { @include dropdown(140px, 34px); left: -1px; @@ -146,9 +185,11 @@ .bln-dropdown-content { padding: 8px 0; - input[type="checkbox"] { + input[type="checkbox"], + input[type="radio"] { & + label { padding-left: 36px; + padding-top: 0; vertical-align: text-top; min-height: 30px; margin-top: 0; @@ -177,11 +218,42 @@ } } } + + #fs-action-sorting-select { + width: 200px; + + .bln-dropdown-spike { + right: 170px; + } + + .bln-dropdown-content { + li { + position: relative; + } + + input[type="radio"] { + position: absolute; + top: 7px; + left: 8px; + display: inline-block; + + & + label { + cursor: pointer; + + &:before { + display: none !important; + } + } + } + } + } + } #fs-browser-tree { width: 100%; + overflow: hidden; // single node .k-item { @@ -214,6 +286,7 @@ .fs-browser-column-name { width: calc(100% - #{$iconWidth + $checkboxWidth}); height: $lineHeight; + line-height: 17px; color: $colorDarkGrey; font-size: $fontSizeParagraph; font-weight: $fontWeightParagraph; @@ -221,6 +294,19 @@ white-space: nowrap; overflow: hidden; + .fs-name { + margin-top: 5px; + display: inline-block; + } + + & > p { + margin-top: 2px; + color: $colorMediumGrey; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .fs-ext { text-transform: uppercase; } @@ -234,6 +320,10 @@ margin-left: -7px; border: 1px solid $colorLightGrey; } + + .fs-browser-column-name-path { + display: none; + } } .fs-browser-column-meta, @@ -257,6 +347,10 @@ height: 24px; } + .gr-icon { + display: block; + } + .fs-color-tag { margin: 2px; width: 20px; @@ -281,20 +375,6 @@ } } - &.fs-browser-search-item { - .fs-browser-column-name { - height: 33px; - margin-top: -2px; - margin-bottom: -1px; - line-height: 17px; - - & > p { - line-height: 14px; - margin-top: 2px; - color: $colorMediumGrey; - } - } - } } &:hover { @@ -319,9 +399,11 @@ padding: ($paddingTopBottom - $borderSize) ($paddingRight - $borderSize) ($paddingTopBottom - $borderSize) ($paddingLeft - $borderSize); .fs-browser-column-checkbox { - @include checkboxChecked; + span { + @include checkboxChecked; - background-size: 20px; + background-size: 20px; + } } } } @@ -353,107 +435,77 @@ } &.fs-folderup { - margin-bottom: 20px; - - .k-in { - .fs-browser-column-icon { - color: $colorActiveBlue; - } - - .fs-browser-column-name { - color: $colorMediumGrey; - } - } + display: none; } } } #fs-action-refresh { - @include icon-button(30px, 30px); + @include icon-button($fsActionRefreshButtonSize, $fsActionRefreshButtonSize); - position: absolute; background-color: $colorWhite; color: $colorActiveBlue; border-radius: 4px; - bottom: 30px; - right: 0; + top: $fsActionRefreshTop; + left: calc(50% - #{$fsActionRefreshButtonSize/2}); &:hover { color: $colorHoverBlue; } - } - - @media screen and (min-width: $breakpointMetaVisible) { - #fs-browser-tree { - $metaPaddingSmallScreen: 25px; - - .k-item { - .k-in { - .fs-browser-column-meta { - display: inline-block; - width: $metaWidth + $metaPaddingSmallScreen; - padding: 0 $metaPaddingSmallScreen 0 0; - } - .fs-browser-column-name { - width: calc(100% - #{$iconWidth + $metaWidth + $metaPaddingSmallScreen + $checkboxWidth}); - } - } - } + .fs-content-paste-active & { + top: #{$fsActionRefreshTop + $fsContentHeightMultiselectS}; } - } - @media screen and (min-width: $breakpointSizeVisible) { - #fs-browser-header { - .fs-browser-column-size { - display: inline-block; - } + .fs-content-multiselect-active & { + top: #{$fsActionRefreshTop + $fsContentHeightMultiselectS}; + } - .fs-browser-column-name { - width: calc(100% - #{$iconWidth} - #{$sizeWidth} - #{$checkboxWidth}); - } + .fs-content-select-active & { + top: #{$fsActionRefreshTop + $fsContentHeightSelectS}; } + } + @media screen and (min-width: $breakpointBreadcrumbM) { #fs-browser-tree { .k-item { - .k-in { - .fs-browser-column-meta { - padding-right: 0; - width: $metaWidth; - } + &.fs-folderup { + display: block; + margin-bottom: 20px; - .fs-browser-column-size { - display: inline-block; - } + .k-in { + .fs-browser-column-icon { + color: $colorActiveBlue; + } - .fs-browser-column-name { - width: calc(100% - #{$iconWidth + $metaWidth + $sizeWidth + $checkboxWidth}); + .fs-browser-column-name { + color: $colorMediumGrey; + line-height: $lineHeight; + + .fs-name { + margin-top: 0; + } + } } } } } } - @media screen and (min-width: $breakpointChangedVisible) { - #fs-browser-header { - .fs-browser-column-changed { - display: inline-block; - } - - .fs-browser-column-name { - width: calc(100% - #{$iconWidth} - #{$changedWidth} - #{$sizeWidth} - #{$checkboxWidth}); - } - } - + @media screen and (min-width: $breakpointMetaVisible) { #fs-browser-tree { + $metaPaddingSmallScreen: 25px; + .k-item { .k-in { - .fs-browser-column-changed { + .fs-browser-column-meta { display: inline-block; + width: $metaWidth + $metaPaddingSmallScreen; + padding: 0 $metaPaddingSmallScreen 0 0; } .fs-browser-column-name { - width: calc(100% - #{$iconWidth + $metaWidth + $changedWidth + $sizeWidth + $checkboxWidth}); + width: calc(100% - #{$iconWidth + $metaWidth + $metaPaddingSmallScreen + $checkboxWidth}); } } } @@ -461,16 +513,7 @@ } @media screen and (min-width: $breakpointPannelVisible) { - #fs-browser-header { - .fs-browser-column-changed, - .fs-browser-column-size { - display: none; - } - - .fs-browser-column-name { - width: calc(100% - #{$iconWidth} - #{$checkboxWidth}); - } - } + padding-bottom: #{$bottomPadding} !important; #fs-browser-tree { .k-item { @@ -487,9 +530,20 @@ } } } + + #fs-action-refresh { + top: #{$fsActionRefreshTop} !important; + left: calc(100% - #{$fsActionRefreshButtonSize}); + } } - @media screen and (min-width: #{$breakpointMetaVisible + 400px}) { + @media screen and (min-width: #{$breakpointMetaVisible + $pannelWidthM + 100px}) { + #fs-browser-header { + #fs-browser-header-checkbox { + right: 20px; + } + } + #fs-browser-tree { $metaPaddingSmallScreen: 25px; @@ -502,21 +556,60 @@ } .fs-browser-column-name { - width: calc(100% - #{$iconWidth + $metaWidth + $metaPaddingSmallScreen + $checkboxWidth}); + width: calc(100% - #{$iconWidth + $metaWidth + $metaPaddingSmallScreen + $checkboxWidth + 2px}); } } } } } - @media screen and (min-width: #{$breakpointSizeVisible + 400px}) { + @media screen and (min-width: #{$breakpointSizeChangedVisible + $pannelWidthM + 100px}) { #fs-browser-header { - .fs-browser-column-size { + border: 1px solid $headerBorderColor; + border-radius: $border-radius; + padding: 0 17px 0 12px; + + + .fs-browser-column-icon, + .fs-browser-column-name, + .fs-browser-column-size, + .fs-browser-column-changed { display: inline-block; } + .fs-browser-column-icon { + padding-top: 2px; + + .gr-icon { + $iconSize: 13px; + + position: relative; + top: 0; + left: 3px; + width: $iconSize; + height: $iconSize; + } + } + .fs-browser-column-name { - width: calc(100% - #{$iconWidth} - #{$sizeWidth} - #{$checkboxWidth}); + width: calc(100% - #{$iconWidth + $changedWidth + $sizeWidth + $checkboxWidth + 2px}); + } + + .fs-browser-column-size, + .fs-browser-column-changed { + border-left: 1px dotted $headerBorderColor; + padding-left: $columnPaddingLeftRight - 1px; + } + + #fs-browser-action-sorting, + #fs-action-sorting-select { + display: none !important; + } + + #fs-browser-header-checkbox { + position: relative; + top: auto; + right: auto; } } @@ -528,38 +621,43 @@ width: $metaWidth; } - .fs-browser-column-size { + .fs-browser-column-size, + .fs-browser-column-changed { display: inline-block; } .fs-browser-column-name { - width: calc(100% - #{$iconWidth + $metaWidth + $sizeWidth + $checkboxWidth}); - } - } - } - } - } + width: calc(100% - #{$iconWidth + $metaWidth + $changedWidth + $sizeWidth + $checkboxWidth}); - @media screen and (min-width: #{$breakpointChangedVisible + 400px}) { - #fs-browser-header { - .fs-browser-column-changed { - display: inline-block; - } + line-height: $lineHeight; - .fs-browser-column-name { - width: calc(100% - #{$iconWidth} - #{$changedWidth} - #{$sizeWidth} - #{$checkboxWidth}); - } - } + .fs-name { + margin-top: 0; + } - #fs-browser-tree { - .k-item { - .k-in { - .fs-browser-column-changed { - display: inline-block; + .fs-browser-column-name-size-changed { + display: none !important; + } + + .fs-browser-column-name-path { + display: block; + } } - .fs-browser-column-name { - width: calc(100% - #{$iconWidth + $metaWidth + $changedWidth + $sizeWidth + $checkboxWidth}); + &.fs-browser-search-item { + .fs-browser-column-name { + line-height: 17px; + + .fs-name { + margin-top: 5px; + } + + & > p { + line-height: 14px; + margin-top: 2px; + color: $colorMediumGrey; + } + } } } } @@ -602,3 +700,8 @@ display: none; } } + + +.is-touch #fs-browser #fs-action-refresh { + display: none; +} diff --git a/src/themes/default/scss/fsLayoutLeft/_fsBrowserAction.scss b/src/themes/default/scss/fsLayoutLeft/_fsBrowserAction.scss index c8edba8..28d6789 100644 --- a/src/themes/default/scss/fsLayoutLeft/_fsBrowserAction.scss +++ b/src/themes/default/scss/fsLayoutLeft/_fsBrowserAction.scss @@ -1,15 +1,72 @@ +$buttonSizeFsBrowserAction: 50px; + #fs-browser-action { - $buttonSize: 50px; + $buttonSize: $buttonSizeFsBrowserAction; + $buttonSizeMobile: 40px; display: block; float: right; position: relative; + #fs-browser-action-mobile { + @include icon-button($buttonSizeMobile); + + border-radius: 4px; + color: $colorActiveBlue; + background-color: $colorWhite; + + .gr-icon { + width: 21px; + height: 21px; + } + + .gr-i-close { + display: none; + } + } + + &.fs-browser-action-mobile-open { + #fs-browser-action-mobile { + + .gr-i-close { + display: block; + } + + .gr-i-plus { + display: none; + } + } + + #fs-browser-action-buttons { + $spikeSize: 6px; + + display: block; + + position: absolute; + right: 0; + top: $buttonSizeMobile + $spikeSize + 4px; + width: $buttonSize * 2; + z-index: 1; + &:after { + content: ""; + position: absolute; + top: -1 * $spikeSize; + right: ($buttonSizeMobile/2 - $spikeSize); + width: 0; + height: 0; + border-left: $spikeSize solid transparent; + border-right: $spikeSize solid transparent; + border-bottom: $spikeSize solid #{$colorWhite}; + } + } + } #fs-browser-action-buttons { @include clearfix; + display: none; + li { @include icon-button($buttonSize, $buttonSize); @@ -44,51 +101,144 @@ } .fs-action-dropwdown { - $dropdownWidth: 220px; + display: none; + } +} - @include dropdown($dropdownWidth, $buttonSize); +#fs-action-add-select-mobile { + $overlayPadding: 10px; + $buttonHeight: 55px; + + display: none; + position: fixed; + bottom: 0; + left: 0; + z-index: 15; + width: 100%; + height: 100%; + background-color: $overlayBackgroundColor; + + &.is-open { + display: block; + } - left: calc(100% - #{$dropdownWidth}); - display: none; + button, + ul { + position: absolute; + left: $overlayPadding; + width: calc(100% - #{2*$overlayPadding}); + background-color: $colorWhite; + border-radius: 8px; + } - ul { - li { - line-height: 40px; - font-size: $fontSizeParagraph; - background-color: $colorWhite; - cursor: pointer; - z-index: 17; - padding: 0 12px 0 4px; - - .gr-icon { - display: inline-block; - vertical-align: middle; - margin-right: 8px; - margin-top: -2px; - } + button { + color: $colorActiveBlue; + bottom: $overlayPadding; + height: $buttonHeight; + margin: 0; + border: none; + font-size: $fontSizeLeadBold; + } - span { - color: $colorMediumGrey; - } + ul { + $bottom: $overlayPadding + $buttonHeight + $overlayPadding; + bottom: $bottom; + max-height: calc(100% - #{$bottom + $overlayPadding}); + height: auto; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: auto; //`touch` will lead iOS safari to ignore border-radius - &:hover { - background-color: $colorBrightGrey; + li { + position: relative; + font-size: $fontSizeLead; + line-height: 20px; + font-weight: $fontWeightLead; + padding: 16px 53px 13px 20px; + + border-bottom: 1px solid $colorLightGrey; + + &:last-child { + border-bottom: none; + } + + .gr-icon { + position: absolute; + right: 15px; + top: 14px; + color: $colorMediumGrey; + } + } + } +} + +@media screen and (min-width: $breakpointPannelVisible) { + #fs-browser-action { + $buttonSize: $buttonSizeFsBrowserAction; + + #fs-browser-action-mobile{ + display: none !important; + } + + #fs-browser-action-buttons { + display: block; + } + + .fs-action-dropwdown { + $dropdownWidth: 220px; + + @include dropdown($dropdownWidth, $buttonSize); + + left: calc(100% - #{$dropdownWidth}); + display: none; + + &.is-open { + display: block; + } + + ul { + li { + line-height: 40px; + font-size: $fontSizeParagraph; + background-color: $colorWhite; + cursor: pointer; + z-index: 17; + padding: 0 12px 0 4px; + + .gr-icon { + display: inline-block; + vertical-align: middle; + margin-right: 8px; + margin-top: -2px; + } span { - color: $colorDarkGrey; + color: $colorMediumGrey; } - } - &:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; - } + &:hover { + background-color: $colorBrightGrey; - &:last-child { - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; + span { + color: $colorDarkGrey; + } + } + + &:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + + &:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + } } } } } + + #fs-action-add-select-mobile { + display: none !important; + } } diff --git a/src/themes/default/scss/fsLayoutLeft/_fsCrumb.scss b/src/themes/default/scss/fsLayoutLeft/_fsCrumb.scss index a60387b..c7c32c2 100644 --- a/src/themes/default/scss/fsLayoutLeft/_fsCrumb.scss +++ b/src/themes/default/scss/fsLayoutLeft/_fsCrumb.scss @@ -1,16 +1,37 @@ #fs-crumb { - width: calc(100% - 150px); + @include clearfix; + + width: calc(100% - 40px); display: block; float: left; - margin: 15px 0 8px 0; + margin: 4px 0; + + #fs-crumb-back { + $buttonSize: 30px; + + @include icon-button($buttonSize); + + display: none; + color: $colorActiveBlue; + cursor: pointer; + float: left; + margin: -2px 16px 0 0; + } + + &.is-child #fs-crumb-back { + display: block; + } ul { + float: left; + width: calc(100% - 46px); + li { display: none; color: $colorMediumGrey; width: auto; - height: 17px; - line-height: 17px; + height: 22px; + line-height: 22px; font-size: $fontSizeParagraph; font-weight: $fontWeightParagraph; letter-spacing: 0.3px; @@ -23,13 +44,6 @@ cursor: pointer; - &:after { - // TODO pixtron - use svg or icon font - content: ">"; - display: inline-block; - padding: 0 10px; - } - &:last-child { display: inline-block; color: $colorDarkGrey; @@ -40,19 +54,24 @@ font-weight: 500; letter-spacing: 0.4px; text-align: left; - - &:after { - display: none; - } } &.removed { cursor: default; } + } + } - @media screen and (min-width: $breakpointBreadcrumb) { - display: inline-block; + @media screen and (min-width: $breakpointBreadcrumbM) { + #fs-crumb-back { + display: none !important; + } + ul { + float: none; + width: 100%; + + li { &:last-child { @include font-headline; height: 27px; @@ -61,4 +80,50 @@ } } } + + @media screen and (min-width: $breakpointBreadcrumbL) { + margin: 15px 0 8px 0; + + ul { + li { + display: inline-block; + + &:after { + // TODO pixtron - use svg or icon font + content: ">"; + padding: 0 10px; + } + + &:last-child { + &:after { + display: none; + } + } + } + } + } + + @media screen and (min-width: $breakpointPannelVisible) { + width: calc(100% - 100px); + + ul { + li { + display: none; + + &:last-child { + display: inline-block; + } + } + } + } + + @media screen and (min-width: $breakpointPannelVisible + $breakpointBreadcrumbL) { + margin: 15px 0 8px 0; + + ul { + li { + display: inline-block; + } + } + } } diff --git a/src/themes/default/scss/includes/_breakpoints.scss b/src/themes/default/scss/includes/_breakpoints.scss index 705e99a..10cb7da 100644 --- a/src/themes/default/scss/includes/_breakpoints.scss +++ b/src/themes/default/scss/includes/_breakpoints.scss @@ -1,7 +1,7 @@ $breakpointSearchBig: 1050px; $breakpointPannelVisible: 800px; -$breakpointMenuVisible: 550px; -$breakpointSearchVisible: 777px; $breakpointUsernameVisible: 945px; -$breakpointBreadcrumb: 420px; +$breakpointBreadcrumbM: 420px; +$breakpointBreadcrumbL: 600px; $breakpointWindowFullscreen: 550px; +$breakpointWindowFormsFloat: $breakpointBreadcrumbM; diff --git a/src/themes/default/scss/includes/_colors.scss b/src/themes/default/scss/includes/_colors.scss index 49dca34..1dd3300 100644 --- a/src/themes/default/scss/includes/_colors.scss +++ b/src/themes/default/scss/includes/_colors.scss @@ -16,3 +16,5 @@ $colorPurple: #854dff; $colorBlue: #4dc9ff; $colorGreen: #4dff88; $colorYellow: #ffe14c; + +$overlayBackgroundColor: rgba(0, 0, 0, 0.4); diff --git a/src/themes/default/scss/includes/_fsContentMobileOverlay.scss b/src/themes/default/scss/includes/_fsContentMobileOverlay.scss new file mode 100644 index 0000000..0644b67 --- /dev/null +++ b/src/themes/default/scss/includes/_fsContentMobileOverlay.scss @@ -0,0 +1,83 @@ + +@mixin fs-content-mobile-overlay() { + position: fixed; + width: 100%; + height: 100%; + right: -100%; + top: 0; + bottom: 0; + background-color: $colorWhite; + box-sizing: border-box; + transition: right 0.2s ease-in-out; + z-index: 18; + + .fs-content-overlay-header { + @include clearfix; + + $iconSize: 30px; + + padding: 25px 0; + + .fs-content-view-back { + @include icon-button($iconSize, $iconSize); + + float: left; + display: block; + margin-left: -3px; + + .gr-icon { + color: $colorActiveBlue; + } + } + + .fs-content-nodename { + $extWidth: 53px; + $extMargin: 3px; + $iconMargin: 16px; + + @include clearfix; + + width: calc(100% - #{$iconSize + $iconMargin}); + margin-left: $iconMargin; + float: left; + display: block; + font-size: $fontSizeLeadBold; + line-height: $lineHeightLeadBold; + font-weight: 400; + padding: #{($iconSize - $lineHeightLeadBold) / 2} 0; + + .fs-value, + .fs-ext { + display: block; + float: left; + white-space: nowrap; + overflow: hidden; + } + + .fs-value { + text-overflow: ellipsis; + max-width: calc(100% - #{$extWidth + $extMargin}); + + + &:last-child { + max-width: 100%; + } + } + + .fs-ext { + text-transform: uppercase; + margin-left: $extMargin; + } + + } + + span { + white-space: nowrap; + text-overflow: ellipsis; + } + } +} + +@mixin fs-content-mobile-overlay-left() { + right: 25%; +} diff --git a/src/themes/default/scss/includes/_mixins.scss b/src/themes/default/scss/includes/_mixins.scss index d79d437..5fdf82d 100644 --- a/src/themes/default/scss/includes/_mixins.scss +++ b/src/themes/default/scss/includes/_mixins.scss @@ -71,7 +71,6 @@ color: $colorActiveBlue; border: 1px solid $colorLightGrey; background-color: $colorWhite; - margin-right: 13px; .fs-tiny-button-count { $size: 16px; diff --git a/src/themes/default/scss/includes/_variables.scss b/src/themes/default/scss/includes/_variables.scss new file mode 100644 index 0000000..1e4fe49 --- /dev/null +++ b/src/themes/default/scss/includes/_variables.scss @@ -0,0 +1,6 @@ +$headerHeight: 60px; +$pannelWidthM: 400px; + + +$fsContentSidePaddingM: 30px; +$fsContentSidePaddingS: 20px; diff --git a/src/themes/default/scss/modals/_base.scss b/src/themes/default/scss/modals/_base.scss index aedd7bf..daceea0 100644 --- a/src/themes/default/scss/modals/_base.scss +++ b/src/themes/default/scss/modals/_base.scss @@ -1,3 +1,15 @@ +.fs-fullscreen-window { + left: 0 !important; + top: 0 !important; + right: 0 !important; + height: 100% !important; + border-radius: 0 !important; + + @media screen and (min-width: $breakpointPannelVisible) { + right: 401px !important; + } +} + .k-window { $headerHeight: 72px; $sidePadding: 60px; @@ -22,28 +34,17 @@ } .fs-window-form { - $labelWidth: 100px; - $labelMarginRight: 15px; - - @include clearfix; - padding-top: 30px; - label, - input { - float: left; - } - label { padding: 0; - width: $labelWidth; + width: 100%; line-height: 45px; - margin-right: $labelMarginRight; margin-bottom: 10px; } input[type=text],input[type=password] { - width: calc(100% - #{$labelWidth + $labelMarginRight}); + width: 100%; font-size: $fontSizeParagraph; font-weight: $fontWeightParagraph; @@ -60,6 +61,7 @@ & > div.k-header { box-sizing: border-box; position: relative; + z-index: 1; left: 0; top: 0; background-color: transparent; @@ -186,12 +188,40 @@ clear: both; &.full-width { - width: calc(100% + #{2 * $sidePadding}; + width: calc(100% + #{2 * $sidePadding}); margin-left: $sidePadding * -1; margin-right: $sidePadding * -1; } } + @media only screen and (min-width: $breakpointWindowFormsFloat) { + .fs-window-form { + $labelWidth: 100px; + $labelMarginRight: 15px; + + @include clearfix; + + .fs-window-form-row { + @include clearfix; + } + + label, + input { + float: left; + } + + label { + width: $labelWidth; + margin-right: $labelMarginRight; + + } + + input[type=text],input[type=password] { + width: calc(100% - #{$labelWidth + $labelMarginRight}); + } + } + } + @media only screen and (max-width: $breakpointWindowFullscreen) { border-radius: 0; top: 0 !important; @@ -232,6 +262,14 @@ .fs-window-secondary-actions { input { margin-bottom: 8px; + + &:last-child { + margin-bottom: 0; + } + + &[name="cancel"] { + display: none; + } } } } @@ -246,9 +284,12 @@ @import './fsHistoryWindow'; @import './fsProfileWindow'; @import './fsPromptWindow'; +@import './fsAlertWindow'; @import './fsUploadmgr'; @import './fsShare'; @import './fsShareLink'; @import './fsShareLinkSettings'; @import './fsDestroyDate'; @import './fsNewNode'; +@import './fsFileHandler'; +@import './fsRenameWindow'; diff --git a/src/themes/default/scss/modals/_fsAlertWindow.scss b/src/themes/default/scss/modals/_fsAlertWindow.scss new file mode 100644 index 0000000..313ca2a --- /dev/null +++ b/src/themes/default/scss/modals/_fsAlertWindow.scss @@ -0,0 +1,36 @@ +$sidePadding: 50px; + +#fs-alert-window { + display: none; +} + +.fs-alert-window { + + & > div.k-header { + padding: 0 $sidePadding; + + .k-window-title { + left: $sidePadding; + width: calc(100% - #{2 * $sidePadding}); + } + } + + & > div.k-window-content { + box-sizing: border-box; + padding-left: $sidePadding; + padding-right: $sidePadding; + } + + hr.full-width { + width: calc(100% + #{2 * $sidePadding}); + margin: 29px #{-1 * $sidePadding}; + } +} + + +.k-window >div.fs-alert-window-inner { + display: none; + padding-bottom: 40px; + min-width: 400px; + max-width: 500px + (2 * $sidePadding); +} diff --git a/src/themes/default/scss/modals/_fsDestroyDate.scss b/src/themes/default/scss/modals/_fsDestroyDate.scss index bc40900..62a6d38 100644 --- a/src/themes/default/scss/modals/_fsDestroyDate.scss +++ b/src/themes/default/scss/modals/_fsDestroyDate.scss @@ -17,21 +17,14 @@ $sidePadding: 60px; } fieldset { - @include clearfix; - margin-top: 30px; div { - float: left; - width: 50%; + width: 100%; box-sizing: border-box; - &:first-child { - padding-right: 10px; - } - &:last-child { - padding-left: 10px; + margin-top: 10px; } label { @@ -74,4 +67,26 @@ $sidePadding: 60px; } + + @media screen and (min-width: $breakpointWindowFullscreen+1px) { + #fs-destroy-date-window-content { + fieldset { + @include clearfix; + + div { + float: left; + width: 50%; + + &:first-child { + padding-right: 10px; + } + + &:last-child { + margin-top: 0; + padding-left: 10px; + } + } + } + } + } } diff --git a/src/themes/default/scss/modals/_fsDisplayLive.scss b/src/themes/default/scss/modals/_fsDisplayLive.scss index 1af1bfb..e1f62e7 100644 --- a/src/themes/default/scss/modals/_fsDisplayLive.scss +++ b/src/themes/default/scss/modals/_fsDisplayLive.scss @@ -56,12 +56,12 @@ } #fs-display-close { - position: absolute; - top: 21px; - right: 21px; + position: fixed; + top: 40px; + right: 40px; width: 28px; height: 28px; - color: $colorMediumGrey; + color: $colorWhite; cursor: pointer; } } diff --git a/src/themes/default/scss/modals/_fsEditLive.scss b/src/themes/default/scss/modals/_fsEditLive.scss index 413a859..5f2c813 100644 --- a/src/themes/default/scss/modals/_fsEditLive.scss +++ b/src/themes/default/scss/modals/_fsEditLive.scss @@ -1,11 +1,5 @@ $sidePadding: 60px; -.fs-edit-live { - width: 70%; - min-height: 70%; - max-height: 100vh; -} - #fs-edit-live { display: none; @@ -13,5 +7,4 @@ $sidePadding: 60px; height: calc(100% - 100px); min-height: 300px; } - } diff --git a/src/themes/default/scss/modals/_fsEventWindow.scss b/src/themes/default/scss/modals/_fsEventWindow.scss index 10cb586..7b69b9a 100644 --- a/src/themes/default/scss/modals/_fsEventWindow.scss +++ b/src/themes/default/scss/modals/_fsEventWindow.scss @@ -9,16 +9,28 @@ $sidePadding: 60px; display: none; width: 680px + (2 * $sidePadding); + overflow-y: hidden; + #fs-event-window-content { - position: relative; height: 100%; width: 100%; - padding-right: $sidePadding; - ul { - padding-bottom: 100px; + #fs-event-window-search { + padding-bottom: 15px; + } + + #fs-events-window-list { + position: relative; + height: calc(100% - 45px); + width: 100%; + padding-right: $sidePadding; + overflow-y: auto; + + ul { + padding-bottom: 100px; - @include events-list; + @include events-list; + } } #fs-event-window-footer { diff --git a/src/themes/default/scss/modals/_fsFileHandler.scss b/src/themes/default/scss/modals/_fsFileHandler.scss new file mode 100644 index 0000000..14597c8 --- /dev/null +++ b/src/themes/default/scss/modals/_fsFileHandler.scss @@ -0,0 +1,31 @@ +$sidePadding: 60px; + +#fs-file-handler-window { + display: none; + width: 440px + (2 * $sidePadding); + padding-bottom: 40px; + padding-top: 13px; + + li { + border: solid 2px #CCC; + margin: 10px 0; + padding: 10px; + cursor: pointer; + line-height: 28px; + } + + li > span { + line-height: 20px; + } + + img { + width: 22px; + margin-right: 5px; + float: left; + } + + .active { + box-shadow: 0 2px 25px 0 rgba(77,75,89,0.24); + border: 2px solid #39a5ff; + } +} diff --git a/src/themes/default/scss/modals/_fsHistoryWindow.scss b/src/themes/default/scss/modals/_fsHistoryWindow.scss index 0682552..2bb302d 100644 --- a/src/themes/default/scss/modals/_fsHistoryWindow.scss +++ b/src/themes/default/scss/modals/_fsHistoryWindow.scss @@ -45,7 +45,15 @@ $sidePadding: 60px; left: 0; input:last-child { - margin-right: 60px; + margin-right: 30px; + } + } + + @media screen and (min-width: $breakpointWindowFullscreen+1px) { + #fs-history-window-button-wrapper { + input:last-child { + margin-right: 60px; + } } } } diff --git a/src/themes/default/scss/modals/_fsProfileWindow.scss b/src/themes/default/scss/modals/_fsProfileWindow.scss index 535641b..d1d7624 100644 --- a/src/themes/default/scss/modals/_fsProfileWindow.scss +++ b/src/themes/default/scss/modals/_fsProfileWindow.scss @@ -6,7 +6,7 @@ $sidePadding: 50px; .fs-profile-window { - width: 800px ; + width: 800px; & > div.k-header { padding: 0 $sidePadding; @@ -35,6 +35,12 @@ $sidePadding: 50px; dl { @include accordion($sidePadding, false); + + dt { + .gr-icon { + right: 50px; + } + } } #fs-profile-window-quota { @@ -137,27 +143,8 @@ $sidePadding: 50px; } } - #fs-profile-window-change-password { - .fs-window-form { - $labelWidth: 150px; - - & > div { - @include clearfix; - - label { - width: $labelWidth; - } - - input[type='password'] { - width: calc(100% - #{$labelWidth + 15px}); - } - } - } - } - - #fs-profile-window-google-authenticator { - @include clearfix; - + #fs-profile-window-google-authenticator, + #fs-profile-window-webauthn { p { margin-bottom: 20px; @@ -171,18 +158,69 @@ $sidePadding: 50px; } } - #fs-profile-window-google-authenticator-buttons { - float: left; - width: calc(100%-200px); + #fs-profile-window-google-authenticator-buttons, + #fs-profile-window-webuathn-buttons, { + width: 100%; } #fs-profile-window-google-authenticator-code { - float: right; width: 200px; height: 200px; + margin-top: 20px; } + #fs-profile-window-google-authenticator-secret { + color: $colorActiveBlue; + cursor: pointer; + } + } +} + +@media only screen and (min-width: $breakpointWindowFormsFloat) { + #fs-profile-window { + #fs-profile-window-change-password { + .fs-window-form { + $labelWidth: 150px; + & > div { + @include clearfix; + + label { + width: $labelWidth; + } + + input[type='password'] { + width: calc(100% - #{$labelWidth + 15px}); + } + } + } + } + + #fs-profile-window-google-authenticator { + @include clearfix; + + #fs-profile-window-google-authenticator-buttons { + float: left; + width: calc(100% - 200px); + } + + #fs-profile-window-google-authenticator-code { + float: right; + margin-top: 0; + } + } + } +} + +@media screen and (min-width: $breakpointWindowFullscreen + 1px) { + #fs-profile-window { + dl { + dt { + .gr-icon { + right: 20px; + } + } + } } } @@ -290,3 +328,52 @@ $sidePadding: 50px; } } } + +@media only screen and (max-width: 455px) { + #fs-profile-window { + #fs-profile-window-quota { + #fs-profile-quota { + table { + th, + td { + display: block; + width: 100%; + text-align: left; + padding-left: 0; + line-height: 24px; + } + + td { + margin-bottom: 12px; + } + } + + #fs-profile-quota-left, + #fs-profile-quota-used { + th:before { + top: 10px; + } + } + } + } + + #fs-profile-window-user { + #fs-profile-user { + table { + th, + td { + display: block; + width: 100%; + text-align: left; + padding-left: 0; + line-height: 24px; + } + + td { + margin-bottom: 12px; + } + } + } + } + } +} diff --git a/src/themes/default/scss/modals/_fsRenameWindow.scss b/src/themes/default/scss/modals/_fsRenameWindow.scss new file mode 100644 index 0000000..9dea7eb --- /dev/null +++ b/src/themes/default/scss/modals/_fsRenameWindow.scss @@ -0,0 +1,100 @@ +#fs-rename-window { + display: none; + + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: $overlayBackgroundColor; + z-index: 13001; + + &.is-open { + display: block; + } + + #fs-rename-window-content { + $left: 20px; + $sidePadding: 20px; + + position: absolute; + top: 110px; + left: $left; + box-sizing: border-box; + padding: $sidePadding; + width: calc(100% - #{2*$left}); + border-radius: 8px; + background-color: $colorWhite; + + h6 { + font-size: $fontSizeLeadBold; + font-weight: 700; + line-height: $lineHeightLeadBold; + margin-top: -2px; + } + + #fs-rename-window-close { + @include icon-button(28px); + position: absolute; + top: 16px; + right: 14px; + + .gr-i-close { + color: $colorMediumGrey; + cursor: pointer; + width: 100%; + height: 100%; + } + } + + hr { + border: 0; + border-top: 1px solid $colorLightGrey; + width: 100%; + margin: 24px #{$sidePadding * -1}; + height: 0; + clear: both; + width: calc(100% + #{2 * $sidePadding}; + } + + #fs-rename-window-close + hr { + margin-top: 18px; + } + + input[type="text"] + hr { + margin-bottom: 20px; + } + + input[type="text"] { + font-size: $fontSizeParagraph; + line-height: 25px; + padding: 5px 11px 6px; + + &:focus { + padding: 4px 10px 5px; + } + } + + input[type="submit"] { + $spacing: 10px; + + &:last-child { + margin-left: $spacing; + } + + min-width: calc(50% - #{$spacing/2}); + float: left; + padding-left: 10px; + padding-right: 10px; + } + } + + @media screen and (min-width: $breakpointWindowFullscreen) { + #fs-rename-window-content { + $renameWidthL: 510px; + width: $renameWidthL; + left: calc(50% - #{$renameWidthL / 2}); + top: calc(50% - 115px); + } + } +} diff --git a/src/themes/default/scss/modals/_fsShare.scss b/src/themes/default/scss/modals/_fsShare.scss index e10f16e..6c25bbb 100644 --- a/src/themes/default/scss/modals/_fsShare.scss +++ b/src/themes/default/scss/modals/_fsShare.scss @@ -60,10 +60,7 @@ $sidePadding: 30px; cursor: pointer; .fs-share-window-selected-privilege-label { - font-size: $fontSizeParagraphSmall; - line-height: 14px; - vertical-align: middle; - margin-right: 2px; + display: none; } .gr-icon { @@ -201,6 +198,22 @@ $sidePadding: 30px; } } } + + @media screen and (min-width: $breakpointWindowFullscreen+1px) { + #fs-share-window-content { + .fs-share-window-privilege-selector { + .fs-share-window-selected-privilege { + .fs-share-window-selected-privilege-label { + display: inline; + font-size: $fontSizeParagraphSmall; + line-height: 14px; + vertical-align: middle; + margin-right: 2px; + } + } + } + } + } } ul.fs-share-window-privileges { diff --git a/src/themes/default/scss/modals/_fsShareLink.scss b/src/themes/default/scss/modals/_fsShareLink.scss index b6cef4f..232ef3c 100644 --- a/src/themes/default/scss/modals/_fsShareLink.scss +++ b/src/themes/default/scss/modals/_fsShareLink.scss @@ -24,9 +24,10 @@ $sidePadding: 30px; #fs-share-link-window { $deleteButtonSize: 30px; + $windowWidth: 700px + (2 * $sidePadding); display: none; - width: 700px + (2 * $sidePadding); + width: $windowWidth; padding-bottom: 30px; #fs-share-link-window-content { @@ -34,13 +35,14 @@ $sidePadding: 30px; @include clearfix; p.fs-hint { - display: block; - float: left; - width: 510px; + display: block; + float: none; + width: 100%; + margin-bottom: 10px; & + input { display: block; - float: right; + float: none; } } @@ -145,7 +147,6 @@ $sidePadding: 30px; border: none; background-color: transparent; background-image: none; - width: auto; input { float: left; @@ -172,4 +173,20 @@ $sidePadding: 30px; } } } + + @media screen and (min-width: $windowWidth) { + #fs-share-link-window-content { + .fs-share-link-window-row { + p.fs-hint { + float: left; + width: 510px; + margin-bottom: 0; + + & + input { + float: right; + } + } + } + } + } } diff --git a/src/themes/default/scss/modals/_fsShareLinkSettings.scss b/src/themes/default/scss/modals/_fsShareLinkSettings.scss index cc8e434..229d7ff 100644 --- a/src/themes/default/scss/modals/_fsShareLinkSettings.scss +++ b/src/themes/default/scss/modals/_fsShareLinkSettings.scss @@ -23,16 +23,16 @@ $sidePadding: 30px; } #fs-share-link-settings-window { + $checkboxSize: 20px; + $labelWidthS: 240px; + $labelWidthM: 250px; + display: none; width: 700px + (2 * $sidePadding); padding-bottom: 31px; - #fs-share-link-settings-window-content { .fs-share-link-settings-window-row { - $checkboxSize: 20px; - $labelWidth: 250px; - @include clearfix; margin-bottom: 25px; @@ -47,9 +47,14 @@ $sidePadding: 30px; & > input[type="password"] { display: block; box-sizing: border-box; + } + + .fs-share-link-settings-window-checkbox, + & > label { float: left; } + .fs-share-link-settings-window-checkbox { $height: 45px; width: $checkboxSize; @@ -64,7 +69,7 @@ $sidePadding: 30px; & + label { padding-left: 10px; - width: $labelWidth; + width: $labelWidthS; line-height: $height; span { @@ -84,7 +89,7 @@ $sidePadding: 30px; & > .k-datepicker, & > .k-timepicker, & > input[type="password"] { - width: calc(100% - #{$checkboxSize + $labelWidth}) !important; + width: 100% !important; .k-select { top: 9px; @@ -112,4 +117,27 @@ $sidePadding: 30px; .fs-window-secondary-actions { margin-top: 30px; } + + @media screen and (min-width: $breakpointWindowFullscreen+1px) { + #fs-share-link-settings-window-content { + .fs-share-link-settings-window-row { + & > input[type="text"], + & > input[type="password"] { + float: left; + } + + .fs-share-link-settings-window-checkbox { + & + label { + width: $labelWidthM; + } + } + + & > .k-datepicker, + & > .k-timepicker, + & > input[type="password"] { + width: calc(100% - #{$checkboxSize + $labelWidthM}) !important; + } + } + } + } } diff --git a/webpack.common.js b/webpack.common.js index e17444b..a4da903 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -5,7 +5,8 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin'); const MergeJsonWebpackPlugin = require('merge-jsons-webpack-plugin'); const GitRevisionPlugin = require('git-revision-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); - +const WebpackPwaManifest = require('webpack-pwa-manifest'); +const {GenerateSW} = require('workbox-webpack-plugin'); var gitRevisionPlugin = new GitRevisionPlugin(); var isDev = process.env.NODE_ENV !== 'production'; @@ -85,6 +86,33 @@ module.exports = { preserveLineBreaks: false, } }), + new WebpackPwaManifest({ + name: 'balloon', + short_name: 'balloon', + description: 'Client for balloon', + background_color: '#F4F4F4', + theme_color: '#1E8EED', + crossorigin: null, + orientation: 'portrait-primary', + display: 'fullscreen', + ios: { + 'apple-mobile-web-app-status-bar-style': 'black', + }, + publicPath: null, + start_url: '/', + icons: [ + { + src: 'src/themes/default/img/icon_512x512.png', + sizes: [96, 128, 192, 256, 384, 512], + ios: true + } + ] + }), + new GenerateSW({ + importWorkboxFrom: 'local', + clientsClaim: true, + skipWaiting: true + }), new webpack.DefinePlugin({ 'process.env.VERSION': JSON.stringify(process.env.VERSION || gitRevisionPlugin.version()), 'process.env.COMMITHASH': JSON.stringify(gitRevisionPlugin.commithash()), diff --git a/webpack.dev.js b/webpack.dev.js index 8d9da8f..4fd87b6 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -4,6 +4,8 @@ const common = require('./webpack.common.js'); module.exports = merge(common, { devtool: 'eval-source-map', devServer: { + //https: true, + //public: 'webpack:8080', proxy: { '/api': { target: process.env.BALLOON_API_URL || 'https://localhost:8081', diff --git a/webpack.prod.js b/webpack.prod.js index f5d0a10..924bfde 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -3,7 +3,7 @@ const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const common = require('./webpack.common.js'); const FaviconsWebpackPlugin = require('favicons-webpack-plugin'); -common.plugins.push( +common.plugins.unshift( new FaviconsWebpackPlugin({ logo: './themes/default/img/icon_blue.svg', prefix: 'icons-[hash]/', @@ -15,8 +15,8 @@ common.plugins.push( title: 'balloon web ui', icons: { android: true, - appleIcon: true, - appleStartup: true, + appleIcon: false, + appleStartup: false, coast: false, favicons: true, firefox: true, @@ -26,6 +26,6 @@ common.plugins.push( windows: true } }) -) +); module.exports = common;