mirror of
https://github.com/nextcloud/server.git
synced 2026-02-11 14:54:02 -05:00
Merge pull request #40192 from nextcloud/feat/sharing-icon-bread
This commit is contained in:
commit
fe692f2c7f
24 changed files with 147 additions and 49 deletions
|
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { Permission, type Node, View, registerFileAction, FileAction } from '@nextcloud/files'
|
||||
import { Permission, type Node, View, registerFileAction, FileAction, FileType } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import InformationSvg from '@mdi/svg/svg/information-variant.svg?raw'
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ export const action = new FileAction({
|
|||
return (nodes[0].root?.startsWith('/files/') && nodes[0].permissions !== Permission.NONE) ?? false
|
||||
},
|
||||
|
||||
async exec(node: Node, view: View) {
|
||||
async exec(node: Node, view: View, dir: string) {
|
||||
try {
|
||||
// TODO: migrate Sidebar to use a Node instead
|
||||
await window.OCA.Files.Sidebar.open(node.path)
|
||||
|
|
@ -60,7 +60,7 @@ export const action = new FileAction({
|
|||
window.OCP.Files.Router.goToRoute(
|
||||
null,
|
||||
{ view: view.id, fileid: node.fileid },
|
||||
{ dir: node.dirname },
|
||||
{ dir },
|
||||
true,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -166,12 +166,15 @@
|
|||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
import type { PropType } from 'vue'
|
||||
import type { Node } from '@nextcloud/files'
|
||||
|
||||
import { CancelablePromise } from 'cancelable-promise'
|
||||
import { debounce } from 'debounce'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { extname } from 'path'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { getFileActions, DefaultType, FileType, formatFileSize, Permission, NodeStatus } from '@nextcloud/files'
|
||||
import { getFileActions, DefaultType, FileType, formatFileSize, Permission, Folder, File } from '@nextcloud/files'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { translate } from '@nextcloud/l10n'
|
||||
import { vOnClickOutside } from '@vueuse/components'
|
||||
|
|
@ -186,7 +189,7 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
|||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
import Vue from 'vue'
|
||||
|
||||
import { ACTION_DETAILS } from '../actions/sidebarAction.ts'
|
||||
import { action as sidebarAction } from '../actions/sidebarAction.ts'
|
||||
import { hashCode } from '../utils/hashUtils.ts'
|
||||
import { isCachedPreview } from '../services/PreviewService.ts'
|
||||
import { useActionsMenuStore } from '../store/actionsmenu.ts'
|
||||
|
|
@ -235,7 +238,7 @@ export default Vue.extend({
|
|||
default: false,
|
||||
},
|
||||
source: {
|
||||
type: Object,
|
||||
type: [Folder, File, Node] as PropType<Node>,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
|
|
@ -243,7 +246,7 @@ export default Vue.extend({
|
|||
required: true,
|
||||
},
|
||||
nodes: {
|
||||
type: Array,
|
||||
type: Array as PropType<Node[]>,
|
||||
required: true,
|
||||
},
|
||||
filesListWidth: {
|
||||
|
|
@ -295,7 +298,7 @@ export default Vue.extend({
|
|||
|
||||
currentDir() {
|
||||
// Remove any trailing slash but leave root slash
|
||||
return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
|
||||
return (this.$route?.query?.dir?.toString() || '/').replace(/^(.+)\/$/, '$1')
|
||||
},
|
||||
currentFileId() {
|
||||
return this.$route.params.fileid || this.$route.query.fileid || null
|
||||
|
|
@ -660,11 +663,10 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
openDetailsIfAvailable(event) {
|
||||
const detailsAction = this.enabledActions.find(action => action.id === ACTION_DETAILS)
|
||||
if (detailsAction) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
detailsAction.exec(this.source, this.currentView)
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
if (sidebarAction?.enabled?.([this.source], this.currentView)) {
|
||||
sidebarAction.exec(this.source, this.currentView, this.currentDir)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -67,11 +67,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import type { Node } from '@nextcloud/files'
|
||||
|
||||
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||
import { getFileListHeaders, type Node } from '@nextcloud/files'
|
||||
import { getFileListHeaders, Folder, View } from '@nextcloud/files'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import Vue from 'vue'
|
||||
import VirtualList from './VirtualList.vue'
|
||||
|
||||
import { action as sidebarAction } from '../actions/sidebarAction.ts'
|
||||
import FileEntry from './FileEntry.vue'
|
||||
|
|
@ -80,6 +82,7 @@ import FilesListTableFooter from './FilesListTableFooter.vue'
|
|||
import FilesListTableHeader from './FilesListTableHeader.vue'
|
||||
import filesListWidthMixin from '../mixins/filesListWidth.ts'
|
||||
import logger from '../logger.js'
|
||||
import VirtualList from './VirtualList.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FilesListVirtual',
|
||||
|
|
@ -97,15 +100,15 @@ export default Vue.extend({
|
|||
|
||||
props: {
|
||||
currentView: {
|
||||
type: Object,
|
||||
type: View,
|
||||
required: true,
|
||||
},
|
||||
currentFolder: {
|
||||
type: Object,
|
||||
type: Folder,
|
||||
required: true,
|
||||
},
|
||||
nodes: {
|
||||
type: Array,
|
||||
type: Array as PropType<Node[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
|
@ -179,7 +182,7 @@ export default Vue.extend({
|
|||
const node = this.nodes.find(n => n.fileid === this.fileId) as Node
|
||||
if (node && sidebarAction?.enabled?.([node], this.currentView)) {
|
||||
logger.debug('Opening sidebar on file ' + node.path, { node })
|
||||
sidebarAction.exec(node, this.currentView, this.currentFolder)
|
||||
sidebarAction.exec(node, this.currentView, this.currentFolder.path)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ const entry = {
|
|||
iconSvgInline: FolderPlusSvg,
|
||||
async handler(context: Folder, content: Node[]) {
|
||||
const contentNames = content.map((node: Node) => node.basename)
|
||||
const name = getUniqueName(t('files', 'New Folder'), contentNames)
|
||||
const name = getUniqueName(t('files', 'New folder'), contentNames)
|
||||
const { fileid, source } = await createNewFolder(context.source, name)
|
||||
|
||||
// Create the folder in the store
|
||||
|
|
|
|||
|
|
@ -25,8 +25,20 @@
|
|||
<!-- Current folder breadcrumbs -->
|
||||
<BreadCrumbs :path="dir" @reload="fetchContent">
|
||||
<template #actions>
|
||||
<NcButton v-if="canShare"
|
||||
:aria-label="shareButtonLabel"
|
||||
:class="{ 'files-list__header-share-button--shared': shareButtonType }"
|
||||
:title="shareButtonLabel"
|
||||
class="files-list__header-share-button"
|
||||
type="tertiary"
|
||||
@click="openSharingSidebar">
|
||||
<template #icon>
|
||||
<LinkIcon v-if="shareButtonType === Type.SHARE_TYPE_LINK" />
|
||||
<ShareVariantIcon v-else :size="20" />
|
||||
</template>
|
||||
</NcButton>
|
||||
<!-- Uploader -->
|
||||
<UploadPicker v-if="currentFolder"
|
||||
<UploadPicker v-if="currentFolder && canUpload"
|
||||
:content="dirContents"
|
||||
:destination="currentFolder"
|
||||
:multiple="true"
|
||||
|
|
@ -77,18 +89,24 @@ import type { Upload } from '@nextcloud/upload'
|
|||
import type { UserConfig } from '../types.ts'
|
||||
import type { View, ContentsWithRoot } from '@nextcloud/files'
|
||||
|
||||
import { Folder, Node } from '@nextcloud/files'
|
||||
import { Folder, Node, Permission } from '@nextcloud/files'
|
||||
import { getCapabilities } from '@nextcloud/capabilities'
|
||||
import { join, dirname } from 'path'
|
||||
import { orderBy } from 'natural-orderby'
|
||||
import { translate } from '@nextcloud/l10n'
|
||||
import { UploadPicker } from '@nextcloud/upload'
|
||||
import { Type } from '@nextcloud/sharing'
|
||||
import Vue from 'vue'
|
||||
|
||||
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
|
||||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
|
||||
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
||||
import Vue from 'vue'
|
||||
import LinkIcon from 'vue-material-design-icons/Link.vue'
|
||||
import ShareVariantIcon from 'vue-material-design-icons/ShareVariant.vue'
|
||||
|
||||
import { action as sidebarAction } from '../actions/sidebarAction.ts'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
import { usePathsStore } from '../store/paths.ts'
|
||||
import { useSelectionStore } from '../store/selection.ts'
|
||||
|
|
@ -100,17 +118,21 @@ import FilesListVirtual from '../components/FilesListVirtual.vue'
|
|||
import filesSortingMixin from '../mixins/filesSorting.ts'
|
||||
import logger from '../logger.js'
|
||||
|
||||
const isSharingEnabled = getCapabilities()?.files_sharing !== undefined
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'FilesList',
|
||||
|
||||
components: {
|
||||
BreadCrumbs,
|
||||
FilesListVirtual,
|
||||
LinkIcon,
|
||||
NcAppContent,
|
||||
NcButton,
|
||||
NcEmptyContent,
|
||||
NcIconSvgWrapper,
|
||||
NcLoadingIcon,
|
||||
ShareVariantIcon,
|
||||
UploadPicker,
|
||||
},
|
||||
|
||||
|
|
@ -139,6 +161,7 @@ export default Vue.extend({
|
|||
return {
|
||||
loading: true,
|
||||
promise: null,
|
||||
Type,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -157,7 +180,7 @@ export default Vue.extend({
|
|||
*/
|
||||
dir(): string {
|
||||
// Remove any trailing slash but leave root slash
|
||||
return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1')
|
||||
return (this.$route?.query?.dir?.toString() || '/').replace(/^(.+)\/$/, '$1')
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -242,6 +265,43 @@ export default Vue.extend({
|
|||
const dir = this.dir.split('/').slice(0, -1).join('/') || '/'
|
||||
return { ...this.$route, query: { dir } }
|
||||
},
|
||||
|
||||
shareAttributes(): number[]|undefined {
|
||||
if (!this.currentFolder?.attributes?.['share-types']) {
|
||||
return undefined
|
||||
}
|
||||
return Object.values(this.currentFolder?.attributes?.['share-types'] || {}).flat() as number[]
|
||||
},
|
||||
shareButtonLabel() {
|
||||
if (!this.shareAttributes) {
|
||||
return this.t('files', 'Share')
|
||||
}
|
||||
|
||||
if (this.shareButtonType === Type.SHARE_TYPE_LINK) {
|
||||
return this.t('files', 'Shared by link')
|
||||
}
|
||||
return this.t('files', 'Shared')
|
||||
},
|
||||
shareButtonType(): Type|null {
|
||||
if (!this.shareAttributes) {
|
||||
return null
|
||||
}
|
||||
|
||||
// If all types are links, show the link icon
|
||||
if (this.shareAttributes.some(type => type === Type.SHARE_TYPE_LINK)) {
|
||||
return Type.SHARE_TYPE_LINK
|
||||
}
|
||||
|
||||
return Type.SHARE_TYPE_USER
|
||||
},
|
||||
|
||||
canUpload() {
|
||||
return this.currentFolder && (this.currentFolder.permissions & Permission.CREATE) !== 0
|
||||
},
|
||||
canShare() {
|
||||
return isSharingEnabled
|
||||
&& this.currentFolder && (this.currentFolder.permissions & Permission.SHARE) !== 0
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
|
@ -348,6 +408,13 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
|
||||
openSharingSidebar() {
|
||||
if (window?.OCA?.Files?.Sidebar?.setActiveTab) {
|
||||
window.OCA.Files.Sidebar.setActiveTab('sharing')
|
||||
}
|
||||
sidebarAction.exec(this.currentFolder, this.currentView, this.currentFolder.path)
|
||||
},
|
||||
|
||||
t: translate,
|
||||
},
|
||||
})
|
||||
|
|
@ -378,12 +445,21 @@ $navigationToggleSize: 50px;
|
|||
// Only the breadcrumbs shrinks
|
||||
flex: 0 0;
|
||||
}
|
||||
|
||||
&-share-button {
|
||||
opacity: .3;
|
||||
&--shared {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__refresh-icon {
|
||||
flex: 0 0 44px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
&__loading-icon {
|
||||
margin: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ declare(strict_types=1);
|
|||
namespace OCA\ShareByMail;
|
||||
|
||||
use OCA\ShareByMail\Settings\SettingsManager;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Capabilities\ICapability;
|
||||
use OCP\Share\IManager;
|
||||
|
||||
|
|
@ -39,10 +40,15 @@ class Capabilities implements ICapability {
|
|||
/** @var SettingsManager */
|
||||
private $settingsManager;
|
||||
|
||||
/** @var IAppManager */
|
||||
private $appManager;
|
||||
|
||||
public function __construct(IManager $manager,
|
||||
SettingsManager $settingsManager) {
|
||||
SettingsManager $settingsManager,
|
||||
IAppManager $appManager) {
|
||||
$this->manager = $manager;
|
||||
$this->settingsManager = $settingsManager;
|
||||
$this->appManager = $appManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,9 +70,12 @@ class Capabilities implements ICapability {
|
|||
* },
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }|array<empty>
|
||||
*/
|
||||
public function getCapabilities(): array {
|
||||
if (!$this->appManager->isEnabledForUser('files_sharing')) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
'files_sharing' =>
|
||||
[
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ namespace OCA\ShareByMail\Tests;
|
|||
|
||||
use OCA\ShareByMail\Capabilities;
|
||||
use OCA\ShareByMail\Settings\SettingsManager;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\Share\IManager;
|
||||
use Test\TestCase;
|
||||
|
||||
|
|
@ -40,13 +41,17 @@ class CapabilitiesTest extends TestCase {
|
|||
/** @var IManager | \PHPUnit\Framework\MockObject\MockObject */
|
||||
private $settingsManager;
|
||||
|
||||
/** @var IAppManager | \PHPUnit\Framework\MockObject\MockObject */
|
||||
private $appManager;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
|
||||
$this->manager = $this::createMock(IManager::class);
|
||||
$this->settingsManager = $this::createMock(SettingsManager::class);
|
||||
$this->capabilities = new Capabilities($this->manager, $this->settingsManager);
|
||||
$this->appManager = $this::createMock(IAppManager::class);
|
||||
$this->capabilities = new Capabilities($this->manager, $this->settingsManager, $this->appManager);
|
||||
}
|
||||
|
||||
public function testGetCapabilities() {
|
||||
|
|
@ -58,6 +63,8 @@ class CapabilitiesTest extends TestCase {
|
|||
->willReturn(false);
|
||||
$this->settingsManager->method('sendPasswordByMail')
|
||||
->willReturn(true);
|
||||
$this->appManager->method('isEnabledForUser')
|
||||
->willReturn(true);
|
||||
|
||||
$capabilities = [
|
||||
'files_sharing' =>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ describe('Versions creation', () => {
|
|||
})
|
||||
|
||||
it('Opens the versions panel and sees the versions', () => {
|
||||
cy.visit('/apps/files')
|
||||
openVersionsPanel(randomFileName)
|
||||
|
||||
cy.get('#tab-version_vue').within(() => {
|
||||
|
|
|
|||
6
dist/9872-9872.js → dist/7761-7761.js
vendored
6
dist/9872-9872.js → dist/7761-7761.js
vendored
File diff suppressed because one or more lines are too long
1
dist/7761-7761.js.map
vendored
Normal file
1
dist/7761-7761.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/9872-9872.js.map
vendored
1
dist/9872-9872.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-common.js
vendored
4
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-personal-settings.js
vendored
4
dist/files-personal-settings.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-personal-settings.js.map
vendored
2
dist/files-personal-settings.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-files_sharing_tab.js
vendored
4
dist/files_sharing-files_sharing_tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-files_sharing_tab.js.map
vendored
2
dist/files_sharing-files_sharing_tab.js.map
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue