From cd43f6dd68cf49a35c41e7e1889a1b88af4e9ff5 Mon Sep 17 00:00:00 2001 From: Christina Toegl <toegl@tugraz.at> Date: Sun, 7 Nov 2021 20:13:07 +0100 Subject: [PATCH] Add support for favorites and recent files; Update webdav library --- package.json | 3 + packages/file-handling/README.md | 6 +- packages/file-handling/package.json | 2 +- packages/file-handling/src/file-sink.js | 3 + packages/file-handling/src/file-source.js | 3 + .../src/i18n/de/translation.json | 6 +- .../src/i18n/en/translation.json | 6 +- .../src/nextcloud-file-picker.js | 500 +++++++++++++++++- yarn.lock | 60 ++- 9 files changed, 539 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 42030b6c..15faba24 100644 --- a/package.json +++ b/package.json @@ -24,5 +24,8 @@ "license": "LGPL-2.1-or-later", "devDependencies": { "lerna": "^4.0.0" + }, + "dependencies": { + "webdav": "4.6.0" } } diff --git a/packages/file-handling/README.md b/packages/file-handling/README.md index 30e19ea6..87694b0b 100644 --- a/packages/file-handling/README.md +++ b/packages/file-handling/README.md @@ -95,6 +95,8 @@ files from a [Nextcloud](https://nextcloud.com/) instance or to a dbp-clipboard. This is supported by the provider! Use this object to sync file source and file sink on one page at first time open. - example `<dbp-file-source initial-file-handling-state="{target: 'local', path:'my/server/path'}"></dbp-file-source>` - example provider `<dbp-file-source subscribe="initial-file-handling-state"></dbp-file-source>` +- `show-nextcloud-favorites` (optional): Needs to be set to show the favorites icon + - example `show-nextcloud-favorites` ### Emitted attributes @@ -160,7 +162,9 @@ files to a [Nextcloud](https://nextcloud.com/) instance or to a dbp-clipboard. This is supported by the provider! Use this object to sync file source and file sink on one page at first time open. - example `<dbp-file-source initial-file-handling-state="{target: 'local', path:'my/server/path'}"></dbp-file-source>` - example provider `<dbp-file-source subscribe="initial-file-handling-state"></dbp-file-source>` - +- `show-nextcloud-additional-menu` (optional): Needs to be set to show the additional menu + - example `show-nextcloud-additional-menu` + ### Emitted attributes The component emits a `dbp-set-property` event for the attribute `initial-file-handling-state`: diff --git a/packages/file-handling/package.json b/packages/file-handling/package.json index 043b9b15..66f82b21 100644 --- a/packages/file-handling/package.json +++ b/packages/file-handling/package.json @@ -42,7 +42,7 @@ "lit": "^2.0.0", "material-design-icons-svg": "^3.0.0", "tabulator-tables": "^4.8.4", - "webdav": "^3.6.1" + "webdav": "4.6.0" }, "scripts": { "clean": "rm dist/*", diff --git a/packages/file-handling/src/file-sink.js b/packages/file-handling/src/file-sink.js index 4e9282d1..f66fa2bb 100644 --- a/packages/file-handling/src/file-sink.js +++ b/packages/file-handling/src/file-sink.js @@ -38,6 +38,7 @@ export class FileSink extends ScopedElementsMixin(DbpFileHandlingLitElement) { this.firstOpen = true; this.fullsizeModal = false; this.nextcloudAuthInfo = ''; + this.showNextcloudAdditionalMenu = false; this.initialFileHandlingState = {target: '', path: ''}; } @@ -74,6 +75,7 @@ export class FileSink extends ScopedElementsMixin(DbpFileHandlingLitElement) { firstOpen: {type: Boolean, attribute: false}, nextcloudPath: {type: String, attribute: false}, fullsizeModal: { type: Boolean, attribute: 'fullsize-modal' }, + showNextcloudAdditionalMenu: { type: Boolean, attribute: 'show-nextcloud-additional-menu' }, initialFileHandlingState: {type: Object, attribute: 'initial-file-handling-state'}, }; } @@ -304,6 +306,7 @@ export class FileSink extends ScopedElementsMixin(DbpFileHandlingLitElement) { directory-path="${this.nextcloudPath}" nextcloud-file-url="${this.nextcloudFileURL}" ?store-nextcloud-session="${this.nextcloudStoreSession}" + ?show-nextcloud-additional-menu="${this.showNextcloudAdditionalMenu}" @dbp-nextcloud-file-picker-file-uploaded="${(event) => { this.uploadToNextcloud(event.detail); }}" diff --git a/packages/file-handling/src/file-source.js b/packages/file-handling/src/file-source.js index fdee6df2..8aed74ef 100644 --- a/packages/file-handling/src/file-source.js +++ b/packages/file-handling/src/file-source.js @@ -59,6 +59,7 @@ export class FileSource extends ScopedElementsMixin(DbpFileHandlingLitElement) { this.nextcloudAuthInfo = ''; this.maxFileSize = ''; this.multipleFiles = Number.MAX_VALUE; + this.showNextcloudAdditionalMenu = false; this.initialFileHandlingState = {target: '', path: ''}; } @@ -95,6 +96,7 @@ export class FileSource extends ScopedElementsMixin(DbpFileHandlingLitElement) { isDialogOpen: { type: Boolean, attribute: 'dialog-open' }, maxFileSize: { type: Number, attribute: 'max-file-size'}, multipleFiles: { type: Number, attribute: 'number-of-files'}, + showNextcloudAdditionalMenu: { type: Boolean, attribute: 'show-nextcloud-additional-menu' }, initialFileHandlingState: {type: Object, attribute: 'initial-file-handling-state'}, }; @@ -526,6 +528,7 @@ export class FileSource extends ScopedElementsMixin(DbpFileHandlingLitElement) { auth-info="${this.nextcloudAuthInfo}" allowed-mime-types="${this.allowedMimeTypes}" max-selected-items="${this.multipleFiles}" + ?show-nextcloud-additional-menu="${this.showNextcloudAdditionalMenu}" @dbp-nextcloud-file-picker-file-downloaded="${(event) => { this.sendFileEvent(event.detail.file, event.detail.maxUpload);}}"> </dbp-nextcloud-file-picker>`; diff --git a/packages/file-handling/src/i18n/de/translation.json b/packages/file-handling/src/i18n/de/translation.json index 3b866e64..514568dc 100644 --- a/packages/file-handling/src/i18n/de/translation.json +++ b/packages/file-handling/src/i18n/de/translation.json @@ -72,7 +72,11 @@ "abort-message": "Vorgang wurde abgebrochen.", "remember-me": "Mit {{name}} verbunden bleiben", "log-out": "Verbindung trennen", - "open-submenu": "Untermenü öffnen" + "open-submenu": "Untermenü öffnen", + "error-save-to-favorites": "Speichern in Favoriten nicht möglich! Bitte wählen Sie einen Ordner innerhalb der Favoriten aus.", + "error-save-to-recent": "Speichern in den neuesten Dateien nicht möglich! Bitte wählen Sie einen Ordner innerhalb der neuesten Dateien aus.", + "recent-files-link-text": "Neueste Dateien", + "favorites-link-text": "Meine Favoriten" }, "clipboard": { "add-files": "Dateien der Zwischenablage hinzufügen", diff --git a/packages/file-handling/src/i18n/en/translation.json b/packages/file-handling/src/i18n/en/translation.json index 999d879b..898223ee 100644 --- a/packages/file-handling/src/i18n/en/translation.json +++ b/packages/file-handling/src/i18n/en/translation.json @@ -73,7 +73,11 @@ "abort-message": "The process was canceled.", "remember-me": "Stay connected with {{name}}", "log-out": "Disconnect", - "open-submenu": "Open submenu" + "open-submenu": "Open submenu", + "error-save-to-favorites": "Saving to Favorites not possible! Please select a folder within the Favorites.", + "error-save-to-recent": "Saving to Recent Files not possible! Please select a folder within the Recent Files.", + "recent-files-link-text": "Recent Files", + "favorites-link-text": "My Favorites" }, "clipboard": { "add-files": "Add files to clipboard", diff --git a/packages/file-handling/src/nextcloud-file-picker.js b/packages/file-handling/src/nextcloud-file-picker.js index 4e262ceb..f5e710e9 100644 --- a/packages/file-handling/src/nextcloud-file-picker.js +++ b/packages/file-handling/src/nextcloud-file-picker.js @@ -5,8 +5,8 @@ import DBPLitElement from '@dbp-toolkit/common/dbp-lit-element'; import {Icon, MiniSpinner} from '@dbp-toolkit/common'; import * as commonUtils from '@dbp-toolkit/common/utils'; import * as commonStyles from '@dbp-toolkit/common/styles'; -import {createClient} from 'webdav/web'; import {classMap} from 'lit/directives/class-map.js'; +import {createClient, parseXML, parseStat} from 'webdav/web'; import {humanFileSize} from '@dbp-toolkit/common/i18next'; import Tabulator from 'tabulator-tables'; import MicroModal from './micromodal.es'; @@ -14,6 +14,7 @@ import {name as pkgName} from './../package.json'; import * as fileHandlingStyles from './styles'; import {encrypt, decrypt, parseJwt} from './crypto.js'; + /** * NextcloudFilePicker web component */ @@ -57,11 +58,14 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { this.abortUpload = false; this.authInfo = ''; this.selectBtnDisabled = true; - this.storeSession = false; this.showSubmenu = false; this.bounCloseSubmenuHandler = this.closeSubmenu.bind(this); this.initateOpensubmenu = false; + this.showAdditionalMenu = false; + this.isInFavorites = false; + this.isInRecent = false; + this.userName = ''; } static get scopedElements() { @@ -100,7 +104,9 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { abortUploadButton: {type: Boolean, attribute: false}, selectBtnDisabled: {type: Boolean, attribute: true}, storeSession: {type: Boolean, attribute: 'store-nextcloud-session'}, - showSubmenu: {type: Boolean, attribute: false} + showSubmenu: {type: Boolean, attribute: false}, + showAdditionalMenu: { type: Boolean, attribute: 'show-nextcloud-additional-menu' }, + userName: { type: Boolean, attribute: false }, }; } @@ -535,8 +541,311 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { } this.loadDirectory(this.directoryPath); + this.userName = data.loginName; + } + } + } + + /** + * + * @param {*} data + * @returns reduced list of objects, including users files + */ + filterUserFilesOnly(data) { //TODO verify + // R = Share, S = Shared Folder, M = Group folder or external source, G = Read, D = Delete, NV / NVW = Write, CK = Create + let result = []; + + for (let i = 0; i < data.length; i++) { + if (data) { + let file_perm = data[i].props.permissions; + if (!file_perm.includes('M') && !file_perm.includes('S')) { + result.push(data[i]); + } } } + return result; + } + + /** + * + * @param {*} path + * @returns array including file path and base name + */ + parseFileAndBaseName(path) { + if (path[0] !== "/") { //TODO verify + path = "/" + path; + } + while (/^.+\/$/.test(path)) { + path = path.substr(0, path.length - 1); + } + path = decodeURIComponent(path); + + let array1 = this.webDavUrl.split('/'); + let array2 = path.split('/'); + for (let i = 0; i < array2.length; i++) { + let item2 = array2[i]; + array1.forEach(item1 => { + if (item1 === item2) { + array2.shift(); + i--; + } + }); + } + array2.shift(); + + let basename = array2[array2.length - 1]; + let filename = '/' + array2.join('/'); + + return [ filename, basename ]; + } + + /** + * + * @param {*} response + * @returns list of file objects containing corresponding information + */ + mapResponseToObject(response) { + let results = []; + + response.forEach(item => { + const [ filePath, baseName ] = this.parseFileAndBaseName(item.href); + + const prop = item.propstat.prop; + let etag = typeof prop.getetag === 'string' ? prop.getetag.replace(/"/g, '') : null; + let sizeVal = prop.getcontentlength ? prop.getcontentlength : '0'; + let fileType = prop.resourcetype && typeof prop.resourcetype === 'object' && typeof prop.resourcetype.collection !== 'undefined' ? 'directory' : 'file'; + + let mimeType; + if (fileType === 'file') { + mimeType = prop.getcontenttype && typeof prop.getcontenttype === 'string' ? prop.getcontenttype.split(';')[0] : ''; + } + + let propsObject = { getetag: etag, getlastmodified: prop.getlastmodified, getcontentlength: sizeVal, + permissions: prop.permissions, resourcetype: fileType, getcontenttype: prop.getcontenttype }; + + let statObject = { basename: baseName, etag: etag, filename: filePath, lastmod: prop.getlastmodified, + mime: mimeType, props: propsObject, size: parseInt(sizeVal, 10), type: fileType }; + + results.push(statObject); + }); + + return results; + } + + /** + * Loads the favorites from WebDAV + * + */ + loadFavorites() { + this.hideMoreMenu(); + const i18n = this._i18n; + + if (typeof this.directoryPath === 'undefined' || this.directoryPath === undefined) { + this.directoryPath = ''; + } + + console.log("load nextcloud favorites"); + this.selectAllButton = true; + this.loading = true; + this.statusText = i18n.t('nextcloud-file-picker.loadpath-nextcloud-file-picker', {name: this.nextcloudName}); + this.lastDirectoryPath = this.directoryPath; + this.directoryPath = ''; + this.isInRecent = false; + this.isInFavorites = true; + + if (this.webDavClient === null) { + // client is broken reload try to reset & reconnect + this.tabulatorTable.clearData(); + this.webDavClient = null; + let reloadButton = html`${i18n.t('nextcloud-file-picker.something-went-wrong')} <button class="button" + title="${i18n.t('nextcloud-file-picker.refresh-nextcloud-file-picker')}" + @click="${async () => { this.openFilePicker(); } }"><dbp-icon name="reload"></button>`; + this.loading = false; + this.statusText = reloadButton; + } + + //see https://github.com/perry-mitchell/webdav-client#customRequest + this.webDavClient + .customRequest('/', {method: 'REPORT', responseType: "text/xml", details: true, data: "<?xml version=\"1.0\"?>" + + " <oc:filter-files xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">" + + " <oc:filter-rules>" + + " <oc:favorite>1</oc:favorite>" + + " </oc:filter-rules>" + + " <d:prop>" + + " <d:getlastmodified />" + + " <d:resourcetype />" + + " <d:getcontenttype />" + + " <d:getcontentlength />" + + " <d:getetag />" + + " <oc:permissions />" + + " </d:prop>" + + " </oc:filter-files>" + }) + .then(contents => { + parseXML(contents.data).then(davResp => { + // console.log("-contents.data-----", davResp); + let dataObject = this.mapResponseToObject(davResp.multistatus.response); + + this.loading = false; + this.statusText = ""; + this.tabulatorTable.setData(dataObject); + this.isPickerActive = true; + this._(".nextcloud-content").scrollTop = 0; + this._("#download-button").setAttribute("disabled", "true"); + }); + }).catch(error => { //TODO verify error catching + console.error(error.message); + + // on Error: try to reload with home directory + if (this.webDavClient !== null && error.message.search("401") === -1) { + console.log("error in load directory"); + this.directoryPath = ""; + this.loadDirectory(""); + } + else { + this.loading = false; + this.statusText = html`<span class="error"> ${i18n.t('nextcloud-file-picker.webdav-error', {error: error.message})} </span>`; + this.isPickerActive = false; + this.tabulatorTable.clearData(); + this.webDavClient = null; + let reloadButton = html`${i18n.t('nextcloud-file-picker.something-went-wrong')} <button class="button" + title="${i18n.t('nextcloud-file-picker.refresh-nextcloud-file-picker')}" + @click="${async () => { this.openFilePicker(); } }"><dbp-icon name="reload"></button>`; + this.loading = false; + this.statusText = reloadButton; + } + this.isInFavorites = false; + }); + } + + /** + * Loads recent files and folders from WebDAV + * + */ + loadRecent() { + this.hideMoreMenu(); + const i18n = this._i18n; + + if (typeof this.directoryPath === 'undefined' || this.directoryPath === undefined) { + this.directoryPath = ''; + } + + console.log("load recent files"); + this.selectAllButton = true; + this.loading = true; + this.statusText = i18n.t('nextcloud-file-picker.loadpath-nextcloud-file-picker', {name: this.nextcloudName}); + this.lastDirectoryPath = this.directoryPath; + this.directoryPath = ''; + this.isInFavorites = false; + this.isInRecent = true; + + let date = new Date(); + date.setMonth(date.getMonth() - 3); + let searchDate = date.toISOString().split('.')[0] + 'Z'; + + if (this.webDavClient === null) { + // client is broken reload try to reset & reconnect + this.tabulatorTable.clearData(); + this.webDavClient = null; + let reloadButton = html`${i18n.t('nextcloud-file-picker.something-went-wrong')} <button class="button" + title="${i18n.t('nextcloud-file-picker.refresh-nextcloud-file-picker')}" + @click="${async () => { this.openFilePicker(); } }"><dbp-icon name="reload"></button>`; + this.loading = false; + this.statusText = reloadButton; + } + + //see https://github.com/perry-mitchell/webdav-client#customRequest + this.webDavClient + .customRequest('../..', { method: 'SEARCH', responseType: "text/xml", headers: { 'Content-Type': "text/xml" }, details: true, data: "<?xml version=\"1.0\" encoding='UTF-8'?>" + + " <d:searchrequest xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\">" + + " <d:basicsearch>" + + " <d:select>" + + " <d:prop>" + + " <d:getlastmodified />" + + " <d:resourcetype />" + + " <d:getcontenttype />" + + " <d:getcontentlength />" + + " <d:getetag />" + + " <oc:permissions />" + + " <oc:size/>"+ + " <oc:owner-id/>" + + " <oc:owner-display-name/>" + + " </d:prop>" + + " </d:select>" + + " <d:from>" + + " <d:scope>" + + " <d:href>/files/" + this.userName + "/</d:href>" + + " <d:depth>infinity</d:depth>" + + " </d:scope>" + + " </d:from>" + + " <d:where> " + + " <d:gte>" + + " <d:prop>" + + " <d:getlastmodified/>" + + " </d:prop>" + + " <d:literal>" + searchDate + "</d:literal>" + + " </d:gte>" + + " </d:where>" + + " <d:orderby>" + + " <d:order>" + + " <d:prop>" + + " <d:getlastmodified/>" + + " </d:prop>" + + " <d:descending/>" + + " </d:order>" + + " </d:orderby>" + + " <d:limit>"+ + " <d:nresults>100</d:nresults>" + + " </d:limit>"+ + " </d:basicsearch>" + + " </d:searchrequest>" + }) + .then(contents => { + parseXML(contents.data).then(davResp => { + console.log('davResp', davResp); + + let dataObject = this.mapResponseToObject(davResp.multistatus.response); + console.log("-contents.data-----", dataObject); + + if (this._("#user_files_only") && this._("#user_files_only").checked) { + dataObject = this.filterUserFilesOnly(dataObject); + // console.log('show only my files'); + } + + this.loading = false; + this.statusText = ""; + this.tabulatorTable.setData(dataObject); + this.tabulatorTable.setSort([ + {column: "lastmod", dir: "desc"} + ]); + this.isPickerActive = true; + this._(".nextcloud-content").scrollTop = 0; + this._("#download-button").setAttribute("disabled", "true"); + }); + }).catch(error => { + console.error(error.message); + + // on Error: try to reload with home directory + if (this.webDavClient !== null && error.message.search("401") === -1) { + console.log("error in load directory"); + this.directoryPath = ""; + this.loadDirectory(""); + } + else { + this.loading = false; + this.statusText = html`<span class="error"> ${i18n.t('nextcloud-file-picker.webdav-error', {error: error.message})} </span>`; + this.isPickerActive = false; + this.tabulatorTable.clearData(); + this.webDavClient = null; + let reloadButton = html`${i18n.t('nextcloud-file-picker.something-went-wrong')} <button class="button" + title="${i18n.t('nextcloud-file-picker.refresh-nextcloud-file-picker')}" + @click="${async () => { this.openFilePicker(); } }"><dbp-icon name="reload"></button>`; + this.loading = false; + this.statusText = reloadButton; + } + + this.isInRecent = false; + }); } toggleCollapse(e) { @@ -603,11 +912,12 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { "</d:propfind>" }) .then(contents => { - this.loading = false; this.statusText = ""; this.tabulatorTable.setData(contents.data); this.isPickerActive = true; + this.isInFavorites = false; + this.isInRecent = false; this._(".nextcloud-content").scrollTop = 0; if (!this.activeDirectoryRights.includes("CK") && !this.activeDirectoryRights.includes("NV")) { this._("#download-button").setAttribute("disabled", "true"); @@ -731,6 +1041,14 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { * @param directory */ sendDirectory(directory) { + if (this.isInFavorites) { + this.statusText = html`<span class="error"> ${ i18n.t('nextcloud-file-picker.error-save-to-favorites') } </span>`; + return; + } else if (this.isInRecent) { //TODO verify + this.statusText = html`<span class="error"> ${ i18n.t('nextcloud-file-picker.error-save-to-recent') } </span>`; + return; + } + const i18n = this._i18n; this.tabulatorTable.deselectRow(); let path; @@ -1297,6 +1615,39 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { return this.nextcloudFileURL + this.directoryPath; } + toggleMoreMenu() { + const menu = this.shadowRoot.querySelector("ul.extended-menu"); + const menuStart = this.shadowRoot.querySelector("a.extended-menu-link"); + + if (menu === null || menuStart === null) { + return; + } + + menu.classList.toggle('hidden'); + + if (this.menuHeight === -1) { + this.menuHeight = menu.clientHeight; + } + + let topValue = menuStart.getBoundingClientRect().bottom; + let isMenuOverflow = this.menuHeight + topValue >= window.innerHeight ? true : false; + + if (isMenuOverflow && !menu.classList.contains('hidden')) { + menu.setAttribute('style', 'position: fixed;top: ' + topValue + 'px;bottom: 0;border-bottom: 0;overflow-y: auto;'); + menu.scrollTop = 0; + document.body.setAttribute('style', 'overflow:hidden;'); + } else if (isMenuOverflow && menu.classList.contains('hidden')) { + document.body.removeAttribute('style', 'overflow:hidden;'); + menu.removeAttribute('style'); + } + } + + hideMoreMenu() { + const menu = this.shadowRoot.querySelector("ul.extended-menu"); + if (menu && !menu.classList.contains('hidden')) + this.toggleMoreMenu(); + } + static get styles() { // language=css return css` @@ -1306,6 +1657,7 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { ${commonStyles.getModalDialogCSS()} ${commonStyles.getRadioAndCheckboxCss()} ${fileHandlingStyles.getFileHandlingCss()} + .visible { display: unset; } @@ -1319,6 +1671,56 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { color: var(--dbp-danger-dark); } + .filter-options-wrapper { + padding-right: 0px; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 0px; + } + + .extended-menu li { + padding: 7px; + padding-right: 46px; + border-bottom: 1px solid #f3f3f3; + } + + .extended-menu li.active { + background-color: var(--dbp-dark); + color: var(--dbp-light); + } + .extended-menu li.active a:hover { + color: var(--dbp-light); + } + + .extended-menu a.inactive { + color: var(--dbp-muted-text); + pointer-events: none; + cursor: default; + } + + .extended-menu a { + padding: 8px; + } + + .extended-menu { + list-style: none; + border: black 1px solid; + position: absolute; + background-color: white; + z-index: 1000; + right: 0px; + } + + .extended-menu a:hover { + color: #E4154B; + } + + ul.extended-menu li.close { + display: block; + padding: 7px 15px 7px 15px; + text-align: right; + cursor: pointer; + } .nextcloud-header { margin-bottom: 2rem; @@ -1409,10 +1811,11 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { } - .add-folder { + .additional-menu { white-space: nowrap; align-self: end; height: 33px; + margin-right: 5px; } .nextcloud-nav p { @@ -1658,24 +2061,34 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { left: 7px; } + .more-menu { + height: 22.4px; + width: 22.4px; + top: 8px; + } + + .nextcloud-nav a.home-link { + font-size: 1.4em; + } @media only screen and (orientation: portrait) and (max-width: 768px) { - .add-folder button { + .additional-menu button { float: right; } - .add-folder { + .additional-menu { position: absolute; right: 0px; + margin-right: 10px; } - .nextcloud-nav { - display: block; - } + /* .nextcloud-nav { + display: block; TODO verify if this is enough + } */ - .add-folder { + .additional-menu { position: inherit; } @@ -1848,29 +2261,53 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { <div class="nextcloud-content ${classMap({hidden: !this.isPickerActive})}"> <div class="nextcloud-nav"> <p>${this.getBreadcrumb()}</p> - <div class="menu-buttons"> - <div class="add-folder ${classMap({hidden: !this.directoriesOnly})}"> - <div class="inline-block"> - <div id="new-folder-wrapper" class="hidden"> - <input type="text" - placeholder="${i18n.t('nextcloud-file-picker.new-folder-placeholder')}" - name="new-folder" class="input" id="new-folder"/> - <button class="button add-folder-button" - title="${i18n.t('nextcloud-file-picker.add-folder')}" - @click="${() => { - this.addFolder(); - }}"> - <dbp-icon name="checkmark-circle" class="nextcloud-add-folder"></dbp-icon> - </button> + + <div class="additional-menu ${classMap({hidden: !this.showAdditionalMenu})}"> + + <a class="extended-menu-link" @click="${() => { this.toggleMoreMenu(); }}" title="${i18n.t('nextcloud-file-picker.more-menu')}"> + <dbp-icon name="more-filled" class="more-menu"></dbp-icon> + </a> + <ul class='extended-menu hidden'> + <li class="${classMap({active: this.isInFavorites})}" id="favorites-item"> + <a class="" @click="${this.loadFavorites}"> + ${i18n.t('nextcloud-file-picker.favorites-link-text')} + </a> + </li> + <li class="${classMap({active: this.isInRecent})}" id="recent-item"> + <a class="" @click="${this.loadRecent}"> + ${i18n.t('nextcloud-file-picker.recent-files-link-text')} + </a> + </li> + <li class="${classMap({hidden: !this.directoriesOnly})}"> + <a class="${classMap({inactive: this.isInRecent || this.isInFavorites})}" @click="${() => { this.openAddFolderDialogue(); }}"> + ${i18n.t('nextcloud-file-picker.add-folder')} + </a> + </li> + + <div class="inline-block"> + <div id="new-folder-wrapper" class="hidden"> + <input type="text" + placeholder="${i18n.t('nextcloud-file-picker.new-folder-placeholder')}" + name="new-folder" class="input" id="new-folder"/> + <button class="button add-folder-button" + title="${i18n.t('nextcloud-file-picker.add-folder')}" + @click="${() => { + this.addFolder(); + }}"> + <dbp-icon name="checkmark-circle" class="nextcloud-add-folder"></dbp-icon> + </button> + </div> </div> - </div> - <button class="button" + <!-- <button class="button ${classMap({hidden: this.showAdditionalMenu})}" title="${i18n.t('nextcloud-file-picker.add-folder-open')}" @click="${() => { this.openAddFolderDialogue(); }}"> <dbp-icon name="plus" class="nextcloud-add-folder" id="add-folder-button"></dbp-icon> - </button> + </button> --> + <li class="close" @click="${this.hideMoreMenu}"><dbp-icon name="close" style="color: red"></dbp-icon></li> + </ul> + </div> <div id="submenu" class="${classMap({hidden: !this.storeSession})}" title="${i18n.t('nextcloud-file-picker.open-submenu')}" @@ -1890,6 +2327,13 @@ export class NextcloudFilePicker extends ScopedElementsMixin(DBPLitElement) { </div> </div> </div> + <div class="filter-options-wrapper ${classMap({hidden: !this.isInRecent})}"> + <label id="user_files_only_wrapper" class="button-container"> + <!-- ${i18n.t('nextcloud-file-picker.replace-mode-all')} --> Show only my files <!--TODO--> + <input type="checkbox" id="user_files_only" name="user_files_only" value="user_files_only" > <!--@click="${() => { this.filterUserFilesOnly(); }}"--> + <span class="checkmark" id="user_files_only_checkmark"></span> + </label> + </div> <div class="table-wrapper"> <table id="directory-content-table" class="force-no-select"></table> </div> diff --git a/yarn.lock b/yarn.lock index afeaef36..49691d33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2792,6 +2792,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + chokidar@3.5.2, chokidar@^3.4.0, chokidar@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" @@ -3205,6 +3210,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + custom-event@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" @@ -3960,12 +3970,10 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fast-xml-parser@^3.17.4: - version "3.21.1" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz#152a1d51d445380f7046b304672dd55d15c9e736" - integrity sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg== - dependencies: - strnum "^1.0.4" +fast-xml-parser@^3.19.0: + version "3.19.0" + resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz" + integrity sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg== fastq@^1.6.0: version "1.13.0" @@ -4851,7 +4859,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^1.1.5: +is-buffer@^1.1.5, is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -5395,6 +5403,11 @@ kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +layerr@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/layerr/-/layerr-0.1.2.tgz#16c8e7fb042d3595ab15492bdad088f31d7afd15" + integrity sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ== + lazystream@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" @@ -5727,6 +5740,15 @@ material-design-icons-svg@^3.0.0: resolved "https://registry.yarnpkg.com/material-design-icons-svg/-/material-design-icons-svg-3.2.0.tgz#0f669dbea24d10403ca5ffe9828deb9d4acd8e7e" integrity sha512-5YECqik/lDKRjyo5ItT5Jh12jdDM6ySULnhTyUKomELFsgrwM+2IMkHvXLw61n/q6bJ9tfK/suAkWiTv1/uB4g== +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -8396,10 +8418,10 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.4.7: - version "1.5.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== +url-parse@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" + integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" @@ -8580,21 +8602,23 @@ web-component-analyzer@~1.1.1: typescript "^3.8.3" yargs "^15.3.1" -webdav@^3.6.1: - version "3.6.2" - resolved "https://registry.yarnpkg.com/webdav/-/webdav-3.6.2.tgz#76e3d8e950e80698a2f1db23ef2496888662cbeb" - integrity sha512-HFRiI1jluMSPQMVgxVD6VVYNtaglO53vHG0uf7Zec+wl0A1Mei2z8/IFgAAAJMUuEWAx2AfBD5lcWhAiYA9LUw== +webdav@4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/webdav/-/webdav-4.6.0.tgz#b12d3631562ed4a7d665dd1757349682dfc8f441" + integrity sha512-amL/NeZ73xe8cNC+uqAF3mOC/j5dNoNWlZswYCd7DKxhnZM7dViAVSv6gdCYeGAETjPEZVubcKDDRwGKz9ShQQ== dependencies: axios "^0.21.1" - base-64 "^0.1.0" - fast-xml-parser "^3.17.4" + base-64 "^1.0.0" + fast-xml-parser "^3.19.0" he "^1.2.0" hot-patcher "^0.5.0" + layerr "^0.1.2" + md5 "^2.3.0" minimatch "^3.0.4" nested-property "^4.0.0" path-posix "^1.0.0" url-join "^4.0.1" - url-parse "^1.4.7" + url-parse "^1.5.1" webidl-conversions@^3.0.0: version "3.0.1" -- GitLab