зеркало из https://github.com/nextcloud/server.git
feat(files): add sharing icon in header
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
This commit is contained in:
Родитель
e2303d0cc1
Коммит
e040d939e5
|
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Загрузка…
Ссылка в новой задаче