aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2023-07-06 08:58:11 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-07-11 13:52:27 +0200
commit6f54f72bb40113662a9b906bebcf37393b45e9fa (patch)
tree430110ae54f18aaf6243c29eaf5e7d85ef4d14fd /apps/files_sharing
parent5c6ed30369f5c4edcf46e5e882c6096a7e3cd01e (diff)
downloadnextcloud-server-6f54f72bb40113662a9b906bebcf37393b45e9fa.tar.gz
nextcloud-server-6f54f72bb40113662a9b906bebcf37393b45e9fa.zip
feat(sharing): add sharing overview view
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files_sharing')
-rw-r--r--apps/files_sharing/js/app.js24
-rw-r--r--apps/files_sharing/lib/AppInfo/Application.php14
-rw-r--r--apps/files_sharing/lib/Controller/ShareAPIController.php14
-rw-r--r--apps/files_sharing/src/actions/acceptShareAction.ts66
-rw-r--r--apps/files_sharing/src/actions/rejectShareAction.ts78
-rw-r--r--apps/files_sharing/src/actions/restoreShareAction.ts65
-rw-r--r--apps/files_sharing/src/main.ts (renamed from apps/files_sharing/src/index.js)8
-rw-r--r--apps/files_sharing/src/services/SharingService.ts183
-rw-r--r--apps/files_sharing/src/services/logger.ts27
-rw-r--r--apps/files_sharing/src/views/shares.ts123
10 files changed, 581 insertions, 21 deletions
diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js
index b9a60c73dab..9435ceb80c3 100644
--- a/apps/files_sharing/js/app.js
+++ b/apps/files_sharing/js/app.js
@@ -370,40 +370,40 @@ OCA.Sharing.App = {
}
window.addEventListener('DOMContentLoaded', function() {
- $('#app-content-sharingin').on('show', function(e) {
+ $('#app-content-sharinginOld').on('show', function(e) {
OCA.Sharing.App.initSharingIn($(e.target))
})
- $('#app-content-sharingin').on('hide', function() {
+ $('#app-content-sharinginOld').on('hide', function() {
OCA.Sharing.App.removeSharingIn()
})
- $('#app-content-sharingout').on('show', function(e) {
+ $('#app-content-sharingoutOld').on('show', function(e) {
OCA.Sharing.App.initSharingOut($(e.target))
})
- $('#app-content-sharingout').on('hide', function() {
+ $('#app-content-sharingoutOld').on('hide', function() {
OCA.Sharing.App.removeSharingOut()
})
- $('#app-content-sharinglinks').on('show', function(e) {
+ $('#app-content-sharinglinksOld').on('show', function(e) {
OCA.Sharing.App.initSharingLinks($(e.target))
})
- $('#app-content-sharinglinks').on('hide', function() {
+ $('#app-content-sharinglinksOld').on('hide', function() {
OCA.Sharing.App.removeSharingLinks()
})
- $('#app-content-deletedshares').on('show', function(e) {
+ $('#app-content-deletedsharesOld').on('show', function(e) {
OCA.Sharing.App.initSharingDeleted($(e.target))
})
- $('#app-content-deletedshares').on('hide', function() {
+ $('#app-content-deletedsharesOld').on('hide', function() {
OCA.Sharing.App.removeSharingDeleted()
})
- $('#app-content-pendingshares').on('show', function(e) {
+ $('#app-content-pendingsharesOld').on('show', function(e) {
OCA.Sharing.App.initSharingPening($(e.target))
})
- $('#app-content-pendingshares').on('hide', function() {
+ $('#app-content-pendingsharesOld').on('hide', function() {
OCA.Sharing.App.removeSharingPending()
})
- $('#app-content-shareoverview').on('show', function(e) {
+ $('#app-content-shareoverviewOld').on('show', function(e) {
OCA.Sharing.App.initShareingOverview($(e.target))
})
- $('#app-content-shareoverview').on('hide', function() {
+ $('#app-content-shareoverviewOld').on('hide', function() {
OCA.Sharing.App.removeSharingOverview()
})
})
diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php
index eff4a3ac5b7..9051d123f1e 100644
--- a/apps/files_sharing/lib/AppInfo/Application.php
+++ b/apps/files_sharing/lib/AppInfo/Application.php
@@ -231,7 +231,7 @@ class Application extends App implements IBootstrap {
if ($shareManager->sharingDisabledForUser($userId) === false) {
$sharingSublistArray[] = [
- 'id' => 'sharingout',
+ 'id' => 'sharingoutOld',
'appname' => 'files_sharing',
'script' => 'list.php',
'order' => 16,
@@ -240,7 +240,7 @@ class Application extends App implements IBootstrap {
}
$sharingSublistArray[] = [
- 'id' => 'sharingin',
+ 'id' => 'sharinginOld',
'appname' => 'files_sharing',
'script' => 'list.php',
'order' => 15,
@@ -251,7 +251,7 @@ class Application extends App implements IBootstrap {
// Check if sharing by link is enabled
if ($shareManager->shareApiAllowLinks()) {
$sharingSublistArray[] = [
- 'id' => 'sharinglinks',
+ 'id' => 'sharinglinksOld',
'appname' => 'files_sharing',
'script' => 'list.php',
'order' => 17,
@@ -261,7 +261,7 @@ class Application extends App implements IBootstrap {
}
$sharingSublistArray[] = [
- 'id' => 'deletedshares',
+ 'id' => 'deletedsharesOld',
'appname' => 'files_sharing',
'script' => 'list.php',
'order' => 19,
@@ -269,7 +269,7 @@ class Application extends App implements IBootstrap {
];
$sharingSublistArray[] = [
- 'id' => 'pendingshares',
+ 'id' => 'pendingsharesOld',
'appname' => 'files_sharing',
'script' => 'list.php',
'order' => 19,
@@ -277,11 +277,11 @@ class Application extends App implements IBootstrap {
];
return [
- 'id' => 'shareoverview',
+ 'id' => 'shareoverviewOld',
'appname' => 'files_sharing',
'script' => 'list.php',
'order' => 18,
- 'name' => $l->t('Shares'),
+ 'name' => $l->t('Shares (old)'),
'classes' => 'collapsible',
'sublist' => $sharingSublistArray,
];
diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php
index e2fb950dceb..e80412f18b4 100644
--- a/apps/files_sharing/lib/Controller/ShareAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareAPIController.php
@@ -182,6 +182,11 @@ class ShareAPIController extends OCSController {
$sharedBy = $this->userManager->get($share->getSharedBy());
$shareOwner = $this->userManager->get($share->getShareOwner());
+ $isOwnShare = false;
+ if ($shareOwner !== null) {
+ $isOwnShare = $shareOwner->getUID() === $this->currentUser;
+ }
+
$result = [
'id' => $share->getId(),
'share_type' => $share->getShareType(),
@@ -225,6 +230,11 @@ class ShareAPIController extends OCSController {
$result['item_type'] = 'file';
}
+ // Get the original node permission if the share owner is the current user
+ if ($isOwnShare) {
+ $result['permissions'] = $node->getPermissions();
+ }
+
$result['mimetype'] = $node->getMimetype();
$result['has_preview'] = $this->previewManager->isAvailable($node);
$result['storage_id'] = $node->getStorage()->getId();
@@ -233,6 +243,8 @@ class ShareAPIController extends OCSController {
$result['file_source'] = $node->getId();
$result['file_parent'] = $node->getParent()->getId();
$result['file_target'] = $share->getTarget();
+ $result['size'] = $node->getSize();
+ $result['mtime'] = $node->getMTime();
$expiration = $share->getExpirationDate();
if ($expiration !== null) {
@@ -1423,7 +1435,7 @@ class ShareAPIController extends OCSController {
try {
$formattedShare = $this->formatShare($share, $node);
$formattedShare['status'] = $share->getStatus();
- $formattedShare['path'] = $share->getNode()->getName();
+ $formattedShare['path'] = '/' . $share->getNode()->getName();
$formattedShare['permissions'] = 0;
return $formattedShare;
} catch (NotFoundException $e) {
diff --git a/apps/files_sharing/src/actions/acceptShareAction.ts b/apps/files_sharing/src/actions/acceptShareAction.ts
new file mode 100644
index 00000000000..8cdc138ca81
--- /dev/null
+++ b/apps/files_sharing/src/actions/acceptShareAction.ts
@@ -0,0 +1,66 @@
+/**
+ * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import type { Node } from '@nextcloud/files'
+import type { Navigation } from '../../../files/src/services/Navigation'
+
+import { generateOcsUrl } from '@nextcloud/router'
+import { registerFileAction, FileAction } from '@nextcloud/files'
+import { translatePlural as n } from '@nextcloud/l10n'
+import axios from '@nextcloud/axios'
+import CheckSvg from '@mdi/svg/svg/check.svg?raw'
+
+import { pendingSharesViewId } from '../views/shares'
+import { emit } from '@nextcloud/event-bus'
+
+export const action = new FileAction({
+ id: 'accept-share',
+ displayName: (nodes: Node[]) => n('files_sharing', 'Accept share', 'Accept shares', nodes.length),
+ iconSvgInline: () => CheckSvg,
+
+ enabled: (files, view) => view.id === pendingSharesViewId,
+
+ async exec(node: Node) {
+ try {
+ const isRemote = !!node.attributes.remote
+ const url = generateOcsUrl('apps/files_sharing/api/v1/{shareBase}/pending/{id}', {
+ shareBase: isRemote ? 'remote_shares' : 'shares',
+ id: node.attributes.id,
+ })
+ await axios.post(url)
+
+ // Remove from current view
+ emit('files:node:deleted', node)
+
+ return true
+ } catch (error) {
+ return false
+ }
+ },
+ async execBatch(nodes: Node[], view: Navigation, dir: string) {
+ return Promise.all(nodes.map(node => this.exec(node, view, dir)))
+ },
+
+ order: 1,
+ inline: () => true,
+})
+
+registerFileAction(action)
diff --git a/apps/files_sharing/src/actions/rejectShareAction.ts b/apps/files_sharing/src/actions/rejectShareAction.ts
new file mode 100644
index 00000000000..3d14896738a
--- /dev/null
+++ b/apps/files_sharing/src/actions/rejectShareAction.ts
@@ -0,0 +1,78 @@
+/**
+ * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import type { Node } from '@nextcloud/files'
+import type { Navigation } from '../../../files/src/services/Navigation'
+
+import { emit } from '@nextcloud/event-bus'
+import { generateOcsUrl } from '@nextcloud/router'
+import { registerFileAction, FileAction } from '@nextcloud/files'
+import { translatePlural as n } from '@nextcloud/l10n'
+import axios from '@nextcloud/axios'
+import CloseSvg from '@mdi/svg/svg/close.svg?raw'
+
+import { pendingSharesViewId } from '../views/shares'
+
+export const action = new FileAction({
+ id: 'reject-share',
+ displayName: (nodes: Node[]) => n('files_sharing', 'Reject share', 'Reject shares', nodes.length),
+ iconSvgInline: () => CloseSvg,
+
+ enabled: (nodes, view) => {
+ if (view.id !== pendingSharesViewId) {
+ return false
+ }
+
+ // disable rejecting group shares from the pending list because they anyway
+ // land back into that same list after rejecting them
+ if (nodes.some(node => node.attributes.remote_id
+ && node.attributes.share_type === window.OC.Share.SHARE_TYPE_REMOTE_GROUP)) {
+ return false
+ }
+ return true
+ },
+
+ async exec(node: Node) {
+ try {
+ const isRemote = !!node.attributes.remote
+ const url = generateOcsUrl('apps/files_sharing/api/v1/{shareBase}/{id}', {
+ shareBase: isRemote ? 'remote_shares' : 'shares',
+ id: node.attributes.id,
+ })
+ await axios.delete(url)
+
+ // Remove from current view
+ emit('files:node:deleted', node)
+
+ return true
+ } catch (error) {
+ return false
+ }
+ },
+ async execBatch(nodes: Node[], view: Navigation, dir: string) {
+ return Promise.all(nodes.map(node => this.exec(node, view, dir)))
+ },
+
+ order: 2,
+ inline: () => true,
+})
+
+registerFileAction(action)
diff --git a/apps/files_sharing/src/actions/restoreShareAction.ts b/apps/files_sharing/src/actions/restoreShareAction.ts
new file mode 100644
index 00000000000..8e5a49a12b6
--- /dev/null
+++ b/apps/files_sharing/src/actions/restoreShareAction.ts
@@ -0,0 +1,65 @@
+/**
+ * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import type { Node } from '@nextcloud/files'
+import type { Navigation } from '../../../files/src/services/Navigation'
+
+import { generateOcsUrl } from '@nextcloud/router'
+import { registerFileAction, FileAction } from '@nextcloud/files'
+import { translatePlural as n } from '@nextcloud/l10n'
+import axios from '@nextcloud/axios'
+import ArrowULeftTopSvg from '@mdi/svg/svg/arrow-u-left-top.svg?raw'
+
+import { deletedSharesViewId } from '../views/shares'
+import { emit } from '@nextcloud/event-bus'
+
+export const action = new FileAction({
+ id: 'restore-share',
+ displayName: (nodes: Node[]) => n('files_sharing', 'Restore share', 'Restore shares', nodes.length),
+
+ iconSvgInline: () => ArrowULeftTopSvg,
+
+ enabled: (nodes, view) => view.id === deletedSharesViewId,
+
+ async exec(node: Node) {
+ try {
+ const url = generateOcsUrl('apps/files_sharing/api/v1/deletedshares/{id}', {
+ id: node.attributes.id,
+ })
+ await axios.post(url)
+
+ // Remove from current view
+ emit('files:node:deleted', node)
+
+ return true
+ } catch (error) {
+ return false
+ }
+ },
+ async execBatch(nodes: Node[], view: Navigation, dir: string) {
+ return Promise.all(nodes.map(node => this.exec(node, view, dir)))
+ },
+
+ order: 1,
+ inline: () => true,
+})
+
+registerFileAction(action)
diff --git a/apps/files_sharing/src/index.js b/apps/files_sharing/src/main.ts
index 95ed017bbf9..c1d91bb470d 100644
--- a/apps/files_sharing/src/index.js
+++ b/apps/files_sharing/src/main.ts
@@ -20,9 +20,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
+import './actions/acceptShareAction'
+import './actions/rejectShareAction'
+import './actions/restoreShareAction'
+import registerSharingViews from './views/shares'
// register default shares types
-Object.assign(OC, {
+Object.assign(window.OC, {
Share: {
SHARE_TYPE_USER: 0,
SHARE_TYPE_GROUP: 1,
@@ -37,3 +41,5 @@ Object.assign(OC, {
SHARE_TYPE_SCIENCEMESH: 15,
},
})
+
+registerSharingViews()
diff --git a/apps/files_sharing/src/services/SharingService.ts b/apps/files_sharing/src/services/SharingService.ts
new file mode 100644
index 00000000000..036eae07bce
--- /dev/null
+++ b/apps/files_sharing/src/services/SharingService.ts
@@ -0,0 +1,183 @@
+/**
+ * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+/* eslint-disable camelcase, n/no-extraneous-import */
+import type { AxiosPromise } from 'axios'
+import type { ContentsWithRoot } from '../../../files/src/services/Navigation'
+
+import { Folder, File } from '@nextcloud/files'
+import { generateOcsUrl, generateRemoteUrl, generateUrl } from '@nextcloud/router'
+import { getCurrentUser } from '@nextcloud/auth'
+import { rootPath } from '../../../files/src/services/WebdavClient'
+import axios from '@nextcloud/axios'
+import logger from './logger'
+
+type OCSResponse = {
+ ocs: {
+ meta: {
+ status: string
+ statuscode: number
+ message: string
+ },
+ data: []
+ }
+}
+
+const headers = {
+ 'Content-Type': 'application/json',
+}
+
+const ocsEntryToNode = function(ocsEntry: any): Folder | File | null {
+ try {
+ const Node = ocsEntry?.item_type === 'folder' ? Folder : File
+
+ // Sometimes it's an int, sometimes it contains the identifier like "ocinternal:123"
+ const fileid = ocsEntry.file_source
+ const previewUrl = generateUrl('/core/preview?fileId={fileid}&x=32&y=32&forceIcon=0', { fileid })
+
+ // Generate path and strip double slashes
+ const path = ocsEntry?.path || ocsEntry.file_target
+ const source = generateRemoteUrl(`dav/${rootPath}/${path}`.replace(/\/\//, '/'))
+
+ // Prefer share time if more recent than mtime
+ let mtime = ocsEntry?.mtime ? new Date((ocsEntry.mtime) * 1000) : undefined
+ if (ocsEntry?.stime > (ocsEntry?.mtime || 0)) {
+ mtime = new Date((ocsEntry.stime) * 1000)
+ }
+
+ return new Node({
+ id: fileid,
+ source,
+ owner: ocsEntry?.uid_owner,
+ mime: ocsEntry?.mimetype,
+ mtime,
+ size: ocsEntry?.size || undefined,
+ permissions: ocsEntry?.permissions,
+ root: rootPath,
+ attributes: {
+ ...ocsEntry,
+ previewUrl,
+ favorite: ocsEntry?.tags?.includes(window.OC.TAG_FAVORITE) ? 1 : 0,
+ },
+ })
+ } catch (error) {
+ logger.error('Error while parsing OCS entry', error)
+ return null
+ }
+}
+
+const getShares = function(shared_with_me = false): AxiosPromise<OCSResponse> {
+ const url = generateOcsUrl('apps/files_sharing/api/v1/shares')
+ return axios({
+ url,
+ headers,
+ params: {
+ shared_with_me,
+ include_tags: true,
+ },
+ })
+}
+
+const getSharedWithYou = function(): AxiosPromise<OCSResponse> {
+ return getShares(true)
+}
+
+const getSharedWithOthers = function(): AxiosPromise<OCSResponse> {
+ return getShares(false)
+}
+
+const getRemoteShares = function(): AxiosPromise<OCSResponse> {
+ const url = generateOcsUrl('apps/files_sharing/api/v1/remote_shares')
+ return axios({
+ url,
+ headers,
+ params: {
+ include_tags: true,
+ },
+ })
+}
+
+const getPendingShares = function(): AxiosPromise<OCSResponse> {
+ const url = generateOcsUrl('apps/files_sharing/api/v1/shares/pending')
+ return axios({
+ url,
+ headers,
+ params: {
+ include_tags: true,
+ },
+ })
+}
+
+const getRemotePendingShares = function(): AxiosPromise<OCSResponse> {
+ const url = generateOcsUrl('apps/files_sharing/api/v1/remote_shares/pending')
+ return axios({
+ url,
+ headers,
+ params: {
+ include_tags: true,
+ },
+ })
+}
+
+const getDeletedShares = function(): AxiosPromise<OCSResponse> {
+ const url = generateOcsUrl('apps/files_sharing/api/v1/deletedshares')
+ return axios({
+ url,
+ headers,
+ params: {
+ include_tags: true,
+ },
+ })
+}
+
+export const getContents = async (sharedWithYou = true, sharedWithOthers = true, pendingShares = false, deletedshares = false, filterTypes: number[] = []): Promise<ContentsWithRoot> => {
+ const promises = [] as AxiosPromise<OCSResponse>[]
+
+ if (sharedWithYou) {
+ promises.push(getSharedWithYou(), getRemoteShares())
+ }
+ if (sharedWithOthers) {
+ promises.push(getSharedWithOthers())
+ }
+ if (pendingShares) {
+ promises.push(getPendingShares(), getRemotePendingShares())
+ }
+ if (deletedshares) {
+ promises.push(getDeletedShares())
+ }
+
+ const responses = await Promise.all(promises)
+ const data = responses.map((response) => response.data.ocs.data).flat()
+ let contents = data.map(ocsEntryToNode).filter((node) => node !== null) as (Folder | File)[]
+
+ if (filterTypes.length > 0) {
+ contents = contents.filter((node) => filterTypes.includes(node.attributes?.share_type))
+ }
+
+ return {
+ folder: new Folder({
+ id: 0,
+ source: generateRemoteUrl('dav' + rootPath),
+ owner: getCurrentUser()?.uid || '',
+ }),
+ contents,
+ }
+}
diff --git a/apps/files_sharing/src/services/logger.ts b/apps/files_sharing/src/services/logger.ts
new file mode 100644
index 00000000000..19be888bf1f
--- /dev/null
+++ b/apps/files_sharing/src/services/logger.ts
@@ -0,0 +1,27 @@
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import { getLoggerBuilder } from '@nextcloud/logger'
+
+export default getLoggerBuilder()
+ .setApp('files_sharing')
+ .detectUser()
+ .build()
diff --git a/apps/files_sharing/src/views/shares.ts b/apps/files_sharing/src/views/shares.ts
new file mode 100644
index 00000000000..4afc9c07ef5
--- /dev/null
+++ b/apps/files_sharing/src/views/shares.ts
@@ -0,0 +1,123 @@
+/**
+ * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import type NavigationService from '../../../files/src/services/Navigation'
+import type { Navigation } from '../../../files/src/services/Navigation'
+
+import { translate as t } from '@nextcloud/l10n'
+import AccountClockSvg from '@mdi/svg/svg/account-clock.svg?raw'
+import AccountGroupSvg from '@mdi/svg/svg/account-group.svg?raw'
+import AccountSvg from '@mdi/svg/svg/account.svg?raw'
+import DeleteSvg from '@mdi/svg/svg/delete.svg?raw'
+import LinkSvg from '@mdi/svg/svg/link.svg?raw'
+import ShareVariantSvg from '@mdi/svg/svg/share-variant.svg?raw'
+
+import { getContents } from '../services/SharingService'
+
+export const sharesViewId = 'shareoverview'
+export const pendingSharesViewId = 'pendingshares'
+export const deletedSharesViewId = 'deletedshares'
+
+export default () => {
+ const Navigation = window.OCP.Files.Navigation as NavigationService
+ Navigation.register({
+ id: sharesViewId,
+ name: t('files_sharing', 'Shares'),
+ caption: t('files_sharing', 'Overview of shared files.'),
+
+ icon: ShareVariantSvg,
+ order: 20,
+
+ columns: [],
+
+ getContents: () => getContents(),
+ } as Navigation)
+
+ Navigation.register({
+ id: 'sharingin',
+ name: t('files_sharing', 'Shared with you'),
+ caption: t('files_sharing', 'List of files that are shared with you.'),
+
+ icon: AccountSvg,
+ order: 20,
+ parent: sharesViewId,
+
+ columns: [],
+
+ getContents: () => getContents(true, false, false, false),
+ } as Navigation)
+
+ Navigation.register({
+ id: 'sharingout',
+ name: t('files_sharing', 'Shared with others'),
+ caption: t('files_sharing', 'List of files that you shared with others.'),
+
+ icon: AccountGroupSvg,
+ order: 20,
+ parent: sharesViewId,
+
+ columns: [],
+
+ getContents: () => getContents(false, true, false, false),
+ } as Navigation)
+
+ Navigation.register({
+ id: 'sharinglinks',
+ name: t('files_sharing', 'Shared by link'),
+ caption: t('files_sharing', 'List of files that you shared with a share link.'),
+
+ icon: LinkSvg,
+ order: 20,
+ parent: sharesViewId,
+
+ columns: [],
+
+ getContents: () => getContents(false, true, false, false, [window.OC.Share.SHARE_TYPE_LINK]),
+ } as Navigation)
+
+ Navigation.register({
+ id: deletedSharesViewId,
+ name: t('files_sharing', 'Deleted shares'),
+ caption: t('files_sharing', 'List of shares that you removed yourself from.'),
+
+ icon: DeleteSvg,
+ order: 20,
+ parent: sharesViewId,
+
+ columns: [],
+
+ getContents: () => getContents(false, false, false, true),
+ } as Navigation)
+
+ Navigation.register({
+ id: 'pendingshares',
+ name: t('files_sharing', 'Pending shares'),
+ caption: t('files_sharing', 'List of unapproved shares.'),
+
+ icon: AccountClockSvg,
+ order: 20,
+ parent: sharesViewId,
+
+ columns: [],
+
+ getContents: () => getContents(false, false, true, false),
+ } as Navigation)
+}