mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 09:42:09 -04:00
Merge pull request #39196 from nextcloud/feat/f2v/sharing
This commit is contained in:
commit
2cf8d6d965
60 changed files with 2229 additions and 1330 deletions
|
|
@ -20,6 +20,7 @@
|
|||
*
|
||||
*/
|
||||
export default {
|
||||
get: async () => ({ status: 200, data: {} }),
|
||||
delete: async () => ({ status: 200, data: {} }),
|
||||
post: async () => ({ status: 200, data: {} }),
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["files.scss","../../../core/css/functions.scss"],"names":[],"mappings":"AAWA,SAEC,YACA,YACA,qBACA,WAED,oEACA,8BACA,kDAEC,+CAED,0BACC,oDAGD,mBACC,kBACA,aACA,SACA,4CACC,iBAIF,gBACC,aAGD,OACC,iBACA,YACA,aACA,aACA,mBAGD,6EAGC,yBACA,gCAID,kBACC,kBACA,WACA,gBACA,cACA,sBAEA,6CACC,aAGD,wBACC,wBACA,gBAEA,SAEA,WACA,cACA,0DAMD,wBACC,cACA,WAEA,mGAEC,8CAEA,6KACC,oCAKF,8DACC,oBAKH,yBACC,aAID,uCACC,cACA,WAGD,wBAGC,yBAEA,qBAGD,6FACC,+DAGD,iCACC,yDAGD,kFACC,0CAGD,4EACC,+DAID,gBCxEC,yCD2ED,iBC3EC,yCD8ED,oBC9EC,0CDiFD,yFCjFC,wCDuFD,uBCvFC,yCD0FD,2BC1FC,2CD6FD,mBC7FC,yCDgGD,2BChGC,4CDmGD,wBCnGC,0CDsGD,4BCtGC,4CD0GD,4CACC,WAGD,iCACC,WACA,YACA,eACA,SACA,eAGD,wCACC,aAGD,0CACC,WAGD,2BACC,YAED,4KAKC,+CAED,wMAKC,oDAGD,qCAEA,yDACC,oCAED,kCACC,iCACA,8BACA,4BACA,yBACA,mBAED,wGAIC,UACA,oCAGD,oBACC,oCAED,uBACC,6BAED,sBACC,cACA,aACA,YACA,sBACA,2BACA,sBACA,oCACC,kBAGF,kCACC,qBACA,mBAED,2BACC,eACA,iBAGD,uCACC,cAGD,yBACC,WACA,WACA,gBACA,qBACA,2BACA,WAED,wJAIC,kBAED,2CACC,eAED,4EAEC,mBAGD,kBAEC,4CACA,gBACA,mBAED,SACC,eACA,kBACA,+BACA,4BAED,qBACC,kBACA,aACA,UAGD,uBACC,kBACA,YAGD,0BACC,gBAED,uCACC,iBAED,8EAEC,2BACA,sBACA,kBAEA,gBAGD,qMAQC,gBACA,qPACC,MAIF,2BACC,0DACA,iBAGD,sDACC,iBAGD,+BACC,kBACA,aAED,kCACC,aAGD,0DAGC,WACA,kBAED,kDAEC,aACA,kBACA,2BACA,sBACA,YACA,iBACA,UAED,qCAEC,QACA,eACA,eACA,YAGA,8DACC,WAED,mEACC,WAGF,6BACC,qBACA,WACA,YACA,wBACA,2BACA,4BACA,gBACA,eACA,mCACA,eACA,kBACA,UAED,oCACC,eAID,2CACC,qCAGD,iDACC,qBACA,4BACA,YAED,uBACC,iBACA,kBACA,SAGD,6IACA,8FAEA,wCACC,kBACA,gBACA,uBACA,YAKA,kBACC,YACA,4BACC,QACA,YACA,aACA,gBACA,mBACA,uBACA,YACA,WACA,mBAID,+BACC,iBACA,aACA,uBACA,mBACA,kCACA,gBAEA,iDACC,iBACA,iBACA,wCACA,iCACA,oCACA,uBACA,mBACA,gBACA,uBACA,iBACA,kBAEA,uDACC,iBACA,sBAID,mEACC,gBAOL,iJAEC,wBAGD,mCACC,iCACA,8BACA,4BACA,yBAED,4BACC,WAGD,2CACC,uBACA,gBACA,kBACA,mBAKD,8BACC,kBACA,mBAEA,iBACA,OACA,SACA,YACA,cAEA,iBACA,eAEA,iBACA,oCACA,uBACA,mBAGD,mBACC,UAID,6DACC,WACA,eAID,iRAIC,UAID,0EACC,WAMA,wEACC,aAGD,oGACC,+CACA,wCACA,wBACA,yDACA,aAIF,oGAEC,mBAGD,+BACC,kBACA,WACA,eACA,gBACA,wJAGD,wFAEC,kBACA,UACA,YAGD,yCACC,qBACA,WAED,8CACC,kBACA,cACA,SACA,WACA,iBACA,kBACA,wDAEC,8CACA,8CACA,oBAEA,WACA,YACA,aACA,qBACA,uBAGF,8DACC,+CAGD,iDAGA,aACC,WAGD,iCACC,kBAID,mDAEC,gBAID,oCACC,qBACA,0BAGD,8EACC,0BAOA,kCACC,eAGD,sEACC,eAGD,sCACC,gBAIF,aACC,YACA,WACA,2BAGD,qCACC,wCAID,iBACI,kBACA,qBACA,sBAEJ,wBACI,aAEJ,mBACC,eACA,iBACA,iBAGD,0BACC,aAED,uBACC,kBACA,2BACA,mBAGD,8CACC,gBAIA,8BACC,eACA,iBACA,iBACA,WACA,2CACC,kBACA,0FAGC,kBACA,cACA,SACA,UACA,WACA,gBAED,mDACC,qBACA,sBAGF,0CACC,iBACA,oBACA,kBACA,mBAGA,oGACC,WAID,qIAEC,WAED,uDACC,WACA,0HACC,WAIH,wEACC,UAED,oCACC,+CACA,wCAGF,uGACC,WAED,wDACC,UAKF,4EACC,qBACA,eACA,gBACA,uBACA,sBACA,gBAGD,2CACC,yBAGD,yCACC,UAGD,kNAKC,UAGD,qCACC,gBAGD,0FAEC,WAGD,mDACC,eAGD,SACC,oCAGA,aAED,wCACC,WAEA,mBAKD,sBACC,aAED,2DAIC,+BAED,YACC,mBACA,mBACA,iBAED,wBACC,UAED,YACC,qBAGD,iBACC,WACA,aAED,6BACC,kBACA,mBACA,YAGA,gBAED,yBACC,kBAED,MACC,WACA,kBACA,MACA,OACA,QACA,SACA,8CACA,sCACA,wBACA,WACA,yBACA,8BACA,4BACA,6BACA,iCAED,kBACC,UAGD,aACC,gBACA,SACA,sBACA,eACA,gBACA,aAGA,oBACC,qBAKF,gBACC,sBACA,wBACA,gBACA,YACA,UACA,SACA,0DACA,WACA,yBACA,sBACA,qBACA,iBACA,aACA,MACA,kBAKE,0IACC,sBACA,qBACA,aACA,YACA,WACA,YACA,mBACA,uBAED,oFACC,aAQJ,0DACC,OAGD,6KAIC,qBACA,sBACA,0BAMA,sDACC,sBAED,yDACC,uDAIF,iJAGC,aAGD,oJAGC,WACA,YAGD,gCACC,kBAGD,YACC,mBAEA,uBACC,mCAIF,0DAEC,oCAED,qBACC,oCACA,4BACC,2BAIF,cACC,iBACA,kBACA,gBACA,6BACA,cACA,gBACA,YAEA,2BACC,aAGD,kCACC,UACA,kBACA,iBAIF,uBACC,oBACA,YACA,gBACA,+BACA,UACA,YACA,wBACA,sBAEA,6BACC,YAKA,oEACC,0BAIF,kCACC,WACA,mCAWA,kDACC,cACA,4CACA,0DACA,qDACC,WACA,YAMH,+CACC,aACA,+CACA,6BACA,aACA,cAGA,+DACC,cACA,kBACA,aACA,mCAEA,0fAKC,+BAEA,oxDAGC,+CAKH,kDACC,eACA,mBAGC,8EACC,YACA,eACA,kBACA,MAvDQ,MAwDR,OAxDQ,MAyDR,QAxDO,KAyDP,MACA,OACA,WAEA,yFACC,4BACA,6BACA,wBACA,SACA,mCACA,4BACA,2BAKA,wGACC,UACA,UACA,YAKH,uEACC,WACA,SACA,MACA,YAEA,YACA,gBAEA,kBAGD,iEACC,YACA,mCAIA,gBAKA,0BAEA,2EACC,aACA,YACA,iBACA,kBACA,iBACA,UAEA,0FACC,qBACA,kBACA,gBACA,uBACA,mBAED,kFACC,WACA,OACA,eAED,iFACC,WACA,OACA,eAID,sFACC,aAKF,8EACC,aAGD,8EACC,eACA,iBACA,aACA,mBACA,kBACA,QAEA,sFACC,QAxJK,KAyJL,WACA,YACA,aACA,mBACA,uBAGA,wGACC,aAQH,2GACC,yBAEA,6HACC,YACA,kBAIF,6GACC,yBAGD,6GACC,yBAIF,gEACC,iBACA,mCAEA,+EACC,WACA,cACA,YAMH,kHAEC,aAGD,sIAEC,kBACA,SACA,UACA,aACA,WAEA,kJACC,WACA,YACA,oBACA,QAzNO,KA0NP,kKACC,SACA,MA5NM,KA6NN,OA7NM,KAmOT,+DACC,OACA,YACA,aAGA,yFACC,gBACA,uBAMJ,+FACC,cAID,+CACC,aAEA,qEACC,qBACA,cAEA,aAEA,wEACC,iBAEA,iKAEC,aAGD,8EACI,cAQR,aACC,0DACA,YACA,SACA,aACA,WACA,YACA,mCACA,iCACA,YACA,gBAEA,uEAGC,UAGD,oEAEC,mEASF,cACC,eACA,MAOC,uGACC,gBAID,4EACC,WAKF,0BACC,kBACA,QACA,MAKF,gBACC,aAGD,8BACC,gBACA,sBACA,kBACA,kBACA,aACA,eACA,mBAEA,iCACC,WACA,eAGD,6DACC,aACA,YACA","file":"files.css"}
|
||||
{"version":3,"sourceRoot":"","sources":["files.scss","../../../core/css/functions.scss"],"names":[],"mappings":"AAWA,SAEC,YACA,YACA,qBACA,WAED,oEACA,8BACA,kDAEC,+CAED,0BACC,oDAGD,mBACC,kBACA,aACA,SACA,4CACC,iBAIF,gBACC,aAGD,OACC,iBACA,YACA,aACA,aACA,mBAGD,6EAGC,yBACA,gCAID,kBACC,kBACA,WACA,gBACA,cACA,sBAEA,6CACC,aAGD,wBACC,wBACA,gBAEA,SAEA,WACA,cACA,0DAMD,wBACC,cACA,WAEA,mGAEC,8CAEA,6KACC,oCAKF,8DACC,oBAKH,yBACC,aAID,uCACC,cACA,WAGD,wBAGC,yBAEA,qBAGD,6FACC,+DAGD,iCACC,yDAGD,kFACC,0CAGD,4EACC,+DAID,gBCxEC,yCD2ED,iBC3EC,yCD8ED,oBC9EC,0CDiFD,qGCjFC,wCDuFD,0BCvFC,yCD0FD,2BC1FC,2CD6FD,mBC7FC,yCDgGD,2BChGC,4CDmGD,2BCnGC,0CDsGD,4BCtGC,4CD0GD,4CACC,WAGD,iCACC,WACA,YACA,eACA,SACA,eAGD,wCACC,aAGD,0CACC,WAGD,2BACC,YAED,4KAKC,+CAED,wMAKC,oDAGD,qCAEA,yDACC,oCAED,kCACC,iCACA,8BACA,4BACA,yBACA,mBAED,wGAIC,UACA,oCAGD,oBACC,oCAED,uBACC,6BAED,sBACC,cACA,aACA,YACA,sBACA,2BACA,sBACA,oCACC,kBAGF,kCACC,qBACA,mBAED,2BACC,eACA,iBAGD,uCACC,cAGD,yBACC,WACA,WACA,gBACA,qBACA,2BACA,WAED,wJAIC,kBAED,2CACC,eAED,4EAEC,mBAGD,kBAEC,4CACA,gBACA,mBAED,SACC,eACA,kBACA,+BACA,4BAED,qBACC,kBACA,aACA,UAGD,uBACC,kBACA,YAGD,0BACC,gBAED,uCACC,iBAED,8EAEC,2BACA,sBACA,kBAEA,gBAGD,qMAQC,gBACA,qPACC,MAIF,2BACC,0DACA,iBAGD,sDACC,iBAGD,+BACC,kBACA,aAED,kCACC,aAGD,0DAGC,WACA,kBAED,kDAEC,aACA,kBACA,2BACA,sBACA,YACA,iBACA,UAED,qCAEC,QACA,eACA,eACA,YAGA,8DACC,WAED,mEACC,WAGF,6BACC,qBACA,WACA,YACA,wBACA,2BACA,4BACA,gBACA,eACA,mCACA,eACA,kBACA,UAED,oCACC,eAID,2CACC,qCAGD,iDACC,qBACA,4BACA,YAED,uBACC,iBACA,kBACA,SAGD,6IACA,8FAEA,wCACC,kBACA,gBACA,uBACA,YAKA,kBACC,YACA,4BACC,QACA,YACA,aACA,gBACA,mBACA,uBACA,YACA,WACA,mBAID,+BACC,iBACA,aACA,uBACA,mBACA,kCACA,gBAEA,iDACC,iBACA,iBACA,wCACA,iCACA,oCACA,uBACA,mBACA,gBACA,uBACA,iBACA,kBAEA,uDACC,iBACA,sBAID,mEACC,gBAOL,iJAEC,wBAGD,mCACC,iCACA,8BACA,4BACA,yBAED,4BACC,WAGD,2CACC,uBACA,gBACA,kBACA,mBAKD,8BACC,kBACA,mBAEA,iBACA,OACA,SACA,YACA,cAEA,iBACA,eAEA,iBACA,oCACA,uBACA,mBAGD,mBACC,UAID,6DACC,WACA,eAID,iRAIC,UAID,0EACC,WAMA,wEACC,aAGD,oGACC,+CACA,wCACA,wBACA,yDACA,aAIF,oGAEC,mBAGD,+BACC,kBACA,WACA,eACA,gBACA,wJAGD,wFAEC,kBACA,UACA,YAGD,yCACC,qBACA,WAED,8CACC,kBACA,cACA,SACA,WACA,iBACA,kBACA,wDAEC,8CACA,8CACA,oBAEA,WACA,YACA,aACA,qBACA,uBAGF,8DACC,+CAGD,iDAGA,aACC,WAGD,iCACC,kBAID,mDAEC,gBAID,oCACC,qBACA,0BAGD,8EACC,0BAOA,kCACC,eAGD,sEACC,eAGD,sCACC,gBAIF,aACC,YACA,WACA,2BAGD,qCACC,wCAID,iBACI,kBACA,qBACA,sBAEJ,wBACI,aAEJ,mBACC,eACA,iBACA,iBAGD,0BACC,aAED,uBACC,kBACA,2BACA,mBAGD,8CACC,gBAIA,8BACC,eACA,iBACA,iBACA,WACA,2CACC,kBACA,0FAGC,kBACA,cACA,SACA,UACA,WACA,gBAED,mDACC,qBACA,sBAGF,0CACC,iBACA,oBACA,kBACA,mBAGA,oGACC,WAID,qIAEC,WAED,uDACC,WACA,0HACC,WAIH,wEACC,UAED,oCACC,+CACA,wCAGF,uGACC,WAED,wDACC,UAKF,4EACC,qBACA,eACA,gBACA,uBACA,sBACA,gBAGD,2CACC,yBAGD,yCACC,UAGD,kNAKC,UAGD,qCACC,gBAGD,0FAEC,WAGD,mDACC,eAGD,SACC,oCAGA,aAED,wCACC,WAEA,mBAKD,sBACC,aAED,2DAIC,+BAED,YACC,mBACA,mBACA,iBAED,wBACC,UAED,YACC,qBAGD,iBACC,WACA,aAED,6BACC,kBACA,mBACA,YAGA,gBAED,yBACC,kBAED,MACC,WACA,kBACA,MACA,OACA,QACA,SACA,8CACA,sCACA,wBACA,WACA,yBACA,8BACA,4BACA,6BACA,iCAED,kBACC,UAGD,aACC,gBACA,SACA,sBACA,eACA,gBACA,aAGA,oBACC,qBAKF,gBACC,sBACA,wBACA,gBACA,YACA,UACA,SACA,0DACA,WACA,yBACA,sBACA,qBACA,iBACA,aACA,MACA,kBAKE,0IACC,sBACA,qBACA,aACA,YACA,WACA,YACA,mBACA,uBAED,oFACC,aAQJ,0DACC,OAGD,6KAIC,qBACA,sBACA,0BAMA,sDACC,sBAED,yDACC,uDAIF,iJAGC,aAGD,oJAGC,WACA,YAGD,gCACC,kBAGD,YACC,mBAEA,uBACC,mCAIF,0DAEC,oCAED,qBACC,oCACA,4BACC,2BAIF,cACC,iBACA,kBACA,gBACA,6BACA,cACA,gBACA,YAEA,2BACC,aAGD,kCACC,UACA,kBACA,iBAIF,uBACC,oBACA,YACA,gBACA,+BACA,UACA,YACA,wBACA,sBAEA,6BACC,YAKA,oEACC,0BAIF,kCACC,WACA,mCAWA,kDACC,cACA,4CACA,0DACA,qDACC,WACA,YAMH,+CACC,aACA,+CACA,6BACA,aACA,cAGA,+DACC,cACA,kBACA,aACA,mCAEA,0fAKC,+BAEA,oxDAGC,+CAKH,kDACC,eACA,mBAGC,8EACC,YACA,eACA,kBACA,MAvDQ,MAwDR,OAxDQ,MAyDR,QAxDO,KAyDP,MACA,OACA,WAEA,yFACC,4BACA,6BACA,wBACA,SACA,mCACA,4BACA,2BAKA,wGACC,UACA,UACA,YAKH,uEACC,WACA,SACA,MACA,YAEA,YACA,gBAEA,kBAGD,iEACC,YACA,mCAIA,gBAKA,0BAEA,2EACC,aACA,YACA,iBACA,kBACA,iBACA,UAEA,0FACC,qBACA,kBACA,gBACA,uBACA,mBAED,kFACC,WACA,OACA,eAED,iFACC,WACA,OACA,eAID,sFACC,aAKF,8EACC,aAGD,8EACC,eACA,iBACA,aACA,mBACA,kBACA,QAEA,sFACC,QAxJK,KAyJL,WACA,YACA,aACA,mBACA,uBAGA,wGACC,aAQH,2GACC,yBAEA,6HACC,YACA,kBAIF,6GACC,yBAGD,6GACC,yBAIF,gEACC,iBACA,mCAEA,+EACC,WACA,cACA,YAMH,kHAEC,aAGD,sIAEC,kBACA,SACA,UACA,aACA,WAEA,kJACC,WACA,YACA,oBACA,QAzNO,KA0NP,kKACC,SACA,MA5NM,KA6NN,OA7NM,KAmOT,+DACC,OACA,YACA,aAGA,yFACC,gBACA,uBAMJ,+FACC,cAID,+CACC,aAEA,qEACC,qBACA,cAEA,aAEA,wEACC,iBAEA,iKAEC,aAGD,8EACI,cAQR,aACC,0DACA,YACA,SACA,aACA,WACA,YACA,mCACA,iCACA,YACA,gBAEA,uEAGC,UAGD,oEAEC,mEASF,cACC,eACA,MAOC,uGACC,gBAID,4EACC,WAKF,0BACC,kBACA,QACA,MAKF,gBACC,aAGD,8BACC,gBACA,sBACA,kBACA,kBACA,aACA,eACA,mBAEA,iCACC,WACA,eAGD,6DACC,aACA,YACA","file":"files.css"}
|
||||
|
|
@ -144,13 +144,13 @@
|
|||
.nav-icon-favorites {
|
||||
@include icon-color('starred', 'actions', variables.$color-black, 2, true);
|
||||
}
|
||||
.nav-icon-sharingin,
|
||||
.nav-icon-sharingout,
|
||||
.nav-icon-pendingshares,
|
||||
.nav-icon-shareoverview {
|
||||
.nav-icon-sharinginOld,
|
||||
.nav-icon-sharingoutOld,
|
||||
.nav-icon-pendingsharesOld,
|
||||
.nav-icon-shareoverviewOld {
|
||||
@include icon-color('share', 'files', variables.$color-black);
|
||||
}
|
||||
.nav-icon-sharinglinks {
|
||||
.nav-icon-sharinglinksOld {
|
||||
@include icon-color('public', 'files', variables.$color-black);
|
||||
}
|
||||
.nav-icon-extstoragemounts {
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
.nav-icon-trashbin-starred {
|
||||
@include icon-color('delete', 'files', #ff0000);
|
||||
}
|
||||
.nav-icon-deletedshares {
|
||||
.nav-icon-deletedsharesOld {
|
||||
@include icon-color('unshare', 'files', variables.$color-black);
|
||||
}
|
||||
.nav-icon-favorites-starred {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -111,6 +111,7 @@
|
|||
var fileInfo = context.fileList.files[$file.index()];
|
||||
var dir = context.dir || context.fileList.getCurrentDirectory();
|
||||
var tags = $file.attr('data-tags');
|
||||
var isFile = $file.attr('data-type') === 'file';
|
||||
|
||||
if (_.isUndefined(tags)) {
|
||||
tags = '';
|
||||
|
|
@ -121,7 +122,7 @@
|
|||
|
||||
// Fake Node object for vue compatibility
|
||||
const node = {
|
||||
type: 'folder',
|
||||
type: isFile ? 'file' : 'folder',
|
||||
path: (dir + '/' + fileName).replace(/\/\/+/g, '/'),
|
||||
root: '/files/' + OC.getCurrentUser().uid
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,11 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { Permission, Node, FileType } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'
|
||||
|
||||
import { registerFileAction, FileAction } from '../services/FileAction'
|
||||
import { registerFileAction, FileAction, DefaultType } from '../services/FileAction'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import type { Navigation } from '../services/Navigation'
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import * as favoriteAction from './favoriteAction'
|
||||
import { action } from './favoriteAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { File, Permission } from '@nextcloud/files'
|
||||
import { FileAction } from '../services/FileAction'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
|
@ -120,6 +120,7 @@ describe('Favorite action enabled tests', () => {
|
|||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
|
|
|
|||
|
|
@ -20,13 +20,15 @@
|
|||
*
|
||||
*/
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { Permission, type Node } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import axios from '@nextcloud/axios'
|
||||
import StarSvg from '@mdi/svg/svg/star.svg?raw'
|
||||
import StarOutlineSvg from '@mdi/svg/svg/star-outline.svg?raw'
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import Vue from 'vue'
|
||||
|
||||
import StarOutlineSvg from '@mdi/svg/svg/star-outline.svg?raw'
|
||||
import StarSvg from '@mdi/svg/svg/star.svg?raw'
|
||||
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { registerFileAction, FileAction } from '../services/FileAction'
|
||||
import logger from '../logger.js'
|
||||
import type { Navigation } from '../services/Navigation'
|
||||
|
|
@ -54,7 +56,7 @@ export const favoriteNode = async (node: Node, view: Navigation, willFavorite: b
|
|||
}
|
||||
|
||||
// Update the node webdav attribute
|
||||
node.attributes.favorite = willFavorite ? 1 : 0
|
||||
Vue.set(node.attributes, 'favorite', willFavorite ? 1 : 0)
|
||||
|
||||
// Dispatch event to whoever is interested
|
||||
if (willFavorite) {
|
||||
|
|
@ -85,8 +87,9 @@ export const action = new FileAction({
|
|||
},
|
||||
|
||||
enabled(nodes: Node[]) {
|
||||
// We can only favorite nodes within files
|
||||
// We can only favorite nodes within files and with permissions
|
||||
return !nodes.some(node => !node.root?.startsWith?.('/files'))
|
||||
&& nodes.every(node => node.permissions !== Permission.NONE)
|
||||
},
|
||||
|
||||
async exec(node: Node, view: Navigation) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
import { action } from './sidebarAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File } from '@nextcloud/files'
|
||||
import { File, Permission } from '@nextcloud/files'
|
||||
import { FileAction } from '../services/FileAction'
|
||||
import type { Navigation } from '../services/Navigation'
|
||||
import logger from '../logger'
|
||||
|
|
@ -51,12 +51,29 @@ describe('Open sidebar action enabled tests', () => {
|
|||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled without permissions', () => {
|
||||
window.OCA = { Files: { Sidebar: {} } }
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.NONE,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
|
||||
})
|
||||
|
||||
test('Disabled if more than one node', () => {
|
||||
window.OCA = { Files: { Sidebar: {} } }
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@
|
|||
*/
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import InformationSvg from '@mdi/svg/svg/information-variant.svg?raw'
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import { Permission, type Node } from '@nextcloud/files'
|
||||
|
||||
import { registerFileAction, FileAction, DefaultType } from '../services/FileAction'
|
||||
import { registerFileAction, FileAction } from '../services/FileAction'
|
||||
import logger from '../logger.js'
|
||||
|
||||
export const ACTION_DETAILS = 'details'
|
||||
|
|
@ -45,7 +45,7 @@ export const action = new FileAction({
|
|||
return false
|
||||
}
|
||||
|
||||
return nodes[0].root?.startsWith('/files/') ?? false
|
||||
return (nodes[0].root?.startsWith('/files/') && nodes[0].permissions !== Permission.NONE) ?? false
|
||||
},
|
||||
|
||||
async exec(node: Node) {
|
||||
|
|
|
|||
|
|
@ -48,12 +48,26 @@ describe('View in folder action enabled tests', () => {
|
|||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled without permissions', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.NONE,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled for non-dav ressources', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
|
|
@ -107,13 +121,14 @@ describe('View in folder action execute tests', () => {
|
|||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
// Silent action
|
||||
expect(exec).toBe(null)
|
||||
expect(goToRouteMock).toBeCalledTimes(1)
|
||||
expect(goToRouteMock).toBeCalledWith(null, { view: 'files' }, { dir: '/' })
|
||||
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { fileid: 1, dir: '/' })
|
||||
})
|
||||
|
||||
test('View in (sub) folder', async () => {
|
||||
|
|
@ -126,13 +141,14 @@ describe('View in folder action execute tests', () => {
|
|||
root: '/files/admin',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
// Silent action
|
||||
expect(exec).toBe(null)
|
||||
expect(goToRouteMock).toBeCalledTimes(1)
|
||||
expect(goToRouteMock).toBeCalledWith(null, { view: 'files' }, { dir: '/Foo/Bar' })
|
||||
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { fileid: 1, dir: '/Foo/Bar' })
|
||||
})
|
||||
|
||||
test('View in folder fails without node', async () => {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { Node, FileType } from '@nextcloud/files'
|
||||
import { Node, FileType, Permission } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import FolderMoveSvg from '@mdi/svg/svg/folder-move.svg?raw'
|
||||
|
||||
|
|
@ -46,6 +46,10 @@ export const action = new FileAction({
|
|||
return false
|
||||
}
|
||||
|
||||
if (node.permissions === Permission.NONE) {
|
||||
return false
|
||||
}
|
||||
|
||||
return node.type === FileType.File
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@
|
|||
<script lang='ts'>
|
||||
import { debounce } from 'debounce'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { formatFileSize } from '@nextcloud/files'
|
||||
import { formatFileSize, Permission } from '@nextcloud/files'
|
||||
import { Fragment } from 'vue-frag'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { translate } from '@nextcloud/l10n'
|
||||
|
|
@ -166,6 +166,7 @@ import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
|||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
import StarIcon from 'vue-material-design-icons/Star.vue'
|
||||
import Vue from 'vue'
|
||||
import type moment from 'moment'
|
||||
|
||||
import { ACTION_DETAILS } from '../actions/sidebarAction.ts'
|
||||
import { getFileActions, DefaultType } from '../services/FileAction.ts'
|
||||
|
|
@ -173,7 +174,6 @@ import { hashCode } from '../utils/hashUtils.ts'
|
|||
import { isCachedPreview } from '../services/PreviewService.ts'
|
||||
import { useActionsMenuStore } from '../store/actionsmenu.ts'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
import type moment from 'moment'
|
||||
import { useKeyboardStore } from '../store/keyboard.ts'
|
||||
import { useSelectionStore } from '../store/selection.ts'
|
||||
import { useUserConfigStore } from '../store/userconfig.ts'
|
||||
|
|
@ -336,11 +336,16 @@ export default Vue.extend({
|
|||
}
|
||||
}
|
||||
|
||||
if (this.source?.permissions & Permission.READ) {
|
||||
return {
|
||||
download: this.source.basename,
|
||||
href: this.source.source,
|
||||
title: this.t('files', 'Download file {name}', { name: this.displayName }),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
download: this.source.basename,
|
||||
href: this.source.source,
|
||||
// TODO: Use first action title ?
|
||||
title: this.t('files', 'Download file {name}', { name: this.displayName }),
|
||||
is: 'span',
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -398,7 +403,15 @@ export default Vue.extend({
|
|||
|
||||
// Actions shown in the menu
|
||||
enabledMenuActions() {
|
||||
return this.enabledActions.filter(action => action.default !== DefaultType.HIDDEN)
|
||||
return [
|
||||
// Showing inline first for the NcActions inline prop
|
||||
...this.enabledInlineActions,
|
||||
// Then the rest
|
||||
...this.enabledActions.filter(action => action.default !== DefaultType.HIDDEN),
|
||||
].filter((value, index, self) => {
|
||||
// Then we filter duplicates to prevent inline actions to be shown twice
|
||||
return index === self.findIndex(action => action.id === value.id)
|
||||
})
|
||||
},
|
||||
openedMenu: {
|
||||
get() {
|
||||
|
|
@ -602,7 +615,7 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
openDetailsIfAvailable(event) {
|
||||
const detailsAction = this.enabledDefaultActions.find(action => action.id === ACTION_DETAILS)
|
||||
const detailsAction = this.enabledActions.find(action => action.id === ACTION_DETAILS)
|
||||
if (detailsAction) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ export default Vue.extend({
|
|||
}
|
||||
|
||||
.files-list__row-name-text {
|
||||
color: var(--color-main-text);
|
||||
// Make some space for the outline
|
||||
padding: 5px 10px;
|
||||
margin-left: -10px;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export const useFilesStore = function(...args) {
|
|||
// Update the store all at once
|
||||
const files = nodes.reduce((acc, node) => {
|
||||
if (!node.fileid) {
|
||||
logger.warn('Trying to update/set a node without fileid', node)
|
||||
logger.error('Trying to update/set a node without fileid', node)
|
||||
return acc
|
||||
}
|
||||
acc[node.fileid] = node
|
||||
|
|
|
|||
|
|
@ -129,9 +129,16 @@ export default () => {
|
|||
// Add a folder to the favorites paths array and update the views
|
||||
const addPathToFavorites = function(path: string) {
|
||||
const view = generateFolderView(path)
|
||||
|
||||
// Skip if already exists
|
||||
if (favoriteFolders.find(folder => folder === path)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update arrays
|
||||
favoriteFolders.push(path)
|
||||
favoriteFoldersViews.push(view)
|
||||
|
||||
// Update and sort views
|
||||
updateAndSortViews()
|
||||
Navigation.register(view)
|
||||
|
|
@ -140,10 +147,18 @@ export default () => {
|
|||
// Remove a folder from the favorites paths array and update the views
|
||||
const removePathFromFavorites = function(path: string) {
|
||||
const id = generateIdFromPath(path)
|
||||
const index = favoriteFolders.findIndex(f => f === path)
|
||||
const index = favoriteFolders.findIndex(folder => folder === path)
|
||||
|
||||
// Skip if not exists
|
||||
if (index === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update arrays
|
||||
favoriteFolders.splice(index, 1)
|
||||
favoriteFoldersViews.splice(index, 1)
|
||||
|
||||
// Update and sort views
|
||||
Navigation.remove(id)
|
||||
updateAndSortViews()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,56 +212,6 @@ class ViewControllerTest extends TestCase {
|
|||
'expanded' => false,
|
||||
'unread' => 0,
|
||||
],
|
||||
'shareoverview' => [
|
||||
'id' => 'shareoverview',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 18,
|
||||
'name' => \OC::$server->getL10N('files_sharing')->t('Shares'),
|
||||
'classes' => 'collapsible',
|
||||
'sublist' => [
|
||||
[
|
||||
'id' => 'sharingout',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 16,
|
||||
'name' => \OC::$server->getL10N('files_sharing')->t('Shared with others'),
|
||||
],
|
||||
[
|
||||
'id' => 'sharingin',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 15,
|
||||
'name' => \OC::$server->getL10N('files_sharing')->t('Shared with you'),
|
||||
],
|
||||
[
|
||||
'id' => 'sharinglinks',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 17,
|
||||
'name' => \OC::$server->getL10N('files_sharing')->t('Shared by link', []),
|
||||
],
|
||||
[
|
||||
'id' => 'deletedshares',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 19,
|
||||
'name' => \OC::$server->getL10N('files_sharing')->t('Deleted shares'),
|
||||
],
|
||||
[
|
||||
'id' => 'pendingshares',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 19,
|
||||
'name' => \OC::$server->getL10N('files_sharing')->t('Pending shares'),
|
||||
],
|
||||
],
|
||||
'active' => false,
|
||||
'icon' => '',
|
||||
'type' => 'link',
|
||||
'expanded' => false,
|
||||
'unread' => 0,
|
||||
]
|
||||
]);
|
||||
|
||||
$expected = new Http\TemplateResponse(
|
||||
|
|
@ -292,30 +242,6 @@ class ViewControllerTest extends TestCase {
|
|||
'id' => 'systemtagsfilter',
|
||||
'content' => null,
|
||||
],
|
||||
'sharingout' => [
|
||||
'id' => 'sharingout',
|
||||
'content' => null,
|
||||
],
|
||||
'sharingin' => [
|
||||
'id' => 'sharingin',
|
||||
'content' => null,
|
||||
],
|
||||
'sharinglinks' => [
|
||||
'id' => 'sharinglinks',
|
||||
'content' => null,
|
||||
],
|
||||
'deletedshares' => [
|
||||
'id' => 'deletedshares',
|
||||
'content' => null,
|
||||
],
|
||||
'pendingshares' => [
|
||||
'id' => 'pendingshares',
|
||||
'content' => null
|
||||
],
|
||||
'shareoverview' => [
|
||||
'id' => 'shareoverview',
|
||||
'content' => null,
|
||||
],
|
||||
],
|
||||
'hiddenFields' => [],
|
||||
'showgridview' => null
|
||||
|
|
|
|||
|
|
@ -1,409 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
if (!OCA.Sharing) {
|
||||
/**
|
||||
* @namespace OCA.Sharing
|
||||
*/
|
||||
OCA.Sharing = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
OCA.Sharing.App = {
|
||||
|
||||
_inFileList: null,
|
||||
_outFileList: null,
|
||||
_overviewFileList: null,
|
||||
_pendingFileList: null,
|
||||
|
||||
initSharingIn($el) {
|
||||
if (this._inFileList) {
|
||||
return this._inFileList
|
||||
}
|
||||
|
||||
this._inFileList = new OCA.Sharing.FileList(
|
||||
$el,
|
||||
{
|
||||
id: 'shares.self',
|
||||
sharedWithUser: true,
|
||||
fileActions: this._createFileActions(),
|
||||
config: OCA.Files.App.getFilesConfig(),
|
||||
// The file list is created when a "show" event is handled, so
|
||||
// it should be marked as "shown" like it would have been done
|
||||
// if handling the event with the file list already created.
|
||||
shown: true,
|
||||
}
|
||||
)
|
||||
|
||||
this._extendFileList(this._inFileList)
|
||||
this._inFileList.appName = t('files_sharing', 'Shared with you')
|
||||
this._inFileList.$el.find('.emptyfilelist.emptycontent').html('<div class="icon-shared"></div>'
|
||||
+ '<h2>' + t('files_sharing', 'Nothing shared with you yet') + '</h2>'
|
||||
+ '<p>' + t('files_sharing', 'Files and folders others share with you will show up here') + '</p>')
|
||||
return this._inFileList
|
||||
},
|
||||
|
||||
initSharingOut($el) {
|
||||
if (this._outFileList) {
|
||||
return this._outFileList
|
||||
}
|
||||
this._outFileList = new OCA.Sharing.FileList(
|
||||
$el,
|
||||
{
|
||||
id: 'shares.others',
|
||||
sharedWithUser: false,
|
||||
fileActions: this._createFileActions(),
|
||||
config: OCA.Files.App.getFilesConfig(),
|
||||
// The file list is created when a "show" event is handled, so
|
||||
// it should be marked as "shown" like it would have been done
|
||||
// if handling the event with the file list already created.
|
||||
shown: true,
|
||||
}
|
||||
)
|
||||
|
||||
this._extendFileList(this._outFileList)
|
||||
this._outFileList.appName = t('files_sharing', 'Shared with others')
|
||||
this._outFileList.$el.find('.emptyfilelist.emptycontent').html('<div class="icon-shared"></div>'
|
||||
+ '<h2>' + t('files_sharing', 'Nothing shared yet') + '</h2>'
|
||||
+ '<p>' + t('files_sharing', 'Files and folders you share will show up here') + '</p>')
|
||||
return this._outFileList
|
||||
},
|
||||
|
||||
initSharingLinks($el) {
|
||||
if (this._linkFileList) {
|
||||
return this._linkFileList
|
||||
}
|
||||
this._linkFileList = new OCA.Sharing.FileList(
|
||||
$el,
|
||||
{
|
||||
id: 'shares.link',
|
||||
linksOnly: true,
|
||||
fileActions: this._createFileActions(),
|
||||
config: OCA.Files.App.getFilesConfig(),
|
||||
// The file list is created when a "show" event is handled, so
|
||||
// it should be marked as "shown" like it would have been done
|
||||
// if handling the event with the file list already created.
|
||||
shown: true,
|
||||
}
|
||||
)
|
||||
|
||||
this._extendFileList(this._linkFileList)
|
||||
this._linkFileList.appName = t('files_sharing', 'Shared by link')
|
||||
this._linkFileList.$el.find('.emptyfilelist.emptycontent').html('<div class="icon-public"></div>'
|
||||
+ '<h2>' + t('files_sharing', 'No shared links') + '</h2>'
|
||||
+ '<p>' + t('files_sharing', 'Files and folders you share by link will show up here') + '</p>')
|
||||
return this._linkFileList
|
||||
},
|
||||
|
||||
initSharingDeleted($el) {
|
||||
if (this._deletedFileList) {
|
||||
return this._deletedFileList
|
||||
}
|
||||
this._deletedFileList = new OCA.Sharing.FileList(
|
||||
$el,
|
||||
{
|
||||
id: 'shares.deleted',
|
||||
defaultFileActionsDisabled: true,
|
||||
showDeleted: true,
|
||||
sharedWithUser: true,
|
||||
fileActions: this._restoreShareAction(),
|
||||
config: OCA.Files.App.getFilesConfig(),
|
||||
// The file list is created when a "show" event is handled, so
|
||||
// it should be marked as "shown" like it would have been done
|
||||
// if handling the event with the file list already created.
|
||||
shown: true,
|
||||
}
|
||||
)
|
||||
|
||||
this._extendFileList(this._deletedFileList)
|
||||
this._deletedFileList.appName = t('files_sharing', 'Deleted shares')
|
||||
this._deletedFileList.$el.find('.emptyfilelist.emptycontent').html('<div class="icon-share"></div>'
|
||||
+ '<h2>' + t('files_sharing', 'No deleted shares') + '</h2>'
|
||||
+ '<p>' + t('files_sharing', 'Shares you deleted will show up here') + '</p>')
|
||||
return this._deletedFileList
|
||||
},
|
||||
|
||||
initSharingPening($el) {
|
||||
if (this._pendingFileList) {
|
||||
return this._pendingFileList
|
||||
}
|
||||
this._pendingFileList = new OCA.Sharing.FileList(
|
||||
$el,
|
||||
{
|
||||
id: 'shares.pending',
|
||||
showPending: true,
|
||||
detailsViewEnabled: false,
|
||||
defaultFileActionsDisabled: true,
|
||||
sharedWithUser: true,
|
||||
fileActions: this._acceptShareAction(),
|
||||
config: OCA.Files.App.getFilesConfig(),
|
||||
// The file list is created when a "show" event is handled, so
|
||||
// it should be marked as "shown" like it would have been done
|
||||
// if handling the event with the file list already created.
|
||||
shown: true,
|
||||
}
|
||||
)
|
||||
|
||||
this._extendFileList(this._pendingFileList)
|
||||
this._pendingFileList.appName = t('files_sharing', 'Pending shares')
|
||||
this._pendingFileList.$el.find('.emptyfilelist.emptycontent').html('<div class="icon-share"></div>'
|
||||
+ '<h2>' + t('files_sharing', 'No pending shares') + '</h2>'
|
||||
+ '<p>' + t('files_sharing', 'Shares you have received but not confirmed will show up here') + '</p>')
|
||||
return this._pendingFileList
|
||||
},
|
||||
|
||||
initShareingOverview($el) {
|
||||
if (this._overviewFileList) {
|
||||
return this._overviewFileList
|
||||
}
|
||||
this._overviewFileList = new OCA.Sharing.FileList(
|
||||
$el,
|
||||
{
|
||||
id: 'shares.overview',
|
||||
fileActions: this._createFileActions(),
|
||||
config: OCA.Files.App.getFilesConfig(),
|
||||
isOverview: true,
|
||||
// The file list is created when a "show" event is handled, so
|
||||
// it should be marked as "shown" like it would have been done
|
||||
// if handling the event with the file list already created.
|
||||
shown: true,
|
||||
}
|
||||
)
|
||||
|
||||
this._extendFileList(this._overviewFileList)
|
||||
this._overviewFileList.appName = t('files_sharing', 'Shares')
|
||||
this._overviewFileList.$el.find('.emptyfilelist.emptycontent').html('<div class="icon-share"></div>'
|
||||
+ '<h2>' + t('files_sharing', 'No shares') + '</h2>'
|
||||
+ '<p>' + t('files_sharing', 'Shares will show up here') + '</p>')
|
||||
return this._overviewFileList
|
||||
},
|
||||
|
||||
removeSharingIn() {
|
||||
if (this._inFileList) {
|
||||
this._inFileList.$fileList.empty()
|
||||
}
|
||||
},
|
||||
|
||||
removeSharingOut() {
|
||||
if (this._outFileList) {
|
||||
this._outFileList.$fileList.empty()
|
||||
}
|
||||
},
|
||||
|
||||
removeSharingLinks() {
|
||||
if (this._linkFileList) {
|
||||
this._linkFileList.$fileList.empty()
|
||||
}
|
||||
},
|
||||
|
||||
removeSharingDeleted() {
|
||||
if (this._deletedFileList) {
|
||||
this._deletedFileList.$fileList.empty()
|
||||
}
|
||||
},
|
||||
|
||||
removeSharingPending() {
|
||||
if (this._pendingFileList) {
|
||||
this._pendingFileList.$fileList.empty()
|
||||
}
|
||||
},
|
||||
|
||||
removeSharingOverview() {
|
||||
if (this._overviewFileList) {
|
||||
this._overviewFileList.$fileList.empty()
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the app
|
||||
*/
|
||||
destroy() {
|
||||
OCA.Files.fileActions.off('setDefault.app-sharing', this._onActionsUpdated)
|
||||
OCA.Files.fileActions.off('registerAction.app-sharing', this._onActionsUpdated)
|
||||
this.removeSharingIn()
|
||||
this.removeSharingOut()
|
||||
this.removeSharingLinks()
|
||||
this._inFileList = null
|
||||
this._outFileList = null
|
||||
this._linkFileList = null
|
||||
this._overviewFileList = null
|
||||
delete this._globalActionsInitialized
|
||||
},
|
||||
|
||||
_createFileActions() {
|
||||
// inherit file actions from the files app
|
||||
const fileActions = new OCA.Files.FileActions()
|
||||
// note: not merging the legacy actions because legacy apps are not
|
||||
// compatible with the sharing overview and need to be adapted first
|
||||
fileActions.registerDefaultActions()
|
||||
fileActions.merge(OCA.Files.fileActions)
|
||||
|
||||
if (!this._globalActionsInitialized) {
|
||||
// in case actions are registered later
|
||||
this._onActionsUpdated = _.bind(this._onActionsUpdated, this)
|
||||
OCA.Files.fileActions.on('setDefault.app-sharing', this._onActionsUpdated)
|
||||
OCA.Files.fileActions.on('registerAction.app-sharing', this._onActionsUpdated)
|
||||
this._globalActionsInitialized = true
|
||||
}
|
||||
|
||||
// when the user clicks on a folder, redirect to the corresponding
|
||||
// folder in the files app instead of opening it directly
|
||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename, context) {
|
||||
OCA.Files.App.setActiveView('files', { silent: true })
|
||||
OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true)
|
||||
})
|
||||
fileActions.setDefault('dir', 'Open')
|
||||
return fileActions
|
||||
},
|
||||
|
||||
_restoreShareAction() {
|
||||
const fileActions = new OCA.Files.FileActions()
|
||||
fileActions.registerAction({
|
||||
name: 'Restore',
|
||||
displayName: t('files_sharing', 'Restore'),
|
||||
altText: t('files_sharing', 'Restore share'),
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_ALL,
|
||||
iconClass: 'icon-history',
|
||||
type: OCA.Files.FileActions.TYPE_INLINE,
|
||||
actionHandler(fileName, context) {
|
||||
const shareId = context.$file.data('shareId')
|
||||
$.post(OC.linkToOCS('apps/files_sharing/api/v1/deletedshares', 2) + shareId)
|
||||
.success(function(result) {
|
||||
context.fileList.remove(context.fileInfoModel.attributes.name)
|
||||
}).fail(function() {
|
||||
OC.Notification.showTemporary(t('files_sharing', 'Something happened. Unable to restore the share.'))
|
||||
})
|
||||
},
|
||||
})
|
||||
return fileActions
|
||||
},
|
||||
|
||||
_acceptShareAction() {
|
||||
const fileActions = new OCA.Files.FileActions()
|
||||
fileActions.registerAction({
|
||||
name: 'Accept share',
|
||||
displayName: t('files_sharing', 'Accept share'),
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_ALL,
|
||||
iconClass: 'icon-checkmark',
|
||||
type: OCA.Files.FileActions.TYPE_INLINE,
|
||||
actionHandler(fileName, context) {
|
||||
const shareId = context.$file.data('shareId')
|
||||
let shareBase = 'shares/pending'
|
||||
if (context.$file.attr('data-remote-id')) {
|
||||
shareBase = 'remote_shares/pending'
|
||||
}
|
||||
$.post(OC.linkToOCS('apps/files_sharing/api/v1/' + shareBase, 2) + shareId)
|
||||
.success(function(result) {
|
||||
context.fileList.remove(context.fileInfoModel.attributes.name)
|
||||
}).fail(function() {
|
||||
OC.Notification.showTemporary(t('files_sharing', 'Something happened. Unable to accept the share.'))
|
||||
})
|
||||
},
|
||||
})
|
||||
fileActions.registerAction({
|
||||
name: 'Reject share',
|
||||
displayName: t('files_sharing', 'Reject share'),
|
||||
mime: 'all',
|
||||
permissions: OC.PERMISSION_ALL,
|
||||
iconClass: 'icon-close',
|
||||
type: OCA.Files.FileActions.TYPE_INLINE,
|
||||
shouldRender(context) {
|
||||
// disable rejecting group shares from the pending list because they anyway
|
||||
// land back into that same list
|
||||
if (context.$file.attr('data-remote-id') && parseInt(context.$file.attr('data-share-type'), 10) === OC.Share.SHARE_TYPE_REMOTE_GROUP) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
actionHandler(fileName, context) {
|
||||
const shareId = context.$file.data('shareId')
|
||||
let shareBase = 'shares'
|
||||
if (context.$file.attr('data-remote-id')) {
|
||||
shareBase = 'remote_shares'
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: OC.linkToOCS('apps/files_sharing/api/v1/' + shareBase, 2) + shareId,
|
||||
type: 'DELETE',
|
||||
}).success(function(result) {
|
||||
context.fileList.remove(context.fileInfoModel.attributes.name)
|
||||
}).fail(function() {
|
||||
OC.Notification.showTemporary(t('files_sharing', 'Something happened. Unable to reject the share.'))
|
||||
})
|
||||
},
|
||||
})
|
||||
return fileActions
|
||||
},
|
||||
|
||||
_onActionsUpdated(ev) {
|
||||
_.each([this._inFileList, this._outFileList, this._linkFileList], function(list) {
|
||||
if (!list) {
|
||||
return
|
||||
}
|
||||
|
||||
if (ev.action) {
|
||||
list.fileActions.registerAction(ev.action)
|
||||
} else if (ev.defaultAction) {
|
||||
list.fileActions.setDefault(
|
||||
ev.defaultAction.mime,
|
||||
ev.defaultAction.name
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
_extendFileList(fileList) {
|
||||
// remove size column from summary
|
||||
fileList.fileSummary.$el.find('.filesize').remove()
|
||||
},
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
$('#app-content-sharingin').on('show', function(e) {
|
||||
OCA.Sharing.App.initSharingIn($(e.target))
|
||||
})
|
||||
$('#app-content-sharingin').on('hide', function() {
|
||||
OCA.Sharing.App.removeSharingIn()
|
||||
})
|
||||
$('#app-content-sharingout').on('show', function(e) {
|
||||
OCA.Sharing.App.initSharingOut($(e.target))
|
||||
})
|
||||
$('#app-content-sharingout').on('hide', function() {
|
||||
OCA.Sharing.App.removeSharingOut()
|
||||
})
|
||||
$('#app-content-sharinglinks').on('show', function(e) {
|
||||
OCA.Sharing.App.initSharingLinks($(e.target))
|
||||
})
|
||||
$('#app-content-sharinglinks').on('hide', function() {
|
||||
OCA.Sharing.App.removeSharingLinks()
|
||||
})
|
||||
$('#app-content-deletedshares').on('show', function(e) {
|
||||
OCA.Sharing.App.initSharingDeleted($(e.target))
|
||||
})
|
||||
$('#app-content-deletedshares').on('hide', function() {
|
||||
OCA.Sharing.App.removeSharingDeleted()
|
||||
})
|
||||
$('#app-content-pendingshares').on('show', function(e) {
|
||||
OCA.Sharing.App.initSharingPening($(e.target))
|
||||
})
|
||||
$('#app-content-pendingshares').on('hide', function() {
|
||||
OCA.Sharing.App.removeSharingPending()
|
||||
})
|
||||
$('#app-content-shareoverview').on('show', function(e) {
|
||||
OCA.Sharing.App.initShareingOverview($(e.target))
|
||||
})
|
||||
$('#app-content-shareoverview').on('hide', function() {
|
||||
OCA.Sharing.App.removeSharingOverview()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,541 +0,0 @@
|
|||
/* eslint-disable */
|
||||
/*
|
||||
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* @class OCA.Sharing.FileList
|
||||
* @augments OCA.Files.FileList
|
||||
*
|
||||
* @classdesc Sharing file list.
|
||||
* Contains both "shared with others" and "shared with you" modes.
|
||||
*
|
||||
* @param $el container element with existing markup for the .files-controls
|
||||
* and a table
|
||||
* @param [options] map of options, see other parameters
|
||||
* @param {boolean} [options.sharedWithUser] true to return files shared with
|
||||
* the current user, false to return files that the user shared with others.
|
||||
* Defaults to false.
|
||||
* @param {boolean} [options.linksOnly] true to return only link shares
|
||||
*/
|
||||
var FileList = function($el, options) {
|
||||
this.initialize($el, options)
|
||||
}
|
||||
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
|
||||
/** @lends OCA.Sharing.FileList.prototype */ {
|
||||
appName: 'Shares',
|
||||
|
||||
/**
|
||||
* Whether the list shows the files shared with the user (true) or
|
||||
* the files that the user shared with others (false).
|
||||
*/
|
||||
_sharedWithUser: false,
|
||||
_linksOnly: false,
|
||||
_showDeleted: false,
|
||||
_showPending: false,
|
||||
_clientSideSort: true,
|
||||
_allowSelection: false,
|
||||
_isOverview: false,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initialize: function($el, options) {
|
||||
OCA.Files.FileList.prototype.initialize.apply(this, arguments)
|
||||
if (this.initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: consolidate both options
|
||||
if (options && options.sharedWithUser) {
|
||||
this._sharedWithUser = true
|
||||
}
|
||||
if (options && options.linksOnly) {
|
||||
this._linksOnly = true
|
||||
}
|
||||
if (options && options.showDeleted) {
|
||||
this._showDeleted = true
|
||||
}
|
||||
if (options && options.showPending) {
|
||||
this._showPending = true
|
||||
}
|
||||
if (options && options.isOverview) {
|
||||
this._isOverview = true
|
||||
}
|
||||
},
|
||||
|
||||
_renderRow: function() {
|
||||
// HACK: needed to call the overridden _renderRow
|
||||
// this is because at the time this class is created
|
||||
// the overriding hasn't been done yet...
|
||||
return OCA.Files.FileList.prototype._renderRow.apply(this, arguments)
|
||||
},
|
||||
|
||||
_createRow: function(fileData) {
|
||||
// TODO: hook earlier and render the whole row here
|
||||
var $tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments)
|
||||
$tr.find('.filesize').remove()
|
||||
$tr.find('td.date').before($tr.children('td:first'))
|
||||
$tr.find('td.filename input:checkbox').remove()
|
||||
$tr.attr('data-share-id', _.pluck(fileData.shares, 'id').join(','))
|
||||
if (this._sharedWithUser) {
|
||||
$tr.attr('data-share-owner', fileData.shareOwner)
|
||||
$tr.attr('data-mounttype', 'shared-root')
|
||||
var permission = parseInt($tr.attr('data-permissions')) | OC.PERMISSION_DELETE
|
||||
$tr.attr('data-permissions', permission)
|
||||
}
|
||||
if (this._showDeleted || this._showPending) {
|
||||
var permission = fileData.permissions
|
||||
$tr.attr('data-share-permissions', permission)
|
||||
}
|
||||
|
||||
if (fileData.remoteId) {
|
||||
$tr.attr('data-remote-id', fileData.remoteId)
|
||||
}
|
||||
|
||||
if (fileData.shareType) {
|
||||
$tr.attr('data-share-type', fileData.shareType)
|
||||
}
|
||||
|
||||
// add row with expiration date for link only shares - influenced by _createRow of filelist
|
||||
if (this._linksOnly) {
|
||||
var expirationTimestamp = 0
|
||||
if (fileData.shares && fileData.shares[0].expiration !== null) {
|
||||
expirationTimestamp = moment(fileData.shares[0].expiration).valueOf()
|
||||
}
|
||||
$tr.attr('data-expiration', expirationTimestamp)
|
||||
|
||||
// date column (1000 milliseconds to seconds, 60 seconds, 60 minutes, 24 hours)
|
||||
// difference in days multiplied by 5 - brightest shade for expiry dates in more than 32 days (160/5)
|
||||
var modifiedColor = Math.round((expirationTimestamp - (new Date()).getTime()) / 1000 / 60 / 60 / 24 * 5)
|
||||
// ensure that the brightest color is still readable
|
||||
if (modifiedColor >= 160) {
|
||||
modifiedColor = 160
|
||||
}
|
||||
|
||||
var formatted
|
||||
var text
|
||||
if (expirationTimestamp > 0) {
|
||||
formatted = OC.Util.formatDate(expirationTimestamp)
|
||||
text = OC.Util.relativeModifiedDate(expirationTimestamp)
|
||||
} else {
|
||||
formatted = t('files_sharing', 'No expiration date set')
|
||||
text = ''
|
||||
modifiedColor = 160
|
||||
}
|
||||
td = $('<td></td>').attr({ 'class': 'date' })
|
||||
td.append($('<span></span>').attr({
|
||||
'class': 'modified',
|
||||
'title': formatted,
|
||||
'style': 'color:rgb(' + modifiedColor + ',' + modifiedColor + ',' + modifiedColor + ')'
|
||||
}).text(text))
|
||||
|
||||
$tr.append(td)
|
||||
}
|
||||
return $tr
|
||||
},
|
||||
|
||||
/**
|
||||
* Set whether the list should contain outgoing shares
|
||||
* or incoming shares.
|
||||
*
|
||||
* @param state true for incoming shares, false otherwise
|
||||
*/
|
||||
setSharedWithUser: function(state) {
|
||||
this._sharedWithUser = !!state
|
||||
},
|
||||
|
||||
updateEmptyContent: function() {
|
||||
var dir = this.getCurrentDirectory()
|
||||
if (dir === '/') {
|
||||
// root has special permissions
|
||||
this.$el.find('.emptyfilelist.emptycontent').toggleClass('hidden', !this.isEmpty)
|
||||
this.$el.find('.files-filestable thead th').toggleClass('hidden', this.isEmpty)
|
||||
|
||||
// hide expiration date header for non link only shares
|
||||
if (!this._linksOnly) {
|
||||
this.$el.find('th.column-expiration').addClass('hidden')
|
||||
}
|
||||
} else {
|
||||
OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments)
|
||||
}
|
||||
},
|
||||
|
||||
getDirectoryPermissions: function() {
|
||||
return OC.PERMISSION_READ | OC.PERMISSION_DELETE
|
||||
},
|
||||
|
||||
updateStorageStatistics: function() {
|
||||
// no op because it doesn't have
|
||||
// storage info like free space / used space
|
||||
},
|
||||
|
||||
reload: function() {
|
||||
this.showMask()
|
||||
if (this._reloadCall?.abort) {
|
||||
this._reloadCall.abort()
|
||||
}
|
||||
|
||||
// there is only root
|
||||
this._setCurrentDir('/', false)
|
||||
|
||||
var promises = []
|
||||
|
||||
var deletedShares = {
|
||||
url: OC.linkToOCS('apps/files_sharing/api/v1', 2) + 'deletedshares',
|
||||
/* jshint camelcase: false */
|
||||
data: {
|
||||
format: 'json',
|
||||
include_tags: true
|
||||
},
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||
}
|
||||
}
|
||||
|
||||
var pendingShares = {
|
||||
url: OC.linkToOCS('apps/files_sharing/api/v1/shares', 2) + 'pending',
|
||||
/* jshint camelcase: false */
|
||||
data: {
|
||||
format: 'json'
|
||||
},
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||
}
|
||||
}
|
||||
|
||||
var pendingRemoteShares = {
|
||||
url: OC.linkToOCS('apps/files_sharing/api/v1/remote_shares', 2) + 'pending',
|
||||
/* jshint camelcase: false */
|
||||
data: {
|
||||
format: 'json'
|
||||
},
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||
}
|
||||
}
|
||||
|
||||
var shares = {
|
||||
url: OC.linkToOCS('apps/files_sharing/api/v1') + 'shares',
|
||||
/* jshint camelcase: false */
|
||||
data: {
|
||||
format: 'json',
|
||||
shared_with_me: this._sharedWithUser !== false,
|
||||
include_tags: true
|
||||
},
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||
}
|
||||
}
|
||||
|
||||
var remoteShares = {
|
||||
url: OC.linkToOCS('apps/files_sharing/api/v1') + 'remote_shares',
|
||||
/* jshint camelcase: false */
|
||||
data: {
|
||||
format: 'json',
|
||||
include_tags: true
|
||||
},
|
||||
type: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||
}
|
||||
}
|
||||
|
||||
// Add the proper ajax requests to the list and run them
|
||||
// and make sure we have 2 promises
|
||||
if (this._showDeleted) {
|
||||
promises.push($.ajax(deletedShares))
|
||||
} else if (this._showPending) {
|
||||
promises.push($.ajax(pendingShares))
|
||||
promises.push($.ajax(pendingRemoteShares))
|
||||
} else {
|
||||
promises.push($.ajax(shares))
|
||||
|
||||
if (this._sharedWithUser !== false || this._isOverview) {
|
||||
promises.push($.ajax(remoteShares))
|
||||
}
|
||||
if (this._isOverview) {
|
||||
shares.data.shared_with_me = !shares.data.shared_with_me
|
||||
promises.push($.ajax(shares))
|
||||
}
|
||||
}
|
||||
|
||||
this._reloadCall = $.when.apply($, promises)
|
||||
var callBack = this.reloadCallback.bind(this)
|
||||
return this._reloadCall.then(callBack, callBack)
|
||||
},
|
||||
|
||||
reloadCallback: function(shares, remoteShares, additionalShares) {
|
||||
delete this._reloadCall
|
||||
this.hideMask()
|
||||
|
||||
this.$el.find('#headerSharedWith').text(
|
||||
t('files_sharing', this._sharedWithUser ? 'Shared by' : 'Shared with')
|
||||
)
|
||||
|
||||
var files = []
|
||||
|
||||
// make sure to use the same format
|
||||
if (shares[0] && shares[0].ocs) {
|
||||
shares = shares[0]
|
||||
}
|
||||
if (remoteShares && remoteShares[0] && remoteShares[0].ocs) {
|
||||
remoteShares = remoteShares[0]
|
||||
}
|
||||
if (additionalShares && additionalShares[0] && additionalShares[0].ocs) {
|
||||
additionalShares = additionalShares[0]
|
||||
}
|
||||
|
||||
if (shares.ocs && shares.ocs.data) {
|
||||
files = files.concat(this._makeFilesFromShares(shares.ocs.data, this._sharedWithUser))
|
||||
}
|
||||
|
||||
if (remoteShares && remoteShares.ocs && remoteShares.ocs.data) {
|
||||
files = files.concat(this._makeFilesFromRemoteShares(remoteShares.ocs.data))
|
||||
}
|
||||
|
||||
if (additionalShares && additionalShares.ocs && additionalShares.ocs.data) {
|
||||
if (this._showPending) {
|
||||
// in this case the second callback is about pending remote shares
|
||||
files = files.concat(this._makeFilesFromRemoteShares(additionalShares.ocs.data))
|
||||
} else {
|
||||
files = files.concat(this._makeFilesFromShares(additionalShares.ocs.data, !this._sharedWithUser))
|
||||
}
|
||||
}
|
||||
|
||||
this.setFiles(files)
|
||||
return true
|
||||
},
|
||||
|
||||
_makeFilesFromRemoteShares: function(data) {
|
||||
var files = data
|
||||
|
||||
files = _.chain(files)
|
||||
// convert share data to file data
|
||||
.map(function(share) {
|
||||
var file = {
|
||||
shareOwner: share.owner + '@' + share.remote.replace(/.*?:\/\//g, ''),
|
||||
name: OC.basename(share.mountpoint),
|
||||
mtime: share.mtime * 1000,
|
||||
mimetype: share.mimetype,
|
||||
type: share.type,
|
||||
// remote share types are different and need to be mapped
|
||||
shareType: (parseInt(share.share_type, 10) === 1) ? OC.Share.SHARE_TYPE_REMOTE_GROUP : OC.Share.SHARE_TYPE_REMOTE,
|
||||
id: share.file_id,
|
||||
path: OC.dirname(share.mountpoint),
|
||||
permissions: share.permissions,
|
||||
tags: share.tags || []
|
||||
}
|
||||
|
||||
if (share.remote_id) {
|
||||
// remote share
|
||||
if (share.accepted !== '1') {
|
||||
file.name = OC.basename(share.name)
|
||||
file.path = '/'
|
||||
}
|
||||
file.remoteId = share.remote_id
|
||||
file.shareOwnerId = share.owner
|
||||
}
|
||||
|
||||
if (!file.mimetype) {
|
||||
// pending shares usually have no type, so default to showing a directory icon
|
||||
file.mimetype = 'dir-shared'
|
||||
}
|
||||
|
||||
file.shares = [{
|
||||
id: share.id,
|
||||
type: OC.Share.SHARE_TYPE_REMOTE
|
||||
}]
|
||||
return file
|
||||
})
|
||||
.value()
|
||||
return files
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts the OCS API share response data to a file info
|
||||
* list
|
||||
* @param {Array} data OCS API share array
|
||||
* @param {boolean} sharedWithUser
|
||||
* @returns {Array.<OCA.Sharing.SharedFileInfo>} array of shared file info
|
||||
*/
|
||||
_makeFilesFromShares: function(data, sharedWithUser) {
|
||||
/* jshint camelcase: false */
|
||||
var files = data
|
||||
|
||||
if (this._linksOnly) {
|
||||
files = _.filter(data, function(share) {
|
||||
return share.share_type === OC.Share.SHARE_TYPE_LINK
|
||||
})
|
||||
}
|
||||
|
||||
// OCS API uses non-camelcased names
|
||||
files = _.chain(files)
|
||||
// convert share data to file data
|
||||
.map(function(share) {
|
||||
// TODO: use OC.Files.FileInfo
|
||||
var file = {
|
||||
id: share.file_source,
|
||||
icon: OC.MimeType.getIconUrl(share.mimetype),
|
||||
mimetype: share.mimetype,
|
||||
hasPreview: share.has_preview,
|
||||
tags: share.tags || []
|
||||
}
|
||||
if (share.item_type === 'folder') {
|
||||
file.type = 'dir'
|
||||
file.mimetype = 'httpd/unix-directory'
|
||||
} else {
|
||||
file.type = 'file'
|
||||
}
|
||||
file.share = {
|
||||
id: share.id,
|
||||
type: share.share_type,
|
||||
target: share.share_with,
|
||||
stime: share.stime * 1000,
|
||||
expiration: share.expiration
|
||||
}
|
||||
if (sharedWithUser) {
|
||||
file.shareOwner = share.displayname_owner
|
||||
file.shareOwnerId = share.uid_owner
|
||||
file.name = OC.basename(share.file_target)
|
||||
file.path = OC.dirname(share.file_target)
|
||||
file.permissions = share.permissions
|
||||
if (file.path) {
|
||||
file.extraData = share.file_target
|
||||
}
|
||||
} else {
|
||||
if (share.share_type !== OC.Share.SHARE_TYPE_LINK) {
|
||||
file.share.targetDisplayName = share.share_with_displayname
|
||||
file.share.targetShareWithId = share.share_with
|
||||
}
|
||||
file.name = OC.basename(share.path)
|
||||
file.path = OC.dirname(share.path)
|
||||
file.permissions = OC.PERMISSION_ALL
|
||||
if (file.path) {
|
||||
file.extraData = share.path
|
||||
}
|
||||
}
|
||||
return file
|
||||
})
|
||||
// Group all files and have a "shares" array with
|
||||
// the share info for each file.
|
||||
//
|
||||
// This uses a hash memo to cumulate share information
|
||||
// inside the same file object (by file id).
|
||||
.reduce(function(memo, file) {
|
||||
var data = memo[file.id]
|
||||
var recipient = file.share.targetDisplayName
|
||||
var recipientId = file.share.targetShareWithId
|
||||
if (!data) {
|
||||
data = memo[file.id] = file
|
||||
data.shares = [file.share]
|
||||
// using a hash to make them unique,
|
||||
// this is only a list to be displayed
|
||||
data.recipients = {}
|
||||
data.recipientData = {}
|
||||
// share types
|
||||
data.shareTypes = {}
|
||||
// counter is cheaper than calling _.keys().length
|
||||
data.recipientsCount = 0
|
||||
data.mtime = file.share.stime
|
||||
} else {
|
||||
// always take the most recent stime
|
||||
if (file.share.stime > data.mtime) {
|
||||
data.mtime = file.share.stime
|
||||
}
|
||||
data.shares.push(file.share)
|
||||
}
|
||||
|
||||
if (recipient) {
|
||||
// limit counterparts for output
|
||||
if (data.recipientsCount < 4) {
|
||||
// only store the first ones, they will be the only ones
|
||||
// displayed
|
||||
data.recipients[recipient] = true
|
||||
data.recipientData[data.recipientsCount] = {
|
||||
'shareWith': recipientId,
|
||||
'shareWithDisplayName': recipient
|
||||
}
|
||||
}
|
||||
data.recipientsCount++
|
||||
}
|
||||
|
||||
data.shareTypes[file.share.type] = true
|
||||
|
||||
delete file.share
|
||||
return memo
|
||||
}, {})
|
||||
// Retrieve only the values of the returned hash
|
||||
.values()
|
||||
// Clean up
|
||||
.each(function(data) {
|
||||
// convert the recipients map to a flat
|
||||
// array of sorted names
|
||||
data.mountType = 'shared'
|
||||
delete data.recipientsCount
|
||||
if (sharedWithUser) {
|
||||
// only for outgoing shares
|
||||
delete data.shareTypes
|
||||
} else {
|
||||
data.shareTypes = _.keys(data.shareTypes)
|
||||
}
|
||||
})
|
||||
// Finish the chain by getting the result
|
||||
.value()
|
||||
|
||||
// Sort by expected sort comparator
|
||||
return files.sort(this._sortComparator)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Share info attributes.
|
||||
*
|
||||
* @typedef {Object} OCA.Sharing.ShareInfo
|
||||
*
|
||||
* @property {number} id share ID
|
||||
* @property {number} type share type
|
||||
* @property {String} target share target, either user name or group name
|
||||
* @property {number} stime share timestamp in milliseconds
|
||||
* @property {String} [targetDisplayName] display name of the recipient
|
||||
* (only when shared with others)
|
||||
* @property {String} [targetShareWithId] id of the recipient
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Recipient attributes
|
||||
*
|
||||
* @typedef {Object} OCA.Sharing.RecipientInfo
|
||||
* @property {String} shareWith the id of the recipient
|
||||
* @property {String} shareWithDisplayName the display name of the recipient
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared file info attributes.
|
||||
*
|
||||
* @typedef {OCA.Files.FileInfo} OCA.Sharing.SharedFileInfo
|
||||
*
|
||||
* @property {Array.<OCA.Sharing.ShareInfo>} shares array of shares for
|
||||
* this file
|
||||
* @property {number} mtime most recent share time (if multiple shares)
|
||||
* @property {String} shareOwner name of the share owner
|
||||
* @property {Array.<String>} recipients name of the first 4 recipients
|
||||
* (this is mostly for display purposes)
|
||||
* @property {Object.<OCA.Sharing.RecipientInfo>} recipientData (as object for easier
|
||||
* passing to HTML data attributes with jQuery)
|
||||
*/
|
||||
|
||||
OCA.Sharing.FileList = FileList
|
||||
})()
|
||||
|
|
@ -117,7 +117,6 @@ class Application extends App implements IBootstrap {
|
|||
$context->injectFn([$this, 'registerMountProviders']);
|
||||
$context->injectFn([$this, 'registerEventsScripts']);
|
||||
$context->injectFn([$this, 'registerDownloadEvents']);
|
||||
$context->injectFn([$this, 'setupSharingMenus']);
|
||||
|
||||
Helper::registerHooks();
|
||||
|
||||
|
|
@ -214,77 +213,4 @@ class Application extends App implements IBootstrap {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function setupSharingMenus(IManager $shareManager, IFactory $l10nFactory, IUserSession $userSession): void {
|
||||
if (!$shareManager->shareApiEnabled() || !class_exists('\OCA\Files\App')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$navigationManager = \OCA\Files\App::getNavigationManager();
|
||||
// show_Quick_Access stored as string
|
||||
$navigationManager->add(function () use ($shareManager, $l10nFactory, $userSession) {
|
||||
$l = $l10nFactory->get('files_sharing');
|
||||
$user = $userSession->getUser();
|
||||
$userId = $user ? $user->getUID() : null;
|
||||
|
||||
$sharingSublistArray = [];
|
||||
|
||||
if ($shareManager->sharingDisabledForUser($userId) === false) {
|
||||
$sharingSublistArray[] = [
|
||||
'id' => 'sharingout',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 16,
|
||||
'name' => $l->t('Shared with others'),
|
||||
];
|
||||
}
|
||||
|
||||
$sharingSublistArray[] = [
|
||||
'id' => 'sharingin',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 15,
|
||||
'name' => $l->t('Shared with you'),
|
||||
];
|
||||
|
||||
if ($shareManager->sharingDisabledForUser($userId) === false) {
|
||||
// Check if sharing by link is enabled
|
||||
if ($shareManager->shareApiAllowLinks()) {
|
||||
$sharingSublistArray[] = [
|
||||
'id' => 'sharinglinks',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 17,
|
||||
'name' => $l->t('Shared by link'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$sharingSublistArray[] = [
|
||||
'id' => 'deletedshares',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 19,
|
||||
'name' => $l->t('Deleted shares'),
|
||||
];
|
||||
|
||||
$sharingSublistArray[] = [
|
||||
'id' => 'pendingshares',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 19,
|
||||
'name' => $l->t('Pending shares'),
|
||||
];
|
||||
|
||||
return [
|
||||
'id' => 'shareoverview',
|
||||
'appname' => 'files_sharing',
|
||||
'script' => 'list.php',
|
||||
'order' => 18,
|
||||
'name' => $l->t('Shares'),
|
||||
'classes' => 'collapsible',
|
||||
'sublist' => $sharingSublistArray,
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,6 +133,8 @@ class DeletedShareAPIController extends OCSController {
|
|||
$result['file_source'] = $node->getId();
|
||||
$result['file_parent'] = $node->getParent()->getId();
|
||||
$result['file_target'] = $share->getTarget();
|
||||
$result['item_size'] = $node->getSize();
|
||||
$result['item_mtime'] = $node->getMTime();
|
||||
|
||||
$expiration = $share->getExpirationDate();
|
||||
if ($expiration !== null) {
|
||||
|
|
|
|||
|
|
@ -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['item_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['item_size'] = $node->getSize();
|
||||
$result['item_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) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use OCA\Files_Sharing\AppInfo\Application;
|
|||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Util;
|
||||
|
||||
class LoadAdditionalListener implements IEventListener {
|
||||
|
|
@ -37,10 +38,13 @@ class LoadAdditionalListener implements IEventListener {
|
|||
return;
|
||||
}
|
||||
|
||||
// After files for the files list shared content
|
||||
Util::addScript(Application::APP_ID, 'files_sharing', 'files');
|
||||
// After files for the breadcrumb share indicator
|
||||
Util::addScript(Application::APP_ID, 'additionalScripts', 'files');
|
||||
Util::addStyle(Application::APP_ID, 'icons');
|
||||
|
||||
$shareManager = \OC::$server->get(IManager::class);
|
||||
if ($shareManager->shareApiEnabled() && class_exists('\OCA\Files\App')) {
|
||||
Util::addScript(Application::APP_ID, 'files_sharing', 'files');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
223
apps/files_sharing/src/actions/acceptShareAction.spec.ts
Normal file
223
apps/files_sharing/src/actions/acceptShareAction.spec.ts
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
/**
|
||||
* @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 { action } from './acceptShareAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Permission } from '@nextcloud/files'
|
||||
import { FileAction } from '../../../files/src/services/FileAction'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import axios from '@nextcloud/axios'
|
||||
import type { Navigation } from '../../../files/src/services/Navigation'
|
||||
import '../main'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as Navigation
|
||||
|
||||
const pendingShareView = {
|
||||
id: 'pendingshares',
|
||||
name: 'Pending shares',
|
||||
} as Navigation
|
||||
|
||||
describe('Accept share action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('accept-share')
|
||||
expect(action.displayName([file], pendingShareView)).toBe('Accept share')
|
||||
expect(action.iconSvgInline([file], pendingShareView)).toBe('<svg>SvgMock</svg>')
|
||||
expect(action.default).toBeUndefined()
|
||||
expect(action.order).toBe(1)
|
||||
expect(action.inline).toBeDefined()
|
||||
expect(action.inline!(file, pendingShareView)).toBe(true)
|
||||
})
|
||||
|
||||
test('Default values for multiple files', () => {
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
const file2 = new File({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.displayName([file1, file2], pendingShareView)).toBe('Accept shares')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Accept share action enabled tests', () => {
|
||||
test('Enabled with on pending shares view', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], pendingShareView)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled on wrong view', () => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled without nodes', () => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], pendingShareView)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Accept share action execute tests', () => {
|
||||
test('Accept share action', async () => {
|
||||
jest.spyOn(axios, 'post')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, pendingShareView, '/')
|
||||
|
||||
expect(exec).toBe(true)
|
||||
expect(axios.post).toBeCalledTimes(1)
|
||||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending/123')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(1)
|
||||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file)
|
||||
})
|
||||
|
||||
test('Accept remote share action', async () => {
|
||||
jest.spyOn(axios, 'post')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
remote: 3,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, pendingShareView, '/')
|
||||
|
||||
expect(exec).toBe(true)
|
||||
expect(axios.post).toBeCalledTimes(1)
|
||||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending/123')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(1)
|
||||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file)
|
||||
})
|
||||
|
||||
test('Accept share action batch', async () => {
|
||||
jest.spyOn(axios, 'post')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foo.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const file2 = new File({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/bar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 456,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.execBatch!([file1, file2], pendingShareView, '/')
|
||||
|
||||
expect(exec).toStrictEqual([true, true])
|
||||
expect(axios.post).toBeCalledTimes(2)
|
||||
expect(axios.post).toHaveBeenNthCalledWith(1, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending/123')
|
||||
expect(axios.post).toHaveBeenNthCalledWith(2, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending/456')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(2)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
|
||||
})
|
||||
|
||||
test('Accept fails', async () => {
|
||||
jest.spyOn(axios, 'post').mockImplementation(() => { throw new Error('Mock error') })
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, pendingShareView, '/')
|
||||
|
||||
expect(exec).toBe(false)
|
||||
expect(axios.post).toBeCalledTimes(1)
|
||||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending/123')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
66
apps/files_sharing/src/actions/acceptShareAction.ts
Normal file
66
apps/files_sharing/src/actions/acceptShareAction.ts
Normal file
|
|
@ -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 { emit } from '@nextcloud/event-bus'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import { translatePlural as n } from '@nextcloud/l10n'
|
||||
import axios from '@nextcloud/axios'
|
||||
import CheckSvg from '@mdi/svg/svg/check.svg?raw'
|
||||
|
||||
import { FileAction, registerFileAction } from '../../../files/src/services/FileAction'
|
||||
import { pendingSharesViewId } from '../views/shares'
|
||||
|
||||
export const action = new FileAction({
|
||||
id: 'accept-share',
|
||||
displayName: (nodes: Node[]) => n('files_sharing', 'Accept share', 'Accept shares', nodes.length),
|
||||
iconSvgInline: () => CheckSvg,
|
||||
|
||||
enabled: (nodes, view) => nodes.length > 0 && 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)
|
||||
97
apps/files_sharing/src/actions/openInFilesAction.spec.ts
Normal file
97
apps/files_sharing/src/actions/openInFilesAction.spec.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* @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 { action } from './openInFilesAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Permission } from '@nextcloud/files'
|
||||
import { DefaultType, FileAction } from '../../../files/src/services/FileAction'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import axios from '@nextcloud/axios'
|
||||
import type { Navigation } from '../../../files/src/services/Navigation'
|
||||
import '../main'
|
||||
import { deletedSharesViewId, pendingSharesViewId, sharedWithOthersViewId, sharedWithYouViewId, sharesViewId, sharingByLinksViewId } from '../views/shares'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as Navigation
|
||||
|
||||
const validViews = [
|
||||
sharesViewId,
|
||||
sharedWithYouViewId,
|
||||
sharedWithOthersViewId,
|
||||
sharingByLinksViewId,
|
||||
].map(id => ({ id, name: id })) as Navigation[]
|
||||
|
||||
const invalidViews = [
|
||||
deletedSharesViewId,
|
||||
pendingSharesViewId,
|
||||
].map(id => ({ id, name: id })) as Navigation[]
|
||||
|
||||
describe('Open in files action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('open-in-files')
|
||||
expect(action.displayName([], validViews[0])).toBe('Open in files')
|
||||
expect(action.iconSvgInline([], validViews[0])).toBe('')
|
||||
expect(action.default).toBe(DefaultType.HIDDEN)
|
||||
expect(action.order).toBe(-1000)
|
||||
expect(action.inline).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Open in files action enabled tests', () => {
|
||||
test('Enabled with on valid view', () => {
|
||||
validViews.forEach(view => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], view)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test('Disabled on wrong view', () => {
|
||||
invalidViews.forEach(view => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], view)).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Open in files action execute tests', () => {
|
||||
test('Open in files', async () => {
|
||||
const goToRouteMock = jest.fn()
|
||||
window.OCP = { Files: { Router: { goToRoute: goToRouteMock } } }
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
root: '/files/admin',
|
||||
permissions: Permission.READ,
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, view, '/')
|
||||
// Silent action
|
||||
expect(exec).toBe(null)
|
||||
expect(goToRouteMock).toBeCalledTimes(1)
|
||||
expect(goToRouteMock).toBeCalledWith(null, { fileid: 1, view: 'files' }, { fileid: 1, dir: '/Foo' })
|
||||
})
|
||||
})
|
||||
56
apps/files_sharing/src/actions/openInFilesAction.ts
Normal file
56
apps/files_sharing/src/actions/openInFilesAction.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* @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 { translate as t } from '@nextcloud/l10n'
|
||||
import type { Node } from '@nextcloud/files'
|
||||
|
||||
import { registerFileAction, FileAction, DefaultType } from '../../../files/src/services/FileAction'
|
||||
import { sharesViewId, sharedWithYouViewId, sharedWithOthersViewId, sharingByLinksViewId } from '../views/shares'
|
||||
|
||||
export const action = new FileAction({
|
||||
id: 'open-in-files',
|
||||
displayName: () => t('files', 'Open in files'),
|
||||
iconSvgInline: () => '',
|
||||
|
||||
enabled: (nodes, view) => [
|
||||
sharesViewId,
|
||||
sharedWithYouViewId,
|
||||
sharedWithOthersViewId,
|
||||
sharingByLinksViewId,
|
||||
// Deleted and pending shares are not
|
||||
// accessible in the files app.
|
||||
].includes(view.id),
|
||||
|
||||
async exec(node: Node) {
|
||||
window.OCP.Files.Router.goToRoute(
|
||||
null, // use default route
|
||||
{ view: 'files', fileid: node.fileid },
|
||||
{ dir: node.dirname, fileid: node.fileid },
|
||||
)
|
||||
return null
|
||||
},
|
||||
|
||||
default: DefaultType.HIDDEN,
|
||||
// Before openFolderAction
|
||||
order: -1000,
|
||||
})
|
||||
|
||||
registerFileAction(action)
|
||||
250
apps/files_sharing/src/actions/rejectShareAction.spec.ts
Normal file
250
apps/files_sharing/src/actions/rejectShareAction.spec.ts
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
/**
|
||||
* @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 { action } from './rejectShareAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { FileAction } from '../../../files/src/services/FileAction'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import axios from '@nextcloud/axios'
|
||||
import type { Navigation } from '../../../files/src/services/Navigation'
|
||||
import '../main'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as Navigation
|
||||
|
||||
const pendingShareView = {
|
||||
id: 'pendingshares',
|
||||
name: 'Pending shares',
|
||||
} as Navigation
|
||||
|
||||
describe('Reject share action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('reject-share')
|
||||
expect(action.displayName([file], pendingShareView)).toBe('Reject share')
|
||||
expect(action.iconSvgInline([file], pendingShareView)).toBe('<svg>SvgMock</svg>')
|
||||
expect(action.default).toBeUndefined()
|
||||
expect(action.order).toBe(2)
|
||||
expect(action.inline).toBeDefined()
|
||||
expect(action.inline!(file, pendingShareView)).toBe(true)
|
||||
})
|
||||
|
||||
test('Default values for multiple files', () => {
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
const file2 = new File({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.displayName([file1, file2], pendingShareView)).toBe('Reject shares')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Reject share action enabled tests', () => {
|
||||
test('Enabled with on pending shares view', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], pendingShareView)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled on wrong view', () => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled without nodes', () => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], pendingShareView)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled if some nodes are remote group shares', () => {
|
||||
const folder1 = new Folder({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Foo/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
const folder2 = new Folder({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/Bar/',
|
||||
owner: 'admin',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
remote_id: 1,
|
||||
share_type: window.OC.Share.SHARE_TYPE_REMOTE_GROUP,
|
||||
},
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([folder1], pendingShareView)).toBe(true)
|
||||
expect(action.enabled!([folder2], pendingShareView)).toBe(false)
|
||||
expect(action.enabled!([folder1, folder2], pendingShareView)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Reject share action execute tests', () => {
|
||||
test('Reject share action', async () => {
|
||||
jest.spyOn(axios, 'delete')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, pendingShareView, '/')
|
||||
|
||||
expect(exec).toBe(true)
|
||||
expect(axios.delete).toBeCalledTimes(1)
|
||||
expect(axios.delete).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/123')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(1)
|
||||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file)
|
||||
})
|
||||
|
||||
test('Reject remote share action', async () => {
|
||||
jest.spyOn(axios, 'delete')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
remote: 3,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, pendingShareView, '/')
|
||||
|
||||
expect(exec).toBe(true)
|
||||
expect(axios.delete).toBeCalledTimes(1)
|
||||
expect(axios.delete).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/remote_shares/123')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(1)
|
||||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file)
|
||||
})
|
||||
|
||||
test('Reject share action batch', async () => {
|
||||
jest.spyOn(axios, 'delete')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foo.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const file2 = new File({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/bar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 456,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.execBatch!([file1, file2], pendingShareView, '/')
|
||||
|
||||
expect(exec).toStrictEqual([true, true])
|
||||
expect(axios.delete).toBeCalledTimes(2)
|
||||
expect(axios.delete).toHaveBeenNthCalledWith(1, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/123')
|
||||
expect(axios.delete).toHaveBeenNthCalledWith(2, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/456')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(2)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
|
||||
})
|
||||
|
||||
test('Reject fails', async () => {
|
||||
jest.spyOn(axios, 'delete').mockImplementation(() => { throw new Error('Mock error') })
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, pendingShareView, '/')
|
||||
|
||||
expect(exec).toBe(false)
|
||||
expect(axios.delete).toBeCalledTimes(1)
|
||||
expect(axios.delete).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/123')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
83
apps/files_sharing/src/actions/rejectShareAction.ts
Normal file
83
apps/files_sharing/src/actions/rejectShareAction.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* @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 { translatePlural as n } from '@nextcloud/l10n'
|
||||
import axios from '@nextcloud/axios'
|
||||
import CloseSvg from '@mdi/svg/svg/close.svg?raw'
|
||||
|
||||
import { FileAction, registerFileAction } from '../../../files/src/services/FileAction'
|
||||
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
|
||||
}
|
||||
|
||||
if (nodes.length === 0) {
|
||||
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)
|
||||
196
apps/files_sharing/src/actions/restoreShareAction.spec.ts
Normal file
196
apps/files_sharing/src/actions/restoreShareAction.spec.ts
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* @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 { action } from './restoreShareAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Permission } from '@nextcloud/files'
|
||||
import { FileAction } from '../../../files/src/services/FileAction'
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import axios from '@nextcloud/axios'
|
||||
import type { Navigation } from '../../../files/src/services/Navigation'
|
||||
import '../main'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
} as Navigation
|
||||
|
||||
const deletedShareView = {
|
||||
id: 'deletedshares',
|
||||
name: 'Deleted shares',
|
||||
} as Navigation
|
||||
|
||||
describe('Restore share action conditions tests', () => {
|
||||
test('Default values', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action).toBeInstanceOf(FileAction)
|
||||
expect(action.id).toBe('restore-share')
|
||||
expect(action.displayName([file], deletedShareView)).toBe('Restore share')
|
||||
expect(action.iconSvgInline([file], deletedShareView)).toBe('<svg>SvgMock</svg>')
|
||||
expect(action.default).toBeUndefined()
|
||||
expect(action.order).toBe(1)
|
||||
expect(action.inline).toBeDefined()
|
||||
expect(action.inline!(file, deletedShareView)).toBe(true)
|
||||
})
|
||||
|
||||
test('Default values for multiple files', () => {
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
const file2 = new File({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.displayName([file1, file2], deletedShareView)).toBe('Restore shares')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Restore share action enabled tests', () => {
|
||||
test('Enabled with on pending shares view', () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.ALL,
|
||||
})
|
||||
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([file], deletedShareView)).toBe(true)
|
||||
})
|
||||
|
||||
test('Disabled on wrong view', () => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], view)).toBe(false)
|
||||
})
|
||||
|
||||
test('Disabled without nodes', () => {
|
||||
expect(action.enabled).toBeDefined()
|
||||
expect(action.enabled!([], deletedShareView)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Restore share action execute tests', () => {
|
||||
test('Restore share action', async () => {
|
||||
jest.spyOn(axios, 'post')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, deletedShareView, '/')
|
||||
|
||||
expect(exec).toBe(true)
|
||||
expect(axios.post).toBeCalledTimes(1)
|
||||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/deletedshares/123')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(1)
|
||||
expect(eventBus.emit).toBeCalledWith('files:node:deleted', file)
|
||||
})
|
||||
|
||||
test('Restore share action batch', async () => {
|
||||
jest.spyOn(axios, 'post')
|
||||
jest.spyOn(eventBus, 'emit')
|
||||
|
||||
const file1 = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foo.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const file2 = new File({
|
||||
id: 2,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/bar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 456,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.execBatch!([file1, file2], deletedShareView, '/')
|
||||
|
||||
expect(exec).toStrictEqual([true, true])
|
||||
expect(axios.post).toBeCalledTimes(2)
|
||||
expect(axios.post).toHaveBeenNthCalledWith(1, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/deletedshares/123')
|
||||
expect(axios.post).toHaveBeenNthCalledWith(2, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/deletedshares/456')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(2)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:node:deleted', file1)
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(2, 'files:node:deleted', file2)
|
||||
})
|
||||
|
||||
test('Restore fails', async () => {
|
||||
jest.spyOn(axios, 'post').mockImplementation(() => { throw new Error('Mock error') })
|
||||
|
||||
const file = new File({
|
||||
id: 1,
|
||||
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
|
||||
owner: 'admin',
|
||||
mime: 'text/plain',
|
||||
permissions: Permission.READ,
|
||||
attributes: {
|
||||
id: 123,
|
||||
share_type: window.OC.Share.SHARE_TYPE_USER,
|
||||
},
|
||||
})
|
||||
|
||||
const exec = await action.exec(file, deletedShareView, '/')
|
||||
|
||||
expect(exec).toBe(false)
|
||||
expect(axios.post).toBeCalledTimes(1)
|
||||
expect(axios.post).toBeCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/deletedshares/123')
|
||||
|
||||
expect(eventBus.emit).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
65
apps/files_sharing/src/actions/restoreShareAction.ts
Normal file
65
apps/files_sharing/src/actions/restoreShareAction.ts
Normal file
|
|
@ -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 { emit } from '@nextcloud/event-bus'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
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 { FileAction, registerFileAction } from '../../../files/src/services/FileAction'
|
||||
import { deletedSharesViewId } from '../views/shares'
|
||||
|
||||
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) => nodes.length > 0 && 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)
|
||||
30
apps/files_sharing/src/files_sharing.ts
Normal file
30
apps/files_sharing/src/files_sharing.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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 registerSharingViews from './views/shares'
|
||||
|
||||
import './actions/acceptShareAction'
|
||||
import './actions/openInFilesAction'
|
||||
import './actions/rejectShareAction'
|
||||
import './actions/restoreShareAction'
|
||||
|
||||
registerSharingViews()
|
||||
|
|
@ -22,7 +22,11 @@
|
|||
*/
|
||||
|
||||
// register default shares types
|
||||
Object.assign(OC, {
|
||||
if (!window.OC) {
|
||||
window.OC = {}
|
||||
}
|
||||
|
||||
Object.assign(window.OC, {
|
||||
Share: {
|
||||
SHARE_TYPE_USER: 0,
|
||||
SHARE_TYPE_GROUP: 1,
|
||||
364
apps/files_sharing/src/services/SharingService.spec.ts
Normal file
364
apps/files_sharing/src/services/SharingService.spec.ts
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
/**
|
||||
* @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 { expect } from '@jest/globals'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { Type } from '@nextcloud/sharing'
|
||||
import * as auth from '@nextcloud/auth'
|
||||
|
||||
import { getContents, type OCSResponse } from './SharingService'
|
||||
import { File, Folder } from '@nextcloud/files'
|
||||
import logger from './logger'
|
||||
|
||||
global.window.OC = {
|
||||
TAG_FAVORITE: '_$!<Favorite>!$_',
|
||||
}
|
||||
|
||||
describe('SharingService methods definitions', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(axios, 'get').mockImplementation(async (): Promise<any> => {
|
||||
return {
|
||||
data: {
|
||||
ocs: {
|
||||
meta: {
|
||||
status: 'ok',
|
||||
statuscode: 200,
|
||||
message: 'OK',
|
||||
},
|
||||
data: [],
|
||||
},
|
||||
} as OCSResponse,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
test('Shared with you', async () => {
|
||||
await getContents(true, false, false, false, [])
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(2)
|
||||
expect(axios.get).toHaveBeenNthCalledWith(1, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {
|
||||
shared_with_me: true,
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
expect(axios.get).toHaveBeenNthCalledWith(2, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/remote_shares', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('Shared with others', async () => {
|
||||
await getContents(false, true, false, false, [])
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(1)
|
||||
expect(axios.get).toHaveBeenCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {
|
||||
shared_with_me: false,
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('Pending shares', async () => {
|
||||
await getContents(false, false, true, false, [])
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(2)
|
||||
expect(axios.get).toHaveBeenNthCalledWith(1, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares/pending', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
expect(axios.get).toHaveBeenNthCalledWith(2, 'http://localhost/ocs/v2.php/apps/files_sharing/api/v1/remote_shares/pending', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('Deleted shares', async () => {
|
||||
await getContents(false, true, false, false, [])
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(1)
|
||||
expect(axios.get).toHaveBeenCalledWith('http://localhost/ocs/v2.php/apps/files_sharing/api/v1/shares', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {
|
||||
shared_with_me: false,
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('Unknown owner', async () => {
|
||||
jest.spyOn(auth, 'getCurrentUser').mockReturnValue(null)
|
||||
const results = await getContents(false, true, false, false, [])
|
||||
|
||||
expect(results.folder.owner).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SharingService filtering', () => {
|
||||
beforeAll(() => {
|
||||
jest.spyOn(axios, 'get').mockImplementation(async (): Promise<any> => {
|
||||
return {
|
||||
data: {
|
||||
ocs: {
|
||||
meta: {
|
||||
status: 'ok',
|
||||
statuscode: 200,
|
||||
message: 'OK',
|
||||
},
|
||||
data: [
|
||||
{
|
||||
id: '62',
|
||||
share_type: Type.SHARE_TYPE_USER,
|
||||
uid_owner: 'test',
|
||||
displayname_owner: 'test',
|
||||
permissions: 31,
|
||||
stime: 1688666292,
|
||||
expiration: '2023-07-13 00:00:00',
|
||||
token: null,
|
||||
path: '/Collaborators',
|
||||
item_type: 'folder',
|
||||
item_permissions: 31,
|
||||
mimetype: 'httpd/unix-directory',
|
||||
storage: 224,
|
||||
item_source: 419413,
|
||||
file_source: 419413,
|
||||
file_parent: 419336,
|
||||
file_target: '/Collaborators',
|
||||
item_size: 41434,
|
||||
item_mtime: 1688662980,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
test('Shared with others filtering', async () => {
|
||||
const shares = await getContents(false, true, false, false, [Type.SHARE_TYPE_USER])
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(1)
|
||||
expect(shares.contents).toHaveLength(1)
|
||||
expect(shares.contents[0].fileid).toBe(419413)
|
||||
expect(shares.contents[0]).toBeInstanceOf(Folder)
|
||||
})
|
||||
|
||||
test('Shared with others filtering empty', async () => {
|
||||
const shares = await getContents(false, true, false, false, [Type.SHARE_TYPE_LINK])
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(1)
|
||||
expect(shares.contents).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SharingService share to Node mapping', () => {
|
||||
const shareFile = {
|
||||
id: '66',
|
||||
share_type: 0,
|
||||
uid_owner: 'test',
|
||||
displayname_owner: 'test',
|
||||
permissions: 19,
|
||||
can_edit: true,
|
||||
can_delete: true,
|
||||
stime: 1688721609,
|
||||
parent: null,
|
||||
expiration: '2023-07-14 00:00:00',
|
||||
token: null,
|
||||
uid_file_owner: 'test',
|
||||
note: '',
|
||||
label: null,
|
||||
displayname_file_owner: 'test',
|
||||
path: '/document.md',
|
||||
item_type: 'file',
|
||||
item_permissions: 27,
|
||||
mimetype: 'text/markdown',
|
||||
has_preview: true,
|
||||
storage_id: 'home::test',
|
||||
storage: 224,
|
||||
item_source: 530936,
|
||||
file_source: 530936,
|
||||
file_parent: 419336,
|
||||
file_target: '/document.md',
|
||||
item_size: 123,
|
||||
item_mtime: 1688721600,
|
||||
share_with: 'user00',
|
||||
share_with_displayname: 'User00',
|
||||
share_with_displayname_unique: 'user00@domain.com',
|
||||
status: {
|
||||
status: 'away',
|
||||
message: null,
|
||||
icon: null,
|
||||
clearAt: null,
|
||||
},
|
||||
mail_send: 0,
|
||||
hide_download: 0,
|
||||
attributes: null,
|
||||
tags: [],
|
||||
}
|
||||
|
||||
const shareFolder = {
|
||||
id: '67',
|
||||
share_type: 0,
|
||||
uid_owner: 'test',
|
||||
displayname_owner: 'test',
|
||||
permissions: 31,
|
||||
can_edit: true,
|
||||
can_delete: true,
|
||||
stime: 1688721629,
|
||||
parent: null,
|
||||
expiration: '2023-07-14 00:00:00',
|
||||
token: null,
|
||||
uid_file_owner: 'test',
|
||||
note: '',
|
||||
label: null,
|
||||
displayname_file_owner: 'test',
|
||||
path: '/Folder',
|
||||
item_type: 'folder',
|
||||
item_permissions: 31,
|
||||
mimetype: 'httpd/unix-directory',
|
||||
has_preview: false,
|
||||
storage_id: 'home::test',
|
||||
storage: 224,
|
||||
item_source: 531080,
|
||||
file_source: 531080,
|
||||
file_parent: 419336,
|
||||
file_target: '/Folder',
|
||||
item_size: 0,
|
||||
item_mtime: 1688721623,
|
||||
share_with: 'user00',
|
||||
share_with_displayname: 'User00',
|
||||
share_with_displayname_unique: 'user00@domain.com',
|
||||
status: {
|
||||
status: 'away',
|
||||
message: null,
|
||||
icon: null,
|
||||
clearAt: null,
|
||||
},
|
||||
mail_send: 0,
|
||||
hide_download: 0,
|
||||
attributes: null,
|
||||
tags: [window.OC.TAG_FAVORITE],
|
||||
}
|
||||
|
||||
test('File', async () => {
|
||||
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
|
||||
data: {
|
||||
ocs: {
|
||||
data: [shareFile],
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const shares = await getContents(false, true, false, false)
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(1)
|
||||
expect(shares.contents).toHaveLength(1)
|
||||
|
||||
const file = shares.contents[0] as File
|
||||
expect(file).toBeInstanceOf(File)
|
||||
expect(file.fileid).toBe(530936)
|
||||
expect(file.source).toBe('http://localhost/remote.php/dav/files/test/document.md')
|
||||
expect(file.owner).toBe('test')
|
||||
expect(file.mime).toBe('text/markdown')
|
||||
expect(file.mtime).toBeInstanceOf(Date)
|
||||
expect(file.size).toBe(123)
|
||||
expect(file.permissions).toBe(27)
|
||||
expect(file.root).toBe('/files/test')
|
||||
expect(file.attributes).toBeInstanceOf(Object)
|
||||
expect(file.attributes['has-preview']).toBe(true)
|
||||
expect(file.attributes.previewUrl).toBe('/index.php/core/preview?fileId=530936&x=32&y=32&forceIcon=0')
|
||||
expect(file.attributes.favorite).toBe(0)
|
||||
})
|
||||
|
||||
test('Folder', async () => {
|
||||
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
|
||||
data: {
|
||||
ocs: {
|
||||
data: [shareFolder],
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const shares = await getContents(false, true, false, false)
|
||||
|
||||
expect(axios.get).toHaveBeenCalledTimes(1)
|
||||
expect(shares.contents).toHaveLength(1)
|
||||
|
||||
const folder = shares.contents[0] as Folder
|
||||
expect(folder).toBeInstanceOf(Folder)
|
||||
expect(folder.fileid).toBe(531080)
|
||||
expect(folder.source).toBe('http://localhost/remote.php/dav/files/test/Folder')
|
||||
expect(folder.owner).toBe('test')
|
||||
expect(folder.mime).toBe('httpd/unix-directory')
|
||||
expect(folder.mtime).toBeInstanceOf(Date)
|
||||
expect(folder.size).toBe(0)
|
||||
expect(folder.permissions).toBe(31)
|
||||
expect(folder.root).toBe('/files/test')
|
||||
expect(folder.attributes).toBeInstanceOf(Object)
|
||||
expect(folder.attributes['has-preview']).toBe(false)
|
||||
expect(folder.attributes.previewUrl).toBeUndefined()
|
||||
expect(folder.attributes.favorite).toBe(1)
|
||||
})
|
||||
|
||||
test('Error', async () => {
|
||||
jest.spyOn(logger, 'error').mockImplementationOnce(() => {})
|
||||
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
|
||||
data: {
|
||||
ocs: {
|
||||
data: [{}],
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const shares = await getContents(false, true, false, false)
|
||||
expect(shares.contents).toHaveLength(0)
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
181
apps/files_sharing/src/services/SharingService.ts
Normal file
181
apps/files_sharing/src/services/SharingService.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* @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 axios from '@nextcloud/axios'
|
||||
import logger from './logger'
|
||||
|
||||
export const rootPath = `/files/${getCurrentUser()?.uid}`
|
||||
|
||||
export 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 isFolder = ocsEntry?.item_type === 'folder'
|
||||
const hasPreview = ocsEntry?.has_preview === true
|
||||
const Node = isFolder ? Folder : File
|
||||
|
||||
const fileid = ocsEntry.file_source
|
||||
const previewUrl = hasPreview ? generateUrl('/core/preview?fileId={fileid}&x=32&y=32&forceIcon=0', { fileid }) : undefined
|
||||
|
||||
// Generate path and strip double slashes
|
||||
const path = ocsEntry?.path || ocsEntry.file_target
|
||||
const source = generateRemoteUrl(`dav/${rootPath}/${path}`.replaceAll(/\/\//gm, '/'))
|
||||
|
||||
// Prefer share time if more recent than item mtime
|
||||
let mtime = ocsEntry?.item_mtime ? new Date((ocsEntry.item_mtime) * 1000) : undefined
|
||||
if (ocsEntry?.stime > (ocsEntry?.item_mtime || 0)) {
|
||||
mtime = new Date((ocsEntry.stime) * 1000)
|
||||
}
|
||||
|
||||
return new Node({
|
||||
id: fileid,
|
||||
source,
|
||||
owner: ocsEntry?.uid_owner,
|
||||
mime: ocsEntry?.mimetype,
|
||||
mtime,
|
||||
size: ocsEntry?.item_size,
|
||||
permissions: ocsEntry?.item_permissions || ocsEntry?.permissions,
|
||||
root: rootPath,
|
||||
attributes: {
|
||||
...ocsEntry,
|
||||
previewUrl,
|
||||
'has-preview': hasPreview,
|
||||
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.get(url, {
|
||||
headers,
|
||||
params: {
|
||||
shared_with_me,
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const getSharedWithYou = function(): AxiosPromise<OCSResponse> {
|
||||
return getShares(true)
|
||||
}
|
||||
|
||||
const getSharedWithOthers = function(): AxiosPromise<OCSResponse> {
|
||||
return getShares()
|
||||
}
|
||||
|
||||
const getRemoteShares = function(): AxiosPromise<OCSResponse> {
|
||||
const url = generateOcsUrl('apps/files_sharing/api/v1/remote_shares')
|
||||
return axios.get(url, {
|
||||
headers,
|
||||
params: {
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const getPendingShares = function(): AxiosPromise<OCSResponse> {
|
||||
const url = generateOcsUrl('apps/files_sharing/api/v1/shares/pending')
|
||||
return axios.get(url, {
|
||||
headers,
|
||||
params: {
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const getRemotePendingShares = function(): AxiosPromise<OCSResponse> {
|
||||
const url = generateOcsUrl('apps/files_sharing/api/v1/remote_shares/pending')
|
||||
return axios.get(url, {
|
||||
headers,
|
||||
params: {
|
||||
include_tags: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const getDeletedShares = function(): AxiosPromise<OCSResponse> {
|
||||
const url = generateOcsUrl('apps/files_sharing/api/v1/deletedshares')
|
||||
return axios.get(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 || null,
|
||||
}),
|
||||
contents,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
|
|
@ -20,6 +19,9 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||
|
||||
import '../js/app.js'
|
||||
import '../js/sharedfilelist.js'
|
||||
export default getLoggerBuilder()
|
||||
.setApp('files_sharing')
|
||||
.detectUser()
|
||||
.build()
|
||||
125
apps/files_sharing/src/views/shares.spec.ts
Normal file
125
apps/files_sharing/src/views/shares.spec.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* @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 n/no-extraneous-import */
|
||||
import { expect } from '@jest/globals'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
import { type Navigation } from '../../../files/src/services/Navigation'
|
||||
import { type OCSResponse } from '../services/SharingService'
|
||||
import NavigationService from '../../../files/src/services/Navigation'
|
||||
import registerSharingViews from './shares'
|
||||
|
||||
import '../main'
|
||||
import { Folder } from '@nextcloud/files'
|
||||
|
||||
describe('Sharing views definition', () => {
|
||||
let Navigation
|
||||
beforeEach(() => {
|
||||
Navigation = new NavigationService()
|
||||
window.OCP = { Files: { Navigation } }
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
delete window.OCP
|
||||
})
|
||||
|
||||
test('Default values', () => {
|
||||
jest.spyOn(Navigation, 'register')
|
||||
|
||||
expect(Navigation.views.length).toBe(0)
|
||||
|
||||
registerSharingViews()
|
||||
const shareOverviewView = Navigation.views.find(view => view.id === 'shareoverview') as Navigation
|
||||
const sharesChildViews = Navigation.views.filter(view => view.parent === 'shareoverview') as Navigation[]
|
||||
|
||||
expect(Navigation.register).toHaveBeenCalledTimes(6)
|
||||
|
||||
// one main view and no children
|
||||
expect(Navigation.views.length).toBe(6)
|
||||
expect(shareOverviewView).toBeDefined()
|
||||
expect(sharesChildViews.length).toBe(5)
|
||||
|
||||
expect(shareOverviewView?.id).toBe('shareoverview')
|
||||
expect(shareOverviewView?.name).toBe('Shares')
|
||||
expect(shareOverviewView?.caption).toBe('Overview of shared files.')
|
||||
expect(shareOverviewView?.icon).toBe('<svg>SvgMock</svg>')
|
||||
expect(shareOverviewView?.order).toBe(20)
|
||||
expect(shareOverviewView?.columns).toStrictEqual([])
|
||||
expect(shareOverviewView?.getContents).toBeDefined()
|
||||
|
||||
const dataProvider = [
|
||||
{ id: 'sharingin', name: 'Shared with you', caption: 'List of files that are shared with you.' },
|
||||
{ id: 'sharingout', name: 'Shared with others', caption: 'List of files that you shared with others.' },
|
||||
{ id: 'sharinglinks', name: 'Shared by link', caption: 'List of files that are shared by link.' },
|
||||
{ id: 'deletedshares', name: 'Deleted shares', caption: 'List of shares that you removed yourself from.' },
|
||||
{ id: 'pendingshares', name: 'Pending shares', caption: 'List of unapproved shares.' },
|
||||
]
|
||||
|
||||
sharesChildViews.forEach((view, index) => {
|
||||
expect(view?.id).toBe(dataProvider[index].id)
|
||||
expect(view?.parent).toBe('shareoverview')
|
||||
expect(view?.name).toBe(dataProvider[index].name)
|
||||
expect(view?.caption).toBe(dataProvider[index].caption)
|
||||
expect(view?.icon).toBe('<svg>SvgMock</svg>')
|
||||
expect(view?.order).toBe(index + 1)
|
||||
expect(view?.columns).toStrictEqual([])
|
||||
expect(view?.getContents).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sharing views contents', () => {
|
||||
let Navigation
|
||||
beforeEach(() => {
|
||||
Navigation = new NavigationService()
|
||||
window.OCP = { Files: { Navigation } }
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
delete window.OCP
|
||||
})
|
||||
|
||||
test('Sharing overview get contents', async () => {
|
||||
jest.spyOn(axios, 'get').mockImplementation(async (): Promise<any> => {
|
||||
return {
|
||||
data: {
|
||||
ocs: {
|
||||
meta: {
|
||||
status: 'ok',
|
||||
statuscode: 200,
|
||||
message: 'OK',
|
||||
},
|
||||
data: [],
|
||||
},
|
||||
} as OCSResponse,
|
||||
}
|
||||
})
|
||||
|
||||
registerSharingViews()
|
||||
expect(Navigation.views.length).toBe(6)
|
||||
Navigation.views.forEach(async (view: Navigation) => {
|
||||
const content = await view.getContents('/')
|
||||
expect(content.contents).toStrictEqual([])
|
||||
expect(content.folder).toBeInstanceOf(Folder)
|
||||
})
|
||||
})
|
||||
})
|
||||
126
apps/files_sharing/src/views/shares.ts
Normal file
126
apps/files_sharing/src/views/shares.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* @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 sharedWithYouViewId = 'sharingin'
|
||||
export const sharedWithOthersViewId = 'sharingout'
|
||||
export const sharingByLinksViewId = 'sharinglinks'
|
||||
export const deletedSharesViewId = 'deletedshares'
|
||||
export const pendingSharesViewId = 'pendingshares'
|
||||
|
||||
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: sharedWithYouViewId,
|
||||
name: t('files_sharing', 'Shared with you'),
|
||||
caption: t('files_sharing', 'List of files that are shared with you.'),
|
||||
|
||||
icon: AccountSvg,
|
||||
order: 1,
|
||||
parent: sharesViewId,
|
||||
|
||||
columns: [],
|
||||
|
||||
getContents: () => getContents(true, false, false, false),
|
||||
} as Navigation)
|
||||
|
||||
Navigation.register({
|
||||
id: sharedWithOthersViewId,
|
||||
name: t('files_sharing', 'Shared with others'),
|
||||
caption: t('files_sharing', 'List of files that you shared with others.'),
|
||||
|
||||
icon: AccountGroupSvg,
|
||||
order: 2,
|
||||
parent: sharesViewId,
|
||||
|
||||
columns: [],
|
||||
|
||||
getContents: () => getContents(false, true, false, false),
|
||||
} as Navigation)
|
||||
|
||||
Navigation.register({
|
||||
id: sharingByLinksViewId,
|
||||
name: t('files_sharing', 'Shared by link'),
|
||||
caption: t('files_sharing', 'List of files that are shared by link.'),
|
||||
|
||||
icon: LinkSvg,
|
||||
order: 3,
|
||||
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: 4,
|
||||
parent: sharesViewId,
|
||||
|
||||
columns: [],
|
||||
|
||||
getContents: () => getContents(false, false, false, true),
|
||||
} as Navigation)
|
||||
|
||||
Navigation.register({
|
||||
id: pendingSharesViewId,
|
||||
name: t('files_sharing', 'Pending shares'),
|
||||
caption: t('files_sharing', 'List of unapproved shares.'),
|
||||
|
||||
icon: AccountClockSvg,
|
||||
order: 5,
|
||||
parent: sharesViewId,
|
||||
|
||||
columns: [],
|
||||
|
||||
getContents: () => getContents(false, false, true, false),
|
||||
} as Navigation)
|
||||
}
|
||||
|
|
@ -576,6 +576,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$file->method('getPath')->willReturn('file');
|
||||
$file->method('getStorage')->willReturn($storage);
|
||||
$file->method('getParent')->willReturn($parentFolder);
|
||||
$file->method('getSize')->willReturn(123465);
|
||||
$file->method('getMTime')->willReturn(1234567890);
|
||||
$file->method('getMimeType')->willReturn('myMimeType');
|
||||
|
||||
$folder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
|
||||
|
|
@ -583,6 +585,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$folder->method('getPath')->willReturn('folder');
|
||||
$folder->method('getStorage')->willReturn($storage);
|
||||
$folder->method('getParent')->willReturn($parentFolder);
|
||||
$folder->method('getSize')->willReturn(123465);
|
||||
$folder->method('getMTime')->willReturn(1234567890);
|
||||
$folder->method('getMimeType')->willReturn('myFolderMimeType');
|
||||
|
||||
[$shareAttributes, $shareAttributesReturnJson] = $this->mockShareAttributes();
|
||||
|
|
@ -637,6 +641,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'status' => [],
|
||||
'item_size' => 123465,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
];
|
||||
$data[] = [$share, $expected];
|
||||
|
|
@ -689,6 +695,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123465,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
];
|
||||
$data[] = [$share, $expected];
|
||||
|
|
@ -747,6 +755,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123465,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
];
|
||||
$data[] = [$share, $expected];
|
||||
|
|
@ -3735,6 +3745,13 @@ class ShareAPIControllerTest extends TestCase {
|
|||
$folder->method('getParent')->willReturn($parent);
|
||||
$fileWithPreview->method('getParent')->willReturn($parent);
|
||||
|
||||
$file->method('getSize')->willReturn(123456);
|
||||
$folder->method('getSize')->willReturn(123456);
|
||||
$fileWithPreview->method('getSize')->willReturn(123456);
|
||||
$file->method('getMTime')->willReturn(1234567890);
|
||||
$folder->method('getMTime')->willReturn(1234567890);
|
||||
$fileWithPreview->method('getMTime')->willReturn(1234567890);
|
||||
|
||||
$cache = $this->getMockBuilder('OCP\Files\Cache\ICache')->getMock();
|
||||
$cache->method('getNumericStorageId')->willReturn(100);
|
||||
$storage = $this->createMock(Storage::class);
|
||||
|
|
@ -3772,7 +3789,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
// User backend down
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_USER,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -3796,7 +3813,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'share_with_displayname' => 'recipient',
|
||||
'share_with_displayname_unique' => 'recipient',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'mail_send' => 0,
|
||||
'mimetype' => 'myMimeType',
|
||||
'has_preview' => false,
|
||||
|
|
@ -3804,13 +3821,15 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'status' => [],
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => '[{"scope":"permissions","key":"download","enabled":true}]',
|
||||
], $share, [], false
|
||||
];
|
||||
// User backend up
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_USER,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiatorDN',
|
||||
|
|
@ -3823,7 +3842,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'ownerDN',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'file',
|
||||
'item_type' => 'file',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -3842,6 +3861,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'status' => [],
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => '[{"scope":"permissions","key":"download","enabled":true}]',
|
||||
], $share, [
|
||||
['owner', $owner],
|
||||
|
|
@ -3864,7 +3885,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
// User backend down
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_USER,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -3877,7 +3898,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'file',
|
||||
'item_type' => 'file',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -3896,6 +3917,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'status' => [],
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -3914,7 +3937,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
// User backend down
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_USER,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -3927,7 +3950,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'currentUser',
|
||||
'displayname_file_owner' => 'currentUser',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'file',
|
||||
'item_type' => 'file',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -3946,6 +3969,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => true,
|
||||
'can_delete' => true,
|
||||
'status' => [],
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -3966,7 +3991,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_GROUP,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -3979,7 +4004,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'file',
|
||||
'item_type' => 'file',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -3996,6 +4021,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4014,7 +4041,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
->setId(42);
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_GROUP,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4026,7 +4053,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'file',
|
||||
'item_type' => 'file',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4043,6 +4070,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4064,7 +4093,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_LINK,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4097,6 +4126,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4119,7 +4150,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_LINK,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4151,6 +4182,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4170,7 +4203,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_REMOTE,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4182,7 +4215,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'folder',
|
||||
'item_type' => 'folder',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4199,6 +4232,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4218,7 +4253,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_REMOTE_GROUP,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4230,7 +4265,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'folder',
|
||||
'item_type' => 'folder',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4247,6 +4282,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4267,7 +4304,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_CIRCLE,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4280,7 +4317,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => '',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'folder',
|
||||
'item_type' => 'folder',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4298,6 +4335,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4316,7 +4355,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_CIRCLE,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4328,7 +4367,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => '',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'folder',
|
||||
'item_type' => 'folder',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4346,6 +4385,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4364,7 +4405,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_CIRCLE,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4376,7 +4417,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => '',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'folder',
|
||||
'item_type' => 'folder',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4394,6 +4435,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'hide_download' => 0,
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4427,7 +4470,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_EMAIL,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4439,7 +4482,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => '',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'folder',
|
||||
'item_type' => 'folder',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4459,6 +4502,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'password_expiration_time' => null,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4478,7 +4523,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_EMAIL,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4490,7 +4535,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'owner',
|
||||
'displayname_file_owner' => 'owner',
|
||||
'note' => '',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'folder',
|
||||
'item_type' => 'folder',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4510,6 +4555,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'password_expiration_time' => null,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4529,7 +4576,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_USER,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4541,7 +4588,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'uid_file_owner' => 'currentUser',
|
||||
'displayname_file_owner' => 'currentUser',
|
||||
'note' => 'personal note',
|
||||
'label' => null,
|
||||
'label' => '',
|
||||
'path' => 'fileWithPreview',
|
||||
'item_type' => 'file',
|
||||
'storage_id' => 'storageId',
|
||||
|
|
@ -4560,6 +4607,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'can_edit' => true,
|
||||
'can_delete' => true,
|
||||
'status' => [],
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, [], false
|
||||
];
|
||||
|
|
@ -4659,6 +4708,9 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$file->method('getParent')->willReturn($parent);
|
||||
|
||||
$file->method('getSize')->willReturn(123456);
|
||||
$file->method('getMTime')->willReturn(1234567890);
|
||||
|
||||
$cache = $this->getMockBuilder('OCP\Files\Cache\ICache')->getMock();
|
||||
$cache->method('getNumericStorageId')->willReturn(100);
|
||||
$storage = $this->createMock(Storage::class);
|
||||
|
|
@ -4683,7 +4735,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_ROOM,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4712,6 +4764,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'label' => '',
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, false, []
|
||||
];
|
||||
|
|
@ -4730,7 +4784,7 @@ class ShareAPIControllerTest extends TestCase {
|
|||
|
||||
$result[] = [
|
||||
[
|
||||
'id' => 42,
|
||||
'id' => '42',
|
||||
'share_type' => IShare::TYPE_ROOM,
|
||||
'uid_owner' => 'initiator',
|
||||
'displayname_owner' => 'initiator',
|
||||
|
|
@ -4759,6 +4813,8 @@ class ShareAPIControllerTest extends TestCase {
|
|||
'label' => '',
|
||||
'can_edit' => false,
|
||||
'can_delete' => false,
|
||||
'item_size' => 123456,
|
||||
'item_mtime' => 1234567890,
|
||||
'attributes' => null,
|
||||
], $share, true, [
|
||||
'share_with_displayname' => 'recipientRoomName'
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
/**
|
||||
* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
|
||||
* @author Morris Jobke <hey@morrisjobke.de>
|
||||
* @author Vincent Petry <vincent@nextcloud.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('OCA.Sharing.App tests', function() {
|
||||
var App = OCA.Sharing.App;
|
||||
var fileListIn;
|
||||
var fileListOut;
|
||||
|
||||
beforeEach(function() {
|
||||
$('#testArea').append(
|
||||
'<div id="app-navigation">' +
|
||||
'<ul><li data-id="files"><a>Files</a></li>' +
|
||||
'<li data-id="sharingin"><a></a></li>' +
|
||||
'<li data-id="sharingout"><a></a></li>' +
|
||||
'</ul></div>' +
|
||||
'<div id="app-content">' +
|
||||
'<div id="app-content-files" class="hidden">' +
|
||||
'</div>' +
|
||||
'<div id="app-content-sharingin" class="hidden">' +
|
||||
'</div>' +
|
||||
'<div id="app-content-sharingout" class="hidden">' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
fileListIn = App.initSharingIn($('#app-content-sharingin'));
|
||||
fileListOut = App.initSharingOut($('#app-content-sharingout'));
|
||||
});
|
||||
afterEach(function() {
|
||||
App.destroy();
|
||||
});
|
||||
|
||||
describe('initialization', function() {
|
||||
it('inits sharing-in list on show', function() {
|
||||
expect(fileListIn._sharedWithUser).toEqual(true);
|
||||
});
|
||||
it('inits sharing-out list on show', function() {
|
||||
expect(fileListOut._sharedWithUser).toBeFalsy();
|
||||
});
|
||||
});
|
||||
describe('file actions', function() {
|
||||
it('provides default file actions', function() {
|
||||
_.each([fileListIn, fileListOut], function(fileList) {
|
||||
var fileActions = fileList.fileActions;
|
||||
|
||||
expect(fileActions.actions.all).toBeDefined();
|
||||
expect(fileActions.actions.all.Delete).toBeDefined();
|
||||
expect(fileActions.actions.all.Rename).toBeDefined();
|
||||
expect(fileActions.actions.all.Download).toBeDefined();
|
||||
|
||||
expect(fileActions.defaults.dir).toEqual('Open');
|
||||
});
|
||||
});
|
||||
it('provides custom file actions', function() {
|
||||
var actionStub = sinon.stub();
|
||||
// regular file action
|
||||
OCA.Files.fileActions.register(
|
||||
'all',
|
||||
'RegularTest',
|
||||
OC.PERMISSION_READ,
|
||||
OC.imagePath('core', 'actions/shared'),
|
||||
actionStub
|
||||
);
|
||||
|
||||
App._inFileList = null;
|
||||
fileListIn = App.initSharingIn($('#app-content-sharingin'));
|
||||
|
||||
expect(fileListIn.fileActions.actions.all.RegularTest).toBeDefined();
|
||||
});
|
||||
it('redirects to files app when opening a directory', function() {
|
||||
var oldList = OCA.Files.App.fileList;
|
||||
// dummy new list to make sure it exists
|
||||
OCA.Files.App.fileList = new OCA.Files.FileList($('<table><thead></thead><tbody></tbody></table>'));
|
||||
|
||||
var setActiveViewStub = sinon.stub(OCA.Files.App, 'setActiveView');
|
||||
// create dummy table so we can click the dom
|
||||
var $table = '<table><thead></thead><tbody class="files-fileList"></tbody></table>';
|
||||
$('#app-content-sharingin').append($table);
|
||||
|
||||
App._inFileList = null;
|
||||
fileListIn = App.initSharingIn($('#app-content-sharingin'));
|
||||
|
||||
fileListIn.setFiles([{
|
||||
name: 'testdir',
|
||||
type: 'dir',
|
||||
path: '/somewhere/inside/subdir',
|
||||
counterParts: ['user2'],
|
||||
shareOwner: 'user2'
|
||||
}]);
|
||||
|
||||
fileListIn.findFileEl('testdir').find('td .nametext').click();
|
||||
|
||||
expect(OCA.Files.App.fileList.getCurrentDirectory()).toEqual('/somewhere/inside/subdir/testdir');
|
||||
|
||||
expect(setActiveViewStub.calledOnce).toEqual(true);
|
||||
expect(setActiveViewStub.calledWith('files')).toEqual(true);
|
||||
|
||||
setActiveViewStub.restore();
|
||||
|
||||
// restore old list
|
||||
OCA.Files.App.fileList = oldList;
|
||||
});
|
||||
});
|
||||
});
|
||||
41
build/integration/sharing_features/sharing-v1-part4.feature
Normal file
41
build/integration/sharing_features/sharing-v1-part4.feature
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
Feature: sharing
|
||||
Background:
|
||||
Given using api version "1"
|
||||
Given using new dav path
|
||||
|
||||
# See sharing-v1-part3.feature
|
||||
|
||||
Scenario: Creating a new share of a file shows size and mtime
|
||||
Given user "user0" exists
|
||||
And user "user1" exists
|
||||
And As an "user0"
|
||||
And parameter "shareapi_default_permissions" of app "core" is set to "7"
|
||||
When creating a share with
|
||||
| path | welcome.txt |
|
||||
| shareWith | user1 |
|
||||
| shareType | 0 |
|
||||
And the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
And Getting info of last share
|
||||
Then the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
And Share fields of last share match with
|
||||
| item_size | A_NUMBER |
|
||||
| item_mtime | A_NUMBER |
|
||||
|
||||
Scenario: Creating a new share of a file you own shows the file permissions
|
||||
Given user "user0" exists
|
||||
And user "user1" exists
|
||||
And As an "user0"
|
||||
And parameter "shareapi_default_permissions" of app "core" is set to "7"
|
||||
When creating a share with
|
||||
| path | welcome.txt |
|
||||
| shareWith | user1 |
|
||||
| shareType | 0 |
|
||||
And the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
And Getting info of last share
|
||||
Then the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
And Share fields of last share match with
|
||||
| item_permissions | 27 |
|
||||
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
2
dist/core-login.js.map
vendored
2
dist/core-login.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/core-main.js.map
vendored
2
dist/core-main.js.map
vendored
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_sharing-files_sharing.js
vendored
4
dist/files_sharing-files_sharing.js
vendored
File diff suppressed because one or more lines are too long
68
dist/files_sharing-files_sharing.js.LICENSE.txt
vendored
68
dist/files_sharing-files_sharing.js.LICENSE.txt
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
|
|
@ -20,3 +20,69 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
2
dist/files_sharing-files_sharing.js.map
vendored
2
dist/files_sharing-files_sharing.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-main.js
vendored
4
dist/files_sharing-main.js
vendored
|
|
@ -1,3 +1,3 @@
|
|||
/*! For license information please see files_sharing-main.js.LICENSE.txt */
|
||||
Object.assign(OC,{Share:{SHARE_TYPE_USER:0,SHARE_TYPE_GROUP:1,SHARE_TYPE_LINK:3,SHARE_TYPE_EMAIL:4,SHARE_TYPE_REMOTE:6,SHARE_TYPE_CIRCLE:7,SHARE_TYPE_GUEST:8,SHARE_TYPE_REMOTE_GROUP:9,SHARE_TYPE_ROOM:10,SHARE_TYPE_DECK:12,SHARE_TYPE_SCIENCEMESH:15}});
|
||||
//# sourceMappingURL=files_sharing-main.js.map?v=325eef5d4954bda3055f
|
||||
(()=>{"use strict";window.OC||(window.OC={}),Object.assign(window.OC,{Share:{SHARE_TYPE_USER:0,SHARE_TYPE_GROUP:1,SHARE_TYPE_LINK:3,SHARE_TYPE_EMAIL:4,SHARE_TYPE_REMOTE:6,SHARE_TYPE_CIRCLE:7,SHARE_TYPE_GUEST:8,SHARE_TYPE_REMOTE_GROUP:9,SHARE_TYPE_ROOM:10,SHARE_TYPE_DECK:12,SHARE_TYPE_SCIENCEMESH:15}})})();
|
||||
//# sourceMappingURL=files_sharing-main.js.map?v=1bbf90b4b4aba9ea0150
|
||||
2
dist/files_sharing-main.js.map
vendored
2
dist/files_sharing-main.js.map
vendored
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"files_sharing-main.js?v=325eef5d4954bda3055f","mappings":";AAwBAA,OAAOC,OAAOC,GAAI,CACjBC,MAAO,CACNC,gBAAiB,EACjBC,iBAAkB,EAClBC,gBAAiB,EACjBC,iBAAkB,EAClBC,kBAAmB,EACnBC,kBAAmB,EACnBC,iBAAkB,EAClBC,wBAAyB,EACzBC,gBAAiB,GACjBC,gBAAiB,GACjBC,uBAAwB","sources":["webpack:///nextcloud/apps/files_sharing/src/index.js"],"sourcesContent":["/**\n * @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>\n *\n * @author John Molakvoæ <skjnldsv@protonmail.com>\n * @author Julius Härtl <jus@bitgrid.net>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\n// register default shares types\nObject.assign(OC, {\n\tShare: {\n\t\tSHARE_TYPE_USER: 0,\n\t\tSHARE_TYPE_GROUP: 1,\n\t\tSHARE_TYPE_LINK: 3,\n\t\tSHARE_TYPE_EMAIL: 4,\n\t\tSHARE_TYPE_REMOTE: 6,\n\t\tSHARE_TYPE_CIRCLE: 7,\n\t\tSHARE_TYPE_GUEST: 8,\n\t\tSHARE_TYPE_REMOTE_GROUP: 9,\n\t\tSHARE_TYPE_ROOM: 10,\n\t\tSHARE_TYPE_DECK: 12,\n\t\tSHARE_TYPE_SCIENCEMESH: 15,\n\t},\n})\n"],"names":["Object","assign","OC","Share","SHARE_TYPE_USER","SHARE_TYPE_GROUP","SHARE_TYPE_LINK","SHARE_TYPE_EMAIL","SHARE_TYPE_REMOTE","SHARE_TYPE_CIRCLE","SHARE_TYPE_GUEST","SHARE_TYPE_REMOTE_GROUP","SHARE_TYPE_ROOM","SHARE_TYPE_DECK","SHARE_TYPE_SCIENCEMESH"],"sourceRoot":""}
|
||||
{"version":3,"file":"files_sharing-main.js?v=1bbf90b4b4aba9ea0150","mappings":";mBAwBKA,OAAOC,KACRD,OAAOC,GAAK,CAAC,GAEjBC,OAAOC,OAAOH,OAAOC,GAAI,CACrBG,MAAO,CACHC,gBAAiB,EACjBC,iBAAkB,EAClBC,gBAAiB,EACjBC,iBAAkB,EAClBC,kBAAmB,EACnBC,kBAAmB,EACnBC,iBAAkB,EAClBC,wBAAyB,EACzBC,gBAAiB,GACjBC,gBAAiB,GACjBC,uBAAwB","sources":["webpack:///nextcloud/apps/files_sharing/src/main.ts"],"sourcesContent":["\"use strict\";\n/**\n * @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>\n *\n * @author John Molakvoæ <skjnldsv@protonmail.com>\n * @author Julius Härtl <jus@bitgrid.net>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n// register default shares types\nif (!window.OC) {\n window.OC = {};\n}\nObject.assign(window.OC, {\n Share: {\n SHARE_TYPE_USER: 0,\n SHARE_TYPE_GROUP: 1,\n SHARE_TYPE_LINK: 3,\n SHARE_TYPE_EMAIL: 4,\n SHARE_TYPE_REMOTE: 6,\n SHARE_TYPE_CIRCLE: 7,\n SHARE_TYPE_GUEST: 8,\n SHARE_TYPE_REMOTE_GROUP: 9,\n SHARE_TYPE_ROOM: 10,\n SHARE_TYPE_DECK: 12,\n SHARE_TYPE_SCIENCEMESH: 15,\n },\n});\n"],"names":["window","OC","Object","assign","Share","SHARE_TYPE_USER","SHARE_TYPE_GROUP","SHARE_TYPE_LINK","SHARE_TYPE_EMAIL","SHARE_TYPE_REMOTE","SHARE_TYPE_CIRCLE","SHARE_TYPE_GUEST","SHARE_TYPE_REMOTE_GROUP","SHARE_TYPE_ROOM","SHARE_TYPE_DECK","SHARE_TYPE_SCIENCEMESH"],"sourceRoot":""}
|
||||
4
dist/files_trashbin-main.js
vendored
4
dist/files_trashbin-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_trashbin-main.js.map
vendored
2
dist/files_trashbin-main.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -23,6 +23,7 @@ import type { Config } from 'jest'
|
|||
|
||||
// TODO: find a way to consolidate this in one place, with webpack.common.js
|
||||
const ignorePatterns = [
|
||||
'@buttercup/fetch',
|
||||
'@juliushaertl',
|
||||
'@mdi/svg',
|
||||
'@nextcloud/vue',
|
||||
|
|
@ -34,6 +35,7 @@ const ignorePatterns = [
|
|||
'strip-ansi',
|
||||
'tributejs',
|
||||
'vue-material-design-icons',
|
||||
'webdav',
|
||||
]
|
||||
|
||||
const config: Config = {
|
||||
|
|
|
|||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -19,7 +19,7 @@
|
|||
"@nextcloud/capabilities": "^1.0.4",
|
||||
"@nextcloud/dialogs": "^4.1.0",
|
||||
"@nextcloud/event-bus": "^3.1.0",
|
||||
"@nextcloud/files": "^3.0.0-beta.10",
|
||||
"@nextcloud/files": "^3.0.0-beta.11",
|
||||
"@nextcloud/initial-state": "^2.0.0",
|
||||
"@nextcloud/l10n": "^2.1.0",
|
||||
"@nextcloud/logger": "^2.5.0",
|
||||
|
|
@ -3711,17 +3711,17 @@
|
|||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/@nextcloud/files": {
|
||||
"version": "3.0.0-beta.10",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.0.0-beta.10.tgz",
|
||||
"integrity": "sha512-cAh2HWkFgktub/GW07qx/kYz9nR2E/D+Zk/qXF8JW7BL/+gNy4/wOJ7mfDisUZy0gCZKZTV0v5wtEkIHwNdTyA==",
|
||||
"version": "3.0.0-beta.11",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.0.0-beta.11.tgz",
|
||||
"integrity": "sha512-eYPtUo+pBAvY8H0pSDyBJrpUKWILIadOmPVoHKpwOFwljNN3xh+AeT1ofT3oJI6ALfrKD/lDTe18BKU8uhLADA==",
|
||||
"dependencies": {
|
||||
"@nextcloud/auth": "^2.0.0",
|
||||
"@nextcloud/l10n": "^2.1.0",
|
||||
"@nextcloud/logger": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.0.0",
|
||||
"npm": "^7.0.0 || ^8.0.0"
|
||||
"node": "^20.0.0",
|
||||
"npm": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nextcloud/initial-state": {
|
||||
|
|
@ -29869,9 +29869,9 @@
|
|||
}
|
||||
},
|
||||
"@nextcloud/files": {
|
||||
"version": "3.0.0-beta.10",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.0.0-beta.10.tgz",
|
||||
"integrity": "sha512-cAh2HWkFgktub/GW07qx/kYz9nR2E/D+Zk/qXF8JW7BL/+gNy4/wOJ7mfDisUZy0gCZKZTV0v5wtEkIHwNdTyA==",
|
||||
"version": "3.0.0-beta.11",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.0.0-beta.11.tgz",
|
||||
"integrity": "sha512-eYPtUo+pBAvY8H0pSDyBJrpUKWILIadOmPVoHKpwOFwljNN3xh+AeT1ofT3oJI6ALfrKD/lDTe18BKU8uhLADA==",
|
||||
"requires": {
|
||||
"@nextcloud/auth": "^2.0.0",
|
||||
"@nextcloud/l10n": "^2.1.0",
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
"@nextcloud/capabilities": "^1.0.4",
|
||||
"@nextcloud/dialogs": "^4.1.0",
|
||||
"@nextcloud/event-bus": "^3.1.0",
|
||||
"@nextcloud/files": "^3.0.0-beta.10",
|
||||
"@nextcloud/files": "^3.0.0-beta.11",
|
||||
"@nextcloud/initial-state": "^2.0.0",
|
||||
"@nextcloud/l10n": "^2.1.0",
|
||||
"@nextcloud/logger": "^2.5.0",
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ module.exports = {
|
|||
additionalScripts: path.join(__dirname, 'apps/files_sharing/src', 'additionalScripts.js'),
|
||||
collaboration: path.join(__dirname, 'apps/files_sharing/src', 'collaborationresourceshandler.js'),
|
||||
files_sharing_tab: path.join(__dirname, 'apps/files_sharing/src', 'files_sharing_tab.js'),
|
||||
files_sharing: path.join(__dirname, 'apps/files_sharing/src', 'files_sharing.js'),
|
||||
main: path.join(__dirname, 'apps/files_sharing/src', 'index.js'),
|
||||
files_sharing: path.join(__dirname, 'apps/files_sharing/src', 'files_sharing.ts'),
|
||||
main: path.join(__dirname, 'apps/files_sharing/src', 'main.ts'),
|
||||
'personal-settings': path.join(__dirname, 'apps/files_sharing/src', 'personal-settings.js'),
|
||||
},
|
||||
files_trashbin: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue